中国大学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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章