【C++】 学习继承这一篇就够啦!【图文+代码】

一篇带你了解继承。

C++继承

  • 前言
  • 一、继承的概念
    • 1.继承定义 
    • 2.继承关系和访问限定符
    • 3.基类和派生类对象赋值转换
    • 4.继承中的作用域
    • 5.派生类的默认成员函数
    • 6.继承与友元
    • 7.继承与静态成员
  • 总结

前言

世间万物都会有一个继承体制,他就或多或少会继承父类的某些特征,比如人,动物,等等,那么我们的代码会不会也有这一特征呢,让我们一起来看看把


提示:以下是本篇文章正文内容。

一、继承

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

1.继承定义 

我们看下面这幅图。

我们在看一下代码

class Base
{
public:
	void fun()
	{
		cout << "Base::fun()" << endl;
	}
};
// 子类          父类
class D :public Base  //  继承
{
public:
	void show()
	{
		cout << "D::shoe()" << endl;
	}
};
void main()
{
	D d;
	d.show();
	d.fun();//  子类可以访问父类的信息
}

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

3、基类和派生类对象赋值转换

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

 我们用代码看一下  (代码里有详细注释

class Person
{
public:
	void Print()
	{
		cout << "name" << _name << endl;
		cout << "age:" << _age << endl;
	}
protected:
	string _name = "peter"; // 姓名
	int _age = 18; // 年龄
};
class Teacher : public Person
{
protected:
	int _stuid; // 学号
};
void main()
{
	Teacher tcher;  //首先他有一个 _name  _age  _stuid 成员
	Person per;  //  _name  _age 成员
	//  因为tcher 中包含有per的所有成员   所以子类就可以给父类赋值  而父类不可以给子类赋值 
	//所以我们将子类就可以给父类赋值的这种现象叫  对象的切片
}

4.继承中的作用域

  1.  在继承体系中基类和派生类都有独立的作用域
  2.  子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定 义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)
  3.  需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
  4.  注意在实际中在继承体系里面最好不要定义同名的成员。

 什么叫同名隐藏,我们用代码看一下

class Base
{
public:
	void fun()
	{
		cout << "Base::fun()" << endl;
	}
};
// 子类          父类
class D :public Base  //  继承
{
public:
	void fun()
	{
		cout << "D::fun()" << endl;
	}
	void show()
	{
		cout << "D::shoe()" << endl;
	}
};
void main()
{
	D d;
	Base *pb = &d;
	pb->fun();//  只能访问子类中父类所有的fun函数
	d.fun();  //  只能访问子类自己的fun函数
}

 

从上面代码可以看出,子类访问的是自己的fun函数,本来子类是可以访问父类的fun函数的,但是因为子类也有了和父类一样的同名函数,所以将父类隐藏掉了  这就叫同名隐藏

就相当于子类没有房子的时候,可以住父亲的房子,但当子类有了房子后,就不能住父类的房子了,必须住自己的

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

  1. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函 数,则必须在派生类构造函数的初始化列表阶段显示调用。
  2.  派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
  3.  派生类的operator=必须要调用基类的operator=完成基类的复制。
  4.  派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类 对象先清理派生类成员再清理基类成员的顺序。
  5.  派生类对象初始化先调用基类构造再调派生类构造。
  6.  派生类对象析构清理先调用派生类析构再调基类的析构

类的六个默认的成员函数,他们分别是构造函数、拷贝构造函数、析构函数、赋值操作符重载、取地址操作符重载、const修饰的取地址操作符重载

在继承关系里面,在派生类中如果没有显示定义这六个默认构造函数,编译器系统会默认合成这六个成员函数。

