类继承(父类派生子类)
父类:基类
子类:派生类
子类继承父类的一切成员,子类可以新增(扩展)自己的成员,默认继承方式: private
子类的构造函数调用之前,则会调用父类的构造函数(默认是无参的)
子类对象空间大小
父类的对象空间大小 + 子类新增属性
按父类的对齐方式
子类的构造函数
默认调用父类的无参构造函数
可以指定父类的有参构造函数
子类对象的生命周期
子类对象创建时,先调用父类的构造函数,再调用自己的构造函数。子类对象在作用域结束时, 则先执行子类的析构, 再调用父类的析构。因为,只有子类对象在回收完空间之后,编译器才能知道父类的属性空间需要释放。
- 调用父类的构造函数:初始化从父类继承过来的成员属性。
- 调用子类的构造函数:初始化新增的成员属性。
- 当子类对象离开有效作用域:调用子类的析构函数,回收子类的动态申请的内存空间。调用父类的析构函数,回收父类的动态申请的内存空间。
cpp
class Father {
public:
Father() {
cout << " Father()" << endl;
}
~Father() {
cout << " ~Father()" << endl;
}
};
class Children :public Father {
public:
Children() {
cout << " Children()" << endl;
}
~Children() {
cout << " ~Children()" << endl;
}
};
int main() {
cout << "start" << endl;
{
Children c;
}
cout << "end" << endl;
return 0;
}

子类拷贝构造函数的行为
- 默认调用父类的无参构造函数
- 子类的拷贝构造函数在重写时,必须要在初始化列表上指定父类的有参构造函数或者拷贝构造函数。
继承类语法
class 类名:继承方式或访问限定 父类名{ };
继承方式
public
protected
private
限定访问
子类对象访问父类成员的限定
cpp
class Person {//父类定义标准的成员
private:
int pid;//身份证号
string name;//姓名
public:
Person():pid(0),name(""){}
Person(int pid,string name):pid(pid),name(name){}
void hi() {
cout << "pid:" << pid << ",name:" << name << endl;
}
};
//从Person类中派生出学生类Student
// 默认继承方式: private
// 子类的构造函数调用之前,则会调用父类的构造函数(默认是无参的)
class Student :protected Person {
private:
int sid;//学号
float score;//成绩
public:
// 默认情况下,编译提供一个无参构造函数
// 当创建类对象时,调用无参构造函数初始化
// 当调用无参构造函数之前,先调用父类的无参构造
Student():sid(0),score(0){}
// 默认调用父类的无参构造函数进行初始化父类中成员属性
// 可以在初始化列表上,指定父类的构造函数进行初始化
Student(int pid, string name, int sid, float score) :Person(pid, name) {
this->sid = sid;
this->score = score;
}
// 可以隐藏(重定义)父类的成员函数
void hi() {
// 可以调用父类的重定义的成员函数: 复现父类的功能
Person::hi();
cout << "Sid: " << sid << ", Score: " << score << endl;
}
};
int main()
{
Student s1;
s1.hi();
return 0;
Student s2(1001, "李江", 902100, 580);
s2.hi();
return 0;
}
不同继承方式的区别
C++ 有3 种继承方式:public(公有继承)、protected(保护继承)、private(私有继承),核心区别是:改变父类成员在子类中的访问权限,并决定父类的成员能否被外部、子类、孙子类访问。
- 继承方式只修改父类 public/protected 在子类的权限;
- 父类 private 永远无法被子类直接访问;
- 日常开发优先用 public 继承,protected/private 仅特殊场景使用
| 父类成员权限 | 公有继承 (public) | 保护继承 (protected) | 私有继承 (private) |
|---|---|---|---|
| public | public | protected | private |
| protected | protected | protected | private |
| private | 不可访问 | 不可访问 | 不可访问 |
cpp
class A {
private:
int n;
protected:
int m;
public:
int k;
A() :n(0), m(0), k(0) {}
A(int n) :n(n), m(0), k(0) {}
A(int n, int m) :n(n), m(m), k(0) {}
A(int n, int m, int k) :n(n), m(m), k(k) {}
void show() {
cout << n << "," << m << "," << k << endl;
}
};
class B : private A {
private:
int x;
public:
B() :x(0) {}
B(int x) :x(x) {}
B(int n, int m, int x) :x(x), A(n, m) {}
B(int n, int m, int k, int x) :x(x), A(n, m, k) {}
void show2() {
// 能访问父类的哪些成员? protected, public
m = 100;
k = 300;
cout << "m=" << m << ", k=" << k << ", x=" << x << endl;
}
};
class C : protected A {
};
int main() {
B b1(15, 20, 5);
// b1对象可以访问A类(父类)中的public 成员? k, show
// 不能, 因为private继承:
// 将从父类继承过来的的public,protected的成员的访问限定改成了private
// b1.k = 1;
// b1.show();
b1.show2();
C c1;
// c1对象可以访问A类(父类)中的public 成员? k, show
// 不能,因为protected继承:
// 将从父类继承过来的的public的成员的访问限定改成了protected
return 0;
}
赋值运算符重载函数
编译器默认提供了赋值运算符重载
void operator=(const 类名& o)
cpp
//编译器默认提供了赋值运算符重载
class N {
private:
int v;
public:
N(int v) :v(v){}
//重写赋值运算符重载函数。默认情况下编译器提供
void operator=(const N& other){
cout << "operator=(const N& other)" << endl;
v = other.v;
}
};
int main()
{
N n1 = 20;
N n2 = 15;
n2 = n1;//两个已存在的对象之间的赋值,则会调用类的赋值运算符重载函数
return 0;
}

