C++继承(图文非常详细)
继承的概念
1.什么是继承
1.简单定义
我们来看一下下面这串代码注意其中的两个类father 和 son
using namespace std;
#include<iostream>
class father
{
public:
void definity()
{
cout << "father" << endl;
}
protected:
int tall = 180;
int age = 20;
};
class son : public father
{
public:
void definityson()
{
cout << "son" << endl;
cout << tall << endl;
}
protected:
int tall1 = 200;
int age1 = 25;
};
int main()
{
son SN;
SN.definity();
SN.definityson();
}
我们可以发现son类定义的SN可以调用father的函数definity()
如上图所示,这便是继承的格式。
class 类名: 继承方式 继承的类名
通过这样继承的方式我们可以想想一下,就是将father里面的内容全部让son可见,如果你觉得不好理解,你可以认为就是father里面的内容全部加进son里面,所以son就可以访问father里面的内容。
注释的内容就是son从father那里继承过来的内容,只是没有显示出来,但同样可以使用
2.使用场景
当我需要定义两个类时,但恰好这两个类的内容有大部分是相同的。
class teacher
{
public:
void teach()
{
cout << "I respond to teach" << endl;
}
void approach()
{
cout << " I can admitted" << endl;
}
protected:
int tall;
int age;
int rank;
};
class student
{
public:
void study()
{
cout << "I respond to teach" << endl;
}
void approach()
{
cout << " I can admitted" << endl;
}
protected:
int tall;
int age;
int rank;
};
我们看以上代码有两个类,但这两个类里面的内容大部分是相同的,如果我们还要定义其它的类,也和这两个类大部分的内容是相同的,那么一个一个定义是不是有点太麻烦了。
那么这时候我们只需将相同的部分全部放入一个类A里面,其它的类只要定义自己特殊的那部分,然后再继承类A,那么就完成了我们需要的定义
代码如下
class person
{
public:
void approach()
{
cout << " I can admitted" << endl;
}
protected:
int tall;
int age;
int rank;
};
class teacher:public person
{
public:
void teach()
{
cout << "I respond to teach" << endl;
cout << rank << endl;
}
void prove()
{
rank = 20;
}
};
class student:public person
{
public:
void study()
{
cout << "I respond to teach" << endl;
cout << rank << endl;
}
void error()
{
rank = 10;
}
};
int main()
{
student xiaoming;
teacher laoxu;
laoxu.prove();
xiaoming.error();
laoxu.teach();
}
3.继承规则
我们来看这一段,当我们用public的方式继承person的时候,变量DNA是不能访问的,但rank可以访问,因为DNA再person的private范围里
2.特殊的继承方式
1.类模板继承
template<class T>
class Stack : public vector<T>
{
public:
void PUSH(const T& x)
{
vector<T>::push_back(x);
}
void pop()
{
vector<T>::pop_back();
}
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);
s1.PUSH(4);
s1.PUSH(5);
s1.PUSH(6);
s1.pop();
cout << s1.top() << endl;
cout << s1.empty() << endl;
}
上述代码中Stack直接继承了库函数中的vector,并且提供了模板参数,这样就大大方便了我们去模拟实现Stack
2.基类和派生类之间的转换
public继承的派生类对象可以赋值给基类的指针,形象地说就是,派生类将基类多出来的那部分切割掉,然后再赋值给基类的指针和引用
如下图代码
class person
{
public:
void study()
{
cout << " I can studying " << endl;
}
void teach()
{
cout << "I can teaching" << endl;
}
string name;
int age;
int sex;
};
class student:public person
{
public:
int NO;
};
int main()
{
student s1;
s1.age = 10;
person* p2;
p2 = &s1;
cout << p2->age << endl;
}
可以通过基类的指针访问派生类的成员
基类不能赋值给派生类
3.隐藏规则
当派生类中有和基类一样的函数,那么派生类便会隐藏基类的函数
class person
{
public:
void study()
{
cout << " I can studying " << endl;
}
void teach()
{
cout << "I can teaching" << endl;
}
};
class student:public person
{
public:
void study()
{
cout << " He can not to studying" << endl;
}
};
int main()
{
student s1;
s1.study();
}
我们可以看到,当我们调用study函数时,并没有调用基类的study函数,而是调用了派生类的study函数,因为派生类中有和基类同名的函数,所以派生类便隐藏了基类的函数
当我们需要调用基类的函数时我们需要特殊声明比如
声明格式 基类名称 :函数
函数隐藏只要是同名便会隐藏,所以不存在所谓重载
并不建议将派生类函数和基类函数用作同一名字
3.四种派生类的默认成员函数
1.默认构造函数
当我们调用派生类的默认构造函数时,同时也会调用基类的默认构造函数
class person
{
public:
person(const char* ch = "xiaoming")
{
name = ch;
cout << "until to person" << endl;
}
protected:
const char* name;
};
class student:public person
{
public:
student()
{
cout << "until to student" << endl;
tall = 100;
age = 10;
}
protected:
int tall;
int age;
};
int main()
{
student s1;
}
我们看如上代码 和 结果
当我实例化派生类对象时,同时也会调用基类的默认构造函数,那是因为要用基类的默认构造函数去初始化基类对象。
2.析构函数
class person
{
public:
person(const char* ch = "xiaoming")
{
name = ch;
cout << "until to person" << endl;
}
~person()
{
cout << " until to ~person" << endl;
}
protected:
const char* name;
};
class student:public person
{
public:
student()
{
cout << "until to student" << endl;
tall = 100;
age = 10;
}
~student()
{
cout << "until to ~student" << endl;
}
protected:
int tall;
int age;
};
int main()
{
student s1;
}
如上图所示,当我调用派生类对象的析构函数时,我们会先调用派生类对象的析构函数,再调用基类的析构函数,这和默认构造函数的顺序有所不同,我们先初始化基类再初始化派生类,先析构派生类再析构基类
3.拷贝构造函数
class person
{
public:
person(const char* ch = "xiaoming")
{
name = ch;
cout << "until to person" << endl;
}
person(const person& ps)
{
name = ps.name;
}
~person()
{
cout << " until to ~person" << endl;
}
protected:
const char* name;
};
class student:public person
{
public:
student()
{
cout << "until to student" << endl;
tall = 100;
age = 10;
}
student(const student& st)
{
tall = st.tall;
age = st.age;
}
~student()
{
cout << "until to ~student" << endl;
}
void inital()
{
tall = 180;
age = 20;
name = "xiaozhang";
}
protected:
int tall;
int age;
};
int main()
{
student s1;
s1.inital();
student s2(s1);
}
如上述代码,我们将s1的数据更改,再用s1的数据去拷贝s2,那么s1和s2的结果会是怎么样呢
我们发现s1和s2位于基类中的成员变量不同,这是因为当调用拷贝构造时,并没有调用到基类的拷贝构造,所以导致基类成员的数据没有拷贝进去
我们需要在初始化列表里调用基类的拷贝构造函数
在派生类的拷贝构造 初始化列表中调用基类的拷贝构造
4.重载赋值运算符
class person
{
public:
person(const char* ch = "xiaoming")
{
name = ch;
cout << "until to person" << endl;
}
person(const person& ps)
{
cout << "COPY until to person" << endl;
name = ps.name;
}
person& operator=(const person& st1)
{
cout << "= person until to =" << endl;
if (this != &st1)
{
name = st1.name;
}
return *this;
}
~person()
{
cout << " until to ~person" << endl;
}
protected:
const char* name;
};
class student:public person
{
public:
student()
{
cout << "until to student" << endl;
tall = 100;
age = 10;
}
student(const student& st):person(st)
{
cout << "COPY until to student" << endl;
tall = st.tall;
age = st.age;
}
~student()
{
cout << "until to ~student" << endl;
}
student& operator=(const student& st1)
{
cout << "= person until to =" << endl;
if (this!=&st1)
{
person::operator=(st1);
tall = st1.tall;
age = st1.age;
}
return *this;
}
void inital()
{
tall = 180;
age = 20;
name = "xiaozhang";
}
protected:
int tall;
int age;
};
int main()
{
student s1;
s1.inital();
student s2;
s2 = s1;
}
通过实现这最后一个函数我们可以看看成员函数的调用规则
首先构造两个派生类,分别调用两次基类构造和派生类构造
然后赋值再调用两次赋值函数
析构先析构派生类再析构基类
4.不能被继承的类
第一种
第一种在基类定义时 名称后面加final
当我们定义基类时,在后面加final ,那么派生类在继承时就失败了
第二种
第二种,将默认构造函数放在private范围内
当我们将基类的默认构造函数放在private里时,派生类在构造对象时会调用基类的默认构造函数,但是因为基类的默认构造函数是存在于private内的,所以会调用失败
5.继承与友元
友元关系不能继承,也就是说基类友元不能访问派生类私有和保护成员
using namespace std;
#include<iostream>
class person
{
friend void print();
public:
person()
{
cout << "I am a person" << endl;
}
private:
int gender=0;
int tall=0;
};
class student :public person
{
public:
student()
{
cout << "I am a student" << endl;
}
private:
int grade=0;
};
void print()
{
person p1;
p1.gender = 10;
cout << p1.gender << endl;
//print()是基类的友元函数,所以可以访问基类构成的对象private成员
student s1;
s1.grade = 10;
cout << s1.grade << endl;
//虽然student继承了person,但友元关系不能继承,所以print()不能访问派生类的private成员
}
int main()
{
print();
}
6.继承与静态成员
基类定义了一个static成员,则整个继承过程中,只会产生一个static,不管继承多少次,都只会有这一个static成员。
using namespace std;
#include<iostream>
class person
{
public:
static int st;
private:
int gender=0;
int tall=0;
};
int person::st = 50;
//类中的静态变量要在类外初始化
class student :public person
{
public:
private:
int grade=0;
};
int main()
{
student s1;
person p1;
cout << p1.st << endl;//输出50
cout << s1.st << endl;//输出50
//不管继承多少次它们的st都是同一个st,因为st存储在静态区中
}
7.继承模型
1.单继承
上述类似单继承模型一个基类只被一个派生类继承一次,并且每个派生类都只被一个派生类继承一次
2.多继承
以上就是多继承模型指的是一个基类或一个派生类被两个或两个以上的派生类所继承
3.菱形继承
以上就是菱形继承的模型,指的就是一个派生类对另一个派生类或者基类继承了两次或两次以上
但是这样的话就会有双份数据,所以会使对象在调用的时候不明确调用那个
using namespace std;
#include<iostream>
class person
{
public:
int gender=0;
int tall=0;
};
class student :public person
{
public:
private:
int grade=0;
};
class classmate :public person
{
public:
private:
int next;
};
class myself : public classmate, public student
{
public:
private:
int myself;
};
int main()
{
myself MY;
cout << MY.gender << endl;//调用gender失败,编译器不知道该调用哪个gender
cout << MY.classmate::gender << endl;//调用成功我们需要指出该调用的类范围
}
C++为了解决这样的问题,发明了虚继承
4.虚继承
当有一个基类被继承两次时我们需要在第一次被继承时加上virtual进行虚继承
比如
通过增加virtual来对person进行虚继承,那么这样myself继承时就只会继承一份person
但是并不建议设计菱形模型,因为这样会复杂非常多
5.多继承指针偏移问题
using namespace std;
#include<iostream>
class student
{
public:
private:
int grade=0;
};
class classmate
{
public:
private:
int next=0;
};
class myself : public classmate, public student
{
public:
private:
int myself;
};
int main()
{
myself MY;
classmate* p1 = &MY;
myself* p2 = &MY;
student* p3 = &MY;
cout <<" classmate : " << p1 << endl;
cout <<" myself : " << p2 << endl;
cout <<" student : " << p3 << endl;
}
我们来看上述代码
myself先是继承了classmate,然后再继承了student那么myself的顺序空间如下
classmate在前student在后
当我们使用myself类型指针和classmate类型指针和student类型指针指向这个myself对象时指针位置如图所示
所以classmate和myself在同一位置而student在它们后面。所以代码运行结果如下
8.继承和组合
1.组合的概念
继承我们已经讲过了,那我们来讲讲组合的方式
这便是组合的方式,我们直接在一个类里面构建另外一个类的对象,我们可以使用这个对象的函数。
2.组合和继承的优缺点
组合将被组合的内部结构封闭了起来,使我们自己构建的类不能访问它的private和protected,安全性比较高,但继承不一样,继承可以访问,所以安全性比较低,我们能使用组合时尽量使用组合。