中國大學MOOC程序設計與算法(三):C++ 面向對象程序設計 第十週 C++11新特性和C++高級主題 筆記 之 C++11新特性(一)

第十週 C++11新特性和C++高級主題
1.C++11新特性(一)
2.C++11新特性(二)
3.強制類型轉換
4.異常處理

1.C++11新特性(一)
C++2011年的新的標準,gcc4.8編譯器完全支持C++11。

統一的初始化方法

int arr[3]{1, 2, 3};
vector<int> iv{1, 2, 3};
map<int, string> mp{{1, "a"}, {2, "b"}};
string str{"Hello World"};
int * p = new int[20]{1,2,3};
struct A {
	int i,j;
	A(int m,int n):i(m),j(n) {  }
};
A func(int m,int n ) { return {m,n}; }
int main() { A * pa = new A {3,7}; }

成員變量默認初始值

class B
{
	public:
		int m = 1234;
		int n;
};
int main()
{
	B b;
	cout << b.m << endl; //輸出 1234
	return 0;
}

auto關鍵字

用於定義變量,編譯起可以自動判斷變量的類型,用auto的時候意味着必須初始化。

auto i = 100; // i 是 int
auto p = new A(); // p 是 A *
auto k = 34343LL; // k 是 long long
map<string,int,greater<string> > mp;
for( auto i = mp.begin(); i != mp.end(); ++i)
	cout << i->first << "," << i->second ;
//i的類型是: map<string,int,greater<string> >::iterator

class A { };
A operator + ( int n,const A & a)
{
	return a;
}
template <class T1, class T2>
auto add(T1 x, T2 y) -> decltype(x + y) {//-> decltype(x + y) 表明這個函數的額返回值類型是x+y決定的
	return x+y;
}
auto d = add(100,1.5); // d是double d=101.5
auto d = add(100,A()); // d是A類型

decltype(表達式) 關鍵字

返回表達式的類型

int i;
double t;
struct A { double x; };
const A* a = new A();
decltype(a) x1; // x1 is A *
decltype(i) x2; // x2 is int
decltype(a->x) x3; // x3 is double
decltype((a->x)) x4 = t; // x4 is double&引用

智能指針shared_ptr,還是很有用的

頭文件:
是個類模板,通過shared_ptr的構造函數,可以讓shared_ptr對象託管一個new運算符返回的指針,寫法如下:

shared_ptr<T> ptr(new T); // T 可以是 int ,char, 類名等各種類型

此後ptr就可以像 T* 類型的指針一樣來使用,即 *ptr 就是用new動態分配的那個對象,而且不必操心釋放內存的事。
new出來的東西,如果所有託管它的智能指針都放棄託管它了,他就會自動被釋放,如果new的是個對象,那它就執行析構函數。
多個shared_ptr對象可以同時託管一個指針,系統會維護一個託管計數。當無shared_ptr託管該指針時,delete該指針。
sp1.get()成員函數可以去除取出智能指針託管的指針。A * p = sp1.get(); //p 指向 sp1託管的指針
sp1.reset()成員函數使智能指針放棄它託管的指針。
sp1.reset(q)成員函數使智能指針開始託管指針q。
shared_ptr對象不能託管指向動態分配的數組的指針,否則程序運行會出錯

#include <memory>
#include <iostream>
using namespace std;
struct A {
	int n;
	A(int v = 0):n(v){ }
	~A() { cout << n << " destructor" << endl; }
};
int main()
{
	shared_ptr<A> sp1(new A(2)); //sp1託管A(2)
	shared_ptr<A> sp2(sp1); //sp2也託管 A(2),調用了複製構造函數
	cout << "1)" << sp1->n << "," << sp2->n << endl;
	//輸出1)2,2
	shared_ptr<A> sp3;
	A * p = sp1.get(); //p 指向 A(2)
	cout << "2)" << p->n << endl;
	sp3 = sp1; //sp3也託管 A(2)
	cout << "3)" << (*sp3).n << endl; //輸出 2
	sp1.reset(); //sp1放棄託管 A(2)
	if( !sp1 )//sp1沒有託管的指針
		cout << "4)sp1 is null" << endl; //會輸出
	A * q = new A(3);
	sp1.reset(q); // sp1託管q
	cout << "5)" << sp1->n << endl; //輸出 3
	shared_ptr<A> sp4(sp1); //sp4託管A(3)
	shared_ptr<A> sp5;
	//sp5.reset(q);//不妥,導致程序出錯,爲什麼?
	sp1.reset(); //sp1放棄託管 A(3),託管它的智能指針都放棄託管,他就會自動被釋放,析構函數
	cout << "before end main" <<endl;
	sp4.reset(); //sp1放棄託管 A(3),但是還有智能指針託管它,所以析構函數不會被調用
	cout << "end main" << endl;
	return 0; //程序結束,會delete 掉A(2),析構函數被調用
}
輸出結果:
1)2,2
2)2
3)2
4)sp1 is
null
5)3
before end
main
3
destructor
#include <iostream>
#include <memory>
using namespace std;
struct A{
	~A() { cout << "~A" << endl; }
};
int main()
{
	A * p = new A();
	shared_ptr<A> ptr(p);
	shared_ptr<A> ptr2;
	ptr2.reset(p); //雖然這裏也是p,但編譯器不會讓ptr2也託管p,並不增加 ptr中對p的託管計數,爲啥?
	cout << "end" << endl;
	return 0;
}
輸出:
end
~A
~A
之後程序崩潰,因爲p被delete兩次

