[學習][記錄] c++語言:從放棄到入門

c++11 新關鍵字以及引入的新特性

nullptr

nullptr 是用於解決 NULL 和 0 的有疑義關係的。NULL 通常被義爲(void*)0。在 如下應用中會引發歧義。

入參

#include <iostream> 
using namespace std; 

void f(int){} 
void f(bool){} 
void f(void*){} 
int main() {
 f(0); // 調用f(int)
 f(NULL); // 可能無法編譯通過,但一般會調用f(int)
 f(nullptr); // 調用f(void*)
}

解釋:

  1. C++ 視 0 首先爲 int 型,因此,調用 f(0) 即調用 f(int)
  2. NULL 的情況複雜些,C++ 首先視其爲廣義整型。假如 NULL 被定義爲普通 的 0,則調用 f(int); 如果 NULL 被定義成 0L,則 long -> int, long -> bool, 0L -> void*, 這三 種情況都是合法的,此時,編譯器會報錯
  3. 使用 nullptr,則不會有重載函數調用模糊的問題 - nullptr 不屬於廣義整型,也不是普通意義上的指針。 - nullptr 的實際類型是 std::nullptr_t,它能夠隱式的轉換成所有的原始指針 類型,故可將其視爲一個可指向所有類型的指針。

辦法

避免NULL重載編譯報錯:

f((int)NULL) 通過強轉的辦法解決

返值

使用 0 與 result 作比較,則一時不能確定 findRecord 的返回值類型 (可能是 廣義整型,也可能是指針類型); 使用 nullptr,可以清楚地知道 findRecord 的返回值, 必定是一個指針類型。

auto result = findRecord( /* arguments */ );
 if (result == 0) { ... }
 
auto result = findRecord( /* arguments */ ); 
if (result == nullptr) { ... }

override

含義

覆蓋重新寫從父類繼承過來的函數
覆寫父類的虛函數時候,好的 IDE 一定會給出斜體等的提示,表示此函數覆寫自 父類。

目的

避免發生,編譯通過,但是邏輯錯誤的情況 例如函數名寫錯了 但是編譯通過,卻不是從父類繼承過來的函數。
利用關鍵字 override 則指明,此種覆寫關係,若此關係不成立,則以報錯的形式提示 給用戶。

問題?

class G{
public:
	virtual void func(int) {
		printf("G::func(int) \n");
	};

};

class H:G{
public:
	//virtual void func(int) override{
	//	printf("H::func(int) \n");
	//}

	virtual void func(double){
		printf("H::func(double) \n");
	}
};

void main() {
	H *p = new H; 
	p->func(5); 
	p->func(5.0); 

	system("pause");
}

vs2015
輸出
H::func(double)
H::func(double)
請按任意鍵繼續. . .

疑問???
沒調用到G類的func(int)

final

含義

關鍵字 final 有兩個用途:

第一,它阻止了子類繼承;

class A final 
{
public: 
	virtual void func() const; 
};

class B: A //錯誤 編譯報錯 A is final
{
public: 
	void func() const override final;//  錯誤 編譯報錯 A is final
{
};

第二,阻止一個虛函數的覆 寫。

class A 
{
public: 
	virtual void func() const; 
};

class B
{
public: 
	void func() const override final;//OK 
};

class C: B {
public:
	void func() const; //error, B::func is final  編譯報錯
};

意義

阻止類的無限擴展。

=default =delete

default

C++ 的類有四類特殊成員函數,它們分別是:
1.默認構造函數
2.析構函數
3.拷貝構造函數
4.拷貝賦值運算符。

class A {
public:
 	A();//構造函數 創建類的實例時 調用
 	~A();//析構函數 銷燬類的實例時 調用
 	A(const A &) =;//拷貝構造函數 類實例之間的拷貝 例如A a;A b = a;//此時調用拷貝構造函數
 	A operator=(const A &);//拷貝賦值運算符   本質重載運算符=進行初始化
}

關鍵字default
如果程序員沒有顯式地爲一個類定義某個特殊成員函數,而又需要用到該特殊成員 函數時,則編譯器會隱式的爲這個類生成一個默認的特殊成員函數。

class A {
public: A() = default; 
A(int x ):_x(x) {} 
private: int _x; 
};

delete

爲了能夠讓程序員顯式的禁用某個函數,C++11 標準引入了一個新特性: "=delete"函數。程序員只需在函數聲明後上“=delete;”,就可將該函數禁用。

class Singleton {
public:
 static Singleton* getInstance() {
  if(_ins == nullptr) 
  _ins = new Singleton; 
  return _ins; 
 }
 Singleton(Singleton &) = delete; 
 Singleton& operator=(Singleton &) = delete; 
private: 
 Singleton(){
 } 
 static Singleton* _ins; 
};

Singleton* Singleton::_ins = nullptr; //類的靜態變量在類外初始化

int main() { 
 Singleton *ps = Singleton::getInstance(); 
 Singleton ps(*ps); 
 *ps = Singleton::getInstance(); 
 return 0; 
}

Raw String Literals

緣由

C/C++中提供了字符串,字符串的轉義序列,給輸出帶來了很多不變,如果需要 原生義的時候,需要反轉義,比較麻煩。

使用

C++提供了 R"()",原生字符串,即字符串中無轉義,亦無需再反義。但是注意() 中的)"會導至提前結束。


