C++之继承

目录

前言

继承

概念

格式

继承类模板

基类和派生类间的转换

 继承中的存在的隐藏

练习

派生类的默认成员函数

4个常见默认成员函数

 一个不能被继承的类

继承与友元

继承与static成员

继承与组合


前言

C++有三大特性,分别是封装,继承,多态。在前面的学习中我们已经学习了封装(其实就是把数据(属性)和操作数据的方法(行为)绑定在一起,形成一个独立的对象(类))。今天我们就开始学习C++的另一个特性——继承

继承

概念

继承就像是复用。比如一个定义一个学生的类,学生的属性有学号、姓名、性别、班级。还有一个定义老师的类,老师的属性有职称、姓名、性别。在这两个类里面我们都发现了它们有相同的属性——姓名、性别。那我们就可以把这两个属性单独拿出来形成一个独立的类,学生类和老师类就可以继承这个独立类。

class Student
{
public:
	void identity()
	{
		cout << "void identity():"<<name << endl;
	}
	void id()
	{
		cout << "void ID():" << ID << endl;
	}
private:
	string name;//姓名
	string tel;//电话
	int ID;//学号
};
class Teacher
{
public:
	void identity()
	{
		cout << "void identity():" << name << endl;
	}
	void Title()
	{
		cout << "void title():" << title << endl;
	}

private:
	string name;//姓名
	string tel ;//电话
	string  title;//职称
};
int main()
{
	Teacher t1;
	Student s1;
	return 0;
}

在这段代码中除了两个属性有相同的,还有成员函数有相同的,那么我们就可以把它们重新组合成一个类,这个类就是基类,而student类和teacher类就是派生类。

class person//基类
{
public:
	void identity()
	{
		cout << "void identity():"<<name << endl;
	}
private:
	string name;
	string tel;
};
class Student:public person//派生类
{
public:
	void id()
	{
		cout << "void ID():" << ID << endl;
	}
private:
	int ID;
};
class Teacher :public person//派生类
{
public:
	void Title()
	{
		cout << "void title():" << title << endl;
	}
private:
	string  title;
};
int main()
{
	Student s1;
	Teacher t1;
	return 0;
}

 

Student类、Teacher类都继承了person类。

格式

继承的格式如下图所示:

类的继承方式有三种: public、private、protected。 

在学习类和对象中的访问限定符的时候我们提到了类的访问限定符有三种:public、private、protected。 但是我们当时没有过多的说明protected,在今天这节课就把它补充完整。

protected在继承中的作用就是:如果派生类中想要访问基类里的私有成员,但是不能在类外面被访问,这时我们就可以用protected访问限定符。

class person
{
public:
	void identity()
	{
		cout << "void identity():"<<name << endl;
	}
protected:
	string name="zhangsan";
private:

	string tel;
};

class Student:public person
{
public:
	void Nameprint()
	{
		cout << person::name << endl;
	}
private:
	int ID;
};

注意:

1.基类private成员在派生类中无论以什么方式方式继承都是不可见的,虽然它被继承到了派生类中,但是它在类里、类外都不能被访问。

2.基类的成员在派生类中访问方式==Min(成员在基类的访问限定符,继承方式),public>protected>private。

3.在实际运用中一般使用public继承,几乎很少用protected/private继承。

class person
{
public:
	void identity()
	{
		cout << "void identity():"<<name << endl;
	}
protected:
	string name="zhangsan";
private:

	string tel;
};

//class Student:private person
//class Student:protected person
class Student:public person
{
public:
	void Nameprint()
	{
		cout << person::name << endl;
	}
	//void Telprint()
	//{
	//	cout << person::tel << endl;
	//}//编译不通过,tel是person的私有成员
private:
	int ID;
};
int main()
{
	Student t1;
	t1.identity();
	//private继承:identity成员函数在派生类的访问方式是private    编译不通过
	//public继承:identity成员函数在派生类的访问方式是public       编译通过
	//protected继承:identity成员函数在派生类的访问方式是protected  编译不通过
	t1.Nameprint();
	return 0;
}	

