C++中的继承

把握住自己能把握住的点滴,把它做到极致,加油!

1.继承的概念及定义

1.1继承的概念

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

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

class Student :public Person
{
protected:
	int _stuid;//学号
};

class Teacher : public Person
{
protected:
	int _jobid;//工号
};


int main()
{
	Student s;
	s._name = "张三";
	s._age = 18;
	s.Print();

	Teacher t;
	t._name = "赵老师";
	t._age = 40;
	t.Print();

	return 0;
}

继承后父类的Person成员(成员变量+成员函数)都会变成子类的一部分,这里体现了Student类和Teacher类复用Person类的成员。

1.2 继承定义

1.2.1 定义格式

在这里插入图片描述

1.2.2 继承方式和访问限定符

在这里插入图片描述

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

类成员/继承方式public继承protected继承private继承
基类的public成员派生类的public成员派生类的protected成员派生类的private成员
基类的protected成员派生类的protected成员派生类的protected成员派生类的private成员
基类的private成员在派生类中不可见在派生类中不可见在派生类中不可见

总结:
1.基类的private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。(它像是在子类中隐身了)
(基类私有成员的意义:不想被子类继承下来使用的成员,可以设计成私有)
2.基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
3.实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见的。基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式), public > protected> private。
4.使用关键字class时默认的继承方式是private,使用struct时的默认继承方式是public,不过最好显示写出继承方式
5.在实际运用中一般使用都是public继承,几乎很少使用protected/private继承,也不提倡使用。
protected/private继承,因为protected/private继承下来的成员只能在派生类的类里面使用,实际中扩展维护性不强。

// 实例演示三种继承关系下基类成员的各类型成员访问关系的变化  
class Person
{
public:
	void Print()
	{
		cout << "name:" << _name << endl;
	}
protected:
	string _name;//姓名
private:
	int _age;//年龄
};

//class Student :private Person
//class Student:protected Person
class Student : public Person
{
public :
	void Print1()
	{
		cout << _name << endl;
	}
protected:
	int _stdid;//学号
};
struct Student : Person //默认是公有继承
{
public :
	void Print1()
	{
		cout << _name << endl;
	}
protected:
	int _stdid;//学号
};
class Student : Person //默认是私有继承
{
public :
	void Print1()
	{
		cout << _name << endl;
	}
protected:
	int _stdid;//学号
};

2.继承中的作用域

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

//Student 的_num和Person的_num构成隐藏关系,可以看出这样的代码虽然能跑,但是非常容易混淆
class Person
{
protected:
	string _name = "小李子";//姓名
	int _num = 11; //身份证号
};

class Student :public Person
{
public:
	void Print()
	{
		cout << "姓名:" << _name << endl;
		cout << "学号:" << _num << endl;//999(默认访问的派生类)
		cout << "身份证号" << Person::_num << endl;//11(访问基类的成员)
	}
protected:
	int _num = 999;//学号
};

练习

//B中的fun()和A中的fun()并不构成重载,因为它们不在同一作用域中
//B中的fun()和A中的fun()构成隐藏,成员函数满足函数名相同就构成隐藏
class A
{
public:
	void fun()
	{
		cout << "func()" << endl;
	}
};
class B : public A
{
public:
	void fun(int i)
	{
		A::fun();
		cout << "func(int i)->" << i << endl;
	}
};
void Test()
{
	B b;
	b.fun(10);
};

int main()
{
	B b; 
	b.fun(10);
	b.A::fun();
	return 0;
}

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

**1.派生类对象可以赋值给基类的对象/基类的指针/基类的引用。这里有个形象的说法叫做切片或者切割。**寓意把派生类中父类那部分切来赋值过去。(当子类公有继承父类,可以说子类是一个特殊的父类,父类即是子类的切割或者切片,子类如果私有或者保护继承父类不行)

2.基类对象不能赋值给派生类对象。
3.基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才安全。这里基类如果是多态类型,可以使用RTT(Run-Time Type Information)的dynamic case 来进行识别后进行安全转换。(后面多态的文章在详细说)

在这里插入图片描述
(pp指针指向子类的成员,rp是子类那一部分的引用)


class Person
{
protected:
	string _name; //姓名
	string _sex; //性别
	int _age; //年龄
};

class Student :public Person
{
public:
	int _stuid;//学号
};

