C 语言

关注公众号 jb51net

关闭
首页 > 软件编程 > C 语言 > C++继承

C++继承模式详解

作者:_End丶断弦

继承机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有的特性基础上进行扩展,增加功能,这样产生新的类,称作是派生类。继承呈现了面向对象程序设计的层析结构,体现了由简单到复杂的认知过程。继承是类设计层次的复用。

继承

继承的概念

继承机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。

继承的定义

上面的基类也可以叫父类,派生类也可以叫子类。

继承关系和访限定符

继承方式

接下来用代码测试上面的继承方式

class Person
{
public :
 void Print ()
 {
 cout<<_name <<endl;
 }
protected :
 string _name = "张三" ; // 姓名
private :
 int _age = 18 ; // 年龄
};

class Student : public Person
{
protected :
 int _stunum = 22; // 学号
};

public继承

上面是给的缺省值来测试没写构造函数

s就继承了Person的name,age,基类中private的age在物理上继承了但在语法上但是不能访问的。

也可以调用基类的成员函数,但是不能直接访问基类中private的成员,prootected可以在派生类中访问,不能再在类外访问

protected继承

protected继承,在类外连基类的public成员函数都不能用了,只能在派生类的类里面使用。

同样基类中私有的不能访问

private继承就都是私有的了。

总结:

父类和子类对象赋值转化

class Person
{
protected:
	string _name; // 姓名
	string _sex; // 性别
	int _age; // 年龄
};
class Student : public Person
{
public:
	int _No; // 学号
};

子类可以给父类,父类不能给子类,不仅可以是子类的对象,也可以是指针和引用


	Student s;
	Person p;
	p = s;
	Person *ptr = &s;//子类赋给父类指针
	Person &ref = s;//子类赋给父类引用

派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。

基类对象不能赋值给派生类对象

基类的指针可以通过强制类型转换赋值给派生类的指针。但是必须是基类的指针是指向派生类对象时才是安全的。等到子类中的默认函数就会用到切片

继承中的作用域

class Person
{
protected:
	string _name = "法外狂徒"; // 姓名
	int _num = 11; // 身份证号
};
class Student : public Person
{
public:
	void Print()
	{
		cout << " 姓名:" << _name << endl;
		cout << " 身份证号:" << _num << endl;
		cout << " 学号:" << _num << endl;
	}
protected:
	int _num = 2; // 学号
};

还有成员函数的隐藏

class A {
public:
	void fun(double x)
	{
		cout << "fun()->x"<< x << endl;
	}
};
class B : public A {
public:
	void fun(int i)
	{
		cout << "fun()->" << i << endl;
	}
};

int main()
{
	B b;
	b.fun(10);
	b.A::fun(11.1);//加作用域
	return 0;
}

父类和子类函数名相同不是重载而是隐藏,函数重载是在同一作用域,不同的作用域是隐藏

在子类成员函数中,可以使用 基类::基类成员 显示访问

在写代码中最好不要定义同名的成员

子类的默认成员函数

在类和对象的时候讲了6个默认的成员函数,现在子类中讲4个,构造,拷贝构造,赋值和析构

class Person //父类
{
public:
	Person(const char* name = "李四")
		: _name(name)
	{
		cout << "Person()" << endl;
	}

	Person(const Person& p)
		: _name(p._name)
	{
		cout << "Person(const Person& p)" << endl;
	}

	Person& operator=(const Person& p)
	{
		cout << "Person operator=(const Person& p)" << endl;
		if (this != &p)
			_name = p._name;

		return *this;
	}

	~Person()
	{
		cout << "~Person()" << endl;
	}
protected:
	string _name; // 姓名
};
//子类
class Student : public Person
{
public:
	//构造函数
	Student(const char* name, int num)
		: Person(name)//调用父类的构造函数初始化父类的成员
		, _num(num)//初始化子类的成员
	{
		cout << "Student()" << endl;
	}
	//拷贝构造
	Student(const Student& s)
		: Person(s)//这里就用到了切片,切父类的成员类拷贝
		, _num(s._num)//拷贝子类的
	{
		cout << "Student(const Student& s)" << endl;
	}

	Student& operator = (const Student& s)
	{
		cout << "Student& operator= (const Student& s)" << endl;
		if (this != &s)
		{
			Person::operator =(s);//调用父类的赋值
			_num = s._num;//赋值子类自己的
		}
		return *this;
	}

	~Student()
	{
		//子类的析构函数完成清理后会自动调用父类的析构函数
		cout << "~Student()" << endl;
	}
protected:
	int _num; //学号
};

总结:

继承与友元

