目录
一、继承的概念和定义
1、继承概念
2、继承的语法格式
3、继承的方式
4、继承类模板
二、基类和派生类之间的转换
三、继承中的作用域
四、派生类的默认成员函数
一、默认成员函数介绍
1、派生类中基类成员的构造
2、派生类中基类成员拷贝构造
3、复制重载
4、析构
二、实现一个无法被继承的类
五、继承与友元
六、继承与静态成员
七、多继承及菱形继承问题
1、虚继承
八、继承与组合
一、继承的概念和定义
1、继承概念
在C++中,继承是面向对象编程的一个重要特性。它允许创建一个新类(派生类),从一个现有的类(基类)继承属性和行为。简单地说,派生类是基类得一种特殊化,它继承了基类的成员变量和成员函数,并且可以在这个基础上添加新的成员或者修改从基类继承来的成员的行为;
###例:现有两个类,学生类和老师类,学生和老师都是人,他们有着某些相同的变量和行为,但是也有不同的;此时我们定义一个人类,这个人类有着学生和老师类相同的成员函数和成员变量,那么我们在写学生或者老师类的时候就可以直接继承人类,此时学生类和老师类就有了人类里面的函数和变量;当然学生或者老师它们也有自己的成员和变量;
已知的老师和学生类的定义:
class Teacher
{
public:void identity()//确认身份{cout << "Teacher identity" << endl;}void teach() //行为{cout << "teach" << endl;}
protected:string _name; //姓名string _add; //地址string _tel; //电话int _age; //年龄string _title; //职称
};class Student : public Person
{
public:void identity()//确认身份{cout << "Student identity" << endl;}void study() //行为{cout << "study" << endl;}
protected:string _name; //姓名string _add; //地址string _tel; //电话int _age; //年龄int _stuid; //学号
};
int main()
{Student s1;Teacher t1;s1.identity();cout << endl;t1.identity();return 0;
}
继承了人这个类的老师类和学生类的定义:
class Person
{
public:void identity(){cout << "Preson identity" << endl;}
protected:string _name; //姓名string _add; //地址string _tel; //电话int _age; //年龄
};class Teacher : public Person
{
public:void teach() //行为{cout << "teach" << endl;}
protected:string _title; //职称
};class Student : public Person
{
public:void study() //行为{cout << "study" << endl;}
protected:int _stuid; //学号
};
int main()
{Student s1;Teacher t1;s1.identity();cout << endl;t1.identity();return 0;
}
2、继承的语法格式
Person叫做基类或者父类,Student和Teacher叫做派生类或者子类;继承要在子类的后面加冒号再写上继承的父类;
3、继承的方式
继承分为public、protected、private;这几种继承方式的不同体现在子类中对父类属性或者行为的访问权限:
- 当继承方式是private时,子类不能在类外或者类内直接访问父类的属性或者行为,无论是哪种访问限定符修饰的属性或者行为,但是父类的行为和属性还是被继承到子类中了;
- protected继承:父类中的public或者protected访问限定符修饰的在子类中的访问权限全部变为protected;若是想父类中的不被类外部访问,但是想要被子类访问,就把父类中的属性或者行为的访问限定符写作protected;可以得知protected就是为了继承而生的;
- 继承时可以不写继承的方式。那么class默认为private继承;struct默认为pubilc继承;
4、继承类模板
继承类模板就是继承一个模板类,大框架和普通的继承相同,但是调用父类中的属性或者行为时要指定在父类的哪个属性或者行为,这样是因为类模板的按需实例化,不指定就不会实例化,会报错;
###示例:用继承写一个栈,这个栈继承了父类vector
#include<iostream>
#include<vector>
using namespace std;namespace S
{template<class T>class stack :public vector<T>{public:void push(const T& val){vector<T>::push_back(val);}void pop(){vector<T>::pop_back();}T& top(){return vector<T>:: back();}bool empty()const{return vector<T>:: empty();}};
}
int main()
{S::stack<int> s;s.push(1);s.push(2);s.push(3);while (!s.empty()){cout << s.top() << endl;s.pop();}return 0;
}
每次复用vector里面的成员函数时都要指定父类类域,否则报错,找不到标识符;
二、基类和派生类之间的转换
- 基类和派生类之间的转换时发生在public继承中的;
- 转换指的是派生类的对象可以被赋值给基类的对象、引用、指针;有个形象的说法叫做切片,就是把派生类里面基类的部分给基类对象;
- 基类对象不能被赋值给派生类;
#include<iostream>
#include<string>
using namespace std;class Person
{
protected:string _name;string _gender;int _age;
};
class Student :public Person
{
protected:int _id;
};
void test1()
{Student s1;Person p1 = s1;Person& p2 = s1;Person* p3 = &s1;
}
父类对象被子类赋值之后,只含有切片过来的成员,并且对象的成员变量的值都相同;
但是反过来就不可以;
三、继承中的作用域
- 基类和派生类都有其独立的作用域;
- 基类中和派生类中有相同名称的属性时,派生类将不会访问到这个属性,这叫做隐藏;
- 当基类中和派生类中的函数名相同时,也形成隐藏关系(只要函数名相同就构成这种关系)
- 当构成隐藏关系时,想要在派生类中访问到基类中的被隐藏的属性或者行为时,要指定基类类域;
- 一般不建议写成含有隐藏关系的基类和派生类
###代码示例:
class Person
{
protected:int _num = 111;string _name = "Lois";
};
class Student:public Person
{
public:void Print(){//基类中的_num被隐藏,只能找到派生类中的_numcout << "姓名:" << _name << endl;cout << "号码:" << _num << endl;//想要访问基类中的_num,需要指定类域cout << "号码:" << Person::_num << endl;}
protected:int _num = 999;
};
void test2()
{Student s1;s1.Print();
}
只要基类和派生类中函数名相同就构成隐藏关系
###代码示例:
class Person
{
public:void func(int a){cout << "Person func" << endl;}
};
class Student :public Person
{
public:void func(){cout << "Student func" << endl;}
};
通过派生类访问不到基类中的func,只能访问到派生类中的
四、派生类的默认成员函数
一、默认成员函数介绍
可以把基类当作派生类中的一个自定义类型的属性,这个属性和string类似要调用自己的默认成员函数
1、派生类中基类成员的构造
在派生类中,会调用基类自己的默认构造函数来构造派生类中从基类继承过来的属性,若是基类没有默认构造,那么基类有传参构造函数时,在派生类中要传参构造基类成员;
其他的普通类里面的构造规则一样;
###代码示例:
class Person
{
public:Person(string name = "Peter"):_name(name){}//Person(string name)// :_name(name)//{}
protected:string _name;
};class Student :public Person
{
public:Student(string name="Peter",int age = 19):Person(name),_age(age){}
protected:int _age;
};
void test3()
{Student s1;
}
2、派生类中基类成员拷贝构造
当用一个派生类对象去拷贝构造另一个派生类对象时,对于基类部分的成员变量的拷贝要去调用基类自己的拷贝构造函数
###代码示例:
class Person
{
public:Person(string name = "Peter"):_name(name){}Person(const Person& p):_name(p._name){cout << "基类拷贝构造" << endl;}
protected:string _name;
};class Student :public Person
{
public:Student(string name="Peter",int age = 19):Person(name),_age(age){}Student(const Student& s):Person(s)//对于基类部分调用基类的拷贝构造函数,_age(s._age){cout << "派生类拷贝构造" << endl;}
protected:int _age;
};
void test3()
{Student s1("Tom", 18);Student s2 = s1;
}
这里的用到了基类和派生类之间的转换,也就是切片,s是派生类,直接给p;
3、复制重载
当两个派生类进行赋值操作时,对于基类部分的赋值需要调用基类的赋值重载函数 ;值得注意的是因为派生类和基类的复制重载函数名相同,所以要进行指定类域;
###代码示例:
class
{
public:Person(string name = "Peter"):_name(name){}void operator=(const Person& p){if (this != &p){_name = p._name;}cout << "基类赋值重载" << endl;}
protected:string _name;
};class Student :public Person
{
public:Student(string name = "Peter", int age = 19):Person(name), _age(age){}void operator=(const Student& s){if (this != &s){Person::operator=(s);_age = s._age;}cout << "派生类赋值重载" << endl;}
protected:int _age;
};
void test3()
{Student s1("Tom", 18);Student s2("Mike", 20);s1 = s2;
}
4、析构
派生类对象会在析构时先析构自己的成员变量,再自动调用基类的析构函数去析构基类部分的成员变量,这样保证了先析构派生类再析构基类的顺序
在继承中,对于一个派生类对象:先构造其基类部分再构造派生类部分 ;先析构其派生类部分再析构基类部分;
###代码示例:
class Person
{
public:Person(string name = "Peter"):_name(name){}~Person(){cout << "基类析构" << endl;}
protected:string _name;
};class Student :public Person
{
public:Student(string name = "Peter", int age = 19):Person(name), _age(age){}~Student(){cout << "派生类析构" << endl;}
protected:int _age;
};
void test3()
{Student s1("Tom", 18);Student s2("Mike", 20);
}
二、实现一个无法被继承的类
- 将这个类的构造函数的访问权限置为private,这样其他类想要继承时,无法调用基类的构造函数,那么派生类就无法实例化出对象,所以无法继承;
- C++11新增关键字final,在类名后面加上一个final,表示最终类,含义是这个类无法被继承
1、
2、
五、继承与友元
友元不能被继承,也就是说在基类中的友元函数不能访问派生类中的成员变量;
###代码示例:
class Student;
class Person
{friend void Print(const Person & p, const Student& s);
protected:string _name="Peter";
};
class Student :public Person
{
//friend void Print(const Person& p,const Student& s);
protected:int _age=19;
};
void Print(const Person& p, const Student& s)
{cout << p._name << endl;cout << s._age << endl;
}
void test4()
{Person p1;Student s1;Print(p1,s1);
}
想要在派生类里面也调用Print,则要把Print置为派生类的友元函数
六、继承与静态成员
被继承的静态成员是被派生类对象和基类公用的,静态成员只是受到类域的访问限定
###代码示例:
class Person
{
public:string _name="Peter";static int _count;
};
int Person::_count = 1;
class Student :public Person
{
protected:int _age = 19;
};
void test5()
{Person p1;Student s1;cout << &p1._name << endl;cout << &s1._name << endl;cout << &p1._count << endl;cout << &s1._count << endl;
}
七、多继承及菱形继承问题
单继承:一个派生类只有一个直接基类
多继承:派生类有两个或以上个基类的继承;
菱形继承是多继承的一种:
菱形继承会出现二义性和数据冗余的问题;
二义性:一个变量同时具有两个含义;
数据冗余:只需要一份的数据却存了多个;
###代码示例:
class Person
{
public:Person(const string name="Lois"):_name(name){}string _name;//姓名
};class Teacher:public Person
{
public:Teacher(string name="Tom_t",int id=101):Person(name),_id(id){}
protected:int _id;//职工编号
};class Student:public Person
{
public:Student(string name="Tom_s",int num=102):Person(name),_num(num){}
protected:int _num;//学号
};class Assistant:public Teacher,public Student
{
protected:string _majorCourse;
};
void test6()
{Assistant a1;//这样访问报错:_name 不明确//cout << a1._name << endl;//这样写可以明确_name,但是数据有冗余,也就是_name有多份,但是助手a1只有一个名字cout << a1.Student::_name << endl;cout << a1.Teacher::_name << endl;
}
1、虚继承
为了解决菱形继承的两个问题,这里使用虚继承,也就是在开始继承相同的基类的派生类后面加上virtual再加继承方式和基类;
###代码示例:
class Teacher:virtual public Person
{
public:Teacher(string name="Tom_t",int id=101):Person(name),_id(id){}
protected:int _id;//职工编号
};class Student:virtual public Person
{
public:Student(string name="Tom_s",int num=102):Person(name),_num(num){}
protected:int _num;//学号
};
void test7()
{Assistant a1;a1._name = "Peter";cout << a1._name << endl;cout << a1.Student::_name << endl;cout << a1.Teacher::_name << endl;a1.Student::_name = "Mike";cout << a1._name << endl;cout << a1.Student::_name << endl;cout << a1.Teacher::_name << endl;
}
改成虚继承相当于_name是公用的了,只有一份;
八、继承与组合
- public继承是一种 is-a 的关系,也就是说每个派生类对象都是基类对象;
- 组合是一种has-a 的关系,例如B组合了A,那么每个B对象中都包含一个A对象;
- 继承允许你根据基类的实现来定义派生类的实现,这种通过生成派生类的复用通常通常被称为白箱复用,白箱是相对于可视性而言;在继承方式中,基类内部细节对派生类可见。继承一定程度上破坏了派生类的封装,基类的改变,对于派生类有很大的影响。基类和派生类关系很强,耦合度高;
- 对象组合是继承之外的另一种复用选择。新的更复杂的功能可以通过组合对象来获得,对象组合要求被组合的对象具有良好定义的接口。这种复用被称为黑箱复用,因为对象内部细节不可见。组合类之间没有很强的依赖关系,耦合度低。使用组合类对象有助于保持每个类被封装;
- 两者据情况二用;
//继承
template<class T>
class stack :public vector<T>
{//······
};
//组合
template<class T>
class stack
{//······
private:vector<T> v;
};