小伙伴们大家好,本片文章将会讲解 C++11中 隐式类型转换 的相关内容。
如果看到最后您觉得这篇文章写得不错,有所收获,麻烦点赞👍、收藏🌟、留下评论📝。您的支持是我最大的动力,让我们一起努力,共同成长!
文章目录
- `1. 关于C++11`
- ==<font color = blue><b>🎧1.1 C++11简介🎧==
- ==<font color = blue><b>🎧1.2 C++11官方介绍🎧==<br>
- `2. 统一的列表初始化`
- ==<font color = blue><b>🎧2.1 C/C++98 数组 & 结构体 初始化🎧==
- ==<font color = blue><b>🎧2.2 单/多参数类型的隐式类型转换🎧==
- `3. STL 中的 Initializer_list 构造函数`
- ==<font color = blue><b>🎧3.1 Initializer_list 🎧==
- ==<font color = blue><b>🎧3.2 Initializer_list 初始化容器 🎧==
1. 关于C++11
🎧1.1 C++11简介🎧
在2003年C++标准委员会曾经提交了一份技术勘误表(简称TC1), 使得 C++03 这个名字已经取代了 C++98 成为 C++11 之前的最新C++标准名称。
不过由于 C++03(TC1) 主要是对 C++98 标准中的漏洞进行修复,语言的核心部分则没有改动,因此人们习惯性的把两个标准合并称为 C++98/03 标准。从 C++0x 到C++11,C++ 标准10年磨一剑,第二个真正意义上的标准珊珊来迟。
相比于 C++98/03,C++11 则带来了数量可观的变化,其中包含了约 140 个新特性,以及对 C++03 标准中约 600 个缺陷的修正,这使得 C++11 更像是从 C++98/03 中孕育出的一种新语言。相比较而言,C++11 能更好地用于系统开发和库开发、语法更加泛华和简单化、更加稳定和安全,不仅功能更强大,而且能提升程序员的开发效率,公司实际项目开发中也用得比较多,所以我们要认真去学习。
🎧1.2 C++11官方介绍🎧
🎉官网链接: C++11
2. 统一的列表初始化
🎧2.1 C/C++98 数组 & 结构体 初始化🎧
在C/C++98中,标准允许使用花括号 { } 对数组或者结构体元素进行统一的列表初始值设定。比如:
struct Point
{
int _x;
int _y;
};
int main()
{
// struct Point类型的初始化
Point p = { 100, 99 };
int arr1[10] = { 10, 20, 30 };
return 0;
}
🎧2.2 单/多参数类型的隐式类型转换🎧
对于自定义类型,如果是单参数类型的构造函数,会发生隐式类型转换,例如:
class A
{
public:
A(int x)
: _x(x)
, _y(x)
{}
void Print()
{
cout << _x << ":" << _y << endl;
}
private:
int _x;
int _y;
};
int main()
{
// 隐式类型转换
A aa1 = 1;
A aa2 = { 2 };
// 直接构造
A aa3(3);
// 也可以省略等号
A aa4{ 1 };
return 0;
}
如果是多参数类型的构造函数,同样会发生隐式类型转换,例如:
class A
{
public:
A(int x, int y)
: _x(x)
, _y(y)
{}
private:
int _x;
int _y;
};
int main()
{
// 隐式类型转换
A aa1 = { 1, 2 };
// 也可以省略等号
A aa2{ 3, 4 };
// 可以省略等号
A aa3(4, 5);
return 0;
}
如果不想发生隐式类型的转换,可以在构造函数之前加上 explicit 关键字,例如:
class A
{
public:
// 不允许发生隐式类型转换
explicit A(int x, int y)
: _x(x)
, _y(y)
{}
// 不允许发生隐式类型转换
explicit A(int x)
: _x(x)
, _y(x)
{}
void Print()
{
cout << _x << ":" << _y << endl;
}
private:
int _x;
int _y;
};
int main()
{
// 下面两行会报错
A aa0 = 1;
A aa3 = { 1 };
A aa1 = { 1, 2 };
// 以下可以正常使用
A aa2{ 3, 4 };
A aa4{ 0 };
return 0;
}
关于单/多参数类型的构造函数的解释:
- 对于形如
A aa1 = { 1, 2 };
的构造,编译器处理时会发生以下两个步骤:- 首先会利用
{ }
中的值调用构造函数生成一个临时变量; - 对
aa1
进行拷贝构造。 - 但是编译器会优化成直接构造。
- 首先会利用
- 对于形如
A aa2{ 3, 4 };
的构造,就会直接调用构造函数,不会产生临时变量。 - 如果产生了临时变量,我们知道临时变量具有常性,就有:
- 如果用:
A& aa1 = { 1, 2 };
会报错。 - 得用:
const A& aa1 = { 1, 2 };
- 如果用:
3. STL 中的 Initializer_list 构造函数
我们这里先以
vector
为例。
🎧3.1 Initializer_list 🎧
initializer_list
是 C++11
引入的一种标准库类型,用于方便地初始化同一种类型的元素列表。initializer_list
允许通过大括号 { }
语法进行初始化,例如:
std::initializer_list<int> my_list = {1, 2, 3, 4, 5};
这里 my_list
是一个包含了整数元素 {1, 2, 3, 4, 5}
的 initializer_list<int>
对象。
主要特点包括:
- 不可变性: 一旦初始化完成,
initializer_list
中的元素不可再被修改。 - 轻量:
initializer_list
本身只包含指向数据的指针和长度信息,因此非常轻量,适合在函数参数传递和对象构造时使用。 - 语法简洁: 通过
{ }
初始化列表的语法,能够清晰地指定一组初始值,而不需要显式地调用构造函数。
底层实现逻辑:
namespace std
{
template<class T>
class initializer_list
{
public:
using value_type = T;
using reference = const T&;
using const_reference = const T&;
using size_type = size_t;
initializer_list() noexcept; // 构造一个空的 initializer_list
size_type size() const noexcept; // 返回列表中元素的个数
const T* begin() const noexcept; // 返回指向第一个元素的指针
const T* end() const noexcept; // 返回指向最后一个元素之后的位置的指针
};
}
🎧3.2 Initializer_list 初始化容器 🎧
当使用vector
时,每次都用不同数量的值初始化容器,那么就要写非常多的构造函数来实现这个需求。
但是用Initializer_list
就可以一劳永逸的解决这个问题。
int main()
{
// 用 10 个 1 构造 vector
vector<int> v(10, 1);
initializer_list<int> il = { 10,9,8,5 };
vector<int>(il);
vector<int> v1 = { 1,2,3,4,5,6 };
vector<int> v2({ 9,8,7,6,5,4 });
return 0;
}
两种构造的区别:
- 对于:
vector<int> v1 = { 1,2,3,4,5,6 };
- 编译器会先将
{ 1,2,3,4,5,6 }
识别成initializer_list
; - 然后调用
Initializer_list
的构造,中间生成临时变量; - 最后拷贝构造给
v1
; - 但是编译器会优化成直接构造。
- 编译器会先将
- 对于:
vector<int> v2({ 9,8,7,6,5,4 });
- 这就是直接构造。
- 总的来说,形如:
X自定义 = Y类型
一定发生了 隐式类型转换。X 支持 Y 为参数类型构造就可以。
对于 map 的 initializer_list 的构造
map 支持 pair 类型的 initializer_list 的构造:
这里的 value_type 就是 pair。
map构造代码示意:
int main()
{
// 方法一:
pair<string, string> kv1 = { "include", "包括" };
pair<string, string> kv2 = { "sort","排序" };
map<string, string> dict1({ kv1, kv2 });
// 方法二:
map<string, string> dict2 = { kv1, kv2 };
map<string, string> dict3 = {{"include", "包括"}, { "sort","排序" }};
return 0;
}
两种构造区别:
- 对于方法一:
- 因为
pair
支持双参数的构造函数,所以{ "include", "包括" }
、{ "sort","排序" }
会发生隐式类型的转换生成临时变量,然后再拷贝构造给kv1
,kv2
(编译器会优化成直接构造); - 因为
map
支持pair
类型的initializer_list
的构造,因此直接使用:map<string, string> dict1({ kv1, kv2 });
即可
- 因为
- 对于方法二:
{"include", "包括"}, { "sort","排序" }
会被识别成两个pair
类型;- 然后外层大括号会被识别成
initializer_list
,进行隐式类型转换,在进行拷贝构造(会被优化)。