1.再谈构造函数
1.1构造函数体赋值
根据之前介绍的内容:在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值,我们之前使用的构造函数都叫做函数体内赋初值
class Date
{
public:
Date(int year = 2024, int month = 1, int day = 1)//使用全缺省,也是默认构造函数
{
//函数体内初始化,在函数体内进行赋值
_year = year;
_month = month;
_day = day;
}
private:
int _year;//变量声明
int _month;
int _day;
};
虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化,构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。
初始化与赋值区别:
1.初始化是在创建变量时为其赋予一个初始值。在构造函数中,初始化通常是在对象创建时对成员变量进行赋值。初始化可以在变量声明时进行,也可以在构造函数的初始化列表中进行(下面就介绍)。
2.赋值是在变量已经存在的情况下改变变量的值。赋值操作符=用于将一个值赋给一个已经存在的变量
3.初始化是在变量创建时进行的,而赋值是在变量已经存在的情况下进行的
4.初始化可以只进行一次,而赋值可以进行多次
5.在一些情况下,初始化可能比赋值更加高效,因为它可以在对象创建时直接将初始值传递给对象,而不需要额外的操作
1.2初始化列表
1.2.1格式和概念
初始化列表:成员变量定义处
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个成员变量后面跟一个放在括号中的初始值或表达式
这里的结果,和上面赋值构造的结果是一样的
那大家可能有疑问了? 之前函数体内赋值不是用的好好的嘛,来这个干嘛? 现在就来解释:
有三种函数体内赋值不行,只能使用初始化列表
类中包含以下成员,必须放在初始化列表位置进行初始化:
1.引用成员变量
2.const成员变量
3.自定义类型成员(且该类没有默认构造函数时)
1.2.2由来
情况1
class Date
{
public:
//这是第一种
Date(int year = 2024, int month = 1, int day = 1)
:_year(year)
,_month(month)
,_day(day) //这三个可以在这,也可以在函数体内
,_ref(year)
,_n(1) //这两个必须在这里
{
}
//这是第二种
// 两个地方可以混着用,这样也行
// Date(int year = 2024, int month = 1, int day = 1)
// : _ref(year)
// , _n(1) //这两个必须在这里
// {
// _year = year;//没有在初始化列表显示出来定义,但是也会定义和初始化,不过内置类型随机值。
//自定义类型调用自己的默认构造函数
// _month = month;
// _day = day;//这三个可以在这,也可以在初始化列表内
// }
private:
int _year;//这些都是声明,还没有空间
int _month;
int _day;
int& _ref;//引用,必须在定义的时候初始化
const int _n;//常量,必须在定义的时候初始化
};
int main()
{
//实例化对象
Date d1();//此时才定义,但是对象整体定义; 那每个成员在哪里定义呢?——就在初始化列表
return 0;
}
可以知道的是:在进去函数体之前,定义和初始化都已经完成了,函数体进行的只是单纯的赋值操作。
所有的初始化行为都是在初始化列表内完成的。如果在初始化列表里没有出现的话一般是会在初始化列表给他初始化为默认值(随机值或自己给的缺省值)
class Date
{
public:
//两个地方可以混着用,这样也行
Date(int year = 2024, int month = 1, int day = 1)
: _ref(year)
, _n(1) //这里给1,看结果
{
//_year = year;
//_month = month; 这几个可以在这里赋值,也可以在初始化列表进行初始化
//_day = day;//但是这三个可以进行赋值
}
private:
int _year = 1;//只给_year缺省值
int _month;
int _day;
int& _ref;//引用,必须在定义的时候初始化
const int _n = 2;//这里给2
};
int main()
{
Date a;
return 0;
}
如果在初始化列表里进行了显示地初始化,那就按照列表里进行(最优先); 没有那才会用缺省值;连缺省值都没有那就随机值了。
上述赋值结果:
情况2
class Stack
{
public:
Stack(int capacity)
{
//.......
}
//没有默认构造函数了
private:
int* _a;
int _top;
int _capacity;
};
class MyQueue
{
public:
//此时都是自定义类型,但是又没有默认构造函数;或者有但是不想用。就要自己初始化
MyQueue()
:_s1(4)//自己显示地初始化
, _s2(5)
{
}
private:
Stack _s1;
Stack _s2;
};
如果自定义类型没有默认构造函数。解决方案:
1.写出来默认构造
2.在初始化列表中写出来
1.2.3特性
1.每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
2.类中包含以下成员,必须放在初始化列表位置进行初始化:(在由来里讲了)
1.引用成员变量
2.const成员变量
3.自定义类型成员(且该类没有默认构造函数时)
4.尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化
5.成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
6.解决的问题:
必须在定义的地方显示地初始化:引用 const
没有默认构造函数的自定义成员
有些自定义成员想要自己控制自己的初始化
1.2.4特殊情况
对于自定义类型:
无论如何自定义类型的默认构造函数都会调用,但是如果我们给了初始化列表的值,那么自定义类型初始化的值,将是初始化列表的值,
如果没有初始化列表,将会调用自定义类型默认构造函数的缺省值
4. 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
在这c为什么是-8?不应该为300吗?
在类中定义顺序是按照声明的顺序来定义的,于初始化列表的顺序无关,在编译器中定义c的时候,b其实是随机值
1.3explicit关键字
构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值的构造函数,还具有类型转换的作用
用explicit修饰构造函数,将会禁止构造函数的隐式转换
- 构造函数是单参数
class A
{
public:
A(int a = 0)
:_a(a)
{ }
private:
int _a;
};
int main()
{
A a1(1);//这样创建对象大家都知道
A a2 = 2;//这样也可以:内置类型对象隐式转换为自定义类型对象
//能这样做,是A的int单参数构造函数支持的
//其实隐式转换中间产生一个临时变量,临时变量是A类型的
const A& ra = 3;//给临时变量起别名,这时临时变量会在引用的作用域结束时销毁
return 0;
}
没写完,不想写了.........