int main()
{
	Student sobj;

	//1.子类对象可以赋值给父类对象/指针/引用
	Person pobj = sobj;
	Person* pp = &sobj;
	Person& rp = sobj;

	//父类是子类的切割或切片
	//Person pobj = sobj;
	//Person& rp = sobj;
	//特殊,这里虽然是不同类型,但是不是隐式类型转换
	//这里算是一个特殊支持,语法天然支持的
	//int i = 0;
	//double& d = i; //error// 隐式类型转换 这里会生成一个const的引用变量,d是const的引用变量的引用
	//const double& d = i;
     
 //2.基类对象不能赋值给派生类对象
// sobj = pobj;//error

//3.基类的指针可以通过强制类型转换赋值给派生类的指针
pp = &sobj;
Student* ps1 = (Student*) pp; //这种情况转换时可以的
ps1->_stuid = 10;

pp = &pobj;
Student* ps2 = (Student*)pp; //这种情况转换时虽然可以,但是会存在越界访问的问题
ps2->_stuid = 10;
	return 0;
}

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

6个默认成员函数,“默认”的意思就是指我们不写,编译器会变帮我们自动生成一个,那么在派生类中,这几个成员函数是如何生成的呢?

1.派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数初始化列表阶段显示调用。
2.派生类的拷贝构造函数必须调用基类的拷贝构造函数完成基类的拷贝初始化。
3.派生类的operator=必须要调用基类的operator=完成基类的赋值。
4.我们知道析构顺序是先定义的后析构,父类是比子类先定义的,所以子类要先析构,父类要后析构,当然其实不需要显示的去调用父类的析构,子类完成析构后会自动调用父类的析构,显示调用会产生重复。

	//子类的析构函数跟父类的析构函数构成隐藏
	//由于后面多态的需要,析构函数名字会统一处理成destructor()
	~Student()
	{
		//不需要显示调用父类的析构函数
		//每个子类析构函数后面,会自动调用父类析构函数,这样才能保证先析构子类,再析构父类
		//Person::~Person();

		//~Person();//error

		//...处理子类自己的
		cout << "~Student()" << endl;
	}

上面这段程序是会报错的,不需要再显示调用父类的,会造成重复。

下面程序,望君细品

class Person
{
public:

	//父类的默认构造函数
	/*Person(const char* name = "Peter")
		:_name(name)
	{
		cout << "Person()" << endl;
	}*/

	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)
	//	:_name(name) //想对继承下来的父类的对象进行初始化,这种方式是错误的
	//{}

	//正确的方式如下
	Student(const char* name, int num)
		:Person(name) //这里不是匿名对象,而是调用Person类的构造函数
		, _num(num)
	{
		cout << "Student(const char* name, int num)" << endl;
	}

	Student& operator=(const Student& s)
	{
		if (this != &s)
		{
			Person::operator=(s);
			_num = s._num;
		}
		cout << "Student& operator=(const Student& s)" << endl;
		return *this;
	}

	void Print()
	{
		cout << _name << endl;
	}

	//子类的析构函数跟父类的析构函数构成隐藏
	//由于后面多态的需要,析构函数名字会统一处理成destructor()
	~Student()
	{
		//不需要显示调用父类的析构函数
		//每个子类析构函数后面,会自动调用父类析构函数,这样才能保证先析构子类,再析构父类
		//Person::~Person();

		//~Person();//error

		//...处理子类自己的
		cout << "~Student()" << endl;
	}

	Student* operator&()
	{
		return this;
	}

protected:
	int _num;
};

int main()
{
	如果子类没有显示写构造函数,定义一个子类对象会去调用其父类的默认构造函数
	//结束时会去调用父类的析构函数
	//Student s; 

	//如果父类没有默认构造函数,需要我们自己显示写子类的构造函数
	Student s1("张三",1);

	//子类没有显示写拷贝构造函数时
	Student s2(s1);

	Student s3("李四", 2);

	s1 = s3;//赋值

	return 0; 
}


5.继承和友元

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

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; //error
}

int main()
{
	Person p;
	Student s;
	Display(p, s);
	return 0;
}

6.继承与静态成员

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

class Person
{
public:
	Person()
	{// 此处构造函数有什么作用?
		++_count; //可以通过这行代码知道我们实例化了多少个对象
		//因为实例化一个父类对象会调用父类的构造函数,实例化一个子类对象也会调用父类的构造函数。
	}
//protected:
	string _name;//姓名
public:
	static int _count;//统计人的个数
};

int Person::_count = 0;

class Student :public Person
{
protected:
	int _stuNum;//学号
};

class Graduate :public Student
{
protected:
	string _seminarCourse; //研究科目
};

int main()
{
	Person p;
	Student s;

	p._name = "张三";
	cout << s._name << endl;

	cout << Student::_count << endl;
	++Person::_count;
	cout << Student::_count << endl;
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值