当前位置: 首页 > news >正文

Modern C++(五)初始化

5、初始化

5.1、初始化器

5.1.1、复制初始化

复制初始化指的是利用赋值符号(=)对变量进行初始化:

class MyClass {
public:MyClass(int val) : value(val) {std::cout << "Conversion constructor called." << std::endl;}MyClass(const MyClass& other) : value(other.value) {std::cout << "Copy constructor called." << std::endl;}
private:int value;
};int main() {// 1 复制初始化,调用转换构造函数将42隐式转换为MyClass对象MyClass obj = 42; // 2 复制初始化,调用拷贝构造函数MyClass obj2 = obj; return 0;
}

5.1.2、直接初始化()

T obj(value),直接调用匹配的构造函数而不需要额外的转换步骤

5.1.3、列表初始化{}

禁止缩窄转换(即不允许将一个可能导致数据丢失的值赋给对象);可用于聚合初始化(如数组、结构体等);如果类提供了初始化列表构造函数(std::initializer_list),列表初始化会优先调用该构造函数。

#include <string>std::string s1;           // 默认初始化
std::string s2();         // 不是初始化!// 实际上声明了没有形参并且返回 std::string 的函数 “s2”
std::string s3 = "hello"; // 复制初始化
std::string s4("hello");  // 直接初始化
std::string s5{'a'};      // 列表初始化(C++11 起)char a[3] = {'a', 'b'}; // 聚合初始化(C++11 起是列表初始化的一部分)
char& c = a[0];         // 引用初始化

要注意的如果没有参数,又想用直接初始化,不需要加括号,直接string s就可以了(默认初始化)。

  • 非局部变量的初始化:具有静态存储期的非局部变量的初始化,会作为程序启动的一部分在 main 函数的执行之前进行。所有具有线程局部存储期的非局部变量的初始化,会作为线程启动的一部分进行,并按顺序早于线程函数的执行开始。
    • 静态初始化:有两种形式,常量初始化,或者零初始化
    • 动态初始化:在所有静态初始化完成后,进行无序的动态初始化、部分有序的动态初始化、有序的动态初始化

5.2、默认初始化

不使用初始化器构造变量时执行的初始化

  • T 对象;
  • new T;

如果 T 是数组类型,那么该数组的每个元素都被默认初始化。

对于const修饰的对象,如果想用默认初始化,需要注意以下几点:

  • 对于非类类型:不能对 const 限定的对象进行默认初始化
const int x;  // 错误,不能对 const int 进行默认初始化
  • 类类型或者类类型数组:要对 const 限定的对象进行默认初始化,该类必须有一个可访问的默认构造函数
#include <iostream>class MyClass {
public:MyClass() {  // 默认构造函数std::cout << "Default constructor called" << std::endl;}
};class AnotherClass {
public:AnotherClass(int value) : data(value) {}  // 没有默认构造函数
private:int data;
};int main() {const MyClass obj1;  // 正确,MyClass 有默认构造函数// const AnotherClass obj2;  // 错误,AnotherClass 没有默认构造函数return 0;
}

默认初始化对于自动存储期的内置类型不会赋予特定的值,为了避免使用到不确定值而引发的潜在问题,在使用具有自动或动态存储期的对象之前,应该对其进行显式初始化

5.3、值初始化

以空初始化器构造对象时进行的初始化,值初始化会将内置类型初始化为0或者nullptr

  • T () // 创建无名临时对象
  • new T ()
  • 类::类(…) : 成员() { … }
  • T 对象 {};
  • T {} // 创建无名临时对象
  • new T {}
  • 类::类(…) : 成员{} { … }

上面创建无名临时变量有什么用呢?其实用的非常多:

// 如果 T 是基本类型(如 int, double),则初始化为 0 或 0.0。
int x = int();          // x = 0
double y = double();    // y = 0.0
int x = int{};  // x = 0// 如果 T 是类类型,则调用默认构造函数:
std::string s = std::string();  // 调用默认构造函数,s 是空字符串
std::string s = std::string{};  // s 是空字符串// 如果 T 是数组,则每个元素被值初始化:
int arr[5] = int();  // arr = {0, 0, 0, 0, 0}
// 如果 T 是聚合类型(如数组、结构体),可以初始化成员:
struct Point { int x, y; };
Point p = Point{};  // p.x = 0, p.y = 0