继承类模板

在我们之前学习的list类、vector类就有类模板,这些都可以继承,但是如果它们作为基类,那么就需要指定类域。因为派生类实例化的时候,基类也要实例化,但基类是按需实例化,不会把模板中的全部成员实例化。

#include<vector>
template<class T>
class Stack:public std::vector<T>
{
public:
	void push(const T& x)
	{
		vector<T>::push_back(x);
//指定类域。Stack实例化的时候,vector类也要实例化。

	}
	void pop()
	{
		vector<T>::pop_back();
	}
	const T& top()
	{
		return vector<T>::back();
	}
	bool empty()
	{
		return vector<T>::empty();
	}
};
int main()
{
	Stack<int> s1;
	s1.push(1);
	s1.push(2);
	s1.push(3);
	while (!s1.empty())
	{
		cout << s1.top() << endl;
		s1.pop();
	}
}

基类和派生类间的转换

public继承的派生类对象可以赋值给基类的指针/基类的引用。实际就是切片。把派生类中基类的那部分切割出来,基类的指针或者引用就指向派生类切出来那部分

但基类对象不能赋值给派生类对象。

基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或引用·。

class person
{
protected:
	string name;
	string tel;
};
class Student:public person
{
public:
	int ID;
};
int main()
{
	Student s;
	//派生类对象可以赋值给基类的指针或引用
	person* pp = &s;
	person& p = s;
	//派生类对象也可以赋值给基类对象
	person pq = s;
	//基类对象不能赋值给派生类对象
	//s = pq;
	return 0;
}

 继承中的存在的隐藏

我们知道在C++中有四个作用域,分别是类域、命名空间域、全局域、局部域,而每个类域都有独立的作用域。同样在继承体系中基类和派生类都有各自独立的作用域。那如果基类和派生类中有相同的会怎么样呢?接下来就学习继承中的隐藏规则。

派生类和基类中有同名成员,派生类成员将屏蔽基类同名成员的直接访问,这就是隐藏。但在派生类的成员函数中可以使用指定类域来显示访问。

如果成员函数名相同,成员函数也构成隐藏。(函数重载:在同一个作用域函数名相同)

所以在实际中在继承体系里最好不要定义同名的函数/成员。

class person
{
public:
	void print()//和Student类中的成员函数print构成隐藏,person类中print函数被隐藏
	{
		cout << tel << endl;
		cout << name << endl;
	}
protected:
	string name="zhangsan";
	string tel="333333";//和Student类中的成员变量tel构成隐藏,person类中tel被隐藏
};
class Student:public person
{
public:
	void print()
	{
		cout << tel << endl;
		cout << person::tel << endl;
	}
protected:
	int ID=45674357;
	string tel = "66666";
};
int main()
{
	Student s;
	s.print();
	return 0;
}

练习

1. A和B类中的两个func构成什么关系()
A. 重载 B. 隐藏 C.没关系


2. 下⾯程序的编译运⾏结果是什么()
A. 编译报错 B. 运⾏报错 C. 正常运⾏

class A
{
public:
void fun()
{
cout << "func()" << endl;
}
};
class B : public A
{
public:
void fun(int i)
{
cout << "func(int i)" <<i<<endl;
}
};
int main()
{
B b;
b.fun(10);
b.fun();
return 0;
}

1.B 解析:重载是在同一作用域中函数名相同,而上述代码中fun函数一个在A类域,一个在B类域,是在两个类域中,所以不构成重载。

2.A 解析:在main函数中B对象只能调用B类中的成员函数,调用不到已经被隐藏的基类函数同名函数了,而B对象中的成员函数是有形参的,所以b.fun()就会编译失败——缺少参数。

派生类的默认成员函数

4个常见默认成员函数

6个默认成员函数,意思是我们不写,编译器也会给我们自动生成。在派生类中,这几个成员函数如何生成的?

