目录
1. 继承
1.1 概念
继承是面向对象的三大特性之一,体现了代码复用的思想。
继承就是在已存在的类的基础上建立一个新的类,并拥有其特性。
● 已存在的类被称为 基类或者父类。
● 新建立的类被称为 派生类或者子类。
继承后父类的 Person成员(成员函数+成员变量)都会变成子类的一部分。
基类和派生类是相对的,一个类可能存在又是基类又是派生类的情况,取决于哪两个类进行比较。
父类子类练习:
#include <iostream>
using namespace std;
class Father
{
private:
string name = "孙";
public:
void set_name(string name)
{
this->name = name;
}
string get_name()
{
return name;
}
void work()
{
cout << "原神启动" << endl;
}
};
// 派生类继承基类Father
// Child中可以调用父类中的成员变量与成员函数
class Child:public Father
{
};
int main()
{
cout << "Hello World!" << endl;
Child cli;
cout << cli.get_name() << endl; // 调用父类中的成员变量与成员函数
cli.set_name("陈");
cout << cli.get_name() << endl;
cli.work();
// cout << son.name << endl; // 错误 name是私有权限
return 0;
}
以上练习中的代码,child类的功能几乎与Father类重叠,在实际的开发使用过程中,派生类会做出一些与基类的差异化。
● 修改继承下来的基类内容
属性:公有属性可以直接改,更改后基类中的属性也会改变,因为改的是同一份变量。私有属性,需要使用基类公有函数进行更改。
行为:成员函数,函数隐藏,通过派生类实现一个同名同参数的函数,来隐藏基类的函数。(对象调用函数时,会优先调用子类中的同名函数)
(父类中被隐藏的函数,可以使用 对象 基类::基类函数 显示访问)
● 新增派生类的内容
子类差异化练习:
#include <iostream>
using namespace std;
class Father
{
private:
string name = "孙";
public:
void set_name(string name)
{
this->name = name;
}
string get_name()
{
return name;
}
void work()
{
cout << "原神启动" << endl;
}
};
// 派生类继承基类Father
// Child中可以调用父类中的成员变量与成员函数
class Child:public Father
{
public:
// 修改私有属性,调用 set_name 函数接口
void init()
{
// name = "原神";
set_name("原神");
}
// 与父类中的函数同名,父类中的同名函数被隐藏,优先调用子类中的函数
void work()
{
cout << "原神,狗都不玩" << endl;
}
// 子类中新增内容
void game()
{
cout << "原神,天下无敌" << endl;
}
};
int main()
{
cout << "Hello World!" << endl;
Child cli;
cout << cli.get_name() << endl; // 调用父类中的成员变量与成员函数
cli.set_name("陈");
cout << cli.get_name() << endl;
cli.init();
cout << cli.get_name() << endl; // 原神
cli.work(); // 原神,狗都不玩 ,与父类中函数同名,优先调用子类中的同名函数
// cout << son.name << endl; // 错误 name是私有权限
cli.Father::work(); // 原神启动 ,调用父类中被隐藏的函数
cli.game(); // 原神,天下无敌
return 0;
}
1.2 构造函数
1.2.1 派生类与基类构造函数的关系
构造函数与析构函数不能被继承。
派生类的任意一个构造函数,都必须直接或者间接调用基类的任意一个构造函数。
1.2.2 派生类创建对象
当创建派生类的对象时,编译器会进行一下操作:
既然创建的是派生类的对象,先调用派生类的无参构造函数,因为派生类继承在基类,所以派生类的无参构造函数会默认调用基类的无参构造函数,等基类中的无参构造函数调用完,相当于创建了基类对象的成员变量部分,然后把创建的基类对象继承给派生类,然后派生类根据继承下下来的内容,再加入派生类独有的那部分内容。这样才算是创建成功了一个派生类对象。
#include <iostream>
using namespace std;
class Father
{
private:
string name = "孙";
public:
// 有参构造函数
Father(string name):name(name){}
string get_name()
{
return name;
}
};
// 派生类继承基类Father
class Son:public Father
{
};
int main()
{
// Son s; // 找不到基类无参构造函数
// Son s("张"); // 找不到派生类的有参构造函数
return 0;
}
以上代码,创建派生类对象时会失败。因为派生类毛绒额问你调用基类中的无参构造函数,但是基类中已经有了一个有参构造函数,系统就不会自动补充一个午餐构造函数,基类中也就没有无参构造函数,派生类调用不到基类的无参构造函数,从而导致创建对象失败。
那么上述问题该怎么解决呢?
最简单直接的办法,既然基类中没有无参构造函数,那我们自己给他补一个不就完了吗。这样创建对象时派生类就可以正常调用到基类中的无参构造函数,也就能正常创建出一个对象。
既然编译器默认调用基类中的无参构造函数,那么我们能不能让派生类去调用基类中的有参构造函数呢?下几个小节就是这种方法。
基类中补充无参构造函数的例子:
#include <iostream>
using namespace std;
class Father
{
private:
string name = "孙";
public:
// 无参构造函数
Father(){}
// 有参构造函数
Father(string name):name(name){}
string get_name()
{
return name;
}
};
class Son:public Father
{
};
int main()
{
cout << "Hello World!" << endl;
Son s; // 调用派生类的无参构造函数,同时默认调用基类中的无参构造函数
cout << s.get_name() << endl;
return 0;
}
1.2.3 透传构造
在派生类的构造函数中,调用基类的构造函数,实际上编译器自动添加的派生类的构造函数,调用基类无参构造函数时,就采用这种方式。
通过透传构造创建对象的实例:
#include <iostream>
using namespace std;
class Father
{
private:
string name = "孙";
public:
// 有参构造函数
Father(string name):name(name){}
string get_name()
{
return name;
}
};
// 派生类继承基类Father
class Son:public Father
{
public:
// 手动添加构造函数调用基类有参构造函数,透传构造
Son():Father("张"){}
Son(string fn):Father(fn){}
};
int main()
{
Son s;
cout << s.get_name() << endl;
Son s2("王");
cout << s2.get_name() << endl;
return 0;
}
1.2.4 委托构造
委托构造是调用派生类内的另一个构造函数,然后被调用的构造函数再通过透传构造来调用基类中的构造函数。
一个类的构造函数可以调用这个类的另一个构造函数,但是要避免循环委托。
委托构造的性能低于透传构造,但是代码的”维护性更好“。因为通常一个类中构造函数都会委托给能力最强(参数最多)的构造函数。代码重构时,只需要更改这个能力最强的构造函数即可。
通过委托构造创建对象实例:
#include <iostream>
using namespace std;
class Father
{
private:
string name = "孙";
public:
// 有参构造函数
Father(string name):name(name){}
string get_name()
{
return name;
}
};
// 派生类继承基类Father
class Son:public Father
{
public:
// 委托构造
Son():Son("张"){}
// 透传构造
Son(string fn):Father(fn){}
};
int main()
{
Son s;
cout << s.get_name() << endl;
Son s2("王");
cout << s2.get_name() << endl;
return 0;
}
1.2.5 继承构造
C++11新增的写法,只需要一句话,就可以自动给派生类添加n(n为基类构造函数的个数)个构造函数。并且每个构造函数的格式都与基类相同,每个派生类的构造函数都通过透传构造调用对应格式的基类构造函数。
继承构造,实际上没有继承基类中的构造函数,而是在子类中创建了跟基类一样的构造函数,相当于把基类中的构造函数复制到了派生类中。
通过继承构造创建对象实例:
#include <iostream>
using namespace std;
class Father
{
private:
string name = "孙";
public:
Father():name("张"){}
// 有参构造函数
Father(string name):name(name){}
string get_name()
{
return name;
}
};
// 派生类继承基类Father
class Son:public Father
{
public:
// 编译器会自动补充派生类的构造函数,根据基类构造函数的个数来补充。
// 并且每个都通过透传构造进行调用
using Father::Father;
// 编译器自动添加跟基类相同类型的构造函数
// Son():Father(){}
// Son(string fn):Father(fn){}
};
int main()
{
Son s;
cout << s.get_name() << endl;
Son s2("王");
cout << s2.get_name() << endl;
return 0;
}
1.3 多继承
1.3.1 多重继承
C++支持多重继承,即一个派生类可以有多个基类。派生类对于每个基类的关系任然可以看作是一个单继承。
多重继承实例:
#include <iostream>
using namespace std;
class Genshin
{
public:
void yuanshen()
{
cout << "原神真好玩" << endl;
}
};
class Star
{
public:
void bengtie()
{
cout << "崩铁真好玩" << endl;
}
};
class Mihoyo:public Genshin,public Star
{
public:
};
int main()
{
cout << "Hello World!" << endl;
Mihoyo laomi;
laomi.yuanshen();
laomi.bengtie();
return 0;
}
1.3.2 多重继承存在的问题
当多个基类具有重名成员时,编译器在编译的过程中会出现二义性的问题。
解决方式:使用 基类+类名:: 方式调用
#include <iostream>
using namespace std;
class Genshin
{
public:
void yuanshen()
{
cout << "原神真好玩" << endl;
cout << endl;
}
void kejin()
{
cout << "给原神充钱" << endl;
cout << endl;
}
};
class Star
{
public:
void bengtie()
{
cout << "崩铁真好玩" << endl;
cout << endl;
}
void kejin()
{
cout << "给崩铁充钱" << endl;
cout << endl;
}
};
class Mihoyo:public Genshin,public Star
{
public:
void kejin()
{
cout << "我选择绝区零" << endl;
cout << endl;
}
};
int main()
{
Mihoyo laomi;
laomi.yuanshen();
laomi.bengtie();
laomi.Genshin::kejin(); // 调用Genshin中的kejin函数
laomi.Star::kejin(); // 调用Star中的kejin函数
laomi.kejin(); // 调用派生类中的kejin函数
return 0;
}
1.3.3 菱形继承
当一个派生类有多个基类,且这些基类又有一个共同的基类时,就会出现二义性的问题,这种现象也被称为菱形(钻石)继承。
1.3.4 菱形继承存在的问题
(1)可以通过加作用域的方式解决二义性
(2)可以通过虚继承解决二义性问题。
当出现虚继承时,Furniture类会产生一张虚基类表。这个表并不占用任何对象的存储空间,属于Furniture类持有,在程序启动时加载进内存,表中记录了Furniture函数的调用地址偏移量。
Bed和Sofa对象中会出现一个隐藏的成员变量指针,指向Furniture类中的虚基类表。占用对象四个字节。
SofaBed类对象会同时拥有两个虚基类表指针成员,在调用时通过查表解决二义性。
(1)使用作用域解决二义性问题实例:
#include <iostream>
using namespace std;
class Mihoyo
{
public:
void game()
{
cout << "米哈游天下无敌" << endl;
cout << endl;
}
};
class Genshin:public Mihoyo
{
public:
};
class Star:public Mihoyo
{
public:
};
class Game:public Genshin,public Star
{
public:
};
int main()
{
Game laomi;
laomi.Genshin::game();
laomi.Star::game();
return 0;
}
(2)使用虚继承解决二义性问题实例:
#include <iostream>
using namespace std;
class Mihoyo
{
public:
void game()
{
cout << "米哈游天下无敌" << endl;
cout << endl;
}
};
// 虚继承
class Genshin2:virtual public Mihoyo
{
public:
};
class Star2:virtual public Mihoyo
{
public:
};
class Game2:public Genshin2,public Star2
{
public:
};
int main()
{
Game2 laomi2;
laomi2.game();
return 0;
}
2. 权限
2.1 权限修饰符
三种权限一共有九种场景,要做到心中有表,遇到任何一种场景都能直接反映出能否访问。
类内 | 派生类中 | 全局 | |
private | √ | × | × |
protected | √ | √ | × |
public | √ | √ | √ |
2.2 公有权限的继承
公有继承也是使用最多的一种继承方式。
在公有继承中,派生类可以继承基类的成员,不可访问基类的私有成员,基类的所有权限在派生类中保持不变。
共有继承实例:
#include <iostream>
using namespace std;
class Mihoyo
{
private: // 私有成员
string s1 = "原神启动";
protected: // 保护成员
string s2 = "崩铁启动";
public: // 公有成员
string s3 = "绝区零启动";
};
class Game:public Mihoyo
{
public:
Game()
{
// cout << s1 << endl; // s1为私有成员,子类不可访问
cout << s2 << endl;
cout << s3 << endl;
}
};
int main()
{
cout << "Hello World!" << endl;
Game g;
// cout << g.s1 << endl; // s1为私有成员,类外不可访问
// cout << g.s2 << endl; // s1为保护成员,类外不可访问
cout << g.s3 << endl;
return 0;
}
2.3 保护继承
在保护继承中,派生类可以继承基类的成员,不可访问基类的私有成员。
基类的公有成员与保护成员在派生类中权限都是保护权限。(只能在基类与派生类中访问,外部无法访问)。
保护继承实例:
#include <iostream>
using namespace std;
class Mihoyo
{
private: // 私有成员
string s1 = "原神启动";
protected: // 保护成员
string s2 = "崩铁启动";
public: // 公有成员
string s3 = "绝区零启动";
};
// 保护继承
class Game:protected Mihoyo
{
public:
Game()
{
// cout << s1 << endl; // s1为私有成员,子类不可访问
cout << s2 << endl;
cout << s3 << endl;
}
};
int main()
{
cout << "Hello World!" << endl;
Game g;
// cout << g.s1 << endl; // s1为私有成员,类外不可访问
// cout << g.s2 << endl; // s1为保护成员,类外不可访问
// cout << g.s3 << endl; // 保护继承,s3升级为保护成员,不可类外访问
return 0;
}
2.4 私有继承
在私有继承中,派生类可以继承基类的成员,但是不可访问基类的私有成员,基类的公有成员与保护成员在派生类中都是私有权限。
私有继承实例:
#include <iostream>
using namespace std;
class Mihoyo
{
private: // 私有成员
string s1 = "原神启动";
protected: // 保护成员
string s2 = "崩铁启动";
public: // 公有成员
string s3 = "绝区零启动";
};
// 私有继承
class Game:private Mihoyo
{
public:
Game()
{
// cout << s1 << endl;
cout << s2 << endl;
cout << s3 << endl;
}
};
// 公有继承
class Game2:public Game
{
public:
Game2()
{
// cout << s1 << endl; // s1为私有成员,子类不可访问
// cout << s2 << endl;
// cout << s3 << endl;
}
};
int main()
{
cout << "Hello World!" << endl;
Game g;
// cout << g.s1 << endl; // s1为私有成员,类外不可访问
// cout << g.s2 << endl; // 私有继承,s2升级为私有成员,类外不可访问
// cout << g.s3 << endl; // 私有继承,s3升级为私有成员,不可类外访问
return 0;
}