c++继承

目录

  • 继承
  • 继承方式
  • 父类和子类对象赋值转换
  • 继承的作用域
    • 常见题目
  • 子类默认成员函数
  • 菱形继承
  • 继承和组合

继承

继承机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类
继承不仅继承父类的成员变量,还继承父类的成员函数
继承是设计层次的复用

简单来说,继承就是让子类使用父类的成员

class Person//父类
{
public:
	void print()
	{
		cout << "name:" << _name << endl;
		cout << "age:" << _age << endl;
	}
protected:
	string _name="David";
	int _age=18;
};

class Student:public Person//子类
{
protected:
	int _stuid;//学号
};

class Teacher:public Person//子类
{
protected:
	int _tuid;//工号
};

c++继承_第1张图片
c++继承_第2张图片

继承方式

c++继承_第3张图片

继承关系和访问限定符

c++继承_第4张图片
所以对应的,继承有9种关系

c++继承_第5张图片
首先来理解不可见,不可见的意思是父类中的私有成员变量还是被继承到了子类当中,但是因为语法的限制,父类的私有成员变量在子类对象的外部还是内部都不可以访问
子类对象外部:c++继承_第6张图片

内部:c++继承_第7张图片
但是父类的私有成员变量还是被继承到了子类对象
c++继承_第8张图片

上面就解决了三种关系,其余六种我们只要知道public > protected > private

保护和私有在父类中没有区别;在子类中,private成员不可见,protected成员是可见的

常见继承的使用
父类成员:公有和保护
子类继承方式:公有继承
class默认访问限定符是private;struct 默认访问限定符是public
class继承中默认继承方式是priavte继承;struct继承默认public继承(跟访问限定符一样,最好写出来,不用默认的)

父类和子类对象赋值转换

子类对象可以赋值给父类的对象 / 父类的指针 / 父类的引用。这里有个说法叫切片或者切割。寓意把子类中父类那部分切来赋值过去。
c++继承_第9张图片
这里不存在类型转换,是语法天然支持的行为

int main()
{
	Person p;
	Student s;
	//这里不存在类型转换
	//父类=子类;赋值兼容->切割 切片
	p = s;
	Person* ptr = &s;
	Person& ref = s;
	
	//类型转换,中间会产生临时变量
    int i = 1;
	double  d = 1.2;
	i = d;
	//int& ri = d;//这里会产生临时变量,临时变量具有常性,所以不能赋值给引用
	//要想用引用来接收,加个const就可以
	const int& ri = d;

	return 0;
}

私有继承不支持切割/切片,这里涉及权限。
如果子类是私有继承,那么父类的成员继承给子类,这些成员在子类里就是私有的。

父类对象不能赋值给子类对象
比较bug的是指针和引用可以,但是很危险,很有会有越界的风险

    //子类=父类
	//s=p;//无论怎样都不行
	Student* pptr = (Student*)&p;
	Student& rref = (Student&)p;

继承的作用域

c语言中我们知道有就近原则,如果全局变量和局部变量名字相同,优先使用局部变量
如果想使用全局变量就得指明作用域 ::

//全局变量
int a = 10;

int main()
{
	int a=1;
	cout << ::a << endl;//打印全局变量
	return 0;
}

在继承中也有就近原则
子类和父类出现同名成员:隐藏/重定义

c++继承_第10张图片

常见题目

class A
{
public:
	void func()
	{
		cout << "func()" << endl;
	}
};

class B:public A
{
public:
	void func(int i)
	{
		cout << "func(int i)" << i << endl;
	}
};

int main()
{
	B b;
	b.func(12);
	return 0;
}

子类B虽然会继承A类中的func(),但是他们不构成函数重载,因为函数的重载必须在同一作用域内;
A和B的func构成函数隐藏,成员函数只要函数名相同就构成隐藏,参数可以相同也可以不相同

当调用b.func(12)时

如果b.func();(不带参数)编译器就会报错,因为继承A的func已经被隐藏了。
如果想调用A中的func()就可以指定类域b.A::func();
c++继承_第11张图片

子类默认成员函数

  1. 从父类继承下来的调用父类的构造和析构
    自己的,如果是内置类型不处理,自定义类型调用默认的构造和析构(跟普通类一样)
  2. 从父类继承下来的调用父类的拷贝构造和父类的operator=
    自己的,内置类型内置类型完成浅拷贝,自定义类型完成它的拷贝构造和赋值(跟普通类一样)

总结:原则是继承下来的调用父类处理,自己的按普通类基本规则

什么情况下需要我们自己写?

  1. 当父类没有默认的拷贝构造需要我们自己写构造
  2. 如果子类有资源需要释放,就需要自己写析构
  3. 如果子类存在浅拷贝问题,就需要自己实现拷贝构造和赋值

c++继承_第12张图片

c++继承_第13张图片

如何自己写?
父类成员调用父类对应构造,拷贝构造,operator=和析构处理
自己成员按普通类处理
c++继承_第14张图片
c++继承_第15张图片

c++继承_第16张图片
c++继承_第17张图片
析构函数的名字会被同一处理成destructor()
那么子类的析构函数和父类的析构函数就会构成隐藏
子类析构函数结束时,会自动调用父类的析构函数,所以我们一般不用显示子类中父类的析构函数
c++继承_第18张图片
子类析构函数结束时,会自动调用父类的析构函数
所以我们自己实现子类析构函数时,不需要显示调用父类的析构函数

子类自己的成员就按以前(普通类)的方式处理,继承的成员,调用父类几个默认成员函数处理.

友元和继承
友元关系不能被继承

继承和静态成员
静态成员继承下来,子类和父类访问的是同一个,整个继承体系里只有这样的成员,无论派生出多少个子类,都只有这一个static成员

菱形继承

单继承:一个子类只有一个父类时叫单继承
c++继承_第19张图片
多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承
c++继承_第20张图片
菱形继承
c++继承_第21张图片

class Person
{
public:
	string _name; // 姓名
};

class Teacher : public Person
{
public:
	int id;
};

class Student : public Person
{
public:
protected:
	int _num; //学号
};

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

int main()
{
	Assistant a;
	a.Student::_name="小李";
	a.Teacher::_name = "李四";

	return 0;
}

c++继承_第22张图片
解决的方法有:1.指定作用域解决了二义性,但还是有数据冗余


	Assistant a;
	a.Student::_name="小李";
	a.Teacher::_name = "李四";

2.虚继承(直接继承父类的地方用)可以解决数据冗余和二义性

class Teacher :virtual public Person
{
public:
	int id;
};

class Student :virtual public Person
{
public:
protected:
	int _num; //学号
};

int main()
{
	Assistant a;
	a._name = "张三";

	return 0;
}

继承和组合

B可以直接访问A的公有和保护成员

c++继承_第23张图片
D只有使用C的公有,不能直接访问保护成员

c++继承_第24张图片

public继承是一种is-a的关系。也就是说每个子类对象都是一个父类对象。(比如学生和人)
组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。(比如车子和轮胎)
优先使用对象组合,而不是类继承

继承通常时是白箱复用,继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关系很强,耦合度高
组合通常是黑箱复用, 组合类之间没有很强的依赖关系,耦合度低
类之间,模块之间关系最好是低耦合,高内聚

有些关系组合不合适,而继承很合适,切片和多态是建立在继承基础之上的,所以继承语法不能抛弃

你可能感兴趣的:(c++,c语言,c++)