一、C++ 11的简介
在2003年C++标准委员会曾经提交了一份技能勘误表(简称TC1),使得C++03这个名字已经代替了C++98称为C++11之前的最新C++标准名称。不过由于C++03(TC1)紧张是对C++98标准中的毛病进行修复,语言的核心部门则没有改动,因此人们风俗性的把两个标准归并称为C++98/03标准。从C++0x到C++11,C++标准10年磨一剑,第二个真正意义上的标准珊珊来迟。相比于
C++98/03,C++11则带来了数目可观的变化,其中包含了约140个新特性,以及对C++03标准中
约600个缺陷的修正,这使得C++11更像是从C++98/03中孕育出的一种新语言。相比较而言,C++11能更好地用于体系开辟和库开辟、语法更加泛华和简单化、更加稳固和安全,不仅功能更强大,而且能提升步伐员的开辟效率,公司现实项目开辟中也用得比较多。
C++11新增加的语法特性很多,这里只记录和演示一些紧张且常用的语法。假如想学习大概了解更多,就去这个网站,去查看。网址:https://en.cppreference.com/w/cpp/11
二、统一的列表初始化
1、{}初始化
在C++98中,标准答应利用花括号{}对数组大概布局体元素进行统一的列表初始值设定。好比:
- #include<iostream>
- using namespace std;
- struct Point
- {
- int _x;
- int _y;
- };
- int main()
- {
- int array[] = { 1,2,3,4,5 };
- Point p = { 1,2 };
- Point ps[] = { {1,2},{3,4} };
- return 0;
- }
复制代码 C++11扩大了花括号{}的利用范围。所有的内置范例和自定义范例都可以利用{}进行初始值的设定。(=可加可不加,但是个人感觉加上更好,至少感觉更悦目)
- #include<iostream>
- using namespace std;
- struct Point
- {
- int _x;
- int _y;
- };
- 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()
- {
- int array[] = { 1,2,3,4,5 };
- Point p = { 1,2 };
- Point ps[] = { {1,2},{3,4} };
- int a{ 4 };
- int b = { 5 };
- Date d1{ 2025,3,20 };//这种方式还是直接调用的构造函数,进行初始化
- Date d2 = { 2025,3,20 };//这种方式同样也是
- int* pa = new int[4]{ 9 };
- for (size_t i = 0; i < 4; i++)
- {
- cout << pa[i] << " "; //结果 9 0 0 0
- }
- cout << endl;
- int* pb = new int[4]{ 1,2,3,4 };
- for (size_t i = 0; i < 4; i++)
- {
- cout << pb[i] << " "; //结果 1 2 3 4
- }
- cout << endl;
- return 0;
- }
复制代码 注意:用花括号{}对new表达式初始化时不能加=
2、initializer_list容器
这个容器相对于其它容器来说提供的成员函数非常少。只支持迭代器和求容器中元素的个数。
所以这个容器的紧张作用不和vector、list这种容器的作用雷同的。这个容器的紧张作用是为了实现利用花括号{}不定长的去给容器设置初始值。
①initializer_list是一种容器
- #include<iostream>
- using namespace std;
- int main()
- {
- auto i = { 1,2,3,4 };
- cout << typeid(i).name() << endl;
- }//结果是:class std::initializer_list<int>
复制代码 ②initializer_list的利用场景
1°initializer_list 一般是用做容器构造函数中的参数,如许可以利用花括号{}不定长初始化容器。
2°initializer_list 还可以用作容器赋值运算符重载的参数,如许可以利用花括号{}对容器重新赋值。
演示这两种利用场景:
- #include<iostream>
- #include<vector>
- #include<list>
- #include<set>
- #include<map>
- #include<string>
- using namespace std;
- int main()
- {
- vector<int> v = { 1,2,3,4,5 };
- list<int> ls = { 3,4,5,6 };
- set<int> s = { 3,2,4,6 };
- map<string, int> m = { {"AAA",1},{"BBB",2},{"CCC",3} };
- for (auto e : v)
- {
- cout << e << " ";
- }
- cout << endl;
- v = { 9,8,7,6 };
- for (auto e : v)
- {
- cout << e << " ";
- }
- cout << endl;
- ls = { 1,2,3,4,5 };
- s = { 9,2,5,7 };
- m = { {"DDD",4},{"EEE",5},{"FFF",6} };
- return 0;
- }
复制代码 这两种利用场景是怎么完成的呢?
场景1:以vector容器为例

