类继承、子类拷贝构造函数、赋值运算符重载函数、多继承(虚继承)

类继承(父类派生子类)

父类:基类

子类:派生类

子类继承父类的一切成员,子类可以新增(扩展)自己的成员,默认继承方式: private

子类的构造函数调用之前,则会调用父类的构造函数(默认是无参的)

子类对象空间大小

父类的对象空间大小 + 子类新增属性

按父类的对齐方式

子类的构造函数

默认调用父类的无参构造函数

可以指定父类的有参构造函数

子类对象的生命周期

子类对象创建时,先调用父类的构造函数,再调用自己的构造函数。子类对象在作用域结束时, 则先执行子类的析构, 再调用父类的析构。因为,只有子类对象在回收完空间之后,编译器才能知道父类的属性空间需要释放。

  1. 调用父类的构造函数:初始化从父类继承过来的成员属性。
  2. 调用子类的构造函数:初始化新增的成员属性。
  3. 当子类对象离开有效作用域:调用子类的析构函数,回收子类的动态申请的内存空间。调用父类的析构函数,回收父类的动态申请的内存空间。
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;
}

子类拷贝构造函数的行为

  1. 默认调用父类的无参构造函数
  2. 子类的拷贝构造函数在重写时,必须要在初始化列表上指定父类的有参构造函数或者拷贝构造函数。

继承类语法

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(私有继承),核心区别是:改变父类成员在子类中的访问权限,并决定父类的成员能否被外部、子类、孙子类访问。

  1. 继承方式只修改父类 public/protected 在子类的权限;
  2. 父类 private 永远无法被子类直接访问;
  3. 日常开发优先用 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);

总结:

  1. 拷贝构造:创建新对象,初始化
  2. 赋值重载:对象已存在,覆盖值
  3. 拷贝构造无返回值;赋值重载返回 A&
  4. 赋值要处理旧资源释放和自赋值

多继承

普通的多继承

一个子类有多个父类(基类 )

语法: 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;
}
相关推荐
swift192212 小时前
Qt多语言问题 —— 静态成员变量
开发语言·c++·qt
用户805533698032 小时前
现代Qt开发教程(新手篇)1.4——容器
c++·qt
ulias2122 小时前
Linux中的开发工具
linux·运维·服务器·开发语言·c++·windows
qq_466302452 小时前
u盘插入拔出,listView不显示盘符变化
c++·qt
小熊Coding2 小时前
Windows 上安装 mysqlclient 时遇到了编译错误,核心原因是缺少 Microsoft Visual C++ 14.0 或更高版本 的编译环境。
c++·windows·python·microsoft·django·mysqlclient·bug记录
艾莉丝努力练剑2 小时前
【Linux线程】Linux系统多线程(六):<线程同步与互斥>线程同步(上)
java·linux·运维·服务器·c++·学习·线程
feng_you_ying_li3 小时前
C++11可变模板参数,包扩展,emplace系列和push系列的区别
前端·c++·算法
tankeven3 小时前
HJ177 可匹配子段计数
c++·算法
白藏y3 小时前
【C++】ifstream、ofstream、fstream的基础使用
c++