我们写代码看一下(代码里有详细注释

class Base
{
public:
	Base()
	{
		cout << "Base::Base()" << endl;
	}
	Base(const Base &b)//  拷贝构造
	{
		cout << "Base::Base(const Base &)" << endl;
		m_b = b.m_b;
	}
	Base operator=(const Base &b)//赋值语句
	{
		cout << "Base operator=(const Base &b)" << endl;
		m_b = b.m_b;
		return *this;
	}
public:
	void fun()
	{
		cout << "Base::fun()" << endl;
	}
	void fun(int a)
	{
		cout << "Base::fun(int a)" << endl;
	}
private:
	int m_b;
};
// 子类          父类
class D :public Base  //  继承
{
public:
	D() :m_b(0)  // 会调用父类的构造方法 但是如果父类构造方法给一个 Base(int b):m_b(b) 就不能成功了 因为父类要求是一个有参的  所以在参数列表改为 D():m_b(0),Base(0)
	{

	}

public:
	void fun()
	{
		cout << "Base::fun()" << endl;
	}
	void show()
	{
		cout << "D::shoe()" << endl;
	}
private:
	int m_b;
};
void main()
{
	D d;
	D d1 = d;//  调用的是父类的拷贝构造方法  说明我们在完成子类的拷贝构造当中首先要拷贝构造的是父类对象
	D d2;
	d2 = d1;
}

结果如下: 

我们可以看出:

其实编译器是先进入派生类的构造函,然后在初始化列表处调用了基类的构造函数,然后再回来执行自己的函数体。

6.继承与友元

友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员

具体我们看一下代码(代码中有详细注释

//友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员  如下
class Student;
class Person;
void Display(const Person& p, const Student& s);
class Person
{
public:
	friend void Display(const Person& p, const Student& s);
protected:
	string _name = "cb"; // 姓名
};
class Student : public Person
{

protected:
	int _stuNum = 418; // 学号
};
void Display(const Person& p, const Student& s)
{
	cout << p._name << endl;
	cout << s._stuNum << endl;// 之所以不能访问就是因为这仅仅是父类的友元函数 子类不能访问
	// 要怎末才能访问  就只能形成自己的友元函数  在子类中在加一句friend void Display(const Person& p, const Student& s);
	//  相当于爸爸的朋友不见得是儿子的朋友  儿子要操作只能自己在声明交朋友形成友元函数
}
void main()
{
	Person p;
	Student s;
	Display(p, s);
}

上面的代码说明,友元这个东西就跟朋友一样,父类的朋友不见得是子类的朋友,同样子类的朋友不见得是父类的朋友,所以要访问谁的时候,必须声明,谁是谁的友元,只有声明了之后才能访问其私有或保护成员。

  1. 友元关系不存在传递性,即每个类负责控制自己的友元类或者友元函数
  2. 友元关系是单向的,不具有交换性。若类B是类A的友元,类A不一定是类B的友元,要看在类中是否有相应的声明。
  3. 友元关系不能被继承。

7.继承与静态成员

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

同样我们看代码 如下:

//继承与静态成员
//基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例 。如下
class Test
{
public:
	Test()
	{
		count++;
	}
public:
	int GetCount()const
	{
		return count;
	}
	//int GetCount()const
	//{
	//	return GetOBJCount();
	//}
private:
	static int count;//  类的静态成员必须在类外初始化
                     //因为静态成员属于整个类,而不属于某个对象,如果在类内初始化,会导致每个对象都包含该静态成员,这是矛盾的
};
int Test::count = 0;
class D1 :public Test
{
public:
	//int GetCount()const
	//{
	//	return GetOBJCount();
	//}
};
class D2 :public Test
{
public:
	//int GetCount()const
	//{
	//	return GetOBJCount();
	//}
};
class D3 :public Test
{
public:
	//int GetCount()const
	//{
	//	return GetOBJCount();
	//}
};
class D4 :public Test
{
public:
	//int GetCount()const
	//{
	//	return GetOBJCount();
	//}
};
void main()
{
	D1 d1;
	cout << d1.GetCount() << endl;
	D2 d2;
	cout << d2.GetCount() << endl;
	D3 d3;
	cout << d3.GetCount() << endl;
	D4 d4;
	cout << d4.GetCount() << endl;
}

通过上面代码  我们不难看出:

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

static修饰的成员,只能在类中进行声明,类外定义,原因是因为静态成员属于整个类,而不属于某个对象,如果在类内初始化,会导致每个对象都包含该静态成员,这是矛盾的。

总结

public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。

组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。

这里还需要说一下  多继承问题  涉及到菱形继承等复杂问题。(后面博客课会有详细介绍)

感谢您的阅读,如有错误,欢迎指正!!!

猜你喜欢

转载自blog.csdn.net/qq_45615577/article/details/115291235
今日推荐