上篇C++57个入门知识点_30 父子类,成员类构造析构顺序(存在父子和成员类的构造析构:构造:父类->成员类->自己;析构:自己->成员类->父类;成员类:A类对象作为B类成员,A为B成员类或朋友类)介绍了父类,成员类的构造析构顺序。本篇将介绍与其紧密相关的初始化列表的概念。
总结:
- 初始化列表及其作用:
用于调用父类的有参构造并做初始化、用于对于自身成员初始化、对于常量成员的初始化
- 函数隐藏:
函数隐藏同数据隐藏类似:不同作用域里两个数据名称相同,由内向外逐步隐藏
- 函数隐藏需要的条件:
(1)作用域不同
(2)函数名相同
(3)优先匹配子类,没有的话匹配父类 ,若要直接调用父类写法:stu.CPerson::test();
(4)匹配时只看函数的名字,匹配时参数列表、调用约定,返回值均不考虑:子test(int n){}
父test(){}
main(){stu.test(5);}
调用首先匹配子类的test
函数,由于子类中参数列表是无参的,因此报错。
1. 初始化列表及其作用
用于调用父类的有参构造并做初始化、用于对于自身成员初始化、对于常量成员的初始化
。
1.1 调用父类的有参构造并做初始化
子类的构造会先调用父类的构造,如果父类的构造是有参的构造,调用父类的时候就得传递参数,此时C++中提供了初始化列表的语法,帮助指定调用父亲的某一个构造
。
CStudent() :CPerson(123){...}
就可以通过子类指定调用父类某一个构造,并传递参数
#include <iostream>
class CPerson
{
public:
CPerson(int nGender) {
m_nGender = nGender;
printf("CPerson(int nGender)\r\n");
}
~CPerson() {
printf("~CPerson()\r\n");
}
public:
int m_nPublic;
protected:
int m_nProtected;
public:
int m_nGender;
};
class CStudent :public CPerson
{
public:
//儿子指定调用父亲哪一个有参构造,子类给父类传参数
CStudent() :CPerson(123)
{
printf("CStudent()\r\n");
}
~CStudent() {
printf("~CStudent()\r\n");
}
private:
int m_nStuID;
};
int main(int argc, char* argv[])
{
CStudent stu;
return 0;
}
当父类存在多个构造函数时,可以通过子类的初始化列表指定父类调用的构造函数。
#include <iostream>
class CPerson
{
public:
CPerson(int nGender) {
m_nGender = nGender;
printf("CPerson(int nGender)\r\n");
}
CPerson(int nGender, int n) {
m_nGender = nGender;
printf("CPerson(int nGender,int n)\r\n");
}
~CPerson() {
printf("~CPerson()\r\n");
}
public:
int m_nPublic;
protected:
int m_nProtected;
public:
int m_nGender;
};
class CStudent :public CPerson
{
public:
//儿子指定调用父亲哪一个有参构造,子类给父类传参数
CStudent() :CPerson(123, 345)
{
printf("CStudent()\r\n");
}
~CStudent() {
printf("~CStudent()\r\n");
}
private:
int m_nStuID;
};
int main(int argc, char* argv[])
{
CStudent stu;
return 0;
}
运行结果:通过CStudent() :CPerson(123, 345){...}
调用了父类CPerson(int nGender, int n) {...}
的构造函数,并传递了参数
1.2 用于对于自身成员初始化
初始化列表不仅可以调用父类指定构造函数,还可以对当前自己类的成员进行初始化
CStudent() :CPerson(123, 345), m_nStuID(4)
中, m_nStuID(4)
实现了对子类成员的初始化
工程中常用写法为:
CStudent()
:CPerson(123, 345)
, m_nStuID(4)
{
}
#include <iostream>
class CPerson
{
public:
CPerson(int nGender) {
m_nGender = nGender;
printf("CPerson(int nGender)\r\n");
}
CPerson(int nGender, int n) {
m_nGender = nGender;
printf("CPerson(int nGender,int n)\r\n");
}
~CPerson() {
printf("~CPerson()\r\n");
}
public:
int m_nPublic;
protected:
int m_nProtected;
public:
int m_nGender;
};
class CStudent :public CPerson
{
public:
//儿子指定调用父亲哪一个有参构造,子类给父类传参数
//也可以对当前类的成员进行初始化
CStudent() :CPerson(123, 345), m_nStuID(4)
{
printf("CStudent()\r\n");
}
~CStudent() {
printf("~CStudent()\r\n");
}
private:
int m_nStuID;
};
int main(int argc, char* argv[])
{
CStudent stu;
return 0;
}
运行结果:子类父类中的成员均进行了初始化
1.3 对于const常量成员的初始化
C++57个入门知识点_02_const常量限制(解决C中define无法指明数据类型问题、常量一旦定义生命周期内不可修改、编译器在程序编译时期做的检查、编译时不确定的,才可以通过指针修改运行时的值)中我们知道const常量成员在定义的时候就需要赋值,其初始化有两种写法。
class CStudent :public CPerson
{
public:
CStudent():CPerson(123,345), m_nStuID(4)
{
printf("CStudent()\r\n");
}
~CStudent() {
printf("~CStudent()\r\n");
}
private:
int m_nStuID;
//const定义时就需要对其进行赋值,也可以使用初始化列表
const int n=4;
};
class CStudent :public CPerson
{
public:
CStudent() :CPerson(123, 345), m_nStuID(4),n(7)
{
printf("CStudent()\r\n");
}
~CStudent() {
printf("~CStudent()\r\n");
}
private:
int m_nStuID;
//const定义时就需要对其进行赋值,也可以使用初始化列表
const int n;
};
2. 父子类中的函数隐藏
(1)函数隐藏同数据隐藏类似:不同作用域里两个数据名称相同,由内向外逐步隐藏
(2)需要的条件:
- 作用域不同
- 函数名相同
- 优先匹配子类,没有的话匹配父类 ,若要直接调用父类写法:
stu.CPerson::test();
- 匹配时只看函数的名字,匹配时参数列表、调用约定,返回值均不考虑:子
test(int n){}
父test(){}
main(){stu.test(5);}
调用首先匹配子类的test
函数,由于子类中参数列表是无参的,因此报错。
2.1 父子类中的成员变量隐藏
前面我们学习过数据隐藏: 不同的作用里两个变量的名字是相同的,由内向外是逐步隐藏的
。参考C++57个入门知识点_09_ 作用域与数据隐藏(作用域的分类、数据隐藏、不同作用域中定义了相同名称的变量,其值不是覆盖,而是存储在不同的地址下,根据作用域(从内->外)查找使用、2.1例子)
在父子类中也存在同样的关系,在上面代码中,父亲有一个int m_nGender;
成员,子类中也定义一个int m_nGender;
#include <iostream>
class CPerson
{
public:
CPerson(int nGender) {
m_nGender = nGender;
printf("CPerson(int nGender)\r\n");
}
CPerson(int nGender, int n) {
m_nGender = nGender;
printf("CPerson(int nGender,int n)\r\n");
}
~CPerson() {
printf("~CPerson()\r\n");
}
public:
int m_nPublic;
protected:
int m_nProtected;
public:
int m_nGender;
};
class CStudent :public CPerson
{
public:
CStudent() :CPerson(123, 345), m_nStuID(4), m_nGender(456)
{
printf("CStudent()\r\n");
}
~CStudent() {
printf("~CStudent()\r\n");
}
private:
int m_nStuID;
int m_nGender;
//const定义时就需要对其进行赋值,也可以使用初始化列表
const int n = 4;
};
int main(int argc, char* argv[])
{
CStudent stu;
return 0;
}
运行结果:父子类中m_nGender
成员均实现了初始化
(1)父类子类均存在的同名成员变量
但是会有一个问题,当在子类的构造中使用如下方式进行赋值,调用的是父类还是子类呢?
得到的结果与数据隐藏的类似,在子类作用域中使用的是子类的成员,父类的成员被隐藏
。
CStudent() :CPerson(123, 345), m_nStuID(4), m_nGender(456)
{
m_nGender = 1;
printf("CStudent()\r\n");
}
运行结果:子类中的m_nGender
被赋值
(2)只有在父类中存在的同名成员变量
(3)如果要指定对父类的进行操作,使用四饼连接符::
2.2 父子类中的成员函数的隐藏
上面的结果也可以适用于函数,父类子类中均定义void test();
#include <iostream>
class CPerson
{
public:
CPerson(int nGender) {
m_nGender = nGender;
printf("CPerson(int nGender)\r\n");
}
CPerson(int nGender, int n) {
m_nGender = nGender;
printf("CPerson(int nGender,int n)\r\n");
}
~CPerson() {
printf("~CPerson()\r\n");
}
void test() {
printf("CPerson::test()\r\n");
}
public:
int m_nPublic;
protected:
int m_nProtected;
public:
int m_nGender;
};
class CStudent :public CPerson
{
public:
//儿子指定调用父亲哪一个有参构造,子类给父类传参数
//也可以对当前类的成员进行初始化
CStudent() :CPerson(123, 345), m_nStuID(4), m_nGender(456)
{
m_nGender = 1;
printf("CStudent()\r\n");
}
~CStudent() {
printf("~CStudent()\r\n");
}
void test() {
printf("CStudent::test()\r\n");
}
private:
int m_nStuID;
int m_nGender;
//const定义时就需要对其进行赋值,也可以使用初始化列表
const int n = 4;
};
int main(int argc, char* argv[])
{
CStudent stu;
//首先匹配子类的,没有的话匹配父类的
stu.test();
return 0;
}
结果:stu.test();
同样由内向外隐藏,首先匹配子类的,没有的话匹配父类的。
- 当需要指定调用父类的
test();
,写法为:stu.CPerson::test();
3.学习视频地址:C++57个入门知识点_31 初始化列表及派生类中的函数隐藏