Linux
Linux software install
Google repo的使用
Vector AP StartApplication编译脚本解析
Yocto的cmake版本升级
不能自动安装的解决方式
S32G-BSP35.0-SDK使用方法
S32G从SDK生成文件系统的制作过程
Linux Samba设置
Linux添加双网卡
S32G USB-Redirector安装指南
VS code自动生成Doxygen格式注释
Linux下使用多线程下载
使用pandoc 生成带中文的pdf
minicom无法输入的问题解决办法
使用 systemd unit 添加一条路由规则
CMake 教程
步骤 1:基本起点
步骤 2:添加lib库
步骤 3:为库添加使用要求
步骤 4:添加生成器表达式
步骤 5:安装和测试
步骤 6:添加支持测试仪表板
步骤 7: 添加系统内省
步骤 8:自定义命令和生成的文
步骤 9:打包安装程序
步骤 10:选择静态库或共享库
步骤 11:添加导出配置
步骤 12:打包 Debug 和 Release
添加虚拟网卡
Vector AP 去掉防篡改校验
Vector AP startapplication编译与使用
Vector AP问题汇总
Vector AP大型项目开发流程
Vector AP EM
Vector AP 最简单的开发示例
Linux kernel 版本降级
Vector AP StartApplicaiton
startappplication-machine-design
startapplicaiton-machine-integration
amsr-vector-fs-em-executionmanager-design
amsr-vector-fs-em-executionmanager
Vector AP 复杂模型的开发
第一章 Machine和MachineDesign
第二章 Execute Manager
第三章 Log
第四章 State Manager
第五章 State Manager 源码的理解
第六章 Someip daemon
第七章 IPC Service Discovery Daemon
crond的使用方法
解决蓝牙鼠标在 Ubuntu 中单位时间内断开的问题
VPS服务器自建教程
v2rayA的客户端使用配置
GDB调试指南入门篇:揭开程序运行世界的神秘面纱
GDB调试指南高级篇:成为调试专家,掌控程序的命运
Linux安装PyQt5并配置Qt Designer
ADB 命令大全
GoogleTest(一)
GoogleTest(二)简单的TEST宏
GoogleTest(三)简单类函数测试
C++ Template
1. 函数模板
2. 类模板
3. 非类型模板参数
软件版本号规范
EPOLL
C++手札
C++ 使用{}初始化有哪些好处?
现代 C++ decltype 深入解析
函数对象(functor)
Linux性能剖析:CPU、内存、网络与I/O压力测试
AP StateManager
C++ Lambda表达式
C++ 中的Lambda表达式
Lambda 表达式语法
Lambda 表达式的示例
手动发送UDP数据包
pyqt5生成的UI界面不能输入中文
自己搭建repo镜像
摄影
Sony仿富士PP值设置
诗词歌赋
本文档使用 MrDoc 发布
-
+
首页
C++ 使用{}初始化有哪些好处?
# C++ 使用{}初始化有哪些好处? C++ 对象初始化是一个关键过程,确保在对象的生命周期开始时为其分配适当的初始值。但是 C++ 中的对象初始化语法有很多选择,例如可以使用括号,等号,花括号。不同的初始化语法提供了灵活性,使得程序员可以根据需要选择合适的初始化方式。通过正确理解和使用对象初始化,可以编写更安全和更高效的代码。 使用 `{}` 来初始化对象在 C++ 中有许多好处,这些好处可以通过具体的例子来更好地理解。以下是一些主要的好处以及对应的示例: ### 1\. 统一初始化语法 花括号 `{}` 初始化语法可以在几乎所有地方使用,并且能够处理多种初始化场景,从而简化代码编写。 ```cpp std::vector<int> v{1, 3, 5}; // v的初始内容是1、3、5 ``` 在这个例子中,我们使用 `{}` 初始化一个 `std::vector` 对象,这种方法比传统的 `push_back` 方法更简洁。 **传统方法** 1. **构造函数初始化**: ```cpp std::vector<int> v; v.push_back(1); v.push_back(3); v.push_back(5); ``` 2. **赋值初始化**: ```cpp int a = 5; ``` 3. **列表初始化**: ```cpp int arr[] = {1, 2, 3}; ``` **现代方法(C++11及以后)** 使用花括号 `{}` 进行统一初始化: 1. **统一初始化**: ```cpp std::vector<int> v{1, 3, 5}; // v的初始内容是1、3、5 int a{5}; // a的值是5 int arr[]{1, 2, 3}; // arr的内容是1、2、3 ``` **对比** + **简洁性**:现代方法用 `{}` 一步到位,比传统的多步初始化简洁。 + **一致性**:现代方法统一了不同类型的初始化方式。 + **安全性**:现代方法避免了窄化转换问题,更安全。 ### 2\. 防止隐式缩窄转换 在C++中,隐式缩窄转换是指将一种类型的数据转换为较小范围的另一种类型时可能发生的数据丢失现象。为了防止这种情况的发生,可以使用 `{}` 初始化,它会强制编译器检查是否存在潜在的缩窄转换问题,并在必要时触发编译错误。 以下是通过逐步讲解,展示如何通过 `{}` 初始化来防止隐式缩窄转换的过程: 1. **定义一些浮点数表示的权重:** ```cpp double weightA = 5.7, weightB = 8.3, weightC = 4.6; int totalWeight = weightA + weightB + weightC; ``` 这里我们定义了三个 `double` 类型的变量 `weightA`、`weightB` 和 `weightC`。 2. **没有 `{}` 初始化:** + **操作:** 同样不会进行缩窄转换检查。表达式 `weightA + weightB + weightC` 结果为 `18.6`。 + **结果:** `18.6` 被截断为 `18`。 + **问题:** 数据丢失,浮点数的精度丢失。 + **操作:** 编译器不会进行缩窄转换检查。表达式 `weightA + weightB + weightC` 结果为 `18.6`。 + **结果:** 由于 `int` 类型不能表示小数部分,`18.6` 被截断为 `18`。 + **问题:** 数据丢失,浮点数的精度丢失。 + **使用圆括号 `()` 初始化 `int` 类型的变量:** ```cpp int totalWeight(weightA + weightB + weightC); // 可以,表达式的值被截断为int ``` + **使用赋值运算符 `=` 初始化 `int` 类型的变量:** ```cpp int totalWeight = weightA + weightB + weightC; // 可以,表达式的值被截断为int ``` 4. **有 `{}` 初始化:** + **操作:** 编译器会进行严格的缩窄转换检查。 + **结果:** 编译器检测到表达式 `weightA + weightB + weightC` 结果为 `18.6`,它不能安全地转换为 `int`。 + **问题:** 编译器报错,防止了数据丢失。 + **解决方案:** 提醒程序员显式处理类型转换,避免隐式缩窄转换。 + **使用 `{}` 初始化 `int` 类型的变量:** ```cpp int totalWeight{weightA + weightB + weightC}; // 错误!双精度浮点数的和可能无法表示为int ``` 通过对比可以看出: + **没有 `{}` 初始化时:** + 编译器不会进行缩窄转换检查,可能导致数据丢失(如浮点数转换为整数时的小数部分丢失)。 + 程序执行时会发生意外的数据截断,导致结果不准确。 + **有 `{}` 初始化时:** + 编译器会进行严格的缩窄转换检查,防止不安全的类型转换。 + 编译器报错,强制程序员处理潜在的数据丢失问题,确保程序的安全性和正确性。 因此,推荐在需要防止隐式缩窄转换时,使用 `{}` 初始化,以提高代码的安全性和健壮性。 ### 3\. 规避解析问题 在某些情况下,使用 `()` 初始化会被解析为函数声明,而不是对象初始化。使用 `{}` 初始化可以避免这种问题。 假设我们有一个类 `Device`,它有多个构造函数: ```cpp class Device { public: Device(int id, bool status); Device(int id, double voltage); Device(std::initializer_list<double> params); }; Device device1(42, true); // 调用第一个构造函数 Device device2{42, true}; // 调用了 std::initializer_list<double> 构造函数。在这种情况下,42 和 true 被转换为 double 类型(其中 true 转换为 1.0),形成了一个初始化列表 {42.0, 1.0}。 Device device3(42, 3.3); // 调用第二个构造函数 Device device4{42, 3.3}; // 调用 std::initializer_list 构造函数 Device device5(); // 最棘手的解析,声明了一个名为 device5 的函数 Device device6{}; // 调用默认构造函数 ``` 使用 `{}` 初始化 `device6`,我们避免了最棘手的解析问题,确保调用了默认构造函数。 1. **问题描述:函数声明的二义性** 在C++中,使用 `()` 初始化对象时,某些情况下编译器会将其解析为函数声明,而不是对象初始化。例如: ```cpp Device device5(); // 这实际上是声明了一个返回 Device 对象的函数,而不是创建一个 Device 对象 ``` 在这段代码中,编译器将 `Device device5();` 解析为一个名为 `device5` 的函数声明,该函数没有参数,并返回一个 `Device` 对象。 2. **解决方案:使用 `{}` 初始化** 使用 `{}` 初始化可以明确告诉编译器,这是一个对象初始化,而不是函数声明。例如: ```cpp Device device6{}; // 这明确告诉编译器创建一个 Device 对象,并调用其默认构造函数 ``` 3. **对比例子:类 Device 的多个构造函数** 假设我们有如下类 `Device`,它有多个构造函数,包括一个接受 `std::initializer_list<double>` 的构造函数: ```cpp class Device { public: Device(int id, bool status); Device(int id, double voltage); Device(std::initializer_list<double> params); }; ``` 4. **不同初始化方式的影响**看几个初始化例子,理解使用 `()` 和 `{}` 的区别: ```cpp Device device1(42, true); // 使用 () 调用第一个构造函数 Device(int, bool) Device device2{42, true}; // 使用 {} 调用 std::initializer_list<double> 构造函数 Device device3(42, 3.3); // 使用 () 调用第二个构造函数 Device(int, double) Device device4{42, 3.3}; // 使用 {} 调用 std::initializer_list<double> 构造函数 ``` ***Device(std::initializer_list\<double> params);这个是声明中的std::initializer_list\<double>很重要,正因为有了这个std::initializer_list\<double>,才会去调用Device(std::initializer_list\<double> params),如果没有这个std::initializer_list\<double>, 那么Device device4{42, 3.3}将调用Device(int id, double voltage)构造函数。*** 5. **最棘手的解析问题** 最棘手的问题出现在以下情况: ```cpp Device device5(); // 编译器将其解析为一个函数声明 Device device6{}; // 编译器明确地将其解析为对象初始化 ``` 通过使用 `{}`,我们避免了函数声明的二义性问题,确保 `device6` 被正确地初始化为一个 `Device` 对象。 使用 `{}` 初始化可以避免 C++ 中某些情况下的二义性问题,确保对象被正确地初始化。这种初始化方式特别适用于避免编译器将初始化表达式误解析为函数声明的情况。在上述例子中,`Device device5()` 声明了一个返回 `Device` 对象的函数,而 `Device device6{}` 确保了对象的初始化。 ### 4\. 支持非静态数据成员的初始化 花括号 `{}` 可以用于指定非静态数据成员的默认初始化值。 假设我们有一个类 `Sensor`,它有一些非静态数据成员: ```cpp class Sensor { private: int sensor_id{0}; // 使用花括号初始化,默认值是0 double sensor_voltage = 3.3; // 使用等号初始化,默认值是3.3 bool sensor_status(1); // 使用括号初始化,这是不允许的 }; ``` 1. **问题描述:初始化非静态数据成员** 在C++中,初始化非静态数据成员有多种方法,包括使用括号、等号和花括号。然而,这些方法并不都适用于所有情况。例如: ```cpp class Sensor { private: int sensor_id(0); // 这是不允许的,会产生编译错误 }; ``` 在这段代码中,使用括号 `()` 初始化 `sensor_id` 会导致编译错误,因为这种语法不被允许用于非静态数据成员的初始化。 2. **解决方案:使用花括号 `{}` 初始化**使用花括号 `{}` 可以明确且有效地初始化非静态数据成员。例如: ```cpp class Sensor { private: int sensor_id{0}; // 使用花括号初始化,默认值是0 }; ``` 这种方式不仅语法上是正确的,还能提高代码的可读性和一致性。 3. **对比例子:类 Sensor 的多种初始化方法** 假设我们有如下类 `Sensor`,它包含三种不同的初始化方法: ```cpp class Sensor { private: int sensor_id{0}; // 使用花括号初始化 double sensor_voltage = 3.3; // 使用等号初始化 bool sensor_status(1); // 使用括号初始化(错误) }; ``` 4. **不同初始化方式的影响** 看几个初始化例子,理解不同方式的区别: ```cpp class Sensor { private: int sensor_id{0}; // 使用花括号初始化,正确且推荐 double sensor_voltage = 3.3; // 使用等号初始化,正确 bool sensor_status(1); // 使用括号初始化,错误 }; ``` 5. **非静态数据成员的花括号初始化** 花括号初始化提供了一种一致且清晰的方式来为非静态数据成员赋予默认值。这不仅避免了某些语法错误,还使得代码更具可读性。例如: ```cpp class Sensor { private: int sensor_id{0}; // 使用花括号初始化,默认值是0 double sensor_voltage = 3.3; // 使用等号初始化,默认值是3.3 public: Sensor() = default; // 默认构造函数 }; ``` 在这段代码中,`sensor_id` 和 `sensor_voltage` 被正确地初始化为0和3.3,确保了对象在创建时具有合理的默认状态。 使用花括号 `{}` 初始化非静态数据成员,可以避免括号 `()` 初始化时的语法错误,并提供一种一致且清晰的方式来为成员变量赋予默认值。这种方式提高了代码的可读性和一致性,确保类的定义更加清晰。例如,在上述例子中,`sensor_id` 被正确地初始化为0,而 `sensor_voltage` 被正确地初始化为3.3。 ### 5\. 处理 `std::initializer_list` 构造函数 使用 `{}` 初始化时,会优先匹配 `std::initializer_list` 类型的构造函数,这在某些情况下会改变构造函数的选择。 假设我们有一个类 `Gadget`,它有多个构造函数,包括一个接受 `std::initializer_list<double>` 的构造函数: ```cpp class Gadget { public: Gadget(int id, bool active); Gadget(int id, double value); Gadget(std::initializer_list<double> il); }; Gadget g1(10, true); // 调用第一个构造函数 Gadget g2{10, true}; // 调用 std::initializer_list 构造函数(10 和 true 转换为 double) Gadget g3(10, 5.0); // 调用第二个构造函数 Gadget g4{10, 5.0}; // 调用 std::initializer_list 构造函数(10 和 5.0 转换为 double) ``` 使用 `{}` 初始化时,会优先匹配 `std::initializer_list` 类型的构造函数,这会导致与预期不同的构造函数被调用。 1. **问题描述:构造函数的选择** 在C++中,类可以有多个重载的构造函数。在某些情况下,使用 `()` 或 `{}` 初始化对象时,可能会调用不同的构造函数。例如: ```cpp class Gadget { public: Gadget(int id, bool active); Gadget(int id, double value); Gadget(std::initializer_list<double> il); }; ``` 2. **使用 `()` 初始化** 使用 `()` 初始化对象时,编译器会选择与参数类型最匹配的构造函数。例如: ```cpp Gadget g1(10, true); // 调用第一个构造函数 Gadget(int, bool) Gadget g3(10, 5.0); // 调用第二个构造函数 Gadget(int, double) ``` 3. **使用 `{}` 初始化** 使用`{}`初始化对象时,编译器会优先选择 `std::initializer_list` 类型的构造函数。例如: ```cpp Gadget g2{10, true}; // 调用 std::initializer_list 构造函数(10 和 true转换为double) Gadget g4{10, 5.0}; // 调用 std::initializer_list 构造函数(10 和 5.0 转换为double) ``` 这种情况下,即使参数类型与其他构造函数匹配,编译器仍会选择 `std::initializer_list` 构造函数。 4. **对比分析**在以下代码中,我们可以看到两种初始化方式的不同结果: ```cpp Gadget g1(10, true); // 调用第一个构造函数 Gadget(int, bool) Gadget g2{10, true}; // 调用 std::initializer_list 构造函数(10 和 true 转换为 double) Gadget g3(10, 5.0); // 调用第二个构造函数 Gadget(int, double) Gadget g4{10, 5.0}; // 调用 std::initializer_list 构造函数(10 和 5.0 转换为double) ``` 5. **需要注意的地方** 使用 `{}` 初始化时,需要特别注意是否存在 `std::initializer_list` 类型的构造函数,因为这会改变预期的构造函数调用。为了避免意外调用 `std::initializer_list` 构造函数,可以显式地使用 `()` 或确保 `{}` 初始化的参数类型和数量与预期的构造函数匹配。 使用 `{}` 初始化对象时,编译器会优先选择 `std::initializer_list` 类型的构造函数。这种行为在处理复杂的重载解析时需要特别注意,以避免意外调用 `std::initializer_list` 构造函数。在上述例子中,`Gadget g2{10, true}` 和 `Gadget g4{10, 5.0}` 都调用了 `std::initializer_list` 构造函数,而不是预期的其他构造函数。这种行为可能会导致意外的结果,因此在使用 `{}` 初始化时,需要特别注意构造函数的选择。 ### 6\. 默认构造函数和空的 `std::initializer_list` 当使用空的 `{}` 初始化时,它表示调用默认构造函数,而不是 `std::initializer_list` 构造函数。 假设我们有一个类 `Gadget`,它有默认构造函数和接受 `std::initializer_list<int>` 的构造函数: ```cpp class Gadget { public: Gadget() { std::cout << "Default constructor called" << std::endl; } Gadget(std::initializer_list<int> il) { std::cout << "std::initializer_list constructor called" << std::endl; } }; Gadget g1{}; // 调用默认构造函数 Gadget g2({}); // 用空列表调用 std::initializer_list 构造函数 Gadget g3{{}}; // 同上 ``` 1. **问题描述:默认构造函数与 `std::initializer_list` 构造函数** 在C++中,一个类可以有多个重载的构造函数,其中包括默认构造函数和接受 `std::initializer_list` 的构造函数。在某些情况下,使用 `{}` 进行初始化时,会涉及到调用哪一个构造函数的问题。 ```cpp class Gadget { public: Gadget() { std::cout << "Default constructor called" << std::endl; } Gadget(std::initializer_list<int> il) { std::cout << "std::initializer_list constructor called" << std::endl; } }; ``` 2. **使用空的 `{}` 初始化** 使用空的 `{}` 进行初始化时,会调用默认构造函数,而不是 `std::initializer_list` 构造函数。这提供了一种明确的方式来调用默认构造函数。 ```cpp Gadget g1{}; // 调用默认构造函数 ``` 3. **使用空列表 `()` 初始化**使用 `()` 并传入空的初始化列表时,会调用 `std::initializer_list` 构造函数。 `Gadget g2({}); // 用空列表调用 std::initializer_list 构造函数 ` 4. **使用嵌套的 `{}` 初始化** 使用嵌套的 `{}` 进行初始化时,也会调用 `std::initializer_list` 构造函数。 ```cpp Gadget g3{{}}; // 用空列表调用 std::initializer_list 构造函数 ``` 5. **对比分析** 在以下代码中,我们可以看到三种不同的初始化方式及其结果: ```cpp Gadget g1{}; // 调用默认构造函数 Gadget g2({}); // 用空列表调用 std::initializer_list 构造函数 Gadget g3{{}}; // 用空列表调用 std::initializer_list 构造函数 ``` 结果将会分别输出: ``` Default constructor called std::initializer_list constructor called std::initializer_list constructor called ``` 6. **需要注意的地方** 使用 `{}` 初始化时,需要注意空的 `{}` 和嵌套的 `{}` 在构造函数调用上的区别。空的 `{}` 表示调用默认构造函数,而嵌套的 `{}` 则表示调用 `std::initializer_list` 构造函数。 使用 `{}` 初始化提供了一个明确且一致的语法,可以有效避免默认构造函数与 `std::initializer_list` 构造函数之间的歧义。空的 `{}` 会调用默认构造函数,而嵌套的 `{}` 会调用 `std::initializer_list` 构造函数。这使得代码在处理默认构造和`std::initializer_list`构造时更加明确。 ### 总结 综上我们可以看到: 1. 使用 `{}` 初始化不仅提供了统一的语法。 2. 能够防止隐式缩窄转换。 3. 对解析问题具有免疫力。 4. 支持非静态数据成员初始化。 5. 在处理 `std::initializer_list` 构造函数时更加明确。 这些优点使得 `{}` 初始化成为一种强大的工具,帮助开发者编写更安全、更高效的代码。
admin
2024年8月20日 10:31
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
分享
链接
类型
密码
更新密码