子类中重写赋值运算符重载函数
子类中重写赋值运算符重载函数时,在函数内部调用父类的赋值运算符重载函数
默认情况下不会调用父类的赋值重载函数,需要手动调用父类的赋值重载函数。
父类名::operator=(实参)
cpp
//编译器默认提供了赋值运算符重载
class N {
private:
int v;
public:
N(int v) :v(v){}
//重写赋值运算符重载函数。默认情况下编译器提供
void operator=(const N& other){
cout << "operator=(const N& other)" << endl;
v = other.v;
}
int GetV() { return v; }
};
class M :public N {
private:
int v2;
public:
M(int v,int v2):N(v),v2(v2){}
void operator=(const M& other) {
//调用父类的赋值运算符重载函数
N::operator=(other);//默认的赋值运算符重载函数也是这样的。
//如果把这一行注释掉,那么v1不会实现赋值,输出为(2,2)
v2 = other.v2;
}
int GetV2() { return v2; }
};
int main()
{
N n1 = 20;
N n2 = 15;
n2 = n1;//两个已存在的对象之间的赋值,则会调用类的赋值运算符重载函数
M m1(1, 2);
M m2(2, 3);
m2 = m1;
cout << m2.GetV() << "," << m2.GetV2() << endl;
return 0;
}

面试题:赋值重载与拷贝构造的区别?
拷贝构造:用一个已有对象,去 "造" 一个新对象
赋值重载:两个都已经存在的对象,把一个的值赋给另一个
1. 拷贝构造
对象还不存在;分配内存 + 初始化;不需要处理旧资源
A(const A& other);
2. 赋值重载
对象早已存在;可能需要先释放自身旧资源,再拷贝;要处理自赋值(this == &other)
A& operator=(const A& other);
总结:
- 拷贝构造:创建新对象,初始化
- 赋值重载:对象已存在,覆盖值
- 拷贝构造无返回值;赋值重载返回
A& - 赋值要处理旧资源释放和自赋值
多继承
普通的多继承
一个子类有多个父类(基类 )
语法: class 类名:继承方式 父类名1,继承方式 父类名2...{ }
cpp
class Person {
private:
string name;
public:
Person(const string &name):name(name){}
string getName() {
return name;
}
};
class Employee {
private:
float salary;//工资
public:
Employee(float salary):salary(salary){}
float getSalary() {
return salary;
}
};
class Worker :public Person, public Employee {
private:
int wid;//工号
string joinTime;//入职时间
public:
Worker(string name, float salary, int wid, string joinTime) :Employee(salary), Person(name) {
this->wid = wid;
this->joinTime = joinTime;
}
void hi() {
cout << getName() << "," << wid << "," << getSalary() << "," << joinTime << endl;
}
};
int main()
{
Worker w1("Disen", 3000, 1001 , "20050306");
w1.hi();
return 0;
}

多继承中存在的菱形继承问题
菱形继承问题:
继承的多个父类都具有同一个父类,最顶的父类的成员会存在多个副本(二义性问题)。二义性的成员,需要指定哪一个路径。

cpp
class A {
public:
int getV()
{
return 10;
}
};
class B :public A {
public:
int getX()
{
return 20;
}
};
class C :public A {
public:
int getY()
{
return 30;
}
};
class D :public B, public C {
};
int main()
{
D d1;
//d1.getV();//无法直接访问A类成员
cout<<d1.C::getV();//可以指定从哪一个父类过程中访问
}

怎么解决?虚继承
虚继承virtual
在中间派生类继承基类时,加上 virtual 关键字,告诉编译器,A 只保留一份实例;最终 D 里只有一个 A 子对象;消除二义性与冗余。
不会存在基类的副本(成员),只存在虚基表和虚基指针。
虚基表存储的是虚基类中成员访问的偏移量。
cpp
class A {
public:
int getV()
{
return 10;
}
};
class B :virtual public A {
public:
int getX()
{
return 20;
}
};
class C :virtual public A {
public:
int getY()
{
return 30;
}
};
class D :public B, public C {
};
int main()
{
D d1;
cout << d1.getV() << endl;//无法直接访问A类成员,加virtual后可以访问
cout<<d1.C::getV()<< endl;//可以指定从哪一个父类过程中访问
}

虚基表指针和偏移量
虚继承内部存在一个虚基表指针和偏移量,用于访问父类A成员。

b1.getV();//偏移量 0(直接访问)
d1.getV();//偏移量 0(直接访问)
d1.B::getV();//偏移量 +8
d1.C::getV();//偏移量 +4
如何保证此类没有子类?final
限制一个类存在派生类
关键字:final(常类)
常类一般什么时候用?工具类,线程池工具......
cpp
class A final {
};
class B :public A {//报错,继承不了
};
const 成员函数
const成员函数的特点
内部不会修改成员变量的值
类的常对象只能访问const成员函数
cpp
class Fruit {
private:
string title;
float price;
public:
Fruit(string title, float price) :title(title), price(price) {}
//const成员函数的特点
//1.内部不会修改成员变量的值
//2.类的常对象只能访问const成员函数
string getTitle()const {
}
float getPrice()const {
}
void setPrice(float price) {
}
};
int main()
{
Fruit f1("橙子",4.5);
const Fruit f2 = f1;
f1.setPrice(20);
//f2不能设置价格 f2.setPrice(20);报错,无法调用,因为它是const修饰的对象
f2.getPrice();
f2.getTitle();
return 0;
}