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}; // 报错

只要遇到类型收窄的情况,初始化列表就会报错。

posted @   Alpha205  阅读(136)  评论(0编辑  收藏  举报
编辑推荐:
· 从 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)
点击右上角即可分享
微信分享提示