目录
一 引入
二 初始化列表概念
三 初始化列表特性
1 引用和const
2 混合使用
3 自定义成员情况
四 初始化列表中的初始化顺序
五 总结
一 引入
构造函数体赋值
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量 的初始化,构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始 化一次,而构造函数体内可以多次赋值
二 初始化列表概念
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟 一个放在括号中的初始值或表达式。
class Date
{
public:
//Date(int year, int month, int day)
//{
// // 函数体内初始化
// _year = year;
// _month = month;
// _day = day;
//}
Date(int year, int month, int day)
:_year(year)
, _month(month)
, _day(day)
{
// 初始化列表
}
void Print()
{
cout << _year << '/' << _month << '/' << _day << endl;
}
private:
// 声明
int _year;
int _month;
int _day;
};
上面的例子中两个构造函数的结果是一样的。上面的构造函数(使用初始化列表的构造函数)显式的初始化类的成员;而没使用初始化列表的构造函数是对类的成员赋值,并没有进行显式的初始化。
初始化和赋值对内置类型的成员没有什么大的区别,像上面的任一个构造函数都可以。对非内置类型成员变量,为了避免两次构造,推荐使用类构造函数初始化列表。但有的时候必须用带有初始化列表的构造函数
三 初始化列表特性
1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
2. 类中包含以下成员,必须放在初始化列表位置进行初始化:
引用成员变量
const成员变量
自定义类型成员(且该类没有默认构造函数时)
总之 这三类不能在函数内部初始化
1 引用和const
class Date
{
public:
Date(int year, int month, int day)
{
// 函数体内初始化
_year = year;
_month = month;
_day = day;
int& _ref;//引用 -->error
const int _n;//const -->error
}
void Print()
{
cout << _year << '/' << _month << '/' << _day << endl;
}
private:
// 声明
int _year; // 缺省值
int _month;
int _day;
int& _ref;//引用
const int _n;//const
};
这是为什么?
引用和const只能初始化而不是赋值
之前有讲过,若是我们自己不去实现构造函数的话,类中会默认提供一个构造函数来初始化成员变量,对于【内置类型】的变量不会处理,对【自定义类型】的变量会去调用它的构造函数。那么对于这里的_year, _month, _day, _ref, _n都属于内置类型的数据,所以编译器不会理睬,可是引用变量, const
修饰的变量又必须要初始化,所以初始化列表诞生
有人说给个缺省值不就好了 这个办法确实是可以解决我们现在的问题,因为C++11里面为内置类型不初始化打了一个补丁,在声明的位置给到一个初始化值,就可以很好地防止编译器不处理的问题
但是现在我想问一个问题:如果不使用这个办法呢?你有其他方法吗?难道C++11以前就那它没办法了吗?
改正如下:
class Date
{
public:
//Date(int year, int month, int day)
//{
// // 函数体内初始化
// _year = year;
// _month = month;
// _day = day;
// int& _ref;//引用 -->error
// const int _n;//const -->error
//}
Date(int year, int month, int day)
:_year(year)
, _month(month)
, _day(day)
, _ref(year)
, _n(1)
{
// 初始化列表
}
void Print()
{
cout << _year << '/' << _month << '/' << _day << endl;
}
private:
// 声明
int _year; // 缺省值
int _month;
int _day;
int& _ref;//引用
const int _n;//const
};
2 混合使用
当然函数体内初始化和初始化列表可以混着用
Date(int year, int month, int day)
: _ref(year)
, _n(1)
{
// 函数体内初始化
_year = year;
_month = month;
_day = day;
}
3 自定义成员情况
class A
{
public:
A(int a = 0)//默认构造
:_a(a)
{
cout << "A(int a = 0)" << endl;
}
private:
int _a;
};
class Date
{
public:
Date(int year, int month, int day)
{
// 函数体内初始化
_year = year;
_month = month;
_day = day;
}
private:
// 声明
int _year;
int _month;
int _day;
A _aa;//调用默认构造
};
int main()
{
Date d(2023, 11, 14);
return 0;
}
1 如果A没有默认构造呢? -->初始化列表
class A
{
public:
A(int a)//这不是默认构造
:_a(a)
{
cout << "A(int a = 0)" << endl;
}
private:
int _a;
};
class Date
{
public:
Date(int year, int month, int day)
:_aa(10)
{
// 函数体内初始化
_year = year;
_month = month;
_day = day;
}
private:
// 声明
int _year;
int _month;
int _day;
A _aa;//没有默认构造
};
int main()
{
Date d(2023, 11, 14);
return 0;
}
2 如果不想使用A的默认构造的值呢?-->初始化列表
class A
{
public:
A(int a = 10)//默认构造
:_a(a)
{
cout << "A(int a = 0)" << endl;
}
private:
int _a;
};
class Date
{
public:
Date(int year, int month, int day)
:_aa(1000)
{
// 函数体内初始化
_year = year;
_month = month;
_day = day;
}
private:
// 声明
int _year;
int _month;
int _day;
A _aa;//调用默认构造
};
int main()
{
Date d(2023, 11, 14);
return 0;
}
尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量, 一定会先使用初始化列表初始化
四 初始化列表中的初始化顺序
成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后 次序无关
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();
}
先走的_a2, 此时_a1还没有初始化, 给的随机值,.
五 总结
初始化列表解决的问题:
1、必须在定义的地方显示初始化 引用 const 没有默认构造自定义成员
2、有些自定义成员想要显示初始化,自己控制
尽量使用初始化列表初始化
构造函数能不能只要初始化列表,不要函数体初始化?
不能,因为有些初始化或者检查的工作,初始化列表也不能全部搞定
80-100%初始化列表搞定,还有需要用函数体,他们可以混着用
我们来看看下面这段代码
class Stack
{
public:
Stack(int n = 2)
:_a((int*)malloc(sizeof(int)* n))
, _top(0)
, _capacity(n)
{
//...
//cout << "Stack(int n = 2)" << endl;
if (_a == nullptr)
{
perror("malloc fail");
exit(-1);
}
memset(_a, 0, sizeof(int) * n);
}
//...
int* _a;
int _top;
int _capacity;
};
class MyQueue
{
public:
MyQueue(int n1 = 10, int n2 = 20)//形成默认构造函数
:_s1(n1)
, _s2(n2)
{}
private:
Stack _s1;
Stack _s2;
int _size = 0;
};
int main()
{
MyQueue q1;
MyQueue q2(100, 100);//有初始化列表那就先走初始化列表
return 0;
}
这样我们就能控制我们怎样控制我们的默认构造.
这节是对上一节类和对象(2)构造函数的续集, 建议两个章节要有先后理解, 本节内容比较简单, 注重理解 上一节的博客【C++】类和对象(2)--构造函数-CSDN博客
继续加油!