我们可以看到,vector容器提供了包含initializer_list 容器的构造函数,所以我们可以通报一个initializer_list对象来构造vector对象。构造过程如下:
当我们利用花括号{}来构建initializer_list对象时,编译器会开辟一个临时数组,存放花括号中的数据,并且设置两个指针,一个指针指向数组的开始位置,一个指针指向数组的竣事位置的下一个位置。然后将两个指针封装起来,就构成了initializer_list对象。
利用initializer_list对象来构造vector对象时,vector的构造函数会根据initializer_list对象中的两个指针遍历数组,将数组中的每个元素,挨个放入vector容器中。
场景二:还是以vector容器为例

我们看到赋值重载函数中也有一个以initializer_list对象为参数的构造函数,那就是说我们也可以利用花括号{}构建的initializer_list对象来对vector容器进行重新赋值。原理与场景一表明的一样,这里不多赘述。
③我们来实现一下vector容器中的initializer_list 拷贝和initializer_list赋值重载
- namespace zrf
- {
- template<class T>
- class vector
- {
- public:
- typedef T* iterator;
- vector(initializer_list<T> lt)
- {
- _start = new T[lt.size()];
- _finish = _start + lt.size();
- _endOfStorage = _start + lt.size();
- //采用范围for
- /*iterator it = _start;
- for (auto e : lt)
- {
- *it = e;
- it++;
- }*/
- //采用迭代器
- iterator it = _start;
- typename initializer_list<T>::iterator itl = lt.begin();
- while (itl != lt.end())
- {
- *it = *itl;
- ++it;
- ++itl;
- }
- }
- vector<T>& operator=(initializer_list<T> lt)
- {
- vector<T> tmp(lt);
- std::swap(_start, tmp._start);
- std::swap(_finish, tmp._finish);
- std::swap(_endOfStorage, tmp._endOfStorage);
- return *this;
- }
- void Print()
- {
- iterator it = _start;
- while (it != _finish)
- {
- cout << *it << " ";
- it++;
- }
- cout << endl;
- }
- private:
- iterator _start;
- iterator _finish;
- iterator _endOfStorage;
- };
- }
- int main()
- {
- zrf::vector<int> v = { 1,2,3,4,5 };
- v.Print();
- v = { 6,7,8,9,4 };
- v.Print();
- return 0;
- }
复制代码 注意:这段代码有一个必要注意的地方,有一个故意思的地方。
必要注意的地方是:我们在利用迭代器拷贝数据的时候,我们initializer_list对象的迭代器前面要加关键字typename声明一下这是initializer_list对象中的范例,而不是静态变量。由于我们在利用其他类中范例大概静态变量的时候都必要在前面加上范例和域作用限定符。
故意思的地方是:我们将上面代码的赋值运算符重载屏蔽掉,也可以打印出重新赋值后的结果。缘故原由是initializer_list对象构造出了一个临时的vector对象,然后通过编译器生成的赋值运算符重载,对临时的vector对象造成了浅拷贝。结果精确是由于,我们没写析构函数,临时的vector对象开辟的空间没有被析构,而我们的对象v也控制着这块空间。但是我们原本的那块空间造成了内存泄漏。假如我们写上析构函数,那临时的vector对象在对象v拷贝完成之后就释放了,而我们的对象v通过浅拷贝,现实上控制的是一块已经被释放的空间。而原来的空间也同样造成了内存泄漏。
假如我们不屏蔽赋值运算符重载,那假如不写析构函数,也同样会造成内存泄漏。所以,析构函数是必须要写上的。写上析构函数的代码,我在下面又写了一份。
- #include<iostream>
- using namespace std;
- namespace zrf
- {
- template<class T>
- class vector
- {
- public:
- typedef T* iterator;
- vector(initializer_list<T> lt)
- {
- _start = new T[lt.size()];
- _finish = _start + lt.size();
- _endOfStorage = _start + lt.size();
- //采用范围for
- /*iterator it = _start;
- for (auto e : lt)
- {
- *it = e;
- it++;
- }*/
- //采用迭代器
- iterator it = _start;
- typename initializer_list<T>::iterator itl = lt.begin();
- while (itl != lt.end())
- {
- *it = *itl;
- ++it;
- ++itl;
- }
- }
- vector<T>& operator=(initializer_list<T> lt)
- {
- vector<T> tmp(lt);
- std::swap(_start, tmp._start);
- std::swap(_finish, tmp._finish);
- std::swap(_endOfStorage, tmp._endOfStorage);
- return *this;
- }
- ~vector()
- {
- delete[] _start;
- _start = _finish = _endOfStorage = nullptr;
- }
- void Print()
- {
- iterator it = _start;
- while (it != _finish)
- {
- cout << *it << " ";
- it++;
- }
- cout << endl;
- }
- private:
- iterator _start;
- iterator _finish;
- iterator _endOfStorage;
- };
- }
- int main()
- {
- zrf::vector<int> v = { 1,2,3,4,5 };
- v.Print();
- v = { 6,7,8,9,4 };
- v.Print();
- return 0;
- }
复制代码 三、声明
1、auto
C++11中废弃auto原来的用法,将其用于自动推断范例。根据初始化值的范例,推断出变量的范例。
- #include<iostream>
- #include<map>
- using namespace std;
- int main()
- {
- auto i = 10;
- auto pi = &i;
- map<string,int> m = { {"string",3},{"map",3} };
- auto it = m.begin();
- cout << typeid(i).name() << endl; //int
- cout << typeid(pi).name() << endl; //int*
- cout << typeid(it).name() << endl; //class std::_Tree_iterator<class std::_Tree_val<struct std::_Tree_simple_types<struct std::pair<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > const ,int> > > >
- return 0;
- }
复制代码 2、decltype
将变量的范例声明为表达式指定的范例,简而言之,表达式的结果是什么范例,那么decltype会自动得到这个表达式的范例,然后进行声明。
- #include<iostream>
- using namespace std;
- template<typename T1,typename T2>
- void F(T1 x, T2 y)
- {
- decltype(x * y) ret;
- cout << typeid(ret).name() << endl;
- }
- int main()
- {
- const int x = 3;
- double y = 2.2;
- decltype(x * y) ret;
- decltype(&x) p;
- cout << typeid(ret).name() << endl; //double
- cout << typeid(p).name() << endl; //const int*
- F(1, 'a'); //int
- return 0;
- }
复制代码 注意:通过typeid(变量名).name()得到一个变量的范例名,但是无法用获取到的这个变量名去定义变量。
decltype出了能推导表达式的范例,还能推导函数本身的范例和函数返回值的范例。
- #include<iostream>
- using namespace std;
- void* GetMemory(size_t size)
- {
- return malloc(size);
- }
- int main()
- {
- //没有带参数,就推导函数的类型
- cout << typeid(decltype(GetMemory)).name() << endl;
- //带参数,就会推导返回值类型。注意:这里只是推导,不是执行
- cout << typeid(decltype(GetMemory(0))).name() << endl;
- return 0;
- }
复制代码 decltype不仅可以指定表达式得到的范例,还可以指定返回范例
- #include<iostream>
- using namespace std;
- template<class T1,class T2>
- auto Add(T1 t1, T2 t2) ->decltype(t1 + t2)
- {
- decltype(t1 + t2) ret;
- ret = t1 + t2;
- cout << typeid(ret).name() << endl;
- return ret;
- }
- int main()
- {
- Add(1, 2); //int
- Add(1, 3.4); //double
- return 0;
- }
复制代码 去掉尾置返回范例大概也可以运行精确,但是存在一定的风险。在C++11中,要decltype(t1+t2)要知道T1和T2的范例,但是假如模版未实例化,那么就无法得到这个范例,那么就会编译报错。在C++14中,存在的风险有两个,一个是decltype(t1+t2)是ret的声明范例,而auto推导的是ret的值的范例,假如在复杂的场景中两者不一样,肯能会出现题目。另一个是,假如函数内有多个return语句,那么大概会导致返回范例推导不一致,从而导致编译错误。
3、nullptr
在C++中NULL被定义为字面量0,所以大概会造成一些题目。由于0既能表示指针常量,又能表示整形常量。所以处于清晰和安全的角度,C++11中新增了nullptr表示空指针。
在C++中利用NULL大概会出现错误,如下代码就是一个示例:
- #include<iostream>
- using namespace std;
- void func(int arg)
- {
- cout << "void func(int arg)" << endl;
- }
- void func(int* arg)
- {
- cout << "void func(int* arg)" << endl;
- }
- int main()
- {
- func(NULL); //void func(int arg)
- func(nullptr); //void func(int* arg)
- return 0;
- }
复制代码 四、范围for
1、在过去我们利用C语言和C++98的时候我们对一个数组进行遍历的方式。
就是如下如许:
- #include<iostream>
- using namespace std;
- int main()
- {
- int array[] = { 1,2,3,4,5 };
- for (size_t i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
- {
- array[i] *= 2;
- }
- for (size_t i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
- {
- cout << array[i] << " ";
- }
- cout << endl;
- return 0;
- }
复制代码 我们对vector、list这种容器进行遍历是采用迭代器的方式,进行遍历的。2、咋i
2、我们在C++11中对数组和容器进行遍历,除了上述方法外,多了一种范围for
- #include<iostream>
- using namespace std;
- int main()
- {
- int array[] = { 2,3,4,5,6,9,1,8 };
- for (auto& e : array)
- {
- e *= 2;
- }
- for (auto e : array)
- {
- cout << e << " ";
- }
- cout << endl;
- return 0;
- }
复制代码 对范围for进行一点表明,auto是为了让这个循环可以对恣意范例的元素进行遍历,假如是对上述代码中的数组进行遍历,完全可以换成int。引用符号,是为了可以在遍历的时候对元素进行修改,假如不加引用符号,那么遍历的时候的数据就是原数据的临时拷贝,改变遍历时的数据并不能改变原数据。e是遍历的数组或容器中元素的名称。:这个是语法规则。array是遍历的数组的名字,遍历哪个数组大概容器,就是哪个数组大概容器的名字。
3、要利用范围for要满足的两个条件
①for循环的范围必须是确定的。数组的范围就是第一个元素到最后一个元素。容器的范围就是begin()到end()的范围。
②利用范围for的对象必须重载了++和==。由于范围for的本质就是迭代器,在编译阶段,编译器会将范围for转换为迭代器。而利用迭代器遍历,是必要对迭代器进行++并且利用==判断竣事的。
五、STL中的一些变化
C++11中STL库新增了四个容器,分别是array、forward_list、unordered_map和unordered_set
1、array容器
array容器有两个模版参数,第一个模板参数代表的是存储的范例,第二个模板参数是一个非范例模板参数,代表的是数组中可存储元素的个数。如:
- #include<iostream>
- #include<array>
- using namespace std;
- int main()
- {
- array<int, 10> a1;
- array<double, 20> a2;
- return 0;
- }
复制代码 array容器与普通数组对比
①array容器与普通数组一样,支持通过[ ]访问指定下标的元素,也支持利用范围for遍历数组元素,并且创建后数组的巨细也不可改变。
②array容器与普通数组的差别之处在于,array容器用一个类对数组进行了封装,并且在访问array容器时,会进行越界检查。用[ ]访问元素时采用断言检查,调用at成员函数访问元素时会抛非常。而对于普通数组来说,越界写操作时会报错,而越界读操作时一般不会报错。
☆array容器的对象是定义在栈上的,因此array容器不适合定义太大的容器。
2、forward_list容器
forward_list容器本质就是一个单链表
forward_list很少利用,由于:
①forward_list容器只支持头插头删,不支持尾插尾删。要尾插尾删的话就得遍历找尾,如许的时间复杂度是O(N)
②forward_list容器提供的插入函数叫做insert_after。这个函数是在当前位置的后面进行插入。其它容器的插入是在当前元素的前面进行插入,但是,要在当前元素的前面进行插入就要遍历找前一个元素,这个时间复杂度是O(N)
③forward_list容器提供的删除函数叫做erase_after。这个函数是删除当前位置的后一个元素。其它容器的删除是删除当前位置的元素,但是删除当前位置的元素必要找到前一个元素,这就必要遍历找前一个元素,如许时间复杂度也是O(N)
3、unordered_map和unordered_set容器
unordered_map和unordered_set容器底层都是采用哈希表实现的。
具体先容在其他章节。
六、字符串转换函数
C++11提供了各种内置范例与string之间相互转换的函数,好比to_string,stoi,stol,stod等函数
1、将内置范例转换成string范例
2、将string范例转换成内置范例
七、容器中的新方法
C++11中为每个容器都新增加了一些方法:
①提供了一个以initializer_list作为参数的构造函数,用于支持列表初始化。
②提供了cbegin和cend方法,用于返回const迭代器。
③提供了emplace系列方法,并在容器原有插入方法的底子上重载了一个右值引用版本的插入函数,用于提高向容器中插入元素的效率
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |