继承(一)--父亲和儿子的那些事

目录

前言

一、继承的概念及定义

1、继承的概念

2、继承的定义

2.1继承的格式

 2.2继承到派生类中的基类成员的访问权限

3、继承类模板 

二、基类和派生类之间的转换

三、继承关系中的隐藏关系


前言

  当我们创建多个类时,他们有着许多共同点,我们还需要创建每个类时都包含这些共同信息吗,那也太麻烦了,于是就有了继承的概念,比如学校职工和学生都拥有自己的工号,姓名等等,我们就可以创建一个父类来存放这些变量,然后子类继承这些东西根据实际情况进行填写,是不是很实用呢,那么我就来聊聊继承。

一、继承的概念及定义

1、继承的概念

  继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许我们在保持原有类特性的基础上进行扩展,增加方法(成员函数)和属性(成员变量),这样产生新的类,称派生类(又叫子类),而被继承的类叫做基类(也叫做父类)。以前我们接触过函数设计层次的复用,也就是函数适配器,而继承则是类设计层次的复用。

  可能到这里大家还不是很清楚,我来给大家举个例子,也就是我前言中提到的例子,下面我们看到没有继承之前我们设计了两个类Student和Teacher,Student和Teacher都有姓名/地址/ 电话/年龄等成员变量,都有identity身份认证的成员函数,设计到两个类里面就是冗余的。当然他们也有一些不同的成员变量和函数,比如老师独有成员变量是职称,学生的独有成员变量是学号;学生的独有成员函数是学习,老师的独有成员函数是授课。

class Student
{
public:
	// 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证 
	void identity()
	{
		// ...
	}
	// 学习 
	void study()
	{
		// ...
	}
protected:
	string _name = "peter"; // 姓名 
	string _address; // 地址 
	string _tel; // 电话 
	int _age = 18; // 年龄 
	int _stuid; // 学号 
};
class Teacher
{
public:
	
	//进⼊校园/图书馆/实验室刷⼆维码等⾝份认证 
	void identity()
	{
		// ...
	}
	// 授课 
	void teaching()
	{
		//...
	}
protected:
	string _name = "张三"; // 姓名 
	int _age = 18; // 年龄 
	string _address; // 地址 
	string _tel; // 电话 
	string _title; // 职称 
};

  下面我们公共的成员都放到Person类中,Student和teacher都继承Person,就可以复用这些成员,就不需要重复定义了,省去了很多麻烦。

class Person
{
public:
	// 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证 
	void identity()
	{
		cout << "void identity()" << _name << endl;
	}
protected:
	string _name = "张三"; // 姓名 
	string _address; // 地址 
	string _tel; // 电话 
	int _age = 18; // 年龄 
};
class Student : public Person
{
public:
	// 学习 
	void study()
	{
		// ...
	}
protected:
	int _stuid; // 学号 
};
class Teacher : public Person
{
public:
	// 授课 
	void teaching()
	{
		//...
	}
protected:
	string title; // 职称 
};

但是有一个点非常重要,继承只是个形象的说法,并不是真的把基类的东西重新拷贝一份到派生类,而是派生类在使用基类成员时会调用派生类成员,两个类有各自的作用域。他们更像是一种融合,父类作为子类的一部分存在,子类会调用父类的成员,而父类又在子类中占据了空间,所以sizeof得到的子类空间大小也包含了父类的成员大小。

2、继承的定义

2.1继承的格式

 

 2.2继承到派生类中的基类成员的访问权限

  很明显,基类中不同访问权限的成员通过不同的继承方式继承到派生类中,这些继承过去的成员在派生类中会得到不同的访问权限,下面是一张获得的权限表给大家参考:

  这里大家应该看出protected和private是有区别的了,之前我们也提到过,这里说一下,基类的private成员在派生类中是不能被访问的,所以就出现了protected访问限定符,如果基类的成员继承到派生类中是受protected访问限制,那么受到该限制的成员可以在派生类内调用,但不能在类外调用。

接着总结几点:

1、基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它(如果要访问还是老套路,在基类中写一个能够访问基类private成员的函数,然后在派生类调用它)。

