【高级程序设计语言C++】C++继承的概念及原理

1.1. 继承的概念

在C++中,继承是一种面向对象编程的重要概念,它允许一个类(称为派生类)从另一个类(称为基类)继承属性和方法。通过继承,派生类可以重用基类的代码,并且可以在基础上添加或修改功能。

class Person
{
public:
	void Print()
	{
		cout << _name << _age << endl;
	}
	string _name = "";
	int _age = 1;
};
class Student : public Person
{
private:
	int _id = 0; //学号
};

1.2. 继承的定义

1.2.1. 定义的格式

img

1.2.2. 继承关系和访问限定符

img

1.2.3. 继承基类成员访问方式的变化

img

总结:

  1. 私有成员不可见
  2. 成员权限和继承权限取最小的那个 (public > protected> private
  3. 不写是什么继承方式的,使用class关键字默认继承方式为私有继承
  4. protected关键字在类外不可以被直接访问,但是在派生类中可以直接访问
class Person
{
public:
	void Print()
	{
		cout << _name  << " " << _age << endl;
	}
protected:
	string _name = "张三";
private:
	int _age = 1;
};
//class Student : public Person  //  输出张三  1
//class Student : protected Person //编译不通过
class Student : private Person  //编译不通过
{
private:
	int _id = 0; //学号
};
int main()
{
	Student s1;
	s1.Print();
	return 0;
}

1.3. 基类和派生类对象赋值转换

派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。形象的说法叫做切片或者切割

img

class Person
{
public:
	void Print()
	{
		cout << _name << " " << _age << endl;
	}
protected:
	string _name = "张三";
	int _age = 1;
};
class Student : public Person  //  输出张三  1
{
private:
	int _id = 0; //学号
};
int main()
{
	Student s1;
	Person p1;
	p1 = s1;
	return 0;
}

记住一定是派生类给基类赋值才能这样!另外保护继承也不能这样

1.4. 继承中的作用域

  1. 在继承体系中基类和派生类都有独立的作用域。
  2. 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)
  3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
  4. 注意在实际中在继承体系里面最好不要定义同名的成员。
class Person
{
public:
	void Print()
	{
		cout << _name << " " << _age << endl;
	}
protected:
	string _name = "张三";
	int _age = 1;
};
class Student : public Person  //  输出张三  1
{
public:
	void Print()
	{
		cout << _id  << " " << _name << " " << _age << endl;
	}
private:
	int _id = 0; //学号
};
int main()
{
	Student s1;
    s1.Print();
	s1.Person::Print();
	return 0;
}

输出结果:

img

1.5. 派生类的默认成员函数

class Person
{
public:
	Person(string name, int age)
		:_name(name)
		, _age(age)
	{
		cout << "Person(string name, int age)" << endl;
	}
	Person(const Person& p)
		:_name(p._name)
		,_age(p._age)
	{
		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;
	}
	void Print()
	{
		cout << _name << " " << _age << endl;
	}
protected:
	string _name = "张三";
	int _age = 1;
};
class Student : public Person  //  输出张三  1
{
public:
	Student(string name, int age, int id)
		:Person(name,age)
		,_id(id)
	{
		cout << "Student(string name, int age, int id);" << endl;

	}
	Student(const Student& s)
		:Person(s)   //这里能用Student赋值,是因为上面讲过的赋值转换
		, _id(s._id)
	{
		cout << "Student(const Student& s);" << endl;
	}
	Student& operator = (const Student& s)
	{
		cout << "Student& operator= (const Student& s)" << endl;
		if (this != &s)
		{
			Person::operator =(s);//这里必须使用基类的运算符重载
                                  //因为派生类函数名和基类函数名相同构成了隐藏
			_id = s._id;
		}
		return *this;
	}
	void Print()
	{
		cout << _id  << " " << _name << " " << _age << endl;
	}
	~Student()
	{
		cout << "~Student()" << endl;
	}
private:
	int _id = 0; //学号
};
int main()
{
	Student s1("jkl",18,006);
	Student s2(s1);
	Student s3("xxx",19,121);
	s3 = s1;
	return 0;
}

总结:

  1. 先是调用基类的默认成员函数(析构函数除外),再去调用派生类自己的默认成员函数
  2. 根据内置类型和自定义类型来处理
  3. 派生类析构函数和基类析构函数构成隐藏关系
  4. 编译器先调用派生类析构函数,再调用基类析构函数

1.6. 友元关系与继承

class Student;
class Person
{
public:
	friend void Display(const Person& p, const Student& s);
protected:
	string _name; // 姓名
};
class Student : public Person
{
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;
}

img

其中原因是友元关系是不能继承的,也就是说Student是没有继承到友元函数的,也就导致了Student的成员不能被访问。

1.7. 继承与静态成员

class Person
{
public:
	Person()
	{
		cout << "Person()" << endl;
		++_count;
	}
	static int _count;
protected:
	string _name; // 姓名

};
int Person::_count = 0;
class Student : public Person
{
public:
	Student()
	{
		cout << "Student()" << endl;
	}
protected:
	int _stuNum; // 学号
};

int main()
{
	Person p;
	Student s;
	cout << Person::_count << endl;
	return 0;
}

输出结果:

img

主函数只有一个Person和一个Student,但是_count却是2.

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

类,都只有一个static成员实例

1.8. 菱形继承和菱形虚拟继承

1.8.1. 单继承

img

以上这种继承叫做单继承,所有的子类只有一个直接的父类。

1.8.2. 多继承

img

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

1.8.3. 菱形继承

img

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

菱形继承的问题:菱形继承有数据冗余和二义性的问题。在D的对象中A成员会有两份。

img

这里主要的问题是二义性和数据的冗余问题。

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

编译结果:

img

可以通过显示访问父类成员的方式解决。

int main()
{
	D d;
	cout << d._d << endl;
	cout << d.B::_a << endl;
	cout << d.C::_a << endl;
	return 0;
}

输出结果:

img

但是这样做并没有解决数据冗余的问题。所以就有了虚拟继承。

1.8.4. 虚拟继承

在B和C对A的继承使用虚拟继承就可以解决问题。

img

下面从内存的角度看虚拟继承

img

其中B和C存的另一个地址是A的偏移量的地址,方便快速的找到A。如下图

img

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值