跳到主要内容C++11 常见特性 | 极客日志C++
C++11 常见特性
综述由AI生成C++11 引入了统一初始化、自动类型推导关键字(auto、decltype、nullptr)、范围 for 循环、STL 新容器及接口、右值引用与移动语义、类的新功能以及可变参数模板。这些特性提升了代码的安全性、可读性和执行效率,是 C++ 现代化开发的核心基础。
星河入梦25 浏览 一、列表初始化
1. { } 初始化
C++98 中的 { } 只能用于数组和C 风格结构体;而 C++11 引入了统一初始化,几乎可以用在任何地方,包括普通变量、类对象和 STL 容器。
在 C++98 中,花括号 { } 被称为聚合初始化,有许多限制,只能用于数组,简单的 C 风格结构体(不能有构造函数、不能有 private 成员、不能有虚函数),STL 容器无法使用,如下所示:
int arr2[] = { 1, 2, 3 };
int arr3[10] = { 0 };
struct Point { int x, y; };
Point p = { 1, 2 };
std::vector<int> vec;
vec.push_back(1);
vec.push_back(2);
而在 C++11 中,就将 { } 提升为统一初始化,扩大了用大括号括起的列表 (初始化列表) 的使用范围,使其可用于所有的内置类型和用户自定义的类型。使用初始化列表时,可添加等号 (=),也可不添加。简单来说就是在 C++11 中一切都可以通过 { } 进行初始化。
- 基本数据类型也可以使用 { } ,并且可以省略 = 。
- 结构体与类对象:没有构造函数的简单结构体(聚合体)允许直接按成员声明顺序赋值;有构造函数的类创建对象时也会调用构造函数初始化。
- STL 容器:允许直接通过 { } 初始化,也可以省略等号。
2. std::initializer_list
std::initializer_list 是 C++11 才增加的一个类型,用于表示和访问一组特定类型的常量值的数组,主要用于支持使用花括号 { } 进行的列表初始化。当我们使用 { } 初始化时,编译器会将数据列表转换成一个 initializer_list。
使用场景
std::initializer_list 一般是作为构造函数的参数。在 C++11 中,STL 的容器中增加了许多这样的构造函数,例如 vector 和 map。
对于其他 STL 容器也有一个 initializer_list 参数的构造函数。也正是因为有了这样的构造函数,我们才可以使用 { } 直接初始化各个容器对象,如:
std::vector<int> v = { 1, 2, 3, 4, 5 };
除了构造时有 initializer_list 的使用,operator= 也重载了 initializer_list 参数的使用。
std::vector<int> v;
v = { 1, 2, 3, 4, 5 };
二、关键字:auto、decltype、nullptr
1. auto
在 C++98/03 标准中,auto 用于声明一个具有自动存储期的变量,实际用途几乎为零。在 C++11 中则彻底废弃了 auto 在 C++98 中的旧含义,赋予了它新的功能:自动类型推导。即可以自动推导变量类型,在处理复杂类型的时候,大大方便了我们在 C++ 中的使用。
std::vector<std::string> v = { "sort","insert","find"};
std::vector<std::string>::iterator it = v.begin();
auto it2 = v.begin();
2. decltype
关键字 decltype 可以将变量的类型声明为表达式指定的类型。比如,我们有一个 int 类型的变量,现在我们想定义一个与 a 相同类型的变量 b,要求不使用 int 关键字,则就可以通过 decltype 来帮助我们完成定义。
它也适用于表达式,比如,我们要定义一个与一个表达式结果相同的变量,则就可以使用 decltype 来解决。
3. nullptr
由于 C++ 中 NULL 被定义成字面量 0,这样就可能会带来一些问题,因为 0 既能指针常量,又能表示整形常量。所以出于清晰和安全的角度考虑,C++11 中新增了 nullptr,用于表示空指针,不会有歧义了。
三、范围 for 循环
C++11 中,引入了基于范围的 for 循环,它适用于对一个有范围的集合进行遍历。
int main() {
int arr[] = { 1, 2, 3, 4, 5 };
for (int e : arr) {
std::cout << e << " ";
}
return 0;
}
这里的意思就是:每次循环,arr 的当前元素会被复制到 e 中(值传递),然后循环会依次访问数组中每一个元素。并且它会自动判断结束。
四、认识 STL 中的变化
- std::array:静态数组,通过 T 控制类型,N 控制数组的大小。相比 C 风格数组,
std::array 内部会进行严格的检查,安全性能更高。
- std::forward_list:底层是一个单链表,使用方法和 list 很类似。
- unordered_map 和 unordered_set:前面已经讲过,使用方法和 set、map 类似。
它们其实就是 const 迭代器和 const 反向迭代器,实际意义不大,一般使用 begin、end、rbegin、rend 就够了。
这里的 emplace 需要我们理解了右值引用和可变模板参数才能理解。
移动构造和移动赋值可以大大节省我们使用容器的效率。
五、右值引用和移动语义
1. 左值引用和右值引用
传统的 C++ 语法中就有引用的语法,而 C++11 中新增了的右值引用语法特性,而我们之前使用到的引用叫做左值引用。无论左值引用还是右值引用,都是给对象取别名。
(1)概念
- 左值:就是一个我们可以对它取地址,一般可以对它赋值的可以表示数据的表达式,可以出现在赋值符号的左边。
- 右值:则是一个我们不可以对它取地址,也不可以修改的可以表示数据的表达式,只能出现在赋值符号的右边。通常是临时的,生命周期很短。
- 左值引用:就是对左值去取别名。
- 右值引用:其实就是对右值取别名,写法不同,使用
&&。
(2)区别
- 左值引用:只能引用左值,不能引用右值。但是 const 左值引用既可引用左值,也可引用右值。
- 右值引用:只能引用右值,不能引用左值。但是右值引用可以 move 以后的左值。可以取地址和被修改。
2. 移动语义(移动构造和移动赋值)
namespace MyCreate {
class string {
public:
string(const char*) :_size(strlen(str)) , _capacity(_size) {
std::cout << "string(char* str)" << std::endl;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
void swap(string& s) {
::swap(_str, s._str);
::swap(_size, s._size);
::swap(_capacity, s._capacity);
}
string(const string& s) :_str(nullptr) {
std::cout << "string(const string& s) -- 深拷贝" << std::endl;
string tmp(s._str);
swap(tmp);
}
string& operator=(const string& s) {
std::cout << "string& operator=(string s) -- 深拷贝" << std::endl;
string tmp(s);
swap(tmp);
return *this;
}
~string() {
delete[] _str;
_str = nullptr;
}
void reserve(size_t n) {
if (n > _capacity) {
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
const char* c_str() const { return _str; }
private:
char* _str;
size_t _size;
size_t _capacity;
};
}
移动构造就是用一个**右值(通常是临时对象)**来构造一个新的对象,即:将参数右值的资源窃取过来,占位已有,那么就不用做深拷贝了。移动构造的代码如下所示:
string(string&& s) noexcept :_str(nullptr) ,_size(0) ,_capacity(0) {
std::cout << "string(string&& s) -- 移动拷贝构造" << std::endl;
swap(s);
}
移动赋值就是用一个右值来给一个已经存在的对象赋值,也就是窃取别人的资源来赋值自己。
string& operator=(string&& s) noexcept {
std::cout << "string& operator=(string&& s) -- 移动赋值重载" << std::endl;
swap(s);
return *this;
}
3. move 的作用
move 函数的作用为:将一个左值转换为右值引用。从而让编译器认为这个对象可以被'移动'(即可以调用移动构造或移动赋值),而不是被'拷贝'。
MyCreate::string copy2 = move(s1);
MyCreate::string copy3;
copy3 = move(s2);
4. 应用场景
对于左值引用的使用场景就是:做函数参数,或则做函数返回值,这样可以减少拷贝,提高效率。
对右值引用,它的价值可以总结为:进一步减少拷贝,弥补左值引用没有解决的场景。
- 函数返回时,对于深拷贝的类必须传值返回的情况。
- 在容器的插入接口中,如果插入的对象是右值,则可以使用移动构造来转移资源给数据结构中的对象。
5. 万能引用
在模板中的 && 代表了万能引用,它既可以引用左值,也可以引用右值。
template<typename T> void PerfectForward(T&& t) {
6. 完美转发
完美转发则可以在传参的过程中保留对象原生类型属性,如果是左值引用那就是左值引用,如果是右值引用,那就是右值引用。它必须在模板中使用,即必须配合万能引用一起使用。
六、类的新功能
1. 默认成员函数
原来 C++ 类中,有 6 个默认成员函数。而 C++11 则新增了两个:移动构造函数和移动赋值运算符重载。
- 如果你没有自己实现移动构造函数,且没有实现析构函数、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动构造。
- 如果你没有自己实现移动赋值重载函数,且没有实现析构函数、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。
- 如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。
2. default、delete 关键字
default 关键字可以显示指定对应的默认成员函数生成。
delete 关键字则和 default 相对,delete 可以指示编译器不生成对应函数的默认版本,使用方法和 default 一样,只需要在该函数声明加上 =delete 即可。
七、可变参数模板
1. 概念
在 C++11 以前,我们的模板中类模版和函数模版只能含固定数量的模版参数。而在 C++11 中增加了可变参数模板的概念,它可以让我们的参数可以传递很多个。
template <class ...Args> void ShowList(Args... args) {
如果我们要获取一个参数包的值,这里有两种方法:递归函数方式展开和逗号表达式展开。
void _ShowList() { std::cout << std::endl; }
template <class T, class ...Args> void _ShowList(T value, Args... args) {
std::cout << value << " ";
_ShowList(args...);
}
template <class ...Args> void PrintList(Args... args) { _ShowList(args...); }
template <class T> void PrintArg(T t) { std::cout << t << " "; }
template <class ...Args> void ShowList(Args... args) {
int arr[] = { (PrintArg(args), 0)... };
std::cout << std::endl;
}
2. STL 中的 emplace 系列
其中 emplace 对应 insert,都是插入的意思,emplace_back 对应 push_back 都是尾插的意思。可以看到,它们都是使用可变参数模板实现的,并且是万能引用。
在 C++11 之前,我们通常使用 push_back 等接口向容器添加对象,但这种方法只能通过传递对象来添加对象。
- 如果使用 push_back 来添加数据,需要先构造,再拷贝构造,此时就需要两次资源的拷贝。
- 而对于具有移动构造的就会先构造,再移动构造,此时拷贝资源也只需要一次。
- 而 emplace 只需要将参数一直向下传递,直接一次构造就可以完成插入了,效率比较高。
而 C++11 中一般都是移动构造,所以一般来说 emplace 系列和常规的插入(insert,push_back 等)的效率其实都差不多。
相关免费在线工具
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
- JSON 压缩
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
- JSON美化和格式化
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online