小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
一、继承
1.1 相关概念
C++最重要的特征是代码重用,通过继承机制可以利用已有的数据类型来定义新的数据类型,新的类不仅拥有旧类的成员,还拥有新定义的成员。
一个B类继承于A类,或称从类A派生类B。这样的话,类A成为基类(父类), 类B成为派生类(子类)。
继承可以实现==代码复用==,也是==实现多态==的必要条件。
派生类中的成员,包含两大部分:
- 一类是从基类继承过来的;
- 一类是自己增加的成员。
涉及继承的类定义的形式:
class 子类类名:继承方式 父类类名{
};
class 子类类名:继承方式 父类类名1, 继承方式 父类类名2...{
};
继承方式:
public公有继承private私有继承protected受保护的继承
1.2 继承方式
1.2.1 public 公有继承
子类类内:父类的公有和受保护的成员可以操作,私有成员无法操作
子类类外:父类的公有成员可以操作,受保护的和私有成员无法操作
#include <iostream>
using namespace std;
class Parent
{
public:
int a;
private:
int b;
protected:
int c;
};
class Son:public Parent
{
public:
void SetGetMsg()
{
this->a = 100;
//this->b = 200;
this->c = 300;
cout << this->a << ", ";
//cout << this->b << ", ";
cout << this->c << endl;
}
};
void test1()
{
Son s;
s.a = 100;
//s.b = 200;
//s.c = 300;
cout << s.a << ", ";
//cout << s.b << ", ";
//cout << s.c << endl;
}
int main()
{
test1();
return 0;
}
1.2.2 private 私有继承
子类类内:父类的公有成员和受保护的成员可以操作,私有成员无法操作
子类类外:都不能操作
#include <iostream>
using namespace std;
class Parent
{
public:
int a;
private:
int b;
protected:
int c;
};
class Son:private Parent
{
public:
void SetGetMsg()
{
this->a = 100;
//this->b = 200;
this->c = 300;
cout << this->a << ", ";
//cout << this->b << ", ";
cout << this->c << endl;
}
};
void test1()
{
Son s;
//s.a = 100;
//s.b = 200;
//s.c = 300;
//cout << s.a << ", ";
//cout << s.b << ", ";
//cout << s.c << endl;
}
int main()
{
test1();
return 0;
}
1.2.3 protected 受保护的继承
子类类内:父类的公有成员和受保护的成员可以操作,私有成员无法操作
子类类外:都不能操作
#include <iostream>
using namespace std;
class Parent
{
public:
int a;
private:
int b;
protected:
int c;
};
class Son:protected Parent
{
public:
void SetGetMsg()
{
this->a = 100;
//this->b = 200;
this->c = 300;
cout << this->a << ", ";
//cout << this->b << ", ";
cout << this->c << endl;
}
};
void test1()
{
Son s;
//s.a = 100;
//s.b = 200;
//s.c = 300;
//cout << s.a << ", ";
//cout << s.b << ", ";
//cout << s.c << endl;
}
int main()
{
test1();
return 0;
}
总结:
- 不管是哪种继承方式,子类**==类内的成员函数==都可以操作父类中==公有==和==受保护==的成员**
- 不管是哪种继承方式,==类外==都无法操作父类中==私有==和==受保护==的成员
- 类外只能操作:子类以公有继承的方式中父类的公有成员
| 公有继承 | 私有继承 | 保护继承 | |||
|---|---|---|---|---|---|
| 父类属性 | 子类属性 | 父类属性 | 子类属性 | 父类属性 | 子类属性 |
| 公有 | 类内外都可以 | 公有 | 类内可以,类外不行 | 公有 | 类内可以,类外不行 |
| 私有 | 类内外不可以操作 | 私有 | 类内外不可以操作 | 私有 | 类内外不可以操作 |
| 保护 | 类内可以,类外不行 | 保护 | 类内可以,类外不行 | 保护 | 类内可以,类外不行 |
1.3 继承后构造函数和析构函数的调用顺序
子类继承父类之后,父类的构造函数和析构函数也都会调用
调用顺序:
- 构造函数:父类 -> 子类
- 析构函数:子类 -> 父类
子类继承父类中几乎所有的内容,但是有些不会继承,比如:
- 父类的构造函数和析构函数不会继承
- 父类中运算符=的重载函数不会继承
- 父类中友元不会继承
#include <iostream>
using namespace std;
class Parent{
public:
Parent()
{
cout << "Parent:构造函数" << endl;
}
~Parent()
{
cout << "Parent:析构函数" << endl;
}
};
class Son:public Parent{
public:
Son()
{
cout << "Son:构造函数" << endl;
}
~Son()
{
cout << "Son:析构函数" << endl;
}
};
void test1()
{
Son s;
}
int main()
{
test1();
return 0;
}
1.4 继承后有成员对象的构造函数和析构函数的调用顺序
如果父类中有成员对象:
- 构造函数的调用顺序:成员对象 --> 父类 --> 子类
- 析构函数的调用顺序:子类 --> 父类 --> 成员对象
如果子类中有成员对象:
- 构造函数的调用顺序:父类 --> 成员对象 --> 子类
- 析构函数的调用顺序:子类 --> 成员对象 --> 父类
#include <iostream>
using namespace std;
class Myclass{
public:
Myclass()
{
cout << "Myclass:构造函数" << endl;
}
~Myclass()
{
cout << "Myclass:析构函数" << endl;
}
};
class Parent{
public:
Parent()
{
cout << "Parent:构造函数" << endl;
}
~Parent()
{
cout << "Parent:析构函数" << endl;
}
};
class Son:public Parent{
public:
Son()
{
cout << "Son:构造函数" << endl;
}
~Son()
{
cout << "Son:析构函数" << endl;
}
Myclass m;
};
void test1()
{
Son s;
}
int main()
{
test1();
return 0;
}
1.5 总结继承中的函数调用状态
1.5.1 继承中的构造函数
构造函数不能被继承
1> 可以通过子类的构造函数的(初始化表显式的调用父类的构造函数,完成对在父类中继承过来的成员的初始化
2> 如果没有在子类的构造函数的初始化表中显式的调用父类的构造函数,默认会调用父类的无参构造函数
3> 调用顺序
父类的构造函数
子类的构造函数
1.5.2 继承中的析构函数
析构函数不能被继承
1> 无论子类中是否显性定义了析构函数,父类的析构函数都会被默认调用
2> 子类的析构函数中无需调用父类的析构函数
3> 调用顺序
子类的析构函数
父类的析构函数
1.5.3 继承中的拷贝构造函数
拷贝构造函数不能被继承
1> 如果子类中没有显式的给定拷贝构造函数
编译器会默认提供一个拷贝构造函数
默认提供的这个拷贝构造函数会调用父类的拷贝构造函数完成对
父类中继承过来的成员的初始化
2> 如果子类中显式的给定了拷贝构造函数,
需要在子类的拷贝构造函数的初始化表中显式的调用
父类的拷贝构造函数,完成对父类中继承过来的成员的初始化//涉及到一些多态知识
如果 没有 在子类的拷贝构造函数的初始化表中显式的调用
父类的拷贝构造函数,会默认调用父类的无参构造完成父类中继承过来的成员的初始化
3> 如果说类中没有指针成员,就可以使用默认的拷贝构造函数----浅拷贝
如果说类中有指针成员,就需要显示的给定子类的拷贝构造函数,
并在拷贝构造函数的初始化表中显示的调用父类的拷贝构造函数。
1.5.4 继承中的拷贝赋值函数
拷贝赋值函数不能被继承
1> 如果子类中没有显式的给定拷贝赋值函数
编译器会默认提供一个拷贝赋值函数
默认提供的这个拷贝赋值函数会调用父类的拷贝赋值函数完成对
父类中继承过来的成员的赋值
2> 如果子类中显式的给定了拷贝赋值函数,
需要在子类的拷贝赋值函数的函数体中显式的调用
父类的拷贝赋值函数,完成对父类中继承过来的成员的赋值//涉及到一些多态知识
如果 没有 在子类的拷贝赋值函数的函数体中显式的调用
父类的拷贝赋值函数,从父类中继承过来的那些成员会保留原来的值
3> 如果说类中没有指针成员,就可以使用默认的拷贝赋值函数----浅拷贝
如果说类中有指针成员,就需要显示的给定子类的拷贝赋值函数,
并在拷贝构造函数的函数体中显示的调用父类的拷贝赋值函数。
1.6继承后成员变量的使用
1.6.1 父类中有成员变量子类的使用
子类继承父类后,父类中的成员变量,子类是可以操作的
当子类实例化对象时,会自动调用构造函数,先调用父类的构造函数,然后调用子类的构造函数,==谁最后修改成员变量的值,最终成员变量的值就是多少==
#include <iostream>
using namespace std;
class Parent{
public:
Parent()
{
cout << "Parent:构造函数" << endl;
cout << &this->num << endl;
this->num = 333;
}
~Parent()
{
cout << "Parent:析构函数" << endl;
}
int num = 0;
};
class Son:public Parent{
public:
Son()
{
cout << "Son:构造函数" << endl;
cout << &this->num << endl;
this->num = 888;
}
~Son()
{
cout << "Son:析构函数" << endl;
}
};
void test1()
{
Son s;
cout << s.num << endl;
}
int main()
{
test1();
return 0;
}
1.6.2 子类中有与父类同名的成员变量的使用
当子类继承父类之后,子类中有与父类同名的成员变量,子类此时有两个成员变量,默认操作的是子类自己定义的,也可以操作父类中成员变量,需要通过==类名结合域解析符==来操作
#include <iostream>
using namespace std;
class Parent{
public:
Parent()
{
cout << "Parent:构造函数" << endl;
cout << &this->num << endl;
this->num = 333;
}
~Parent()
{
cout << "Parent:析构函数" << endl;
}
int num = 0;
};
class Son:public Parent{
public:
Son()
{
cout << "Son:构造函数" << endl;
cout << &this->num << endl;
this->Son::num = 888;
this->Parent::num = 1000;
}
~Son()
{
cout << "Son:析构函数" << endl;
}
int num = 0;
};
void test1()
{
Son s;
cout << "sizeof(s) = " << sizeof(s) << endl;
cout << "s.num = " << s.num << endl;
s.num = 100;
cout << "&s.num = " << &s.num << endl;
cout << "s.num = " << s.num << endl;
cout << "s.Parent::num = " << s.Parent::num << endl;
}
int main()
{
test1();
return 0;
}
1.7 继承后有参构造函数的调用
1.7.1 父子类中没有同名成员变量的调用
#include <iostream>
using namespace std;
class Parent{
public:
Parent()
{
cout << "Parent:无参构造函数" << endl;
this->num = 333;
}
Parent(int n)
{
cout << "Parent:有参构造函数" << endl;
this->num = n;
}
~Parent()
{
cout << "Parent:析构函数" << endl;
}
int num = 0;
};
class Son:public Parent{
public:
Son()
{
cout << "Son:无参构造函数" << endl;
this->num = 888;
}
Son(int n)
{
cout << "Son:有参构造函数" << endl;
this->num = n;
}
~Son()
{
cout << "Son:析构函数" << endl;
}
};
void test1()
{
//由于父类的构造函数子类没有继承,所以调用一下形式时,必须有子类的有参构造函数
//一下形式会先调用父类的无参构造函数,再调用子类的有参构造函数
Son s(9999);
}
int main()
{
test1();
return 0;
}
1.7.2 父子类中有同名的成员变量的调用
#include <iostream>
using namespace std;
class Parent{
public:
Parent()
{
cout << "Parent:无参构造函数" << endl;
this->num = 333;
}
Parent(int n)
{
cout << "Parent:有参构造函数" << endl;
this->num = n;
}
~Parent()
{
cout << "Parent:析构函数" << endl;
}
int num = 0;
};
class Son:public Parent{
public:
Son()
{
cout << "Son:无参构造函数" << endl;
this->num = 888;
}
//子类有参构造函数设置两个num的值
#if 0
Son(int n1, int n2)
{
cout << "Son:有参构造函数" << endl;
this->num = n1;
this->Parent::num = n2;
}
#endif
//使用初始化列表可以调用父类的有参构造函数
Son(int n1, int n2):Parent(n2)
{
cout << "Son:有参构造函数" << endl;
this->num = n1;
}
~Son()
{
cout << "Son:析构函数" << endl;
}
int num = 0;
};
void test1()
{
Son s(9999, 8888);
cout << "Son: num = " << s.num << endl;
cout << "Parent: num = " << s.Parent::num << endl;
}
int main()
{
test1();
return 0;
}
二、多继承
2.1 链式继承
class Grandfather
Grandfather(int num)
class Father:public Grandfather
Father(int num1, int num2):Grandfather(num2)
class Son:public Father
Son(int num1, int num2, int num3):Father(num2, num3)
#include <iostream>
using namespace std;
class Grandfather{
public:
Grandfather()
{
cout << "Grandfather:无参构造函数" << endl;
}
Grandfather(int num)
{
cout << "Grandfather:有参构造函数" << endl;
this->num = num;
}
int num;
};
class Father:public Grandfather{
public:
Father()
{
cout << "Father:无参构造函数" << endl;
}
Father(int num1, int num2):Grandfather(num2)
{
cout << "Father:有参构造函数" << endl;
this->num = num1;
}
int num;
};
class Son:public Father{
public:
Son()
{
cout << "Son:无参构造函数" << endl;
}
//通过层层初始化列表的形式可以调用每个类的有参构造函数,
//链式继承中如果要调用所有类的有参构造函数,只需要管自己类的父类即可
Son(int num1, int num2, int num3):Father(num2, num3)
{
cout << "Son:有参构造函数" << endl;
this->num = num1;
}
int num;
};
void test1()
{
Son s;
s.num = 100;
s.Father::num = 200;
s.Grandfather::num = 300;
cout << s.num << ", " << s.Father::num << ", " << s.Grandfather::num << endl;
Son s1(333, 666, 999);
cout << s1.num << ", " << s1.Father::num << ", " << s1.Grandfather::num << endl;
}
int main()
{
test1();
return 0;
}
2.2 扇形继承
class Son:public Father, public Mother
#include <iostream>
using namespace std;
class Father{
public:
Father()
{
cout << "Father:无参构造函数" << endl;
}
Father(int num)
{
cout << "Father:有参构造函数" << endl;
this->num = num;
}
int num;
};
class Mother{
public:
Mother()
{
cout << "Mother:无参构造函数" << endl;
}
Mother(int num)
{
cout << "Mother:有参构造函数" << endl;
this->num = num;
}
int num;
};
//扇形继承
class Son:public Father, public Mother{
public:
Son()
{
cout << "Son:无参构造函数" << endl;
}
//扇形继承,继承多个父类,
//还是可以通过初始化列表调用所有类的有参构造函数
Son(int num1, int num2, int num3):Father(num2),Mother(num3)
{
cout << "Son:有参构造函数" << endl;
this->num = num1;
}
int num;
};
void test1()
{
Son s;
s.num = 100;
s.Father::num = 200;
s.Mother::num = 300;
cout << s.num << ", " << s.Father::num << ", " << s.Mother::num << endl;
Son s1(333, 666, 999);
cout << s1.num << ", " << s1.Father::num << ", " << s1.Mother::num << endl;
}
int main()
{
test1();
return 0;
}
2.3 菱形继承
class Animals
class Horse:virtual public Animals
class Dunkey:virtual public Animals
class Mule:public Horse, public Dunkey
#include <iostream>
using namespace std;
class Animals{
public:
string name;
};
//虚继承:
//子类继承父类的时候采用虚继承的方式,那么在编译阶段
//不会确定成员变量的地址,而是会多定义一个指针来保存地址,
//当代码运行的时候在确定地址
class Horse:virtual public Animals{
public:
};
class Dunkey:virtual public Animals{
public:
};
class Mule:public Horse, public Dunkey{
public:
};
void test1()
{
Mule m1;
cout << "sizeof(m1) = " << sizeof(m1) << endl;
m1.name = "骡子";
cout << "m1.name = " << m1.name << endl;
cout << sizeof(Dunkey) << endl;
}
int main()
{
test1();
return 0;
}
2.4 虚继承
虚继承:子类继承父类的时候采用虚继承的方式,那么在编译阶段不会确定成员变量的地址,而是会多定义一个指针来保存地址,当代码运行的时候在确定地址。
cout << sizeof(Animals) << endl;
cout << sizeof(Horse) << endl;
cout << sizeof(Dunkey) << endl;
cout << sizeof(Mule) << endl;
32 //string
40 //加了一个指针(64位的一个指针是8字节,因为要寻遍所有地址)
40 //加了一个指针
48 //加了俩指针