1. 派⽣类的构造函数必须调⽤基类的构造函数初始化基类的那⼀部分成员。如果基类没有默认的构造函数,则必须在派⽣类构造函数的初始化列表阶段显⽰调⽤。
2. 派⽣类的拷⻉构造函数必须调⽤基类的拷⻉构造完成基类的拷⻉初始化。(拷贝构造:先基类再派生)
3. 派⽣类的operator=必须要调⽤基类的operator=完成基类的复制。需要注意的是派⽣类的
operator=隐藏了基类的operator=,所以显⽰调⽤基类的operator=,需要指定基类作⽤域
4. 派⽣类的析构函数会在被调⽤完成后⾃动调⽤基类的析构函数清理基类成员。因为这样才能保证派⽣类对象先清理派⽣类成员再清理基类成员的顺序。(析构:先派生类在基类)
5. 派⽣类对象初始化先调⽤基类构造再调派⽣类构造。(构造:先基类在派生类)
6. 因为多态中⼀些场景析构函数需要构成重写,重写的条件之⼀是函数名相同(这个我们多态章节会讲解)。那么编译器会对析构函数名进⾏特殊处理,处理成destructor(),所以基类析构函数不加virtual的情况下,派⽣类析构函数和基类析构函数构成隐藏关系

class Person
{
public:
	Person(const char* name = "zhangsan")
		:_name(name)
	{
		cout << "Person()" << endl;
	}
	Person(const Person&p)
		:_name(p._name)
	{
		cout << "Person(const Person&p)" << endl;
	}
	~Person()
	{
		cout << "~Person()" << endl;
	}
	Person& operator=(const Person& p)
	{
		cout << "Person& operator=(const Person& p)" << endl;
		if(this!=&p)
		{
			_name = p._name;
		}
		return *this;
	}
protected:
	string _name;
};
class Student:public Person
{
public:
	Student(const char*name,const int& num)
        :Person(name)
		,_num(num)
	{
		cout << "Student()" << endl;
	}
	Student(const Student& s)
        :Person(name)
		,_num(s._num)

	{
		cout << "Student(const Student& s)" << endl;
	}
	~Student()
	{
		cout << "~Student()" << endl;
	}
	Student& operator=(const Student& s)
	{
		cout << "Student& operator=(const Student& s)" << endl;
		if (this != &s)
		{
			Person::operator = (s);//派⽣类的operator=隐藏了基类的operator = ,
								   //所以显⽰调⽤基类的operator = ,需要指定基类作⽤域
			_num = s._num;
		}
		return *this;
	}
protected:
	int _num;
};
int main()
{
	Student s1("lisi", 45);
	Student s2(s1);
	Student s3("rose",18);
	s1=s3;
	return 0;

}

 一个不能被继承的类

有两种方法:

1.把基类的构造函数私有。派生类构造要调用基类的构造函数,但基类的构造函数私有化后,派生类不能访问,就不能实例化出对象。

2.final关键字,final修改基类,派生类就不能继承。

class Person final//第二种方法
{
public:
//private://第一种方法
	Person(const char* name = "zhangsan")
		:_name(name)
	{
		cout << "Person()" << endl;
	}
protected:
	string _name;
};
class Student:public Person
{
public:
	Student(const char*name,const int& num)
		:_num(num)
		,Person(name)
	{
		cout << "Student()" << endl;
	}
protected:
	int _num;
};
int main1()
{
	Student s1("lisi", 45);
	Student s2(s1);
	Student s3("rose",18);
	s1=s3;
	return 0;

}

继承与友元

友元(friend)关系(用途:当我们在全局定义的时候需要访问类中的成员变量,但是成员变量受类访问限定符限制,我们就用到了友元在类中声明函数)不能被继承,即基类友元不能访问派生类私有和保护成员。

