C++进阶 —— 继承

本文深入探讨了C++中的继承机制,包括继承的概念、定义格式、成员访问权限、基类与派生类对象赋值转换、作用域规则、默认成员函数、继承与友元、静态成员以及复杂的菱形继承和虚拟继承。强调了继承在代码复用和层次结构中的重要性,同时也提到了继承可能带来的问题,如数据冗余和二义性,并建议在设计时优先考虑对象组合。

目录

一,继承概念及定义

定义格式

基类成员继承后的访问方式

 二,基类和派生类对象赋值转换

三,继承中的作用域

四,派生类的默认成员函数

五,继承与友元

六,继承与静态成员

七,复杂的菱形继承及菱形虚拟继承

菱形继承

虚拟继承

虚拟继承原理

八,附


一,继承概念及定义

  • 继承机制是面向对象程序设计是代码复用的最重要手段,呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程;
  • 以前接触的复用都是函数复用,继承是类设计层次的复用,是类与类之间的关系;
  • 被继承的类称为父类或基类,继承的类称为子类或派生类;
  • 派生类是在保持原有类特性的基础上进行扩展,增加功能,产生的新类,可获取父类的成员变量和成员函数;
class Person
{
protected:
	string _name = "peter";
	int _age = 20;
};

class Teacher :public Person
{
public:
	void print()
	{
		cout << "child teacher class" << endl;
		cout << "name:" << _name << endl;
		cout << "age:" << _age << endl;
		cout << "jobid:" << _jobid << endl;
	}
private:
	int _jobid = 123;
};

class Stu :public Person
{
public:
	void print()
	{
		cout << "child stu class" << endl;
		cout << "name:" << _name << endl;
		cout << "age:" << _age << endl;
		cout << "jobid:" << _stuid << endl;
	}
private:
	int _stuid = 456;
};

int main()
{
	Teacher teacher;
	teacher.print();

	Stu stu;
	stu.print();
	return 0;
}

定义格式

//继承方式:public、private、protected,不写默认为private;
class 派生名:[继承方式] 基类名 
{
    派生类新增加成员;
}

基类成员继承后的访问方式

  • 基类的private成员,无论什么继承方式派生类中均不可见;不可见是指基类的private成员还是继承到派生类中,只是限制派生类对象访问(不管是类内还是类外访问);
  • private成员在派生类中是不可被访问的,如基类成员不想在类外被直接访问,但想在派生类中被访问,可将成员定义为protected,可看出protected成员限定符是因继承才出现的;
  • 基类成员在派生类中的访问方式 = min(基类访问限定符,继承方式),public>protected>private;
  • 关键字class默认的继承方式为private,关键字struct默认的继承方式为public,建议最好显示的写出继承方式;
  • 实际应用中,一般使用public继承,很少使用protected、private继承,也不提倡使用;因为protected、private继承下来的成员都只能在派生类的类里面使用,扩展维护性不强;

 二,基类和派生类对象赋值转换

  • 派生类对象可赋值给基类对象/基类指针/基类引用;形象的称为切片或切割,即把派生类中父类那部分切来赋值过去;
  • 基类对象不可赋值给派生类对象;
  • 基类指针可通过强制类型转换赋值给派生类指针;但必须是基类的指针是指向派生类对象时才是安全的;如基类是多态类型,可使用RTTI(Run-Time Type Information)的dynamic_cast来进行识别后进行安全转换;
//子类切片
基类对象 = 子类对象;
基类指针 = 子类指针;
基类引用 = 子类引用;

//基类对象不可赋值给子类对象
子类对象 = 基类对象; //报错

//dynamic_cast进行安全转换检查,基类需有虚函数
Teacher* pt = dynamic_cast<Teacher*>(&person); //转换失败,返回nullptr
Teacher& rt = dynamic_cast<Teacher&>(person); //转换失败,抛std::bad_cast异常
class Person
{
protected:
	string _name = "peter";
	int _age = 20;
};

class Teacher :public Person
{
public:
	Teacher(){
		_name = "zhansan";
		_age = 18;
	}
public:
	int _jobid = 123;
};