友元关系不能继承,父类友元不能访问子类私有和保护成员

class Student;
class Person
{
public:
	friend void Display(const Person& p, const Student& s);
protected:
	string _name; // 姓名
};
class Student : public Person
{
public:
	friend void Display(const Person& p, const Student& s);
protected:
	int _stuNum; // 学号
};
void Display(const Person& p, const Student& s) {
	cout << p._name << endl;//可以访问
	cout << s._stuNum << endl;//要在子类中加上友元才能访问,不加会报错
}
int main()
{
	Person p;
	Student s;
	Display(p, s);
	return 0;
}

继承与静态成员

基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例 。

class Person
{
public:
	Person() { ++_count; }
protected:
	string _name; // 姓名
public:
	static int _count; // 统计人的个数。
};
int Person::_count = 0;
class Student : public Person
{
protected:
	int _id; // 学号
};
class Graduate : public Student
{
protected:
	string _Course; // 科目
};

int main()
{
	Student s1;
	Student s2;
	Student s3;
	Graduate s4;
	cout << " 人数 :" << Person::_count << endl;
	cout << " 人数 :" << Student::_count << endl;
	cout << " 人数 :" << &Person::_count << endl;
	cout << " 人数 :" << &Student::_count << endl;
	return 0;
}

再加上count的地址可以看出是同一个的count。计算出子类实例化了多少个对象就可以在父类中定义个count自加。

复杂的菱形继承

单继承:一个子类只有一个直接父类时称这个继承关系为单继承

多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承

菱形继承:菱形继承是多继承的一种特殊情况。

菱形继承的问题:从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。在Assistant的对象中Person成员会有两份。

class Person
{
public:
	string _name; // 姓名
};
class Student : public Person
{
protected:
	int _num; //学号
};
class Teacher : public Person
{
protected:
	int _id; // 编号
};
class Assistant : public Student, public Teacher
{
protected:
	string _Course; // 课程
};
int main()
{
	// 这样会有二义性无法明确知道访问的是哪一个
	Assistant a;
	//a._name = "peter";

	// 显示的调用解决了二义性,但数据冗余了
	a.Student::_name = "盖伦";
	a.Teacher::_name = "亚索";
	return 0;
}

虚继承

虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在Student和Teacher的继承Person时使用虚拟继承,即可解决问题。需要注意的是,虚拟继承不要在其他地方去使用。

在菱形的腰部加上virtual关键字可以解决冗余。那虚继承是怎么解决的呢?先来看看不用虚继承的

class A {
public:
	int _a;
};

class B :  public A {
public:
	int _b;
};

class C :  public A {
public:
	int _c;
};
class D : public B, public C {
public:
	int _d;
};
int main()
{
	D d;
	d.B::_a = 1;
	d.C::_a = 2;
	d._b = 3;
	d._c = 4;
	d._d = 5;
	return 0;
}

我们可以通过内存窗口来观察对象成员的模型

菱形继承带来了二义性和数据冗余。

再来看看虚继承的

虚继承就解决了数据冗余和二义性,B和C中多了地址,在用内存窗口看看这里的地址

2个指针叫虚基表指针指向虚基表,可以通过偏移量找到公共虚基类,此时A是在下面那为什么要找呢?

	D d;
	d.B::_a = 1;
	d.C::_a = 2;
	d._b = 3;
	d._c = 4;
	d._d = 5;
	B b = d;//把d赋给b,把d切过去那此时要怎么找到A呢?,所以就要用虚基表找

B类中各个成员在内存中的分布:

通过偏移量找到虚基类。

还是不要用菱形继承出现问题,虚继承使得对象模型很复杂,并且会有效率的影响。

继承的总结

组合

继承是建立了父类与子类的关系,是一种“是”的关系,例如白猫是猫,组合是“有”的关系实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用继承,可以用组合就用组合。

面试题

  • 什么是菱形继承?菱形继承的问题是什么?
    菱形继承是多继承的一种特殊继承,两个子类继承同一个父类,而又有子类同时继承这两个子类。可以看出菱形继承有数据冗余和二义性的问题。
  • 什么是菱形虚拟继承?如何解决数据冗余和二义性的
    在菱形继承的腰部加上virtual,通过虚基表指针和虚基表中的偏移量可以找到虚基类,只存1份
  • 继承和组合的区别?什么时候用继承?什么时候用组合?
    继承是一种"是",组合是"有"的关系,父类和子类是的关系用继承,是有的关系用组合。

以上就是C++继承,由于作者水平有限,如有问题还请指出!

到此这篇关于C++继承模式详解的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

您可能感兴趣的文章:
阅读全文