继承(上)

继承

继承概念:

一个类中包含成员变量和成员函数,不同的类它们的数据和函数往往是不同的,但是有时候两个类的内容基本相同或者有一部分相同,例如以下两个类:

第一个类:

class student
{
     public:
    void display()
{
     cout<<num<<endl;
    cout<<name<<endl;
    cout<<sex<<endl;
}
private:
    int  num;               //学生基本信息
   char name[20];
   char sex;
};

第二个类:

class student1
{
public:
      void display();
{
     cout<<num<<endl;
    cout<<name<<endl;
    cout<<sex<<endl;
    cout<<age<<endl;

}
private:
int  num;               //第一个类中也有
char name[20];          //第一个类中也有
char sex;               //第一个类中也有

int age;                //新增内容

};

所以继承就是在一个已经存在的类的基础上建立一个新类,原来的类称为 基类 或者父类,产生的新类叫做 派生类或者 子类。

继承机制是面向对象中 重复使用代码 的重要手段。
如图:
这里写图片描述

继承格式

(以代码一和代码二为例)

新类名称:继承权限 基类名称
{
};

class student1:public student   
{
public:
void dispaly()
{
cout<<age<<endl;
}

private:
int age ;

};
继承权限

注意继承时的第一行,**class student1:public student **,这里的public是继承权限,类的成员变量和成员函数都有三种权限,public,private和protect,继承权限也有三种:public,private和protect

  • public继承:基类中的公有成员和受保护成员保持原有的访问属性,基类的私有成员,在派生类中不可见(存在,但不能访问)。

  • protect继承:基类中的公有成员和受保护的成员在派生类中成为受保护状态,基类的私有成员,依旧处位基类私有状态

  • private继承:基类中的公有成员和受保护成员在派生类中成为私有成员,基类的私有成员,依旧为基类的私有成员。

记忆方法总结:权限大小排序 ,private<protect<public,继承的三种权限和成员的三种访问权限,派生类成员究竟处于什么状态,取决于那个权限小的。(例如,公有成员,私有继承,则公有成员在派生类中为私有,受保护成员 公有继承,则受保护成员在派生类中为受保护成员),觉大多数情况下继承为公有继承。
不管哪种继承,基类中的私有成员在子类中都不可见。 关键字为struct的类,继承默认为public,关键字为class的类,继承默认private。

has a 与is a

is a(公有继承)

众所周知,C++具有三种继承:公有继承、私有继承、保护继承。

最常见的就是公有继承,它建立一种is-a的关系。
如何理解is-a呢?即派生类对象也是一个基类对象,可以对基类对象执行的任何操作,也可以对派生类对象执行。

例如:

有一个水果Fruit类,可以保存水果的重量和热量;

香蕉是一种水果,所以可以从Fruit类派生出Banana类;

Banana类继承了父类的所有数据成员,
因此,Banana对象将包含表示香蕉重量和热量的成员;

此外,Banana类可以添加专门用于香蕉的成员。

但是,Banana类不能删除基类的Fruit的属性。

因此Banana is a kind of Fruit,即我们所说的is-a关系。


class Banana:public Fruit

{
......
};

那么has-a关系呢?组合关系

同样举个例子:
午餐可能包括水果,但是午餐并不是水果;
所以不能从Fruit公有派生出Lunch类;
在午餐中加入水果的正确方法是将其作为一种has-a关系。

那么如何实现has-a呢?
通常的是有两种方法:包含和私有继承
所谓的包含就是将Fruit对象作为Lunch类的数据成员,即新的类包含一个类的对象。
所谓的私有继承就是class Lunch:private Fruit。

题:

class A
{
public:
	A()
	{
		a = 1;    //a是公有,b是私有,两个地址是连续的吗?
		b = 2;
	}
	int a;
private:
	int b;
};

a,b地址是连续的,说明访问限定符对数据在内存中存储是不影响的。
那么,在这种情况下继承时,父类中的私有数据对子类是不可见的,子类一旦访问,就会报错?
要说清楚这个问题,我们需要知道,程序本身是存放在磁盘中的,一旦运行,就会加载到内存中,此时会为数据开辟空间,但是,在程序运行前,需要经历编译,汇编,链接过程,c++用法规定父类的私有数据,子类不能访问,所以在编译期间就会报错,不会通过,所以对于内存来说,私有公有数据连续存放也没有关系,并不是说由内存来判断哪些数据子类可以访问,哪些数据,子类不可以访问。

赋值兼容-----切片

子类赋值给父类,多余的数据会被“切掉”。
在这里插入图片描述

  1. 子类对象可以赋值给父类对象(切割/切片,因为子类对象数据通常多)

  2. 父类对象不能赋值给子类对象

  3. 父类的指针/引用可以指向子类对象

  4. 子类的指针/引用不能指向父类对象(可以通过强制类型转换完成,但不能调用成员函数,会崩溃)