int main()
{
	Teacher teacher;
	//派生类可赋值给基类对象、指针、引用
	Person person = teacher;
	Person* pperson = &teacher;
	Person& rperson = teacher;

	//基类对象不可赋值给派生类
	//teacher = person;
	
	//基类的指针可通过强制类型转换赋值给派生类指针
	Teacher* pteacher = (Teacher*)pperson; //pperson指向的是派生类对象
	pteacher->_jobid = 100;

	Person person1;
	Teacher* pteacher1 = (Teacher*)&person1; //存在越界访问
	//pteacher1->_jobid = 100; //报错
	return 0;
}

class Animal {
public:  
    virtual string type() const { return "Animal"; }
    virtual ~Animal() = default;
};

class Dog : public Animal {
public:
    string type() const override { return "Dog"; }
    void bark() { cout << "汪汪!" << endl; }
};

class Cat : public Animal {
public:
    string type() const override { return "Cat"; }
    void meow() { cout << "喵喵!" << endl; }
};

//用于不确定类型的探索性转换
void processAnimal(Animal* animal) {
    cout << "动物类型: " << animal->type() << endl;
    if (Dog* dog = dynamic_cast<Dog*>(animal)) {
        cout << "这是一只狗: ";
        dog->bark();
    }
    else if (Cat* cat = dynamic_cast<Cat*>(animal)) {
        cout << "这是一只猫: ";
        cat->meow();
    }
    else {
        cout << "未知动物类型" << endl;
    }
    cout << endl;
}

//用于确定类型的强制性转换
void processAnimal(Animal& animal) {
    cout << "动物类型: " << animal.type() << endl;
    try{
        Dog& dog = dynamic_cast<Dog&>(animal);
        cout << "这是一只狗: ";
        dog.bark();
    }
    catch(const std::bad_cast&){
        cout << "这不是一只狗;" << endl;
    }

    try {
        Cat& cat = dynamic_cast<Cat&>(animal);
        cout << "这是一只猫: ";
        cat.meow();
    }
    catch (const std::bad_cast&) {
        cout << "这不是一只猫;" << endl;
    }
    cout << endl;
}

int main(){
    Animal ani;
    Dog dog;
    Cat cat;

    processAnimal(&ani);
    processAnimal(&dog);
    processAnimal(&cat);

    processAnimal(ani);
    processAnimal(dog);
    processAnimal(cat);    
	return 0;
}

三,继承中的作用域

  • 在继承体系中,基类和派生类都有独立的作用域;
  • 有同名成员时,子类成员将屏蔽父类对同名成员的直接访问,即隐藏,也叫重定义;在子类成员函数中,可使用显式访问(基类::基类成员);
  • 注意如成员函数,只要函数同名就构成隐藏,可使用显示调用(基类::基类成员函数);
  • 在实际中继承体系里最好不要定义同名成员;
//子类成员_name构成隐藏
class Person
{
protected:
	string _name = "peter";
	int _age = 20;
};

class Teacher :public Person
{
public:
	void print(){cout << Person::_name << endl;} //显式访问基类成员
public:
	string _name = "zhansan"; //与基类成员同名,基类成员被屏蔽
	int _jobid = 123;
};
//father中的fun和child中的fun不构成重载,因为不在一个作用域
//father中的fun和child中的fun构成隐藏,成员函数满足函数名相同就构成隐藏
class father
{
public:
	void fun(){cout << "fun()" << endl;}
protected:
	int _a = 1;
};

class child :public father
{
public:
    //与父类同名函数构成隐藏
	void fun(int i = 0){cout << "fun(i)" << endl;}
public:
	int _b = 10;
};

四,派生类的默认成员函数

六个默认成员函数,即不自己写,编译器会默认自动生成的函数;

  • 派生类构造函数,必须调用基类的构造函数初始化基类的成员;
    • 如基类没有默认构造函数,则必须在派生类构造函数的初始化列表处显式调用;
  • 派生类拷贝构造函数,必须调用基类的拷贝构造函数完成基类的拷贝构造初始化;
    • Base(other)
    • 自动调用 Base() 而不是 Base(other),基类部分会是默认值,没有正确拷贝;
  • 派生类的operator=必须要调用基类的operator=完成基类的复制;
    • Base::operator=(other)
    • 基类的赋值运算符不会自动调用,基类部分会保持不变,没有赋值;
  • 派生类的析构函数,会在被调用完之后自动调用基类的析构函数清理基类成员;
    • 只有这样才能保证派生类对象先清理派生类成员在清理基类成员的顺序;
  • 派生类对象初始化,先调用基类构造在调用派生类构造;
  • 派生类对象析构清理,先调用派生类析构在调用基类析构;

