一半烟火以谋生
一半诗意以谋爱
契子✨
我们之前已经讲过了构造函数的初始化:前期回顾-构造函数
但是难免会遇到连构造函数都不好解决的问题
比如:
错误示范
class A
{
public:
A(int n)
{
this->_a = n;
this->_b = n;
}
private:
const int _a;
int _b;
};
因为 const 修饰的变量具有常性,也就是只能读不能写
所以 const 修饰的变量只能在定义的时候初始化,我们可以在声明的时候给缺省值来解决这个问题
const int _a = 10;
但是铁铁们知道这么做的本质是什么吗?
接下来我将带大家介绍:
初始化列表介绍
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个 成员变量 后面跟一个放在括号中的初始值或表达式
以下就是我们的要传参初始化列表: |
class A
{
public:
A(int n)
:_a(n)
,_b(n)
{
}
private:
int _a ;
int _b;
};
当然我们也可以给 缺省值 进行无参的初始化定义 |
class A
{
public:
A(int n = 4)
:_a(n)
,_b(n)
{
}
private:
int _a ;
int _b;
};
我们还可以在初始化列表中进行 malloc 的动态空间开辟操作 |
class A
{
public:
A(int n = 4)
:_a(n)
, _b((int*)malloc(40))
{
memset(_b, 0, 40);
}
private:
int _a;
int* _b;
};
注意:
初始化列表本质可以理解为每个对象中成员定义的地方
所有的成员可以在初始化列表初始化,也可以在函数体内初始化
比如:以上代码我在初始化列表中定义了 10 个 int 类型的空间,并在函数体内全部初始化为 0
我们这里规定一下:在构造函数进行初始化操作叫做在函数体内部进行初始化,在初始化列表初始化时叫做在对象定义的时候进行初始化
可能你会问:为什么要专门规定一个对象的成员变量初始化的地方呢?普通的变量不管是在函数体内部初始化还是在初始化列表初始化,都没有什么影响
但是有些特殊的成员变量,比如:
引用成员变量
const成员变量
没有默认构造函数的自定义类型成员变量
是不能在函数体内部初始化的
初始化列表的必用场景
引用成员变量✨
错误示范
class A
{
public:
A(int n = 4)
{
_a = n;
}
private:
int& _a ;
};
为什么呢?因为引用必须在定义的时候初始化:
定义一般变量时: <1>声明:声明变量类型和名字 <2>定义:根据类型分配内存地址空间 <3>初始化:将初始值拷贝到变量的内存地址空间中 |
定义引用类型时: 将引用绑定到初始化对象 因此定义引用类型时必须有初始值对象,也就是必须在定义的时候初始化 |
正确写法
class A
{
public:
A(int n = 4)
:_a(n)
{
}
private:
int& _a;
};
因为初始化列表本质可以理解为每个对象中成员定义的地方,所以用初始化列表写
const 成员变量✨
因为 const 修饰的变量具有常性,所以必须在定义的时候初始化:
class A
{
public:
A(int n = 4)
:_a(n)
{
}
private:
const int _a;
};
自定义类型成员(且该类没有默认构造函数)✨
错误示范
class A
{
public:
A(int a,int b,int c)
{
this->_a = a;
this->_b = b;
this->_c = c;
}
private:
int _a;
int _b;
int _c;
};
class B
{
public:
B(int n = 4)
{
_cc = n;
_aa(0, 0, 0);
_bb(1, 1, 1);
}
private:
A _aa;
A _bb;
int _cc;
};
在构造函数的章节我们提过:
-
自动生成的构造会处理自定义类型,它会去调用自定义类型的默认构造
所以自定义类型会先调自己的默认拷贝构造,而这里我们恰恰没写呢?这就是报错的原因
没事~交给我们的初始化列表即可
正确写法
class A
{
public:
A(int a, int b, int c)
{
this->_a = a;
this->_b = b;
this->_c = c;
}
private:
int _a;
int _b;
int _c;
};
class B
{
public:
B(int n = 4)
:_aa(0, 0, 0)
,_bb(1, 1, 1)
{
_cc = n;
}
private:
A _aa;
A _bb;
int _cc;
};
这样我们就成功初始化了呢~
初始化的一些小细节
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
我们来看一下,之前写的日期类 Date :
虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化,构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。
简单来说:若是没有用初始化列表,在函数内的 初始化 语句并不是真正的初始化,而是相当于初赋值,只有最后的赋值等式才会初始化生效
Date(int year, int month, int day) { _year = year; _year = 10; _month = month; _month = 20; _day = day; _day = 30; }
比如说以上代码:最后初始化成功的就是:_year = 10,_month = 20,_day = 30
如果用我们的初始化列表的话
Date(int year, int month, int day) :_year(year) ,_month(month) ,_day(day) { }
以上是正确的写法,但是不能初始化两次:
错误示范
Date(int year, int month, int day) :_year(year) , _year(10) ,_month(month) ,_day(day) { }
但是初始化后,仍可以赋初值
Date(int year, int month, int day) :_year(year) ,_month(month) ,_day(day) { this->_year = 10; }
总结
每个成员变量在初始化列表中初始化只能初始化一次 |
类中包含以下成员,必须放在初始化列表位置进行初始化: |
<1>引用成员变量 |
<2>const成员变量 |
<3>自定义类型成员(且该类没有默认构造函数时) |
尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,
一定会先使用初始化列表初始化
注意:初始化列表在对象定义时,自动调用初始化列表,即使不写系统也会默认生成
如果我们不显式写出初始化列表,编译器会自动生成,对于内置类型,我们会对他进行初始化,对于自定义类型,会去调用它的默认构造函数 |
这里跟构造函数一样~
内置类型传缺省值和初始化列表联系
回顾一下我们构造函数里讲过的给内置类型传缺省值
这里传的缺省值是给谁用的呢?
此时的你心里估计已有了明确的答案 -- 初始化列表
#include<iostream>
#include<assert.h>
#include<cstdlib>
using std::cout;
using std::endl;
class A
{
public:
A()
: _b(5)
{
}
void Print();
private:
int _a = 10;
int _b = 20;
};
void A::Print()
{
cout << _a <<" "<<_b << endl;
}
int main()
{
A aa;
aa.Print();
system("pause");
return 0;
}
看看以上的程序的结果是什么呢?
首先我们赋予了内置类型缺省值,编译器会将缺省值给初始化列表
由 初始化列表 初始化,也就是 ~ 10 20
但是我们的初始化列表已将 _b 初始化为 5,说明不接受 _b 的缺省值
所以正确的结果是:10 5
成员变量的顺序
成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后
次序无关
#include<iostream>
#include<cstdlib>
using std::cout;
using std::endl;
class A
{
public:
A(int a)
:_a1(a)
, _a2(_a1)
{}
void Print() {
cout << _a1 << " " << _a2 << endl;
}
private:
int _a2;
int _a1;
};
int main() {
A aa(1);
aa.Print();
system("pause");
return 0;
}
看看以上的代码分析程序:
A、输出1 1B、程序崩溃C、编译不通过D、输出1 随机值
我们可以看到程序运行的结果为 D:
<1>根据成员变量声明的顺序,理应先初始化 _a2 但是 _a1 还没有初始化所以是随机值
<2>接下来到 _a1 初始化,就是传参初始化,所以是 1
先介绍到这里啦~
有不对的地方请指出💞