空指針nullptr,類似NULL

#include <memory>
#include <iostream>
using namespace std;
int main() {
	int* p1 = NULL;
	int* p2 = nullptr;
	shared_ptr<double> p3 = nullptr;
	if(p1 == p2)
		cout << "equal 1" <<endl;
	if( p3 == nullptr)
		cout << "equal 2" <<endl;
	//if( p3 == p2) ; // error,編譯出錯
	if( p3 == NULL)
		cout << "equal 4" <<endl;
	bool b = nullptr; // b = false,nullptr可以被自動轉換成布爾值false
	int i = nullptr; //error,nullptr不能自動轉換成整型
	return 0;
}
輸出:
equal 1
equal 2
equal 4

基於範圍的for循環

牛逼啊,高大上!!!!裝逼神器

#include <iostream>
#include <vector>
using namespace std;
struct A { 
	int n; 
	A(int i):n(i) { } 
};
int main() {
	int ary[] = {1,2,3,4,5};
	for(int & e: ary)
		e*= 10;
	for(int e : ary)
		cout << e << ",";
	cout << endl;
	vector<A> st(ary,ary+5);
	for( auto & it: st)
		it.n *= 10;
	for( A it: st)
		cout << it.n << ",";
	return 0;
}
輸出:
10,20,30,40,50,
100,200,300,400,500,

右值引用和move語義,很有趣

右值:一般來說,不能取地址的表達式,就是右值,能取地址的,就是左值

class A { };
A & r = A(); // error , A()是無名變量,是右值,不能取臨時變量的地址
A && r = A(); //ok, r 是右值引用

普通的&是左值引用,&&是右值引用,可以引用右值,如臨時變量。
主要目的是提高程序運行的效率,減少需要進行深拷貝的對象進行深拷貝的次數。有意識地避免深拷貝。

例子:自己寫的string類

#include <iostream>
#include <string>
#include <cstring>
using namespace std;
class String
{
	public:
		char * str;
		String():str(new char[1]) { str[0] = 0;}//無參構造函數
		String(const char * s) {//構造函數
			str = new char[strlen(s)+1];
			strcpy(str,s);
		}
		String(const String & s) {//複製構造函數
			cout << "copy constructor called" << endl;
			str = new char[strlen(s.str)+1];
			strcpy(str,s.str);
		}
		String & operator=(const String & s) {//重載賦值號,深拷貝
			cout << "copy operator= called" << endl;
			if( str != s.str) {
				delete [] str;
				str = new char[strlen(s.str)+1];
				strcpy(str,s.str);
			}
			return * this;
		}
		//移動構造函數
		String(String && s):str(s.str) {//移動構造函數,右值引用
			cout << "move constructor called"<<endl;
			s.str = new char[1];//讓原str指向一塊新的空白,鳩佔鵲巢,就節省了深拷貝的時間
			s.str[0] = 0;
		}
		//移動賦值號,和移動構造函數類似
		String & operator = (String &&s) {
			cout << "move operator= called"<<endl;
			if (str!= s.str) {
				delete [] str;
				str = s.str;
				s.str = new char[1];
				s.str[0] = 0;
			}
			return *this;
		}
		~String() { delete [] str; }//析構函數
};
template <class T>
void MoveSwap(T& a, T& b) {//前提是調用完這個函數後a,b不會再用了,因爲出了這個函數a,b就被指向新的空內存了
	T tmp(move(a)); // 將左值a變爲右值,std::move(a)爲右值,這裏會調用移動構造函數
	a = move(b); // move(b)爲右值,因此這裏會調用移動賦值號
	b = move(tmp); // move(tmp)爲右值,因此這裏會調用移動賦值號
}
int main()
{
	//String & r = String("this"); // error
	String s;
	s = String("ok"); // String("ok")是右值,調用移動賦值號
	cout << "******" << endl;
	String && r = String("this");
	cout << r.str << endl;
	String s1 = "hello",s2 = "world";
	MoveSwap(s1,s2);
	cout << s2.str << endl;
	return 0;
}
輸出:
move operator= called
******
this
move constructor called
move operator= called
move operator= called
hello

move()表達式:轉換成右值

函數返回值爲對象時,返回值對象如何初始化?

寫複製構造函數:
return 局部對象 -> 複製
return 全局對象 ->複製
寫移動構造函數:
return 局部對象 -> 移動
return 全局對象 ->默認複製
return move(全局對向) -〉移動
同時寫 複製構造函數和 移動構造函數:
return 局部對象 -> 移動
return 全局對象 -> 複製
return move(全局對向) -〉移動
dev c++中,return 局部對象 會導致優化,不調用移動或複製構造函數

可移動但不可複製的對象:

struct A{
	A(const A & a) = delete;
	A(const A && a) { cout << "move" << endl; }
	A() { };
};
A b;
A func() {
	A a;
	return a;
}
void func2(A a) { }
int main() {
	A a1;
	A a2(a1); //compile error
	func2(a1); //compile error
	func();
	return 0;
}

發佈了53 篇原創文章 · 獲贊 4 · 訪問量 1983
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章