#include <iostream>
using namespace std;

class Base {
public:
    Base() { cout << "Base()" << endl; }   

    Base(const Base& other) { cout << "Base(const Base& other)" << endl; }

    Base& operator=(const Base& other) {
        cout << "Base& operator=(const Base& other)" << endl;
        return *this;
    }

    ~Base() { cout << "~Base()" << endl; }
};

class Derived : public Base {
public:
    Derived() { cout << "Derived()" << endl; }

    Derived(const Derived& other)
        :Base(other) // 显式调用基类构造函数
    { cout << "Derived(const Derived& other)" << endl; }

    Derived& operator=(const Derived& other) {
        Base::operator=(other); //显式调用基类赋值运算符
        cout << "Derived& operator=(const Derived& other)" << endl;
        return *this;
    }

    ~Derived() { cout << "~Derived()" << endl; }
};

int main() {
    //也可以使用 Derived* d = new Derived;delete d; 测试
    Derived d;
    cout << endl;
    Derived d1(d);
    cout << endl;
    d1 = d;
    cout << endl;
    return 0;
}

//基类默认成员函数
class Person
{
public:
	Person(const char* name = "peter") //默认构造函数
		:_name(name) { cout << "Person(const char* name = \"peter\")" << endl; }

	Person(const Person& person) //拷贝构造函数
		:_name(person._name) { cout << "Person(const Person& person)" << endl; }

	Person& operator=(const Person& person) { //赋值运算符重载
		cout << "Person& operator=(const Person& person)" << endl;
		if (this != &person)
			_name = person._name;
		return *this;
	}

	~Person(){ cout << "~Person()" << endl; } //析构函数
protected:
	string _name;
};

//派生类默认成员函数
class Stu :public Person
{
public:
	Stu(const char* name, int stuid) //构造函数
		:Person(name) //调用基类构造函数初始化基类成员
		, _stuid(stuid)
	{ cout << "Stu(const char* name, int stuid)" << endl; }

	Stu(const Stu& stu) //拷贝构造函数
		:Person(stu) //调用基类拷贝构造函数初始化基类成员
		, _stuid(stu._stuid)
	{ cout << "Stu(const Stu& stu)" << endl; }

	Stu& operator=(const Stu& stu) { //赋值运算符重载
		cout << "Stu& operator=(const Stu& stu)" << endl;
		if (this != &stu)
		{
			Person::operator=(stu); //调用基类operator=
			_stuid = stu._stuid;
		}
		return *this;
	}

	~Stu(){	cout << "~Stu()" << endl; } //析构函数
protected:
	int _stuid;
};

int main()
{
	Person person("zhansan");
	Person person1(person);
	person1 = person;
	cout << endl;
	Stu stu("lisi", 123);
	Stu stu1(stu);
	stu1 = stu;
	cout << endl;
	return 0;
}

五,继承与友元

  • 友元关系不能继承,即基类友元不能访问派生类protected、private成员;
//基类友元
class Stu;
class Person
{
	friend void print(const Person& person, const Stu& stu);
protected:
	string _name = "peter";
};

class Stu :public Person
{
	//friend void print(const Person& person, const Stu& stu);
protected:
	int _stuid = 123;
};

void print(const Person& person, const Stu& stu)
{
	cout << person._name << endl;
	cout << stu._stuid<< endl; //报错,无法访问_stuid
}

int main()
{
	Person person;
	Stu stu;
	print(person, stu);
	return 0;
}

六,继承与静态成员

  • 基类定义了static静态成员,则整个继承体系只有一个这样的成员,无论派生多个子类,都只有一个static成员实例;
class Person
{
public:
	Person(){++_count;}
protected:
	string _name;
public:
	static int _count;
};
//static成员
int Person::_count = 0;

