C++11相比C++98和03而言,它带来了数量可观的变化,其中包含了140个特性,以及对C++03标准中600个缺陷的修正,这使得C++11更像是从 C++98和03中孕育出的一种新语言
C++11能够更好的用于系统开发和库开发,语法更加泛化和简单化,更加稳定安全,不仅功能强大,而且能够提升程序员的开发效率,公司实际项目也用的比较多,我们需要作为重点去学习它…
C++11特性在编译器的契合度列表:链接
struct T
{
int a;
double b;
char c;
};
void Test()
{
int Array[] = { 1, 2, 3, 4, 5 };
int Array[10] = { 0 };
T t = { 1, 1.0, 'a' };
}
class Date
{
public:
Date(size_t _year, size_t _month, size_t _day)
: year(_year)
, month(_month)
, day(_day)
{}
private:
size_t year;
size_t month;
size_t day;
};
void Test_Init()
{
int a = { 0 };
int aa{ 0, 1, 2, 3, 4 };
int Array[]{ 0 }
// 初始化列表的应用 -- 对自定义类型,调用其构造函数进行初始化
vector<int> v{ 1, 2, 3, 4, 5 };
list<string> l{ "abc", "def", "hlj" };
int* pa = new int{ 0 };
int* pb = new int[10]{ 0, 1, 2, 3, 4 };
// 自动调用Date的构造函数进行初始化
Date* pd = new Date{ 2022, 9, 21 };
Date* ppd = new Date[3]{ {2022, 9, 21}, {2022, 9, 22}, {2022, 9, 23} };
// 键对值也是如此,因为map的底层pair也是一个模板,所以要套二个{}
map<string, string> dict { {"string", "字符串"} };
map<string, string> dict2 { {"left", "左边"}, {"right", "右边"}};
}
初始化列表的优点:
C++98使用new构建一段连续空间时,不能对其进行初始化,而C++11后就解决了该问题
C++98对自定义类型进行初始化时,只能初始化一次,而C++11可以批量初始化
缺点:
【std::initializer_list文档】
std::initializer_list是什么类型:
int main()
{
// the type of il is an initializer_list
auto il = { 10, 20, 30 };
cout << typeid(il).name() << endl;
return 0;
}
运行结果:class std::initializer_list
std::initializer_list使用场景:
std::initializer_list一般是作为构造函数的参数,C++11对STL中的不少容器就增加
std::initializer_list作为参数的构造函数,这样初始化容器对象就更方便了。也可以作为operator=的参数,这样就可以用大括号赋值
map构造函数
int main()
{
vector<int> v = { 1, 2, 3, 4, 5 };
list<int> lt = { 1, 2, 3, 4, 5 };
// 这里{"sort", "排序"}会先初始化构造一个pair对象
map<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} };
// 使用初始化列表对vector进行赋值拷贝操作
v = {10, 20, 30};
return 0;
}
模拟实现vector也支持使用{}初始化(简版):
namespace myvector
{
template<class T>
class vector
{
public:
typedef T* iterator;
// 套了一层初始化列表类,使用它的迭代器逐个赋值给vector
vector(initializer_list<T> l)
{
_start = new T[l.size()];
_finish = _start + l.size();
_endofstorage = _start + l.size();
iterator vit = _start;
typename initializer_list<T>::iterator lit = l.begin();
while (lit != l.end())
{
*vit++ = *lit++;
}
}
// 这里复用了vector的构造函数
vector<T>& operator=(initializer_list<T> l)
{
vector<T> tmp(l);
std::swap(_start, tmp._start);
std::swap(_finish, tmp._finish);
std::swap(_endofstorage, tmp._endofstorage);
return *this;
}
private:
iterator _start;
iterator _finish;
iterator _endofstorage;
};
}
简介:
在C++98中auto是一个存储类型的说明符,表明变量是局部自动存储类型,但是局部域中定义局部的变量默认就是自动存储类型,所以auto就没什么价值了
C++11中废弃auto原来的用法,将其用于实现自动类型推导。这样要求必须进行显示初始化,让编译器将定义对象的类型设置为初始化值的类型
void Test_auto()
{
// 普通类型
int a = 10;
auto b = a;
cout << typeid(b).name() << endl;
// 指针类型
auto pa = &a;
cout << typeid(pa).name() << endl;
// 常量普通类型
const int ca = 10;
auto cb = ca;
cout << typeid(cb).name() << endl;
// 常量指针类型
auto pcb = &ca;
cout << typeid(pcb).name() << endl;
// 引用类型
int d = 10;
int& pd = d;
auto ppd = pd;
cout << typeid(ppd).name() << endl;
// 常量引用类型
const int& pq = 10;
auto ppq = pq;
cout << typeid(ppq).name() << endl;
// 注意:auto虽然支持连续推导,但是类型必须一致
auto p1 = 10, p2 = 20, p3 = 30; // 可以
auto x1 = 1, x2 = 1.0, x3 = '\0'; // error
}
结论:对常量内置/自定义类型进行推导时,会忽略常量的属性,只有常量指针类型才不会忽略其常量属性,但是指针常量的const属性也会被忽略
例子1:
void Test_decltype()
{
// 关键字decltype将变量的类型声明为表达式指定的类型
int a = 10, &pa = a;
decltype(a) a2 = 20; // int
decltype(pa) pa2 = a; // int&
decltype(&a) pa2 = &a; // int*
const int b = 20, &pb = b;
decltype(b) b2 = b; // const int
decltype(pb) pb3 = pb; // const int&
decltype(&b) pb4 = &b; // const int*
decltype(a + 0) c = 30; // int
const Date d1(1, 1, 1);
decltype(d1) d2(d1); // const Date
}
结论:decltype类型说明符可以推导常量属性的内置类型和自定义类型
例子2:
void Test_decltype2()
{
// 关键字decltype将变量的类型声明为表达式指定的类型
int i = 20, *p = &i, &pi = i;
decltype(*p) pi2 = i; // pi2的类型是int&,因为解引用p后拿到的是左值,可修改
decltype(*p) pi3; // error,引用必须初始化
}
例子3:
void Test_decltype3()
{
int i = 10;
decltype((i)) pi = i; // int&
const int ci = 20;
decltype((ci)) pci = 30; // const int&
}
结论:无论什么类型,在decltype的推导中加上()时,都会被识别成引用类型
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
具体的问题我在C++初识的章节中有讲到,请转到【C++初识(四)】
简介:C++11引入了一种更简单的for语句,这种语句可以遍历容器和其他序列的所有元素,语法:
for (declaration : expression)
{
statement
}
语法解析:
expression:表示必须必须是一个序列,序列可以是{}括起来的初始值列表,数组,序列容器(vector, list, sting等等)关联式容器(set, map),它们共同点是都拥有返回begin()和end()的迭代器
declaration:表示定义一个变量,保证序列中每个元素都能转换成对应的变量类型
statement:迭代内容,一般显示输出该序列中元素的值
void Test_For()
{
// 初始化列表
for (auto& e : { 0, 1, 2, 3, 4 })
{
cout << e << " ";
}
cout << endl;
// 序列
int Array[] = { 10, 20, 30, 40, 50 };
for (auto& e : Array)
{
cout << e << " ";
}
cout << endl;
// 序列容器
vector<int> v{ 0, 1, 2, 3, 4, 5 };
for (auto& e : v)
{
cout << e << " ";
}
cout << endl;
// 关联式容器
map<string, string> dict{ {"left", "左边"}, {"right", "右边"} };
for (auto& kv : dict)
{
cout << kv.first << ": " << kv.second << endl;
}
cout << endl;
}
结论:其实范围for底层会转换成使用迭代器进行遍历,范围for并没有对遍历进行优化
新容器:
如果我们再细细去看会发现基本每个容器中都增加了一些C++11的方法,但是其实很多都是用得比较少的
比如提供了cbegin和cend方法返回const迭代器等等,但是实际意义不大,因为begin和end也是可以返回const迭代器的,这些都是属于锦上添花的操作
实际上C++11更新后,容器中增加的新方法最后用的插入接口函数的右值引用版本:
【vector右值引用插入版本】
【vector中push_back的右值引用版本】
【map中insert的右值引用版本】
【map中的右值引用插入版本】
这些接口都母庸置疑的提高了许多效率,但是效率是怎么提高的,等下学习右值引用就知道了!!!