C++11 新特性整理 (1)
C++11对模板细节的改进:
1. C++11改善了编译器的解析规则,尽可能地将多个右尖括号(>)解析为模板参数结束符,例如在C++98中:
Foo<A<int> > xx; // 两个右尖括号必须有空格,否则会被编译器解析成右移操作符 return 0;
在C++11中则完全取消了这种限制。
2. 模板的别名:
typedef可以重定义类型,例如:
typedef unsigned int uint;
但是无法重定义模板,在C++11中出现了重定义一个模板的语法:
using 的别名语法涵盖了typedef的全部功能:
template<typename Val> using map_str_int = std::map<std::string, Val>; map_str_int<int> map1;
而在C++98中,定义模板的别名只能通过一个外敷类来实现:
template<typename Val> struct map_str_int // 外敷类 { typedef std::map<std::string, Val> type; }; map_str_int<int>::type map1;
列表初始化:
C++11中引入了列表初始化的概念,C++98中,只有数组和结构体可以进行列表初始化:
// 数组 int array[] = { 1,2,3 }; // 结构体 struct A { int x; int y; } a = {1, 2};
C++11中,列表初始化的范围增大了,现在它可以用于任何类型对象的初始化:
class Foo { public: Foo(int) {} }; int main() { Foo a1(23); // 常规的初始化 Foo a2 = { 123 }; // 列表初始化,虽然有=,但是也是初始化 Foo a3 { 123 }; // 与a2等价 int b1 = { 3 }; int b2{ 3 }; // 列表初始化 int c1[3]{ 1,2,3 }; // POD类型的初始化 // plain old data , 可以直接使用memcpy复制的对象 struct A { int x; struct B { float y; float z; } b; }; A a = { 2, {2.3, 4.4} }; A b { 2,{ 2.3, 4.4 } }; // 指针初始化 int* d1 = new int{ 12 }; int* arr = new int[3] { 1,2,4 }; return 0; } // 列表初始化可以直接使用在函数的返回值上面 struct Value { int x; int y; Value(int x, int y) {} }; Value func() { return {12, 22}; }
列表初始化中的一些细节问题:
在这里需要区分一下:列表初始化被用于自定义类型的时候:
1. 自定义的类型中没有构造函数
聚合类型初始化,以拷贝的形式,用初始化列表中的值来初始化成员
// 结构体 struct A { int x; int y; } a = {1, 2}; // C++98 聚合类型初始化
2. 自定义的类型中有构造函数
利用构造函数进行初始化
struct B { int x; int y; B(int x, int y) {} } b = {1, 2};
在使用列表初始化时,如果类型满足以下的条件,C++会认为它是一个聚合体:
1. 类型是普通的数组
2. 类型是一个类,且满足:
a. 没有自定义的构造函数
b. 没有基类
c. 没有虚函数
d. 没有private或者protected的非静态数据成员(也就是说非静态的数据成员不能是private或者protected)
e. 不能有{}和=直接初始化的非静态数据成员
例如:
// 有private或者protected的数据成员 struct ST { int x; int y; private: int z; }; ST s(1, 2, 3); // invalid // 有private或者protected的静态数据成员 struct SU { int x; int y; protected: static int z; }; SU s(1, 2); // valid protected的时静态数据成员 // 有== {} 初始化的非静态数据成员 struct SN { int x; int y = 3; }; SN s(1, 4);
对于非聚合类型的对象,如果需要使用列表初始化,就需要自定义构造函数才可以.
聚合类型的定义是非递归的:当一个类的非静态成员是非聚合类型的时候,这个类也有可能是聚合类型。
例如:
struct ST { int x; double y; private: int z; }; struct Foo { ST st; // 非聚合类型 int x; double y; }; // 初始化 Foo foo{ {}, 1, 2.4 }; // 第一个{}相当于调用ST的无参构造函数
所以,列表初始话的过程可以总结为:对于聚合类型,使用初始化列表相当于对其中的每个元素分别赋值;对于非聚合类型,则需要先定义一个合适的构造函数,是用初始化列表相当于调用对应的构造函数。
STL中的容器也支持初始化列表的操作,例如:
// 初始化列表 int arr[] {1,3,4}; // container std::map<std::string, int> mp = { {"a", 2}, {"b", 4}, {"c", 6} }; std::set<int> ss {1,2,3,4}; std::vector<int> v {1,2,3,4};
STL中的容器是通过使用std::initializer_list实现对初始化列表的支持的,std::initializer_lis用来接收传进来的参数,然后将接收到的参数传入到容器中,例如,自定义一个容器:
// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。 #include "stdafx.h" #include <iostream> #include <memory> #include <map> #include <vector> class myVector { private: std::vector<int> content; public: myVector(std::initializer_list<int> list) //构造函数 { // 使用初始化列表相当于调用构造函数, 构造函数将传进来数据进行处理 for (auto iter = list.begin(); iter != list.end(); iter++) { // 将list中的数据放入容器 content.push_back(*iter); } } }; class myMap { private: std::map<std::string, int> content; public: using pair_t = std::map<std::string, int>::value_type; // using重定义 pair的类型 myMap(std::initializer_list<pair_t> list) { for (auto iter = list.begin(); iter != list.end(); iter++) { content.insert(*iter); } } }; int main() { myVector v1 {1,2,3,4}; myMap mp1{ {"a", 1},{"b", 2},{"c", 3} }; return 0; }
通过上述的方法,了解了std::initializer_list的工作原理,它用来接受初始化列表,此外,它还可以作为函数的参数进行传递,例如:(传递同类型参数)
template<typename T> void func(std::initializer_list<T> l) { for (auto iter = l.begin(); iter != l.end(); iter++) { std::cout << *iter << " "; } std::cout << std::endl; } int main() { func({ "hello","ya","oaix" }); return 0; }
std::initializer_list的细节介绍:
1. 首先,它是一个轻量级的容器,内部定义了iterator等容器必须的概念
2.他可以按照任意的长度接受初始化列表,但是列表中的元素必须是同一种类型的
3.3个成员接口,begin, end, size
4. 只能被整体初始化或者赋值
5. 返回的迭代器是只读的,不能用返回的迭代器对元素进行修改
实际上,std::initializer_list的效率是非常高的,因为他保存的不是初始化列表中元素的拷贝,而实元素的引用,所以不能用于函数返回值
防止类型收窄:
除了上面介绍的功能之外,初始化列表还有一个作用,那就是防止类型收窄,在以下的几种情况中,会出现类型收窄:
1. 从浮点隐式的转化为整数
’2.从高精度隐式的转化为低精度
3. 从整型数隐式的转化为浮点数,并且超出了浮点数的范围
4. 从整型数隐式的转化为一个长度较短的整型数,且超出了范围
如果使用初始化列表,就可以防止上述的错误:
int a = 1.1; // ok int b {1.2}; // 报错 float fa = 1e40; // ok float fb {1e40}; // 报错 超出范围 int x = 1024; char y = x; //ok char z {x}; // 报错
只要遇到类型收窄的情况,初始化列表就会报错。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)