class Teacher :public Person
{
protected:
	int _jobid = 1234;
};
class Stu :public Person
{
protected:
	int _stuid = 123;
};

int main()
{
	Teacher teacher1;
	Teacher teacher2;
	Stu stu1;
	Stu stu2;
	cout << Person::_count << endl;
	Person::_count = 0;
	cout << Person::_count << endl;
	return 0;
}

七,复杂的菱形继承及菱形虚拟继承

  • 单继承:一个子类只有一个直接父类时,称这个继承关系为单继承;
  • 多继承:一个子类有两个及两个以上直接父类时,称这个继承关系为多继承;
  • 菱形继承:菱形继承是多继承的一种特殊情况;

菱形继承

  • 菱形继承有数据冗余和二义性问题,即在Assistant对象中Person成员有两份;
//菱形继承
class Person
{
public:
	string _name;
};

class Teacher :public Person
{
protected:
	int _jobid;
};

class Stu :public Person
{
protected:
	int _stuid;
};

class Assistant :public Teacher, public Stu
{
protected:
	string _majorCourse;
};

int main()
{
	Assistant a;

	//报错,无法明确访问哪个,有二义性
	//a._name = "peter"; 

	//需明确指定访问哪个父类,但会有数据冗余
	a.Teacher::_name = "zhansan";
	a.Stu::_name = "lisi";
	return 0;
}

虚拟继承

  • 虚拟继承可解决菱形继承的数据冗余和二义性问题;
  • 注意虚拟继承不要在其他地方使用;
//虚拟继承
class Person
{
public:
	string _name;
};

class Teacher :virtual public Person
{
protected:
	int _jobid;
};

class Stu :virtual public Person
{
protected:
	int _stuid;
};

class Assistant :public Teacher, public Stu
{
protected:
	string _majorCourse;
};

int main()
{
	Assistant a;
	a._name = "peter"; 
	return 0;
}

虚拟继承原理

  • 虚基表指针,存偏移量地址;
  • 虚基表,存偏移量值,通过偏移量可找到Assistant a;

八,附

  • C++语法复杂,多继承就是一个体现;多继承,菱形继承,虚拟继承,底层实现很复杂,所以一般不要设计出多继承,一定不要设计出菱形继承,否则复杂度及性能都会有问题;
  • 多继承可以说是C++的一个缺陷之一,后来很多OO语言都没有了多继承,如Java;

继承和组合

  • public继承是一种is-a的关系,即每个派生类对象都是一个基类对象;
  • 组合是一种has-a的关系,假设B组合了A,则每个B对象中都有一个A对象;
  • 优先使用对象组合,而不是类继承;
  • 继承允许根据基类的实现来定义派生类的实现,这种通过生成派生类的复用通常称为白箱复用(white-box reuse);术语“白箱”是相对可视性而言的,在继承方式中,基类的内部细节对子类可见;继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响;派生类和基类之间的依赖关系很强,耦合度高;
  • 对象组合是类继承之外的另一种复用选择,新的更复杂的功能可以通过组装或组合对象来获得;对象组合要求被组合的对象具有良好定义的接口,这种复用风格被称为黑箱复用(black-box reuse),因为对象的内部细节不可见,对象只以“黑箱”的形式出现;组合类之间没有很强的依赖关系,耦合度低;优先使用对象组合有助于保持每个类被封装;
  • 实际尽量多去使用组合,组合耦合度低,代码维护性好;不过有些关系就适合继承,另外要实现多态,也必须要继承;类之间的关系可以用继承,可以用组合,就用组合;
//Car和BMW/Benz构成is-a的关系
class Car
{
protected:
	string _color = "white";
	string _num = "GB12334";
};

class BMW :public Car
{
public:
	void Drive()
	{cout << "good drive" << endl;}
};

class Benz :public Car
{
public:
	void Drive()
	{cout << "good sit" << endl;}
};
//Tire和Car构成has-a的关系
class Tire
{
protected:
	string _brand = "Michelin";
	size_t _size = 17;
};

class Car
{
protected:
	string _color = "white";
	string _num = "GB12334";
	Tire _t;
};

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值