我们要养成使用值初始化的习惯,避免未定义的行为:

int x;          // 未初始化,可能是垃圾值
int y = int();  // y = 0
int z{};        // z = 0

可以将他们作为默认参数或者返回值:

std::string getDefaultString() {return std::string{};  // 返回空字符串
}

构造临时对象用于函数传参

void process(const std::string& s);
process(std::string{});  // 传入一个空字符串

在容器中填充默认值

std::vector<int> vec(10, int{});  // vec = {0, 0, 0, ..., 0}

对于某些类型(如 std::unique_ptr),T() 和 T{} 都返回 nullptr,可能不如直接 nullptr 清晰:

std::unique_ptr<int> p1 = std::unique_ptr<int>();  // p1 = nullptr
std::unique_ptr<int> p2{};                         // p2 = nullptr
std::unique_ptr<int> p3 = nullptr;                // 更清晰

优先使用T{},它是现代C++ 推荐的初始化方式,支持初始化列表,支持聚合类型,支持成员初始化。

5.4、零初始化

将一个对象的初始值设为零。零初始化没有专用语法,以下用法会将对象初始化为0

  • static T 对象 ;
  • T () ;
  • T t = {} ;
  • T {} ; (C++11 起)
  • CharT 数组 [ n ] = " 短序列 "; // 若初始化列表中的元素少于数组长度,剩余元素会被默认初始化为 0
  • int arr[3] = {0}; // 第一个元素设为 0,其余自动初始化为 0

5.5、复制初始化

从另一个对象初始化对象。

  • T 对象 = 其他对象; //
  • T 对象 = {其他对象}; // C++11起归类为列表初始化
  • f(其他对象) // 当按值传递实参到函数时。
  • return 其他对象; // 从按值返回的函数中返回时
  • throw 对象; catch (T 对象) // 按值抛出或捕获异常时
  • T 数组[N] = {其他序列}; // 作为聚合初始化的一部分,用以初始化每个提供了初始化器的元素
class A {
public:A(int) {}  // 非 explicit 构造函数
};A a = 1; 

语法上属于复制初始化,这里会发生隐式转换:将 1 转换为 A 类型的临时对象(通过 A(int) 构造函数),再调用复制构造函数 A(const A&),用临时对象初始化 a。但实际编译时,编译器会优化掉临时对象和复制构造函数(称为 返回值优化,RVO),直接调用 A(int) 构造函数。如果复制构造函数是私有的或被删除,这段代码会编译失败(即使有优化)。

5.6、直接初始化

以一组明确的构造函数实参对对象进行初始化。

  • T 对象 ( 实参 );
  • T 对象 { 实参 };
  • T ( 其他对象 )
  • static_cast< T >( 其他对象 )
  • new T(实参列表, …)
  • 类::类() : 成员(实参列表, …) { … }
  • 实参{ … } // 在 lambda 表达式中从按复制捕获的变量初始化闭包对象的成员

5.7、聚合初始化

从初始化器列表初始化聚合体

  • T 对象 = { 实参1, 实参2, … };
  • T 对象 { 实参1, 实参2, … };
  • T 对象 = { .指派符1 = 实参1 , .指派符2 { 实参2 } … };
  • T 对象 { .指派符1 = 实参1 , .指派符2 { 实参2 } … };

5.8、列表初始化 (C++11 起)

从花括号包围的初始化器列表列表初始化对象。

