C++基础专栏:http://t.csdnimg.cn/UjhPR
目录
1.定义初始化列表(Initializer List)
2.类成员初始化列表
3.无默认构造函数的类的默认初始化(C++11 及以后版本)
4.初始化器列表构造函数(C++11 及以后版本)
5.定义枚举类型(Enum Types)
6.代码块(作用域)
7.定义Lambda函数(Lambda Expressions)
8.总结
1.定义初始化列表(Initializer List)
列表初始化是一种全新的初始化方式,它可以用于任何类型的对象,包括基本类型、数组、结构体和类。
例如,下面的 C++ 代码展示了如何使用列表初始化:
int a[]{1, 3, 5, 77, 78}; //使用列表初始化初始化数组
int arr[] = {1, 2, 3, 4, 5}; // 初始化整数数组
std::vector<int> vec = {10, 20, 30}; // 初始化整数向量
struct Point { int x, y; };
Point p = {5, 7}; // 初始化结构体
int b{2352}; //初始化变量
在编程中,窄化转换是一种常见的问题。窄化转换是指一种可能丢失信息的转换,例如从浮点数转换到整数或从大整数转换到小整数。列表初始化可以帮助我们避免这种转换。
例如,下面的代码尝试用一个浮点数初始化一个整数:
int c{ 8.52 }; //编译错误
这段代码无法编译,因为 {8.52}
试图将浮点数转换为整数,这是一种窄化转换。通过这种方式,我们可以在编译时检测到这种可能的错误,防止数据丢失。
2.类成员初始化列表
在类的构造函数中,花括号可以用于成员初始化列表。
class MyClass {
public:MyClass(int x, int y) : a{x}, b{y} {}
private:int a, b;
};
在这个例子中,MyClass
的构造函数使用列表初始化来初始化 a, b
。这就保证了 MyClass
在对象创建时总是被正确地初始化。
使用列表初始化来初始化类的成员变量有几个好处。
1)它可以避免成员变量的未初始化。如果我们忘记在构造函数中初始化一个成员变量,那么这个成员变量的值就会是未定义的,这可能会引发各种难以预测的问题。使用列表初始化可以确保成员变量总是被初始化,即使我们忘记在构造函数中初始化它。
2)提高效率。成员初始化列表在对象创建时直接初始化成员变量,而不是先创建成员变量,然后再为其赋值。这意味着使用成员初始化列表通常比在构造函数体内为成员变量赋值更高效。例如:
class CMyTestClassA
{
public:CMyTestClassA(int i = 0): m_i{ i } {//1}
private:int m_i;
};class CMyTestClassAHelper1
{
public:CMyTestClassAHelper1() : m_a{ 100 } {//2}
private:CMyTestClassA m_a;
};class CMyTestClassAHelper2
{
public:CMyTestClassAHelper2() {m_a = { 100 };//3}
private:CMyTestClassA m_a;
};int main()
{CMyTestClassAHelper1 m;CMyTestClassAHelper2 n;return 0;
}
定义变量m在2处利用初始化列表构造初始化了m_a,在1处调用了一次CMyTestClassA的构造函数。
定义变量n在3处看似只写了一行代码m_a={100}, 殊不知这个地方实际上初始化了两次m_a, 第一次是利用初始化列表构造初始化了m_a,这个和m的m_a构造差不多; 第二次是m_a = { 100 },
这里赋值又调用了一次构造函数,实际上在3处调用了两次构造函数; 在类比较简单的时候,多走一遍构造流程算不了什么; 在类比较复杂的时候,就会严重影响程序的执行效率,多次初始化,也有可能给程序带来一些未知的影响。
3.无默认构造函数的类的默认初始化(C++11 及以后版本)
对于没有默认构造函数的类,花括号可以用于默认初始化。
class NoDefaultConstructor {
public:NoDefaultConstructor(int x) { /* constructor code */ }
};
NoDefaultConstructor a = {1000}; // 使用初始化列表构造函数
4.初始化器列表构造函数(C++11 及以后版本)
花括号可以用于调用初始化器列表构造函数,这在创建对象时非常有用。
std::vector<int> vec = {44,5,99};
std::map<int, std::string> myMap = {{1, "one"}, {2, "two"}};
之所以std::vector、std::map等容器可以用{}构造,那是因为有std::initializer_list的存在。std::initializer_list在中间起了一个桥梁的作用,{}表达式、std::initializer_list和容器构造之间的转换流程如下图所示:
std::initializer_list的用法可参阅我的另外一篇博客:
深入理解可变参数(va_list、std::initializer_list和可变参数模版)-CSDN博客
5.定义枚举类型(Enum Types)
{} 可以用于定义枚举类型,其中包含一组可能的值。例如:
enum myEnum {Opt1,Opt2,Opt3
};//C++11之后版本可以加class,限制枚举的作用域,如:
enum class myEnum {Opt1,Opt2,Opt3
};
6.代码块(作用域)
花括号用于创建代码块,也就是一个作用域。在一个代码块中,可以定义局部变量,控制变量的作用范围,以及变量的生命周期。代码块可以在函数、条件语句、循环语句等地方使用。如:
void exampleFunction()
{int x = 10;{int y = 20; // 局部变量 y 只在此代码块内可见}// y 在这里不可见
}
很多时候{}代码块配合RAII自动释放资源。如下:
void TimeWheelScheduler::Stop() {{std::lock_guard<std::mutex> lock(mutex_);stop_ = true;//当lock离开这个作用域时,它会自动调用mutex_的unlock}thread_.join();
}
在这个例子中,当lock
离开其作用域时,std::lock_guard
的析构函数会被调用,从而释放锁对象mutex_。这种自动管理锁方式避免了手动调用unlock
可能导致的错误。
C++惯用法之RAII思想: 资源管理-CSDN博客
7.定义Lambda函数(Lambda Expressions)
{} 可以用于定义Lambda函数。Lambda函数是一种匿名的函数对象,可以在函数中传递。例如:
auto lambda = [](int x) {return x * x;
};
8.总结
在 C++ 中,我们建议尽可能使用列表初始化(即大括号 {})。这种初始化方式可以提供更一致的语法,防止窄化转换,避免 “最令人困惑的语法” 问题,还可以用于初始化任何类型的对象,包括基本类型、数组、结构体,以及类的成员变量。此外,我们还应该养成总是初始化变量的习惯,以避免未初始化的变量引发的未定义行为。
通过掌握 C++ 的初始化规则,我们可以编写出更安全、更清晰、更易于理解的代码,这将有助于我们更好地表达思想,解决问题。