试着执行下面的代码,观察结果

class Person
{
public :
 void Display ()
 {
 cout<<_name <<endl;
 }
protected :
 string  _name ; // 姓名
};
class Student : public Person
{
public :
 int _num ; // 学号
};
void Test ()
{
 Person p ;
 Student s ;
 // 1.子类对象可以赋值给父类对象(切割 /切片)
 p = s ;
 // 2.父类对象不能赋值给子类对象
 //s = p;
 // 3.父类的指针/引用可以指向子类对象
 Person* p1 = &s;
Person& r1 = s;
 //4.子类的指针/引用不能指向父类对象(可以通过强制类型转换完成)
 Student* p2 = (Student*)& p;
 Student& r2 = (Student&) p;
 // 这里会发生什么?
 p2->_num = 10;
 r2._num = 20;
}
继承中的作用域
  1. ** 在继承体系中基类和派生类都有独立的作用域。**

  2. 子类和父类中有同名成员,子类成员将屏蔽父类的同名成员。(在子类成员函数中,可以使用 基 类::基类成员 访问) 这种现象叫隐藏,也叫重定义 。

同名隐藏: 在基类和派生类中,具有相同名称的成员(成员函数 /成员变量),如果用派生类的对象去访问这个同名成员,则只能访问到派生类自己的,这就叫重定义。只能通过加基类作用域的方法去访问同名成员的基类成员。

  1. 注意在实际中在继承体系里面最好不要定义同名的成员。
派生类的6个默认成员函数

继承体系下,派生类中如果没有显式定义这六个默认成员函数,编译器则会合成这六个默认的成员函数,但重点关注4个:构造函数,拷贝构造函数。赋值运算符重载,析构。

构造函数的主要作用就是进行初始化,在派生类中,要对新增成员进行初始化就要定义新的派生类构造函数,同时,还要对继承下来的基类成员进行初始化,这是由父类的构造函数完成。但基类构造函数和析构函数不能被继承,所以要在派生类的构造函数中调用父类的构造函数,派生类的清理工作也要定义新的析构函数

在继承时,子类不仅有父类的内容,也有自己的内容,那么,如何初始化父类中的内容?在子类的构造函数中调用父类的构造函数。
定义一个派生类对象时,基类和派生类构造函数调用的先后顺序:先构造 基类,在构造派生类
析构时先析构派生类(清空派生类资源),在析构基类(清空基类资源)。

基类代码

class  person
{
public:
	person(int n)   //父类构造
	{
		age= n;
		cout << "this is base\n" << endl;

	}
	~person()  //父类析构
	{
		cout << "base destrucct\n" << endl;
	}
	person(const  person &n)  //父类拷贝构造
		
	{

		age = n.age;
		cout << "copy succcessful\n" << endl;
	}
	person &operator=(const person &p)  //父类赋值运算符重载
	{
		if (this != &p)
		{
			age  = p.age;

		}
		cout << "operator successful\n" << endl;
		return *this;
	}
private:
	int  age;
};

派生类

class student:public person
{
public:
    //派生类构造函数的定义
	/*

	派生类名(参数总表)
	:基类名(基类的参数)

	*/
	student(int age, int n)    //派生类构造函数
		:person(age)    //先构造基类
		,num(n)
	{
		cout << "deriver class" << endl;
	}

	~student()                //派生类析构
	{
		cout << "desturct deriver" << endl;
	}
	
	student(const student&s)  //派生类拷贝构造
		:person(s)
		,num(s.num)
	{
		cout << "devire copy successful\n" << endl;
 
	}
	student&operator=(const student &s)  //派生类的运算符重载
	{
		cout << "devire operator succsefull\n";
		if (this != &s)
		{
			person::operator = (s);
			num = s.num;
		}

	}

private:
	int num;   //学号

};
  1. 派生类的构造函数定义
    基类没有构造函数,则派生类的构造函数可以定义也可以不定义。
    基类有带有形参的构造函数,则派生类一定要定义构造函数。
    构造一个类的对象之前,必须先构造其中的嵌套类对象,若没给嵌套类传参数,则调用嵌套类的默认构造函数,否则调用嵌套类的带参数的构造函数
派生类名(参数总表)
	:基类名(基类的参数)
//派生类的拷贝构造函数若没有定义,编译器会自定生成拷贝构造函数,自动调用拷贝父类的拷贝构造函数(不管是自定义的函数自动生成的)
  1. 派生类的析构函数
    清理完派生类的资源后,在清理基类的资源。

  2. 派生类的赋值运算符重载
    和基类里面的赋值运算符重载构成重定义,使用时注意加作用域。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值