初始化列表
//初始化列表:以一个冒号开始,接着是一个以 逗号分隔的数据成员列表,每个“成员变量”后面跟着一个 放在括号中的
//初始值或表达式。
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、类中包含以下成员,必须放在初始化列表 位置进行初始化:
// A 自定义类型成员(且该类没有默认构造函数时)
// B const成员变量
// C 引用成员变量
A 自定义类型成员
a 用默认构造函数 初始化(不如用 初始化列表 去初始化)
class Time
{
public:
Time(int hour = 0) //默认构造函数
{
_hour = hour;
}
private:
int _hour;
};
class Date
{
public:
//要初始化_t 对象,可以在函数体内赋值,但是还是会走初始化列表(调用Time的默认构造)
Date(int year, int hour)
{
_year = year;
Time t(hour);
_t = t;
}
private:
int _year;
Time _t;
};
int main()
{
Date d(2022, 1);
return 0;
}
b 用初始化列表 初始化
class Time
{
public:
Time(int hour) //不是默认构造函数
{
_hour = hour;
}
private:
int _hour;
};
class Date
{
public:
//要初始化_t对象,只能通过初始化列表
Date(int year, int hour)
:_t(hour) //这儿的初始化列表相当于构造: Time _t(hour);
, _year(year)
{
}
private:
int _year;
Time _t;
};
int main()
{
//日期类对象整体定义
Date d(2022, 1);
return 0;
}
c 结论
//Ⅰ、自定义类型的成员推荐用 初始化列表初始化。
//(原因:(1)当自定义类型成员没有默认构造函数时,无法编译通过 (2)当自定义类型成员有默认构造函数时,也需要先走初始化列表,去调用默认构造;再进行拷贝构造,并赋值。)
//Ⅱ、初始化列表可以认为是 成员变量定义的地方。{初始化列表是 成员变量定义的地方,即使我们不写初始化列表,它也存在; 如果在初始化列表处定义了
// 自定义类型的成员变量,就直接用;如果没有在初始化列表处定义 自定义类型的成员变量,就会去调用自定义类型成员变量的默认构造函数(若默认构造函数不存在,就会编译报错) }
// {上面是针对初始化列表处对于自定义类型成员变量的处理。 如果在初始化列表处没有定义 内置类型成员变量,内置类型成员变量(就不会被处理),保持随机值 }
B const成员变量 C 引用成员变量
class Date
{
public:
Date(int year, int N, int x)
// 初始化列表是成员变量定义的地方(这里的定义可以可以理解为(初始化)赋值【开空间】)
:_N(N) // const成员变量 必须在定义的地方初始化(赋值),只有一次赋值的机会
, _ref(x) // 引用成员变量 必须在定义的地方初始化
{
_year = year;
}
private:
int _year = 0; //成员变量申明时,给的缺省值,是在 初始化列表 时没有显示给值就会用这个缺省值。
const int _N;
int& _ref;
};
int main()
{
int y = 0;
//这儿是对象整体定义的地方
Date d(2022, 3, y);
return 0;
}
D 总结
// a\ 自定义类型成员,一定会用初始化列表进行初始化。(初始化列表可以认为是成员变量定义{赋值、开空间}}的地方)
// b、 内置类型成员也推荐使用 初始化列表,当然内置类型在函数体内初始化也没有什么明显的问题。
// c、统一建议的话,能使用初始化列表就使用,使用初始化列表基本没什么毛病。
E 虽然大部分情况下,在初始化列表对内置成员变量进行初始化较好;但在某些情况下,在构造函数内部初始化成员变量更佳(规整)
class A
{
public:
/*A(int N)
:_a((int*)malloc(sizeof(int)*N))
,_N(N)
{
if (nullptr==_a)
{
perror("malloc fail");
}
memset(_a,0,sizeof(int)*N);
}*/
A(int N)
{
_N = N;
_a = (int*)malloc(sizeof(int) * N);
if (nullptr == _a)
{
perror("malloc fail");
}
memset(_a, 0, sizeof(int) * N);
}
//有些初始化工作还是必须在函数体内完成
private:
//声明
int* _a;
int _N;
};
int main()
{
A aa(10);
return 0;
}
F 成员变量在初始化列表中定义的顺序是按照成员变量声明的前后顺序来的
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();
//编译的结果为:1 随机值
return 0;
}
G 初始化列表的感悟
//所有成员都会走一遍初始化列表,因为初始化列表是成员定义(赋值)的地方。
//对于内置类型成员变量而言:首先,如果在初始化列表内定义了,直接用;其次,若在变量声明时有缺省值,就用缺省值;最后,如果前面两点都没有,就不处理,进入构造函数内部。
//对于自定义类型成员变量而言:首先,如果在初始化列表内定义了,直接用;其次,如果没有定义,就去调用默认构造函数(如果没有默认构造函数,就报错)。