2、基类的私有成员在派生类都是不可见。基类的其他成员在派生类的访问方式 == Min(成员在基类的访问权限,继承方式),访问权限大小:public  > protected >  private。

4. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显式的写出继承方式。

5. 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。

3、继承类模板 

  不止普通的类可以被继承,类模板也同样可以,还是举一个例子:

namespace xiaolong
{
	template<class T>
	class stack : public std::vector<T>
	{
	public:
		void push(const T& x)
		{
			// 基类是类模板时,需要指定⼀下类域, 
			// 否则编译报错:error C3861: “push_back”: 找不到标识符 
			// 因为stack<int>实例化时,也实例化vector<int>了 
			// 但是模版是按需实例化,push_back等成员函数未实例化,所以找不到 
			vector<T>::push_back(x);
			//push_back(x);//编译报错
		}
		void pop()
		{
			vector<T>::pop_back();
		}
		const T& top()
		{
			return vector<T>::back();
		}
		bool empty()
		{
			return vector<T>::empty();
		}
	};

}

int main()
{
	xiaolong::stack<int> st;
	st.push(1);
	st.push(2);
	st.push(3);
	return 0;
}

这里有几点需要注意:

1、如果在派生类内调用基类的函数务必标注上函数所在域,即使用域访问操作限定符,否则编译会报错。原因:模板是按需实例化的,也就是说只有当调用push_back()这个基类函数时,编译器才会去实例化这个函数,在编译到派生类内未注明类域的push_back()语句时,由于基类模板还未实例化(也就是说模板还没有成为函数),那么编译器就找不到该函数,所以我们需要指明类域告诉编译器该函数在基类中。同时按需实例化也提高了编译的效率。

2、但是在类外将派生类实例化成对象后,用该对象调用基类的函数是不会报错的,比如我在上述main函数中加上一个st.push_back(1) 是完全被允许的,不会报错,因为此时派生类已经被实例化成对象,而在实例化派生类之前,编译器会优先将父类进行实例化,所以基类的push_back()函数是可以被调用的。

3、如果是之前举的校园成员的例子,那么在派生类中放入identify()函数则不需要指明类域,因为该函数所继承的基类本来就不需要显式地进行实例化,该函数并不需要你指明类型才能成为函数,所以直接调用就可以了。

二、基类和派生类之间的转换

1、public继承(继承方式是public)的派生类对象可以直接赋值给基类的指针/基类的引用(不需要拷贝构造)。这里有个形象的说法叫切片或者切割。寓意把派生类中基类那部分切出来,基类指针或引用指向的是派生类中切出来的基类那部分。

2、派生类对象能赋值给基类对象(但需要用到拷贝构造,也会发生切片)。

3、基类对象不能赋值给派生类对象(因为派生类对象包含基类对象所没有的成员)。

4、 基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。这里基类如果是多态类型,可以使用RTTI(Run-Time Type  Information)的dynamic_cast 来进行识别后进行安全转换。

class Person
{
protected :
 string _name; // 姓名 
 string _sex; // 性别 
 int _age; // 年龄 
};
class Student : public Person
{
public :
 int _No ; // 学号 
};
int main()
{
 Student sobj ;
 // 1.派⽣类对象可以赋值给基类的指针/引⽤ 
 Person* pp = &sobj;
 Person& rp = sobj;
 
 // 2.派⽣类对象可以赋值给基类的对象是通过调⽤后⾯会讲解的基类的拷⻉构造完成的 
 Person pobj = sobj;
 
 //3.基类对象不能赋值给派⽣类对象,这⾥会编译报错 
 //sobj = pobj;
 
 return 0;
}

三、继承关系中的隐藏关系

1. 在继承体系中基类和派生类都有独立的作用域。

2. 派生类和基类中有同名成员,派生类成员将屏蔽基类对同名成员的直接访问,这种情况叫隐藏。 (在派生类成员函数中,可以使用基类::基类成员显式访问)

3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏(特别的,析构函数不同名也构成隐藏)。

4. 注意在实际中在继承体系里面最好不要定义同名的成员。

OK,我们继承(一)就到这里吧,接下来的内容我会放到继承(二),我尽量早点写,嘿嘿。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值