C++构造函数初始化列表注意的坑
原文链接:https://www.zhoubotong.site/post/87.html
之所以写这篇文章,是觉得里面有些细节如果不注意,很容易出错或踩坑,网上有很多教程对这块的描述部分存在错误。希望下面的介绍能给大家带来帮助。
大家知道当我们需要初始化类中的成员变量时,除了可以直接在构造函数里面进行直接赋值,还可以使用初始化列表的方式来对成员变量进行初始化。
提到这里,顺便说下什么是构造函数初始化列表。
C++初始化列表
语法
Contructor(type1 var1, type2 var2): m_var1(var1), m_var2(var2)
{
}
参数
属性 | 描述 |
---|---|
type1 | 形参 var1 的类型。 |
var1 | 形参 var1。 |
type2 | 形参 var2 的类型。 |
var2 | 形参 var2。 |
m_var1 | 成员变量 m_var1。 |
m_var2 | 成员变量 m_var2。 |
说明
我们使用初始化列表的方式,用形参 var1 初始化了成员变量 m_var1,用形参 var2 初始化了成员变量 m_var2。举个例子:
#include <iostream>
using namespace std;
class Student {
private:
char *m_name;
int m_age;
float m_score;
public:
Student(char *name, int age, float score);
void show();
};
// 采用初始化列表
Student::Student(char *name, int age, float score)
: m_name(name), m_age(age), m_score(score) {
// TODO
}
void Student::show() {
cout << m_name << " age is " << m_age << "score is " << m_score << endl;
}
int main() {
Student stu((char *)"鸠摩智", 28, 5);
stu.show();
Student *pstu =
new Student((char *)"慕容复", 27, 86); // 使用指针对象 new 实例化
pstu->show();
return 0;
}
上面的例子定义构造函数时并没有在函数体中对成员变量赋值,而是在函数首部与函数体之间添加了一个冒号:,后面紧跟m_name(name), m_age(age), m_score(score)语句,
可以理解成相当于函数体内部的m_name = name; m_age = age; m_score = score;也是赋值的意思。
注意:使用构造函数初始化列表并没有效率上的优势,仅仅是书写方便,当成员变量较多时这种写法非常简洁。
下面重点说下需要注意的地方:
初始化列表可以用于全部成员变量,也可以只用于部分成员变量。什么意思?比如我们只对 m_name 使用初始化列表,其他成员变量还是=赋值:
// 采用初始化列表
Student::Student(char *name, int age, float score)
: m_name(name) { // 只用于部分成员变量
m_age = age;
m_score = score;
}
输出结果是一样的。 注意!注意!注意!,成员变量的初始化顺序与初始化列表中列出的变量的顺序无关,它只与成员变量在类中声明的顺序有关。
请大家仔细看下面的代码:
#include <iostream>
using namespace std;
class Student {
private:
int m_age;
float m_score;
public:
Student(float s);
void show();
};
Student::Student(float s) : m_score(s), m_age(m_score) {} //注意这里:我们将m_score放在了m_age的前面,看起来是先给m_score赋值,再给m_age赋值(m_age=m_score),其实不是的
void Student::show() { cout << m_age << ", " << m_score << endl; }
int main() {
Student stu(99);
stu.show();
return 0;
}
结论
所以成员变量的赋值顺序由它们在类中的声明顺序决定,在 上面的Student 类中,我们先声明的 m_age,再声明的 m_score,
给 m_age 赋值时,m_score 还未被初始化,所以输出的m_age的值是默认类型的0;给 m_age 赋值完成后才给m_score 赋值,此时 m_score 的值才是99。
如果大家对上面理解了,我们再看下面的代码,大家可以猜下输出啥?
#include <iostream>
using namespace std;
class Student {
private:
int m_age;
float m_score;
public:
Student(float s);
void show();
};
Student::Student(float s) : m_score(s), m_age(m_score) {
m_age = m_score;
m_score = s;
}
void Student::show() { cout << m_age << ", " << m_score << endl; }
int main() {
Student stu(99);
stu.show();
return 0;
}
输出:99, 99,大家请细品!写到最后再提下:构造函数初始化列表还有一个很重要的作用,那就是初始化 const 成员变量。
网上很多教程说初始化 const 成员变量的唯一方法就是使用初始化列表。这是错误的,描述的不够严谨,为什么这样说,我们来看网上很多教程的例子:
#include <iostream>
using namespace std;
class Student {
public:
Student(string name, int age) {
m_name = name;
m_age = age;
}
void show();
private:
const string m_name;
int m_age;
};
void Student::show() { cout << m_name << ", " << m_age << endl; }
int main() {
Student stu("鸠摩智", 28);
stu.show();
Student stu1("慕容复", 27);
stushow();
return 0;
}
输出:
于是网上很多教程说只能使用初始化列表的方式,来进行初始化,现在,按照网上的说法修改程序如下:
#include <iostream>
using namespace std;
class Student {
public:
Student(string name, int age) : m_name(name), m_age(age) {}
void show();
private:
const string m_name;
int m_age;
};
void Student::show() { cout << m_name << ", " << m_age << endl; }
int main() {
Student stu("鸠摩智", 28);
stu.show();
Student stu1("慕容复", 27);
stushow();
return 0;
}
这次在构造函数上面,使用了初始化列表的方式,初始化了 const 成员变量,这次程序没有报错,因此,const 成员变量只可以使用初始化列表的方式进行初始化( 误人)。
这个描述是错误的,并不是 const 成员变量只能使用初始化列表。在构造函数里面初始化赋值是没有任何问题的。
针对上面的问题我们来改下代码。注意这里只是改变了m_name的数据类型为指针型。
#include <iostream>
using namespace std;
class Student {
public:
Student(char *name, int age) { // 使用构造方法初始化赋值
m_name = name;
m_age = age;
}
void show();
private:
const char *m_name; // 注意这里是指针变量的常量
int m_age;
};
void Student::show() { cout << m_name << ", " << m_age << endl; }
int main() {
Student stu((char *)"鸠摩智", 28);
stu.show();
Student stu1((char *)"慕容复", 27);
stushow();
return 0;
}
所以类中对于 const 修饰,既可以使用初始化列表的方式赋值,也可以使用构造函数的方式赋值。