观看慕课网《C++远征》系列课程的笔记,链接https://www.imooc.com/u/1349694/courses?sort=publish
基本概念
1、继承的条件,父类是子类的超集
2、父类与子类是一对概念,基类与派生类是一对概念,不过两对都是一回事
3、例子
class Person{
public:
string name;
void eat();
}
class Worker:public Person{
public:
void work();
int m_iSalary;
}
4、在实例化时,先实例化父类,再实例化子类,因此是先调用父类的构造函数,再调用子类的构造函数,销毁的时候则相反
5、
继承方式
class A:public B //公有继承
class A:protected B //保护继承
class A:private B //私有继承
公有继承
1、protected关键字在不涉及继承时,它与private的作用是一样的,在外部无法直接访问,只能通过成员函数访问
2、在公有继承中,子类能对应地继承父类的public和protected成员,访问方式也没有变化,但是将父类的private成员继承过来后不可见,用子类的成员函数是不可以去访问。
3、protected关键字的意义在公有继承时有所体现,它保留private关键字不允许外界直接访问的特性,又保留了public关键字允许子类成员函数去访问的特性。
class Person{
public:
Person();
~Person();
void eat();
string m_strName;
int m_iAge;
protected:
bool health;
private:
string birthday;
};
class Worker: public Person{
public:
Worker();
~Worker();
void work();
};
void Person::eat(){
m_strName = "Jim";
health = true;
birthday = "0904"; //birthday是自己的私有成员,允许访问
}
void Worker::work(){
m_strName = "Jim";
health = true; //不会报错,health是父类的保护成员,但是在子类中是可以通过成员函数访问的
birthday = "0904"; //报错,birthday是父类Person的私有成员,在子类中不可见
}
int main(){
//直接访问
Worker *p = new Worker();
p->m_strName = "Jim"; //正确
p->m_iAge = 30; //正确
p->eat(); //正确
p->work(); //错误,编译都无法通过
p->health = false; //错误,从父类继承过来的保护成员仍是保护成员,不可以直接访问。
delete p;
p=nullptr;
}
保护继承
1、父类的public和protected都被继承成protected,父类的private跟公有继承一样,能继承但不可访问
2、所以直接访问只能访问到子类自己的public,无法直接访问到父类的public,只能通过protected
私有继承
1、父类的public和protected都被继承成private,父类的private跟公有继承一样,能继承但不可访问
2、同样父类的public和protected都无法直接访问了只能在内部访问,而父类的private以任何方式都不能访问
2、私有继承代表了一种“has a”的关系,子类包含父类(?)
隐藏
1、如果子类的成员函数与父类的成员函数同名,在实例化子类时,父类的同名成员函数就会被隐藏,调用时会默认调用子类的成员函数,但也不是不能去访问父类的同名函数,所以,“隐藏”不是“覆盖”。
2、只要同名就会隐藏,不管参数列表是否相同,只要隐藏就是默认调用子类的成员函数
3、调用父类的同名成员是在前面加上父类名::
class Person{
public:
string m_strName;
Person(){ m_strName = "Merry";}
~Person(){ }
void play(){
cout<<"Person--play()"<<endl;
cout<<m_strName<<endl;
}
}
class Soldier:public Person{
public:
string m_strName; //同名变量
Soldier(){ }
~Soldier(){ }
void play(){ //同名成员函数
cout<<"Soilder--play()"<<endl;
}
void work(){
m_strName = "Jim"; //默认操作子类的同名变量
Person::m_strName = "Jim" //操作父类的同名变量
cout<<"Soilder--work()"<<endl;
}
}
int main(){
Soldier soldier;
soldier.play(); //调用子类的同名函数(默认)
soldier.work();
soldier::Person.play(); //调用父类的同名函数
}
is a 关系
1、soldier是person的子类,士兵也是人,但人不一定是士兵
2、一个person指针是可以指向一个soldier对象的,
3、一个soldier对象也可以给person对象赋值,
4、如果要用2、3的写法,那么Person的析构函数要加入virtual关键字,否则在销毁时只会调用父类的析构函数,子类的析构函数没有被调用,会造成内存泄漏
class Person{
public:
Person(string name="Jim"){
m_strName = name;
cout<<"Person()"<<endl;
}
Person(const Person& per){
cout<<"copy Person()"<<endl;
}
~Person(){ }
// virtual ~Person(){ } 避免内存泄漏
void play(){
cout<<"Person--play()"<<endl;
cout<<m_strName<<endl;
}
protected:
string m_strName;
};
class Soldier:public Person{
public:
Soldier(string name="James",int age=20){
m_strName=name;
m_iAge = age;
cout<<"Soldier()"<<endl;
}
Soldier(const Soldier& sol){
cout<<"copy Soldier()"<<endl;
}
~Soldier(){ }
void work(){ }
protected:
int m_iAge;
};
int main(){
Soldier soldier;
//对象赋值
Person p = soldier; //允许用soldier对象赋值给person对象,赋值只在Soldier与Person共同拥有的成员变量中进行
p.play(); //允许,play是父类的成员函数
p.work(); //不允许,无法去调用子类的成员函数
//指针指向
Person *pPerson = &soldier;
pPerson->play(); //允许,play是父类的成员函数
pPerson->work(); //不允许,无法去调用子类的成员函数,这跟后面讲的多态还不是一回事,后面要实现多态必须要在父类中也声明了
//有work()这个成员函数,并且用virtual修饰
//实例化时先调用父类的构造函数,再调用子类的构造函数,注意,这种情况下子类的构造函数也是会调用的,尽管实例化的是一个父类的对象
delete pPerson; //只会执行父类的析构函数,不会调用子类的析构函数,有可能会造成内存泄漏,解决办法是在父类Person的析构函数前加上virtual关键字,称为虚析构函数,在父类中写上即可,子类也会自动继承这个关键字,不过建议也写上,代码更清楚。
pPerson=nullptr;
}
4、用person的引用或者指针作为函数形参时,也可以用soldier作为实参
void test(Person p){
p.play();
}
void test2(Person &p){
p.play();
}
void test3(Person *p){
p->play();
}
int main(){
Soldier s;
Person p;
//下面六种情况都允许,且输出相同,运行过程有点不同
test(p); //进入函数时,会复制一个person对象副本,所以会调用一次拷贝构造函数,出函数时会调用Person的析构函数
test(s); //形参是Person类,实参是Soldier类,这是允许。只不过很奇怪,即使把virtual加上,退出函数时也只调用了person的析构函数,却没有调用soldier的析构函数。因为进入函数后只是调用了Person的拷贝构造函数,没有调用Soldier的,所以析构时也只有Person的被调用
test2(p); //不会复制一份person副本
test2(s);
test3(&p); //不会复制一份person副本,但是会复制一份person的指针副本,不是这里的重点
test3(&s);
}
多继承与多重继承
1、多重继承是指一层接一层地继承下去,不再赘述,多继承是指一个子类继承了多个父类,比如下面
class Worker{};
class Farmer{};
class MigrantWorker:public Worker,public Farmer{}; //继承方式默认是private
2、多重继承中构造函数的调用顺序是先调用父类的构造函数再调用子类的构造函数,父类的构造函数的调用顺序遵循继承方式表的顺序,如上方的MigrantWorker在实例化时就是先调用Worker的构造函数在调用Farmer的
3、菱形继承
贴两幅课程中的图如下。这种继承的问题在于实例化D类时,对象会具有两份A类的成员,造成数据冗余,解决的方法是用下面准备讲述的virtual
虚继承
1、一个父类派生出多个子类时要注意重定义的问题(注意区分一下这不是多重继承,也不是多继承),解决方法是ifndef语句。在菱形定义中是一定遇到重定义的问题
//Person.h
class Person{ }
//Worker.h
#include "Person.h"
class Worker{ }
//Worker.h
#include "Person.h" //Person被重复include了,会报错
class Farmer{ }
//解决方法:在父类Person.h中加入ifndef语句,PERSON_H是可以随便写的。
#ifndef PERSON_H
#define PERSON_H
class Person{ }
#endif
2、在定义类B和类C时,分别用virtual关键字继承类A,此时类D多继承B、C时就不会拥有两份类A的成员了。注意,不用virtual时是不会报错的,运行也是能通过,只是存在冗余数据而已。
class Person{};
class Worker{}:virtual public Person;
class Farmer{}:virtual public Person;
class MigrantWorker:public Worker,public Farmer{}; //继承方式默认是private
3、当加入virtual关键字时,父类A的成员变量不接收子类的参数传递,只会使用默认的参数
class Person{
public:
Person(string color="blue"):m_color(color){
cout<<"Person()"<<endl;
}
virtual ~Person(){
cout<<"~Person()"<<endl;
}
string m_color;
};
class Farmer:virtual public Person{
public:
Farmer(string color="yellow"):Person("Farmer"+color){
cout<<"Farmer()"<<endl;
}
~Farmer(){
cout<<"~Farmer()"<<endl;
}
};
class Worker:virtual public Person{
public:
Worker(string color="green"):Person("Worker"+color){
cout<<"Worker()"<<endl;
}
~Worker(){
cout<<"~Worker()"<<endl;
}
};
class Migrant:public Farmer,public Worker{
public:
Migrant(string color="black"):Worker(color),Farmer(color){
cout<<"Migrant()"<<endl;
}
~Migrant(){
cout<<"~Migrant()"<<endl;
}
};
int main(){
Migrant *p = new Migrant("white");
cout<<&(p->Farmer::m_color)<<endl; //farmer的m_color和worker的m_color地址相同,说明是同一份
cout<<&(p->Worker::m_color)<<endl;
cout<<(p->Farmer::m_color)<<endl; //farmer的m_color和worker的m_color输出都是blue,说明migrant的参数
cout<<(p->Worker::m_color)<<endl; //white没有传下去,worker和farmer各自的也没有传下去,用的就是person自己默认的那份
delete p;
}