文章目录
- ♫一.文章前言
- ♫二.C++11新特性
- ♫一.统一的列表初始化
- ♫二.std::initializer_list
- ♫三.声明
- ♫四.decltype关键字
- ♫五.nullptr
- ♫六.新增加容器---静态数组array、forward_list以及unordered系列
- ♫6.1unordered_map与unoredered_set
- ♫6.2array
- ♫6.3 forward_list(单链表)
♫一.文章前言
C++11文档链接:link
2011年之前,C++98(C++03)称为C++11之前的最新C++标准名称。相比于C++98/03,C++11则带来了数量可观的变化,其中包含了约140个新特性,以及对C++03标准中约600个缺陷的修正,这使得C++11更像是从C++98/03中孕育出的一种新语言。相比较而言,C++11能更好地用于系统开发和库开发、语法更加泛华和简单化、更加稳定和安全,不仅功能更强大,而且能提升程序员的开发效率,C++11的新特性将会分为三个文章进行讲解,这是第一节。
♫二.C++11新特性
♫一.统一的列表初始化
- 在C++98中,标准允许使用花括号{ }对数组或者结构体元素进行统一的列表初始值设定。比如下面的代码:
struct person
{
int _id;
string _name;
};
int main()
{
person p = { 21314341,"张三"};
int arr[] = { 1,2,3,4 };
return 0;
}
- C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号(=),也可不添加。例如下面的代码:
struct person
{
int _id;
string _name;
};
int main()
{
//int _a = 1;
int _a{ 1 }; //用1初始化_a变量
double _d{ 1.0 };
person s{ 12345,"李四" };
int arr[]{ 1,2,3,4 };
return 0;
}
- C++11中列表初始化也可以适用于new表达式中
int main()
{
int* p = new int[4] {1, 2, 3, 4};
return 0;
}
- 创建对象时也可以使用列表初始化方式调用构造函数初始化
struct Date
{
Date(int year,int month,int day)
:_year(year)
,_month(month)
,_day(day)
{}
private:
int _year;
int _month;
int _day;
};
int main()
{
//C++98支持的调用构造函数初始化
Date d1(2024, 4, 6);
//C++11,这里也会调用构造函数
Date d{ 2024,4,6 };
Date d ={ 2024,4,6 };
map<string, string> m{ {"string","字符串"},{"world","世界"} };
set<int> s{ 1,2,5,3 };
return 0;
}
♫二.std::initializer_list
initializer_list 文档:链接: link
int main()
{
auto il = { 10, 20, 30 };
cout << typeid(il).name() << endl;
//运行结果:class std::initializer_list<int>
return 0;
}
int main()
{
auto il1 = { 10, 20, 30 };
initializer_list<int> il2 = { 10,20,30 };
cout << sizeof(il1) << endl;
cout << sizeof(il2) << endl;
//运行结果
// 8
// 8
return 0;
}
根据文档以及上面的代码可以理解为:
initializer_list 类似于一个容器,但是它不存储数据,{10,20,30}这个数组被放到了常量区,然后initializer_list里面有两个指针,一个指向数组起始位置,一个指向数组的最后。
代码调试验证:
initializer_list有相关接口:
以代码的形式讲解:
int main()
{
auto il1 = { 10, 20, 30 };
initializer_list<int> il2 = { 10,20,30 };
cout << &il2 << endl;
cout << il2.begin() << endl; //返回指向的数组的第一个元素的指针
cout << il2.end() << endl; //返回指向的数组的最后一个元素的下一个元素的指针
cout << il2.size() << endl; //求指向的数组的大小
//运行结果:
//0053FBC4
//0053FBB0
//0053FBBC
//3
return 0;
}
从上面的运行结果来看, il2.begin() 返回的地址是被指向数组的起始位置,( {10,20,30}是才能放在常量区的,取不到地址的,这里取的地址是被拷贝到栈上的数组的地址)
std::initializer_list使用场景:
std::initializer_list一般是作为构造函数的参数,C++11对STL中的不少容器就增加std::initializer_list作为参数的构造函数(如下图),例如vector,map,list,set等这样初始化容器对象就更方便了。也可以作为operator=的参数,这样就可以用大括号赋值。
例如以下代码:
int main()
{
vector<int> v{ 1,2,3,4,5 }; //大括号进行初始化
v = { 8,13,1 }; //大括号赋值
//下面详解这一个怎么初始化的
map<string, string> dict = {{"string", "字符串"}};
return 0;
}
//下面详解详解{"string", "字符串"}怎么初始化dict的
map<string, string> dict = {{"string", "字符串"}};
//{"string", "字符串"}-->pair<const char*,const char*>
//但是map里面存储的键值对类型为pair<const key,T>
//那么pair<const char*, const char* >是怎么转换为pair<const key,T>的呢?
//这里是借助的pair的一个拷贝构造,pair的拷贝构造利用了模板
//pair的拷贝构造:template<class U, class V>
// pair (constpair<U,V>& pr);
//先利用pair的拷贝构造将pair<const char*, const char* >转换为pair<const string,string>
//然后再利用initializer_list 去构造初始化dict
♫三.声明
c++11提供了多种简化声明的方式,尤其是在使用模板时。
- auto
在C++98中auto是一个存储类型的说明符,表明变量是局部自动存储类型,但是局部域中定义局部的变量默认就是自动存储类型,所以auto就没什么价值了。C++11中废弃auto原来的用法,将其用于实现自动类型推断。这样要求必须进行显示初始化,让编译器将定义对象的类型设置为初始化值的类型。
例子:
int main()
{
int _a = 10;
auto p = &_a;
cout << typeid(p).name() << endl;
//运行结果:
//int *
map<string, int> mapcount;
string arr[] = { "apple","string","right","left","apple","right","right","left" };
for (auto& e : arr) //自动识别e的类型为pair<string,int>
{
mapcount[e]++;
}
//map<string, int>::iterator it = mapcount.begin();
auto it = mapcount.begin();
while (it != mapcount.end())
{
cout << it->first << ":" << it->second << endl;
++it;
}
return 0;
}
注意:不建议用auto去做返回值,因为有弊端,例如下面这种场景:
auto func()
{
int ret = 10;
return ret;
}
auto func2()
{
auto ret = func();
return ret;
}
auto func3()
{
auto ret = func2();
return ret;
}
auto func1()
{
auto ret = func3();
return ret;
}
/当我们想要知道func1中ret的类型时,还要去看func3的返回值,func3的返回值也是auto,又得去看func2的,如果一个项里面函数特别多时,则就会很麻烦。
♫四.decltype关键字
关键字decltype将变量的类型声明为表达式指定的类型。
举个例子:
int main()
{
int _a = 10;
decltype(_a) y; //用 _a 声明的类型为 y 的类型
cout << typeid(y).name() << endl;
//运行结果
// int
return 0;
}
使用场景:需要定义一个变量(对象等),但是需要根据另一个变量或则对象的类型来定义时,可以使用关键字decltype。
例如:
template<class T1,class T2>
void func(const T1 x, const T2 y)
{
decltype(x * y) ret;
ret = x * y;
cout << ret << endl;
}
♫五.nullptr
由于C++中NULL被定义成字面量0,这样就可能回带来一些问题,因为0既能指针常量,又能表示整形常量。所以出于清晰和安全的角度考虑,C++11中新增了nullptr,用于表示空指针。
♫六.新增加容器—静态数组array、forward_list以及unordered系列
如图:圈出来的四个容器为新增
♫6.1unordered_map与unoredered_set
unordered_map与unoredered_set的使用与相关接口以及模拟实现在上篇文章已经讲过,这里就不做详解(文章链接link)
♫6.2array
array文档链接link
array是一个静态数组,大小为N,数据存储类型为T。
array的相关接口:
函数名 | 作用 |
---|---|
size | 返回数组中元素的个数 |
empty | 判断是否为空 |
front | 返回第一个元素 |
[ ] | 下标随机访问 |
back | 返回最后一个元素 |
data | 返回指向第一个元素的指针 |
at(size_t i) | 返回数组中下标为i的元素的引用 |
演示代码:
C++11 array(静态数组)
int main()
{
array<int, 10> arr;
vector<int> v = { 1,2,3,4,5,6,7,8,9 };
for (size_t i = 0; i < v.size(); i++)
{
arr.at(i) = v[i];
}
for (auto e : arr)
{
cout << e << " ";
}
cout << endl;
cout << arr.size() << endl;
cout << arr.back() << endl;
cout << arr.front() << endl;
int* p = arr.data();
cout << *p << endl;
//运行结果:
1 2 3 4 5 6 7 8 9 -858993460
10
-858993460
1
1
return 0;
}
当数组没有初始化完时,剩余的没有被处理,为随机值。
array所支持的接口,vector基本上都能支持,并且vector容器使用比array更方便,接口也更多,所以容器array的出现,感觉多此一举。
♫6.3 forward_list(单链表)
forward_list 文档链接link
文档总结:forward_list是一个单链表,节点里面只有一个指向下一个节点的指针和一个存储数据的变量,因为它不方便找前一个,所以不支持当前位置插入与删除,只支持在当前位置的下一个节点后面插入,删除当前位置的下一个节点,但是支持头插头删(效率高),不支持尾删(因为要遍历找尾,效率低)。
代码演示:
//forward_list(单链表)
int main()
{
forward_list<int> flist;
flist.push_front(1);
flist.push_front(3); //头插
flist.push_front(2);
for (auto e : flist)
{
cout << e << " ";
}
cout << endl;
flist.insert_after(flist.begin(), 1000); //在第一个位置的后面插入1000
flist.insert_after(flist.begin(),100); //在第一个位置的后面插入100
flist.erase_after(flist.begin()); //删除第一个位置的后面的一个元素100
flist.sort(); //排序接口
for (auto e : flist)
{
cout << e << " ";
}
cout << endl;
return 0;
}
//运行结果:
2 3 1
1 2 3 1000
🐵🐵🐵🐵🐵🐵🐵🐵🐵🐵🐵🐵🐵🐵
本章节完~ 后续的关于C++11的新特性在下篇