直接列表初始化:

  • T 对象 { 实参1, 实参2, … };
  • T { 实参1, 实参2, … }
  • new T { 实参1, 实参2, … }
  • 类 { T 成员 { 实参1, 实参2, … }; };
  • 类::类() : 成员 { 实参1, 实参2, … } {…

复制列表初始化:

  • T 对象 = { 实参1, 实参2, … };
  • 函数 ({ 实参1, 实参2, … })
  • return { 实参1, 实参2, … };
  • 对象 [{ 实参1, 实参2, … }]
  • 对象 = { 实参1, 实参2, … } // 在赋值表达式中,以列表初始化对重载的运算符的形参进行初始化

5.9、引用初始化

将一个引用绑定到一个对象。

非列表初始化:

  • T & 引用 = 目标 ;
  • T & 引用 ( 目标 );
  • T && 引用 = 目标 ;
  • T && 引用 ( 目标 );
  • return 目标 ; // 在返回引用的函数的定义中

一般列表初始化 (C++11 起)

  • T & 引用 = { 实参1, 实参2, … };
  • T & 引用 { 实参1, 实参2, … };
  • T && 引用 = { 实参1, 实参2, … };
  • T && 引用 { 实参1, 实参2, … };

5.10、常量初始化

设置静态变量的初值为编译时常量。常量初始化在实践中通常在程序被加载到内存时进行,作为程序运行环境的初始化的一部分。在定义常量时,如果依赖于未初始化的变量,会导致该常量不是常量表达式。

5.11、复制消除

在 C++ 里,当创建一个对象时,可能会涉及到复制构造函数或移动构造函数的调用,这些操作会带来一定的性能开销。复制消除技术允许编译器在某些情况下直接创建对象,而无需进行额外的复制或移动操作,从而提高程序的性能。

比较常见的有:

  • 函数返回值优化(RVO,Return Value Optimization)
class MyClass {
public:MyClass() {std::cout << "Default constructor" << std::endl;}MyClass(const MyClass& other) {std::cout << "Copy constructor" << std::endl;}~MyClass() {std::cout << "Destructor" << std::endl;}
};MyClass createObject() {return MyClass();
}int main() {MyClass obj = createObject();return 0;
}

按照常规情况,返回的临时对象会调用复制构造函数将其内容复制到 main 函数中的 obj 对象中。但由于复制消除技术,编译器会直接在 obj 的位置构造临时对象,从而省略了复制构造函数的调用。

  • 直接初始化临时对象
class MyClass {
public:MyClass() {std::cout << "Default constructor" << std::endl;}MyClass(const MyClass& other) {std::cout << "Copy constructor" << std::endl;}~MyClass() {std::cout << "Destructor" << std::endl;}
};int main() {MyClass obj = MyClass();return 0;
}

语句原本需要先创建一个临时的 MyClass 对象,然后使用复制构造函数将其内容复制到 obj 中。但编译器会进行复制消除,直接在 obj 的位置构造对象,避免了复制构造函数的调用。

相关文章:

  • 为什么晶振电路要并联1MΩ电阻?为什么有的并联了,有的又没有?
  • 华为云Flexus+DeepSeek征文 | 基于华为云ModelArts Studio搭建Chatbox AI聊天助手
  • 当 GitLab 服务器网络配置发生变化,如何修改
  • 基于python机器学习来预测含MLP决策树LGBM随机森林XGBoost等
  • ArkUI-X与Android联动编译开发指南
  • C++ 学习 C++独有的核心特性 2025年6月16日18:11:04
  • 什么是FlinkSQL中的时态表?以及怎么使用?
  • 智能制造——解读117页大型制造型集团五年发展战略规划项目规划方案【附全文阅读】
  • window显示驱动开发—渲染管道
  • 输入网址到网页显示
  • 设计模式-开闭原则(Open/Closed Principle, OCP)
  • FastAPI:(1)并发async与await
  • 用Keil调试出现 “not in scope“ 问题解决
  • 时序数据库的起源与基础概念简介
  • 数据结构第八章(三)-选择排序
  • 如何用div手写一个富文本编辑器(contenteditable=“true“)
  • AT_abc410_f [ABC410F] Balanced Rectangles 题解
  • 远程桌面连接 - 允许电脑从网络外部访问计算机
  • 视频设备:直联正常,通过卫星无画面,因为延迟太大
  • Flutter动画全解析:从AnimatedContainer到AnimationController的完整指南
  • 网站开发费用周期/百度开发平台
  • 网站做外链平台有哪些/女儿考试没圈关键词
  • 网站特效网/百度推广中心
  • 免费元素素材网站/推广是什么意思
  • 专业制作网站 上海/长沙网站推广有哪些啊
  • 33岁改行做网站建设/100个电商平台