【c++】类和对象(六)深入了解隐式类型转换
🔥个人主页:Quitecoder
🔥专栏:c++笔记仓
朋友们大家好,本篇文章我们来到初始化列表,隐式类型转换以及explicit的内容
目录
- 1.初始化列表
- 1.1构造函数体赋值
- 1.2初始化列表
- 1.2.1隐式类型转换与复制初始化
- 1.3explicit关键字
1.初始化列表
1.1构造函数体赋值
在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值
class Date { public: Date(int year, int month, int day) { _year = year; _month = month; _day = day; } private: int _year; int _month; int _day; };虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化,构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值
1.2初始化列表
class Date { public: Date(int year,int month,int day) :_year(year) ,_month(month) ,_day(day) {} private: int _year; int _month; int _day; };初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个成员变量后面跟一个放在括号中的初始值或表达式
那么,为什么要使用初始化列表呢?它的优势在哪里呢?
我们来看构造函数对于下面类的初始化:
class Date2 { public: Date2(int year, int month, int day) { _n=10; _year = year; _month = month; _day = day; } private: int _year; int _month; int _day; const int _n; };我们发现const成员变量并不能用函数体进行初始化
int _year; int _month; int _day;
这三个成员既可以在函数体,又可以在初始化列表,但是类中包含以下成员,必须放在初始化列表位置进行初始化:
- 引用成员变量
- const成员变量
- 自定义类型成员(且该类没有默认构造函数时)
int _year; int _month; int _day; const int _n;
我们知道,这个只是一个声明,定义是对象实例化时候完成的,有些成员,必须在定义的时候进行初始化
初始化列表中的每个元素都直接对应一个成员变量或基类,允许在构造函数体执行之前对这些成员或基类进行初始化。这对于const成员变量、引用类型成员变量以及某些没有默认构造函数的类型尤其重要
Date2(int year, int month, int day) :_n(1) { _year = year; _month = month; _day = day; }初始化列表是每个成员变量定义初始化的位置
class Date2 { public: Date2(int year, int month, int day) :_n(1) { _year = year; _month = month; _day = day; } private: int _year; int _month; int _day; const int _n; };没有在初始化列表中显式初始化_year、_month、和_day这三个成员变量,它们仍然会在初始化列表阶段被默认初始化,然后在构造函数体内被赋新的值
对于基本类型(如int),如果它们未在类的初始化列表中显式初始化,则它们会进行默认初始化。对于类内的基本类型成员变量,默认初始化意味着不进行初始化(保留未定义值),除非它们是静态存储持续时间的对象(例如全局或静态变量,它们会被初始化为零)。然而,对于自动存储持续时间(如函数内的局部变量)的对象,如果未显式初始化,则其值是未定义的。在类构造函数中,成员变量的行为类似于局部变量,如果不在初始化列表中显式初始化,它们将不会被自动初始化
_n是通过初始化列表初始化的,因为它是const类型的,必须在那里初始化。而_year、_month、和_day虽然没有在初始化列表中被显式赋值,但它们会在构造函数体开始执行前完成默认初始化(对于基本数据类型,这意味着它们的初始值是未定义的)。然后,在构造函数体内,它们被赋予新的值
因此,可以说成员变量_year、_month、和_day先经历了默认初始化(在这个场景下,这意味着它们的值是未定义的),然后在构造函数体内被赋值
我们不妨提到前面讲的声明时给缺省值:
private: int _year=1; int _month; int _day; const int _n;
缺省值的本质就是给初始化列表用的
Date2(int year, int month, int day) : _n(1), _year(year), _month(month), _day(day) { // 构造函数体可以留空,因为所有成员变量都已经在初始化列表中初始化 }在这个版本中,所有成员变量都是通过初始化列表直接初始化的,这是推荐的做法,特别是对于复杂类型或类类型的成员变量
引用类型必须在定义的时候初始化,所以也得使用初始化列表
class A { public: A(int a=0) :_a(a) {} private: int _a; }; class Date2 { public: Date2(int year, int month, int day,int x) :_n(1) ,_year(year) ,_month(month) ,_day(day) ,_ref(x) { } private: int _year=1; int _month; int _day; const int _n; int& _ref; A aa; };这里aa也会走初始化列表,来调用它的默认构造函数
我们可以在初始化列表来直接控制自定义类型的初始化
Date2(int year, int month, int day,int x) :_n(1) ,_year(year) ,_month(month) ,_day(day) ,_ref(x) ,aa(1) { }初始化列表和构造函数共同定义了类对象的初始化行为。初始化列表提供了一种高效、直接初始化成员变量和基类的方式,而构造函数则完成剩余的初始化逻辑和设置,比如动态开辟一个数组进行赋值的时候,就用到函数体
成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
我们来看下面的代码:
class A { public: A(int a) :_a1(a) ,_a2(_a1) {} void Print() { cout A aa(1); aa.Print(); } public: A(int a) :_a1(a) // 现在_a1首先初始化 ,_a2(_a1) // 然后是_a2 {} void Print() { cout public: C(int x) :_x(x) {} private: int _x; }; int main() { C cc1(1); C cc2 = 2; return 0; } public: C(int x) :_x(x) {} private: int _x; }; public: C(int x) :_x(x) {} C(const C& cc) { cout public: C(int x) :_x(x) {} private: int _x; }; int main() { C& cc3 = 2; return 0; } public: void Push(const C& c) { // } }; public: //explicit A(int a1, int a2) A(int a1, int a2) :_a1(a1) ,_a2(a2) {} private: int _a1; int _a2; }; int main() { A aa={1,2}; return 0; }





