C++11新特特性
统一初始化列表
这是C++98中花括号可以给结构体或者数组进行初始化!
struct Point { int _x; int _y; }; int main() { int array1[] = { 1, 2, 3, 4, 5 }; int array2[5] = { 0 }; Point p = { 1, 2 }; return 0; }
==而C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自 定义的类型==
==使用初始化列表时,可添加等号(=),也可不添加。==
struct Point { int _x; int _y; }; int main() { int array1[] = { 1, 2, 3, 4, 5 }; int array2[]{ 1, 2, 3, 4, 5 };//C++11可以省略等号 Point p1 = { 1, 2 }; Point p2{1,2};//C++11可以省略等号 int x1 = 1; int x2 = {1};//变量也可以用花括号初始化 int x3 {1};//也可以省略等号 //前面的建议不要用花括号初始化变量,还是使用传统的初始化这样可读性更好 //但是其实这种初始化方式更主要是为了支持下面的 int* p = new int[10]{1,2,3,4,5,5}; //C++11之前new出来的数组都是未初始化的,C++11之后可以用花括号初始化 Point* pp = new Point[2]{{1,2},{3,4}}; //我们甚至可以使用这样子来进行初始化! //这不是一个二维数组!这是一个一维数组 //里面的的花括号是为了初始化数组里面的Point类型的对象 //因为我们上面也演示过了,Point类型的对象可以用花括号初始化 return 0; }
class Date { public: Date(int year, int month, int day) :_year(year) ,_month(month) ,_day(day) { cout << "Date(int year, int month, int day)" << endl; } private: int _year; int _month; int _day; }; int main() { Date d{1,1,1};//这里的花括号严格来说是调用了date的构造函数来进行初始化的! Date* ds = new Date[2]{{1,1,1},{2,2,2}};//这里调用量两次构造函数! return 0; }
std::initializer_list
class Date { public: Date(int year, int month, int day) :_year(year) ,_month(month) ,_day(day) { cout << "Date(int year, int month, int day)" << endl; } private: int _year; int _month; int _day; }; int main() { Date d1{1,1,1};//这样子写是没有问题的! //因为本质是调用了构造函数的参数列 //但是如果下面这样写就会出现问题! //Date d2{1,1,1,1}; //但是为什么vector,list就可以一直往里面插入元素呢? //vector,list其实本质也是一个自定义类型! vector<int> v ={1,1,1,1,1}; list<int> ls ={1,2,3,4,55}; //这是怎么做到的? }
==因为C++11中的{}出了可以匹配类的构造函数之外还能去匹配另一个类型——initializer_list==
int main() { auto il = {1,2,3,4,5,6,7}; cout << typeid(il).name()<<endl; }
==可以认为initializer_list底层就是一个常量数组!存在常量区==
==initializer_list这个容器自己不开辟空间,里面有两个指针!指向一个常量数组的开始和结束==
里面还支持了一个类似迭代器的东西——那不是迭代器是类似迭代器
我们可以这个容器也给我提供了begin与end接口!
int main() { //所以我们要区分虽然都是使用花括号!但是调用的却是不一样的! Date d1{1,1,1};//这是调用构造函数! vector<int> v ={1,1,1,1,1}; list<int> ls ={1,2,3,4,55}; //这两个是调用initializer_list }
==而list和vector都是有支持了initializer_list的构造函数==
==所以无论多少个参数的都能支持!因为本质都是传给了initializer_list,然后再遍历initializer_list,push_back进vector或者list里面!==
==我们也可以自己写一个支持initializer_list的vector容器==——具体情读者自行实现,这里我们只实现关于initializer_list的构造函数
当我们不进行实现,且使用自己的写的vector的试试
==其实就是识别成了一个类型转换!==
当我们实现了这个构造函数
vector(std::initializer_list<T> il)//一般都是传值!因为initializer_list里面有只两个指针所以直接传值就可以 :_start(nullptr), _finish(nullptr), _endofstorage(nullptr) { typename std::initializer_list<T>::iterator it = il.begin(); while(it != il.end()) { push_back(*it); it++; } }
==这样子就成功支持了!==
几乎所有的容器都支持这个构造函数!
int main() { vector<Date> v1{{1,1,1},{2,2,2},{3,3,3}}; //这样子vector就可以怎么写! //里面的花括号是调用了date的构造函数! //外面的那个花括号本质是先去调用了initializer_list //然后才去调用了vector的构造函数! }
==有了花括号我们还可以做一些很方便的事情!==
int main() { map<string,string> mp; mp.insert(make_pair("1","1"));//我们以前使用map都是这样子插入的! mp.insert(std::pair<string,string>("1","1"));//或者是这样子插入的! //但是有了花括号初始化之后,我们可以这样子插入 mp.insert({"1","1"});//因为花括号会自动的去调用pair的构造函数去构造一个pair出来! //因为库里面的map也是支持initializer_list的构造函数!本质也是遍历initializer_list然后插入! map<int,int> mp2{{1,1},{2,2},{3,3}};//这样子也是可以的! //里面的花括号是调用用pair的构造函数! //外面的花括号是调用initializer_list然后遍历initializer_list进行插入! }
声明方式
C++11提供了很多简化的声明方式
auto
在C++98中auto是一个存储类型的说明符,表明变量是局部自动存储类型,但是局部域中定义局 部的变量默认就是自动存储类型,所以auto就没什么价值了。
C++11中废弃auto原来的用法,将 其用于实现自动类型腿断。这样要求必须进行显示初始化,让编译器将定义对象的类型设置为初 始化值的类型。
int main() { int i = 10; auto p = &i; auto pf = strcpy; cout << typeid(p).name() << endl; cout << typeid(pf).name() << endl; map<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} }; //map<string, string>::iterator it = dict.begin(); //等价于上面的 auto it = dict.begin(); return 0; }
decltype
可以通过表达式来推导类型!然后还可以用这个类型来定义变量!
decltype的功能和typeid.name有点类似!——只不过typeid.name得到是字符串!不能用于定义变量
int main() { int x =0; cout << typeid(x).name() << endl; //tyepid(x).name y;//这样子是不行的!因为tyepid(x).name是一个字符串! decltype(x) y;//这样子是可以的!因为decltype(x)可以推导x的类型!用这样子的方式来定义变量! }
不过更经常的是下面的用法
template<class T1,class T2> void F(T1 t1,T2 t2) { decltype(t1 * t2) ret = t1*t2;//通过表达式来推导ret的类型! //然后用这个类型来定义对象 //这样子我们可以保证精度不会丢失! //如果固定某个类型就可能丢失精度 //主要还是因为不知道T1与T2的类型 cout << typeid(ret).name() << endl; } //当我们在不知道t1与t2两个不同类型的相乘后的结果是什么的时候!我们可以用这个来进行推导! int main() { F(1,2); F(1.0,2); }
nullptr
由于C++中NULL被定义成字面量0,这样就可能回带来一些问题,因为0既能指针常量,又能表示 整形常量。
==所以出于清晰和安全的角度考虑,C++11中新增了nullptr,用于表示空指针。==
#ifndef NULL #ifdef __cplusplus #define NULL 0 #else #define NULL ((void *)0) #endif #endif
default——强制生成默认函数
==假如我们真的遇到那非要写析构函数 、拷贝构造、拷贝赋值重载呢?——但是其实默认生成的移动构造就已经够用了!那么我们要怎么办呢?——我们可以使用defualt来强制生成!==
class Person { public: Person(const char *name = "", int age = 0) : _name(name), _age(age) { } Person(const Person &p) : _name(p._name), _age(p._age) { } //我们可以看到这样子写其实很麻烦! /*Person(Person &&p) : _name(forward<MySTL::string>(p._name))//要使用move或者forward,否则p._name是左值,不能调用右值引用的构造函数 , _age(p._age) { }*/ //有了default我们就可以怎么写 Person(Person &&p) = default; //这样子就可以生成一个默认移动构造! ~Person() { } private: MySTL::string _name; int _age; }; int main() { Person s1; Person s2 = s1;//拷贝构造 Person s3 = move(s1);//移动构造 return 0; }
delete
假如我们想要写一个类!这个类==禁止被拷贝!==——那么我们应该怎么办?
在C++98的时候我们想要做到这件事——我们就要在==私有对拷贝构造进行声明(而不是进行实现!)==
==为什么不要进行实现呢?因为如果实现了那么在A类里面就可以调用拷贝构造进行拷贝A类了!==
//如果实现了拷贝构造! class A { public: A* func() { return new A(*this); } //就可以怎么玩来完成拷贝! A() { } ~A() { delete[] p; } private: A(const A& a) { //..... }//实现而不是声明 int* p = new int[10]; }; int main() { A a; A* b = a.func(); return 0; }
==如果是声明==
class A { public: A* func() { return new A(*this); } A() { } ~A() { delete[] p; } private: A(const A& a); private: int* p = new int[10]; };
正常的拷贝
调用函数进行拷贝
我们发现这样子都不可以!——==就可以防止拷贝了!==
==我们可以发现C++98的方式很绕!==
==但是C++11的方式就十分的直接!——使用delete!==
class A { public: A* func() { return new A(*this); } A() {} A (A& a) = delete;//直接这样子就可以! ~A() { delete[] p; } private: int* p = new int[10]; }; int main() { A a; A* b = a.func(); return 0; }
==这样子无论如何都无法调用拷贝构造了!==