class Student;
class Person
{
public:
	friend void Print(const Person& p,const Student& s);
protected:
	string _name;
};
class Student :public Person
{
//public:
	//friend void Print(const Person& p, const Student& s);
//解决办法:在派生类中也把该函数变成该类的友元函数
protected:
	int _stuNum;
};
void Print(const Person& p, const Student& s)//基类友元不能访问派生类保护或私有成员。
{
	cout << p._name << endl;
	cout << s._stuNum << endl;
}
int main()
{
	Person p;
	Student s;
//C2248	“Student::_stuNum”: 无法访问 protected 成员(在“Student”类中声明)
	Print(p, s);
}

继承与static成员

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

class Person
{
public:
	string name="zhangsan";
	string tel="333333";
	static int _count;
};
int Person::_count = 0;
class Student:public Person
{
public:
	int ID=45674357;
};
int main()
{
	Person p;
	Student s;
	cout << &p.name << endl;
	cout << &s.name << endl;
	//对象p和对象s中name的地址不同,说明基类和派生类对象各有一份name。
	cout << &p._count << endl;
	cout << &s._count << endl;
	//静态成员变量_count地址相同,说明派生类和基类共用一份静态成员。
}

继承与组合

1.public继承是⼀种is-a(是一种)的关系。也就是说每个派⽣类对象都是⼀个基类对象。(当派生类确实是基类的一种特例时,例如“狗”是“动物”的一种。)

2.组合是⼀种has-a(有一个)的关系。假设B组合了A,每个B对象中都有⼀个A对象。当组合类需要使用成员类的功能,但又不是成员类的一种特例时,例如“汽车”有一个“发动机”。)

3.继承允许你根据基类的实现来定义派⽣类的实现。这种通过⽣成派⽣类的复⽤通常被称为⽩箱复⽤(white-box reuse)。术语“⽩箱”是相对可视性⽽⾔:在继承⽅式中,基类的内部细节对派⽣类可⻅ 。继承⼀定程度破坏了基类的封装,基类的改变,对派⽣类有很⼤的影响。派⽣类和基类间的依赖关系很强,耦合度⾼。
4.对象组合是类继承之外的另⼀种复⽤选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接⼝。这种复⽤⻛格被称为⿊箱复⽤(black-box reuse),因为对象的内部细节是不可⻅的。对象只以“⿊箱”的形式出现。 组合类之间没有很强的依赖关系,耦合度低。优先使⽤对象组合有助于你保持每个类被封装。
5. 优先使⽤组合,⽽不是继承。实际尽量多去⽤组合,组合的耦合度低,代码维护性好。不过也不太那么绝对,类之间的关系只适合继承(is-a)那就⽤继承,另外要实现多态,也必须用继承。类之间的关系既适合⽤继承(is-a)也适合组合(has-a),就⽤组合。

class Person
{
public:
	void identity()
	{
		cout << "identity: " << _name << endl;
	}
protected:
	string _name="liming";
};
class Student:public Person
{
public:
	void studying()
	{
		cout << "studying: " << _nums << endl;
	}
protected:
	int _nums = 666666;
};
class Engine
{
public:
	void start()
	{
		cout << "Engine is starting" << endl;
	}
};
class Car {
private:
	Engine engine;
public:
	void startCar()
	{
		engine.start();
	}
};
int main()
{
	Student s;//学生是人类的一种。适合继承
	s.identity();
	s.studying();
	//车里面有一个发动机,“有一个”适合组合。
	Car car;
	car.startCar();
	return 0;
}

 在上面学习继承类模板时,vector和stack类既符合组合关系,也符合继承关系。上面已经展示了继承关系,下面展示以下组合关系。

#include<vector>
template<class T>
class Stack
{
public:
	void push(const T& x)
	{
		_con.push_back(x);
	}
	void pop()
	{
		_con.pop_back();
	}
	const T& top()
	{
		return _con.back();
	}
	bool empty()
	{
		return _con.empty();
	}
private:
	vector<T> _con;
};
int main()
{
	Stack<int> s1;
	s1.push(1);
	s1.push(2);
	s1.push(3);
	while (!s1.empty())
	{
		cout << s1.top() << endl;
		s1.pop();
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值