欢迎来到CILMY23的博客
🏆本篇主题为: 再识构造函数:初始化列表新方式
🏆个人主页:CILMY23-CSDN博客
🏆系列专栏:Python | C++ | C语言 | 数据结构与算法 | 贪心算法 | Linux
🏆感谢观看,支持的可以给个一键三连,点赞关注+收藏。
✨写在前头:
在之前我们花了大篇幅了解了构造函数,包括但不限于构造函数的详解,构造函数和析构函数的顺序,以及构造函数中特殊的拷贝构造函数。那这次的构造函数又会带给我什么惊喜呢?
目录
再见构造函数
1️⃣ 构造函数赋值
2️⃣ 初始化列表的概念
3️⃣ 为什么会有初始化列表?
4️⃣ 初始化列表的特点
5️⃣ 构造函数新的写法
6️⃣ explicit关键字
7️⃣ 多参数类型的隐式转换
再见构造函数
1.1 构造函数赋值
在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。
class Student
{
public:
//无参构造函数
Student()
{
strcpy(_name, "xxxxxx");
_age = 0;
strcpy(_ID, "xxxxxxx");
}
void Print()
{
cout << "学生姓名:" << _name << endl;
cout << "学生年龄:" << _age << endl;
cout << "学生学号:" << _ID << endl;
}
private:
char _name[20];
int _age;
char _ID[20];
};
int main()
{
Student stu1;//调用了无参的构造函数
stu1.Print();
return 0;
}
虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量 的初始化,构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始 化一次,而构造函数体内可以多次赋值
1.2 初始化列表的概念
初始化列表是C++中的一个功能,允许在构造函数中直接初始化成员变量,而非在构造函数体内赋值。初始化列表直接跟在构造函数的参数列表后面,以冒号开始,后面紧跟一个或多个用逗号分隔的初始化表达式。这些表达式直接对成员变量或自定义类型进行初始化。
例如:这里有个学生类,我们要用初始化列表。(在这里对字符的拷贝我们仍然采用C的方法来实现,利用strncpy或者strcpy,在之后我们会用string来实现这些)
class Student {
public:
// 使用初始化列表来初始化_age
Student(const char* name, int age, const char* ID)
: _age(age)
{
// 为_name和_ID赋值
strncpy(_name, name, sizeof(_name) - 1);
_name[sizeof(_name) - 1] = '\0';
strncpy(_ID, ID, sizeof(_ID) - 1);
_ID[sizeof(_ID) - 1] = '\0';
}
private:
char _name[20];
int _age;
char _ID[20];
};
int main()
{
Student student1("Alice", 20, "1234567890");
student1.Print();
return 0;
}
1.3 为什么会有初始化列表?
例如:就像下面这段代码一样,有些类的成员是必须要在初始化的时候定义的,这里的_n = -1;是会报错的。就比如const成员是这样的。那哪个地方是初始化的呢?那它就需要去构造函数去找空间给它初始化,但也会有安全性的问题,所以有了初始化列表这个概念。初始化列表是每个成员变量定义初始化的位置。
顺序是先走上面的初始化列表,然后再走函数体内的赋值修改,能用初始化列表就用初始化列表。
class Student
{
public:
//Student(const char* name, int age, const char* ID)
// : _age(age)
//{
// //赋值修改
// strncpy(_name, name, sizeof(_name) - 1);
// _name[sizeof(_name) - 1] = '\0';
//}
//构造函数
Student(const char* name, int age)
{
strncpy(_name, name, sizeof(_name) - 1);
_name[sizeof(_name) - 1] = '\0';
_age = age;
_n = -1; // 无法修改
}
private:
//声明
char _name[20];
int _age = 1; //缺省值
const int _n;
};
int main()
{
//对象实例化
Student student1("Alice", 20);
return 0;
}
1.4 初始化列表的特点
- 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
- 类中包含以下成员,必须放在初始化列表位置进行初始化,这三个在函数体内不能初始化:
✔️引用成员变量
✔️const成员变量
✔️自定义类型成员(且该类没有默认构造函数时) - 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量, 一定会先使用初始化列表初始化。
- 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后 次序无关
这些特点如下所示:
class A
{
public:
A(int x)
//初始化列表
:_a(1)
{
//赋值修改
_a = x;
}
private:
int _a;
};
class Student
{
public:
//初始化列表是每个成员变量定义初始化的位置
//能用初始化列表就用初始化列表
Student(const char* name, int age, int &x)
: _age(age)
, _n(1)
, _aa(1)//显式调用构造函数
, ref(x)
{
strncpy(_name, name, sizeof(_name) - 1);
_name[sizeof(_name) - 1] = '\0';
}
private:
//声明
char _name[20];
int _age = 1; //缺省值
//必须走初始化列表
//1. const 成员
//2. 引用成员变量
//3. 自定义类型成员
const int _n;
int& ref;
A _aa;
};
int main()
{
//对象实例化
int x = 0;
Student student1("Alice", 20,x);
return 0;
}
1.5 构造函数新的写法
构造函数结合初始化列表后,我们可以写成main函数中的另外两种cc的形式。
class C
{
public:
C(int x = 0)
:_x(x)
{}
private:
int _x;
};
class B
{
public:
B()
:_p(2)
,_p1((int*)malloc(sizeof(4)*10))
{
if (_p1 == nullptr)
{
perror("malloc fail");
}
}
private:
//缺省值是给初始化列表的
int _p = 1;
int* _p1 = (int*)malloc(sizeof(4));
};
int main()
{
B bb;
//构造函数的写法
C cc1(1);
//2构造一个临时对象,再拷贝构造
C cc2 = 2;
return 0;
}
其实通过这个例子我们可以发现,单参数构造函数支持隐式类型的转换,2构造一个临时对象,然后再拷贝构造
如果遇到同一个表达式连续步骤的构造,一般会被编译器优化。
隐式类型转换是有好处的,例如我们在栈传参的时候,可以直接将int类型的4转换成自定义类型。
1.6 explicit关键字
如果你不想让上述这种隐式类型转换发生,那你就可以加一个关键字 - explicit。
1.7 多参数类型的隐式转换
在目前的C++11中,支持多参数类型的隐式转换,C++98还不支持,用花括号括起来。
总结:
- 初始化列表直接跟在构造函数的参数列表后面,以冒号开始,后面紧跟一个或多个用逗号分隔的初始化表达式。
- 顺序是先走上面的初始化列表,然后再走函数体内的赋值修改
- 当出现函数赋值修改,缺省值,和初始化列表的时候,推荐是能用初始化列表就用初始化列表。
- 有些成员必须走初始化列表,1. const 成员 2. 引用成员变量 3. 自定义类型成员(没有默认构造的自定义类型成员)
- 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后 次序无关
- 缺省值是给初始化列表的。
- 单参数构造函数支持隐式类型的转换
- 临时变量具有常性,需要加const修饰。
- 构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值的构造函数,还具有类型转换的作用。
- explicit 可以限制隐式类型的转换
- C++11 支持多参数类型的隐式类型转换
感谢各位同伴的支持,本期C++就讲解到这啦,如果你觉得写的不错的话,可以给个一键三连,点赞,关注+收藏,若有不足,欢迎各位在评论区讨论。