面向对象三大特征:封装、继承、多态
继承:所谓继承,是类与类之间的关系。就是基于一个已有的类,来创建出一个新类的过程叫做继承。主要提高代码的复用性。
1> 实现代码的复用性
2> 继承是实现多态的重要基础,没有继承就没有多态
此时我们称A类为父类、基类。B类为子类、派生类
class 子类名 :继承方式 class 父类名1, class 父类2。。。
{
子类扩展的成员;
};
1> 继承方式一共有三种:public、protected、private
2> 回顾访问权限
类内 子类 类外
public ! ! !
protected ! ! X
private ! X X
3> 继承方式时父类中的所有访问权限在子类中的表现形式
父类中 public|protected|private|不可访问 public|protected|private|不可访问 public|protected|private|不可访问
继承方式 public protected private
子类中 public|protected|不可访问|不可访问 protected|protected|不可访问|不可访问 private|private|不可访问|不可访问
总结:继承方式是父类成员的权限在子类中的最高访问权限
4> 访继承方式也可以省略不写,如果省略不写,则默认的继承方式为私有的(private)
5> 常用的继承方式是:public
#include
using namespace std;
//定义父类
class Person
{
public:
string name = "张三"; //公共成员姓名
protected:
int pwd = 666666; //受保护成员 银行卡密码
private:
int money = 888888; //私有成员 私房钱
public:
Person() {}
Person(string n, int p, int m):name(n),pwd(p),money(m) {}
~Person() {}
void show()
{
cout<<"name = "<
1> 子类会继承父类中的所有成员,包括私有成员,只不过不能子类中不能使用父类的私有成员。如果非要使用父类的私有成员,需要在父类中提供public或者protected类型的接口函数完成对私有成员的操作。
2> 想要完成对 子类从父类中继承下来的成员的初始化工作,需要在子类的初始化列表中,显性调用父类的构造函数来完成。如果没有显性调用父类的有参构造,那么系统会自动调用父类的无参构造,来完成对继承下来的成员的初始化工作。
在这个过程中,虽然调用的父类的构造函数,但是并没有实例化父类对象,最终对象的个数只有一个
3> 类与类之间的关系模型
1、继承关系:is a 模型,是特殊的包含关系(has a模型)
2、包含关系:has a模型,在一个类中,有另一个类的成员子对象
3、友元关系:use a模型,在一个类中,使用另一类中的内容
#include
using namespace std;
//定义父类
class Person
{
public:
string name = "张三"; //公共成员姓名
protected:
int pwd = 666666; //受保护成员 银行卡密码
private:
int money = 888888; //私有成员 私房钱
public:
Person() {cout<<"Person::无参构造"<
1> 继承步骤
1、全盘吸收父类的内容:子类会继承父类的所有成员,包括私有成员
2、更改父类中的成员属性(using):子类可以更改从父类中继承下来的能够访问的成员的权限
3、拓展新成员:子类也可以定义独属于子类自己的成员
2> 子类中可以定义与父类中同名的相关成员,如果通过子类对象进行调用时,默认使用的是子类的成员,如果非要使用父类中的成员,需要使用父类名和作用域限定符来找到父类的内容进行访问
#include
using namespace std;
class Father
{
public:
int num;
protected:
int value;
private:
int key;
public:
Father() {}
Father(int n, int v, int k):num(n), value(v), key(k) {}
~Father() {}
void show()
{
cout<<"Father::num = "<
1> 构造函数:父子类中拥有不同的构造函数,在构造子类时,需要显性调用父类的有参构造完成对子类从父类中继承下来成员的初始化工作,如果没有显性调用,系统会自动调用父类的无参构造完成对成员的初始化工作
构造顺序:先构造父类再构造子类
2> 析构函数:父子类中拥有不同的析构函数,在析构子类时,不需要显性调用父类的析构函数,系统会自动调用父类的析构函数,来完成对子类从父类中继承下来成员的空间回收
析构顺序:先析构子类再析构父类
3> 拷贝构造函数:父子类中拥有不同的拷贝构造函数,需要在子类的拷贝构造函数的初始化列表中显性调用父类的拷贝构造函数来完成对子类从父类继承下来成员的初始化工作,如果没有显性调用父类的拷贝构造函数,那么系统会自动调用父类的无参构造来完成对那些成员的初始化工作。
4> 拷贝赋值函数:父子类中拥有不同的拷贝赋值函数,需要在子类的拷贝赋值函数的函数体内,显性调用父类的拷贝赋值函数来完成对子类从父类中继承的成员的赋值工作,如果没有显性调用父类的拷贝赋值函数,那么系统不会自动调用其他函数。当前对象中父类从子类中继承下来的成员的值保持不变。
#include
using namespace std;
class Father
{
public:
int num;
protected:
int value;
private:
int key;
public:
Father() {cout<<"Father::无参构造"<num = other.num;
this->key = other.key;
this->value = other.value;
}
cout<<"Father::拷贝赋值函数"<Father::operator=(other);
this->name = other.name;
}
cout<<"Son::拷贝赋值函数"<
1> C++中允许多级继承,子类继承自父类,孙子类继承自子类
2> 当前子类会用于祖先类的所有成员,包括私有成员
1> C++的面向对象是支持多重继承的
2> 允许一个子类由多个父类共同派生出来,当前子类会继承所有父类的成员
3> 继承格式
class 子类名:继承方式1 父类1, 继承方式2 父类2,。。。,继承方式n 父类n
{
子类拓展成员
}
4> 需要在子类的构造函数的初始化列表中,显性调用所有父类的构造函数来完成对从不同父类中继承下来成员的初始化工作,如果没有显性调用有参构造,系统会自动调用对应父类的无参构造完成初始化工作
5> 子类中调用父类的构造函数的顺序跟继承顺序有关,跟构造函数的摆放顺序无关
6> 产生问题:多个父类中可能会出现同名的成员,子类对象访问起来就会产生歧义。
解决办法:需要使用对应的父类名和作用域限定符来指定
#include
using namespace std;
class A
{
public:
string name;
int value_a;
public:
A() {}
A(string n, int va):name(n), value_a(va) {cout<<"A::有参构造"<
1> 问题模型
A 公共基类
/ \
B C 中间子类
\ /
D 汇聚子类
2> 问题阐述
中间子类会正常继承公共基类的所有成员,但是,使用多个中间子类共同派生出一个汇聚子类时,该汇聚子类中,就会同时拥有多份公共基类的成员,会造成汇聚子类类体膨胀,访问数据也比较麻烦。这个问题成为菱形基础问题也称钻石继承。
1> 虚继承是为了解决菱形继承问题引入的
2> 继承格式:在生成中间子类时,在继承方式前加关键字 virtual ,那么该继承方式就是虚继承
3> 汇聚子类中,仅仅只保留一份公共基类的成员
4> 由于传递给公共基类的数据只有一份,但是有多个父类的构造函数。原则上来说,从父类中继承的成员需要调用直接父类来进行初始化操作,但是这一份成员,不能确定是由哪一个父类来进行构造。索性,直接父类的构造函数全部都不用,直接由公共基类进行构造。
5> 需要在汇聚子类的构造函数初始化列表中,显性调用公共基类的有参构造,来完成对汇聚子类中从公共基类中继承的成员的初始化工作,如果没有显性调用公共基类的有参构造,则系统会自动调用公共基类的无参构造来完成。
#include
using namespace std;
class A
{
protected:
int value;
public:
A() {cout<<"A::无参构造"<
面向对象的三大特征:封装、继承、多态
多态:就是多种状态,能够实现“一物多用”,是实现泛型编程的重要途径
父类的指针或引用可以指向子类的对象,进而调用子类中重写的父类的虚函数。
1> 函数重写是发生在父子类中
2> 要求在子类中定义与父类中函数原型相同的函数
原型相同:返回值类型、函数名、参数列表都相同
1> 定义格式:在定义函数前加关键字 virtual,此时的函数就是虚函数
2> 需要在父类中定义虚函数,子类中可以进行重写也可以不重写
3> 当一个类中,如果定义了虚函数,那么该类会增加一个指针的大小,这个指针指向虚函数表
4> 如果一个类中的某个函数定义成虚函数,那么该类的子子孙孙类中的该函数都是虚函数,即使没有加virtual
#include
using namespace std;
class Animal
{
public:
string name; //名称
public:
Animal() {}
Animal(string n):name(n) {}
~Animal() {}
//定义虚函数
virtual void voice()
{
cout<<"~~~~~~~~"<
#include
using namespace std;
class Hero
{
public:
string name;
int attack = 10; //基础攻击力
static int boss_blood ; //暴君的血量
public:
Hero() {}
Hero(string n, int a):name(n), attack(a) {}
~Hero() {}
//在父类中定义虚函数
virtual void jungle()
{
boss_blood -= attack; //每攻击一次野怪就掉血
}
};
int Hero::boss_blood = 1000; //初始血量
//定义具体英雄类
class Assassin:public Hero
{
public:
int speed ; //移速加成
public:
Assassin(){}
Assassin(string n, int a, int s):Hero(n, a+50), speed(s){}
~Assassin(){}
//重写子父类的虚函数
void jungle()
{
boss_blood -= attack; //每攻击一次野怪就掉血
}
};
//定义具体英雄类
class Master:public Hero
{
public:
int speed ; //移速加成
public:
Master(){}
Master(string n, int a, int s):Hero(n, a+5), speed(s){}
~Master(){}
//重写父类的虚函数
void jungle()
{
boss_blood -= attack; //每攻击一次野怪就掉血
}
};
//功能函数完成打野功能
void fun(Hero &hero)
{
hero.jungle();
cout<
练习:
定义一个图形类(shape)类,包含成员函数输出图像的周长和面积
定义一个圆形类(Circle),继承自图形类,有私有成员半径,重写父类的输出周长和面积函数
定义一个矩形类(Rectangle),继承自图形类,有私有成员宽(width)和高(height),重写父类中输出周长和面积函数
定义一个全局函数,要求传入任意图形,都可以输出该图形的周长和面积
#include // 引入输入输出流库
class Shape { // 定义一个抽象基类Shape
public:
virtual double perimeter() const = 0; // 纯虚函数,计算周长
virtual double area() const = 0; // 纯虚函数,计算面积
};
class Circle : public Shape { // 定义Circle类,继承自Shape
private:
double radius; // 私有成员变量,表示圆的半径
public:
Circle(double r) : radius(r) {} // 构造函数,初始化半径
double perimeter() const override { // 重写基类的perimeter方法
return 2 * 3.14 * radius; // 计算圆的周长
}
double area() const override { // 重写基类的area方法
return 3.14 * radius * radius; // 计算圆的面积
}
};
class Rectangle : public Shape { // 定义Rectangle类,继承自Shape
private:
double width; // 私有成员变量,表示矩形的宽度
double height; // 私有成员变量,表示矩形的高度
public:
Rectangle(double w, double h) : width(w), height(h) {} // 构造函数,初始化宽度和高度
double perimeter() const override { // 重写基类的perimeter方法
return 2 * (width + height); // 计算矩形的周长
}
double area() const override { // 重写基类的area方法
return width * height; // 计算矩形的面积
}
};
void printShapeInfo(const Shape& shape) { // 定义一个打印图形信息的函数
std::cout << "周长为: " << shape.perimeter() << std::endl; // 输出图形的周长
std::cout << "面积为: " << shape.area() << std::endl; // 输出图形的面积
}
int main() { // 主函数
Circle circle(1); // 创建一个半径为1的圆对象
Rectangle rectangle(4, 6); // 创建一个宽为4、高为6的矩形对象
printShapeInfo(circle); // 调用函数打印圆的信息
printShapeInfo(rectangle); // 调用函数打印矩形的信息
return 0; // 程序正常结束
}
2.5 虚函数的底层实现
1、作用域相同
2、函数名相同
3、参数列表必须不同(个数、类型)
4、有无 virtual 都无所谓
5、跟返回值没有关系
1、作用域发生在父子类中
2、函数原型相同(返回值、参数个数、参数类型、函数名)
3、父类中的函数必须要有 virtual 关键字
1、作用域发生在父子俩中
2、函数名相同
3、返回值可以不同
4、参数相同
5、没有virtual修饰
1> 对于有些类而言,类中的相关成员函数没有实现的意义,主要是让子类来完成重写操作的
2> 以便于使用父类的指针或引用指向子类对象,调用子类中重写的父类的虚函数
3> 我们就可以将这样的函数设置成纯虚函数
4> 定义格式: virtual 返回值类型 函数名(形参列表) = 0;
5> 要求子类中必须对这些纯虚函数进行重写
6> 抽象类:包含纯虚函数的类叫做抽象类,抽象类是不能实例化对象的
7> 如果包含纯虚函数的子类中没有重写其虚函数,那么其子类也是抽象类,子类中的该函数也还是纯虚函数
#include
using namespace std;
class shape
{
public:
double perimeter;
double area;
public:
virtual void output() = 0;
};
class Circle:public shape
{
private:
double radius;
public:
Circle():radius(0){}
Circle(double r):radius(r){}
~Circle(){}
void output()
{
cout<<"周长="<<2*radius<<"pi"<output(); //父类?子类?
cout<<"************************"<shape::output();
return 0;
}
1> 当使用父类指针指向子类对象时,构造时会正常先构造父类后构造子类,但是在使用delete释放内存空间时,由于父类指针的作用域,只作用在子类的父类空间内,所以,只会调用父类的析构函数,子类自己的空间就泄露了
2> 此时可以使用虚析构函数来解决:定义析构函数时,在函数头前面加关键字virtual即可
3> 虚析构函数能正确引导delete关键字,在释放父类空间时,把子类的空间一并释放
4> 如果父类的析构函数为虚析构函数,那么该类的子子孙孙类中的析构函数都是虚析构函数
#include
using namespace std;
class shape
{
public:
double perimeter;
double area;
public:
shape(){cout<<"shape ::构造函数"<output(); //正常输出
delete ptr;
return 0;
}
自己封装 栈和队列
#include // 引入输入输出流库
#include // 引入向量库
template // 定义一个模板类,用于实现栈和队列的功能
class Stack {
public:
void push(const T& value) { // 向栈中压入元素
data.push_back(value);
}
void pop() { // 从栈中弹出元素
if (!isEmpty()) {
data.pop_back();
}
}
T top() const { // 获取栈顶元素
if (!isEmpty()) {
return data.back();
}
return T(); // 返回默认值,可以根据需要修改
}
bool isEmpty() const { // 判断栈是否为空
return data.empty();
}
private:
std::vector data; // 使用vector作为栈的底层数据结构
};
template // 定义一个模板类,用于实现栈和队列的功能
class Queue {
public:
void enqueue(const T& value) { // 向队列中插入元素
data.push_back(value);
}
void dequeue() { // 从队列中移除队首元素
if (!isEmpty()) {
data.erase(data.begin());
}
}
T front() const { // 获取队首元素
if (!isEmpty()) {
return data.front();
}
return T(); // 返回默认值,可以根据需要修改
}
bool isEmpty() const { // 判断队列是否为空
return data.empty();
}
private:
std::vector data; // 使用vector作为队列的底层数据结构
};
int main() {
Stack stack; // 创建一个整数类型的栈对象
stack.push(1); // 向栈中压入元素1
stack.push(2); // 向栈中压入元素2
stack.push(3); // 向栈中压入元素3
std::cout << "栈顶元素: " << stack.top() << std::endl; // 输出栈顶元素
stack.pop(); // 从栈中弹出元素
std::cout << "栈顶元素: " << stack.top() << std::endl; // 输出栈顶元素
Queue queue; // 创建一个整数类型的队列对象
queue.enqueue(1); // 向队列中插入元素1
queue.enqueue(2); // 向队列中插入元素2
queue.enqueue(3); // 向队列中插入元素3
std::cout << "队首元素: " << queue.front() << std::endl; // 输出队首元素
queue.dequeue(); // 从队列中移除队首元素
std::cout << "队首元素: " << queue.front() << std::endl; // 输出队首元素
return 0; // 程序正常结束
}