这一篇文章我们主要讲一下如何初始化类成员,并给出一个初始化类成员的小技巧。我们都知道,我们会使用构造函数来初始化我们的类成员变量。
首先我们来举一个简单的小例子,展现一下构造函数的功能:
#include<iostream>
#include<string>
class Entity {
private:
std::string m_Name;
public:
Entity() {
m_Name = "Unkown";
}
Entity(const std::string& name) {
m_Name = name;
}
void PrintName() const {
std::cout << m_Name << std::endl;
}
};
int main() {
Entity e0;
e0.PrintName();
Entity e1("Cherno");
e1.PrintName();
std::cin.get();
}
我们可以看到,我们使用了两个构造函数,一个是调用了默认的构造函数,还有一个调用了输入一个string的构造函数,然后我们打印了它们的名字变量,分别是Unknown和Cherno,没有问题,很棒。
不过我们还有另一种构造函数的写法,称为列表化构造方法。我们展现一下该怎样用:
class Entity {
private:
std::string m_Name;
int m_Score;
public:
Entity()
:m_Name("Unknown"), m_Score(0)
{
}
Entity(const std::string& name, int score)
:m_Name(name), m_Score(score)
{
}
void PrintName() const {
std::cout << m_Name << std::endl;
}
};
可以看到,我们通过使用冒号,然后一个一个列出需要初始化的变量,就可以实现和构造函数初始化相同的功能了。其实冒号部分是可以与函数名字放在同一行的,但是切一行的话会看起来更整洁一些。
但是需要注意的一点是,这个初始化顺序一定要按照声明的顺序来,不过这个我自己尝试了一下,在VS2022上,默认的C++14版本,是可以调换顺序且输出正确结果的。不过按顺序来也没啥麻烦的。
这样做首先是一种更加简洁的语言风格,因为我们想象一下,如果这个类有十多个甚至更多成员变量,那我们可能得写上十几行几十行初始化,看起来就很麻烦,但是如果使用列表化初始化,那就没有这个问题了,所以听起来是不错的。
但是这样写不仅仅有简洁的效果,实际上列表化的写法也会一定程度上提高效率,我们举一个例子。
#include<iostream>
#include<string>
class Print {
public:
Print() {
std::cout << "Print number" << std::endl;
}
Print(int value) {
std::cout << "Print number " << value << "!" << std::endl;
}
};
class Entity {
private:
std::string m_Name;
Print p;
public:
Entity() {
m_Name = "Unknown";
p = Print(8);
}
};
int main() {
Entity e;
std::cin.get();
}
我们看上面的例子,我们不用列表化的方法,而直接用在函数体里面初始化成员的方式进行初始化。按道理来说我们应该只会构造一个Print对象,但是如果我们真的输出结果的话,会发现情况其实更加复杂一些:
神奇的是我们居然打印了两次!相当于我们构造了两次Print类型的对象,那么这个实际上发生了什么?它实际上做的事情是先调用了一次默认构造函数构造了一个Print变量,然后调用了有输入的构造函数构造了一个新的Print变量覆盖掉了旧的那个。
但是如果我们用列表的方式进行初始化,那么会发生什么呢?我们把构造函数改成如下这个样子:
Entity()
:m_Name("Unknown"), p(Print(8))
{
}
然后我们再运行一遍代码,看看结果是什么样子的:
很好,这样的话我们只初始化了一个Print类型的变量,所以如果使用列表化初始化方法,会提高我们的运行效率。