#include <iostream> 

using namespace std;

string path = "C:\Program Files (x86)\alipay\aliedit\5.1.0.3754"; // 錯誤,\沒轉義
string path2= "C:\\Program Files (x86)\\alipay\\aliedit\\5.1.0.3754"; //正確,\轉義了 用\\或者/,但麻煩
string path3= R"(C:\Program Files (x86)\alipay\aliedit\5.1.0.3754)"; //正確,使用了R()
string path4= R"(C:\Program "Files" (x86)\\alipay\aliedit\5.1.0.3754)";//正確,使用了R()

//string path5= R"(C:\Program "Files" (x86)\\alipay\aliedit\)"5.1.0.3754)";//錯誤,R()的缺陷

int main(int argc, char *argv[]) {
	cout << path.c_str() << endl;
	cout << path2.c_str() << endl;
	cout << path3.c_str() << endl;
	cout << path4.c_str() << endl;
	system("pause");
	return 0; 
}



輸出結果:
C:Program Files (x86)lipayliedit.1.0.3754
C:\Program Files (x86)\alipay\aliedit\5.1.0.3754
C:\Program Files (x86)\alipay\aliedit\5.1.0.3754
C:\Program "Files" (x86)\\alipay\aliedit\5.1.0.3754


Range-Based for 循環Foreach

一種基於STL容器遍歷的一種for循環形式 類似Java的 foreach

語法形式:

for (declaration : expression) 
      statement

例如
vector<int> vecInt = {0,1,2,3,4,5}; //expression 列表
for(auto &i:vecInt){//auto &i  is declaration 申明
	cout << i << endl; 
}

等價寫法1
vector<int> vecInt = { 1,2,3,4,5 };
for (int i = 0; i<vecInt.size(); i++) {
	int tmp = vecInt.at(i);
	cout << tmp << endl;
}

等價寫法2
vector<int> vecInt = { 1,2,3,4,5 };
vector<int>::iterator itr = vecInt.begin();
for (; itr != vecInt.end(); itr++) {
	cout << *itr << endl;
}

解釋:

expression 部分是一個對象,必須是一個序列,比方說
用花括號括起來的初始值
1.列表
2.數組
int[] a={1,2,3,4,5}; char ch[] = “123”;string str = “china”;
3.vector 或 string 等類型的對象。
vector vet = {1,2,3};list list= {1,2,3} map<int,string>map = {1,“123”};
這些類型的共同特點是擁有能返回迭 代器的 begin 和 end 成員。

declaration 部分負責定義一個變量,該變量將被用於訪問序列中的基礎元素。
每 次迭代,declaration 部分的變量會被初始化爲 expression 部分的下一個元素值。
確 保類型相容最簡單的辦法是使用 auto 類型說明符。

initialization list {} STL初始化

normal init 常規初始化

所謂的常規初始化,即調用類的構 造器。

vector

vector<int> vi; 
vector<int> vi(10,10);//size 10,each value 10
vector<int> vi(arr,arr+10);//begin,end

list

list<int> li(10);
list<int> li2(10);

list<int> li3(10,1);//size 10,each value 1

int arr2[10] = {};
list<int> li4(arr2,arr2+10);//begin,end

map


//map[key] = value
map<int,string> mis; 
mis[0] = "first"; 
mis[1] = "second"; 
mis[2] = "third"; 


//make_pair
map<int,string> mis2(mis.begin(),mis.end());
mis.insert(std::make_pair(3, "fourth"));
mis.insert(pair<int, string>(3, "fourth"));

//value_type
mis.insert(map<int, string>::value_type(3, "fourth")); 
for (auto& pair : mis) 
	cout << pair.first << ":" << pair.second.c_str() << endl;

initialization List {} 列表方式初始化

以初始化列表的方式來進行實始化,打破了,原有的初始化格局,令實始化更直觀 化,人性化。

vector<int> vi = {1,2,3,4,5}; 
list<int> li = {1,2,3,4,5}; 
map<int,string> mis = { 
	{1,"c"}, {2,"c++"}, 
	{3,"java"},{4,"scala"},
	{5,"python"}
	};
	
mis.insert({6,"ruby"}); 

for(auto &is: mis) {
 cout<<is.first<<is.second<<endl; 
}

未完待續

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章