当前位置: 首页 > news >正文

day47——面向对象特征之继承

一、继承(inhert)

面向对象三大特征:封装、继承、多态

继承:所谓继承,是类与类之间的关系。就是基于一个已有的类,来创建出一个新类的过程叫做继承。主要提高代码的复用性。

1.1 继承的作用

1> 实现代码的复用性

2> 继承是实现多态的重要基础,没有继承就没有多态

1.2 一个类B继承自类A

此时我们称A类为父类、基类。B类为子类、派生类

1.3 继承的格式

class 子类名 :继承方式  class 父类名1, class 父类2。。。
{
    子类扩展的成员;
};

1.4 继承方式

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 <iostream>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 = "<<name<<endl;         //父类中的公共权限下的成员,父类中可以访问cout<<"pwd = "<<pwd<<endl;         //父类中的受保护权限下的成员,父类中可以访问cout<<"money = "<<money<<endl;         //父类中的私有权限下的成员,父类中可以访问}
};//基于一个已有的父类,定义一个新类
class Stu : public Person        //公共继承
//class Stu : protected Person       //受保护继承
//class Stu :  Person       //私有继承
{
public:string sex = "男";         //性别
protected:int score = 99;          //受保护成员分数
private:int age = 18;            //私有成员 年龄public:Stu() {}~Stu() {}//定义show函数void show(){cout<<"name = "<<name<<endl;         //从父类中继承的的公共权限下的成员,子类中可以访问cout<<"pwd = "<<pwd<<endl;         //从父类中继承的受保护权限下的成员,子类中可以访问//cout<<"money = "<<money<<endl;         //从父类中继承的私有权限下的成员,子类中不可以访问cout<<"sex = "<<sex<<endl;            //子类中的公共权限下的成员,子类中可以访问cout<<"score = "<<score<<endl;            //子类中的受保护权限下的成员,子类中可以访问cout<<"age = "<<age<<endl;            //子类中的私有权限下的成员,子类中可以访问}};int main()
{cout << sizeof (Person) << endl;cout << sizeof (Stu) << endl;//使用子类实例化对象Stu s1;//s1.show();cout<<"name = "<<s1.name<<endl;           //继承的公共权限下的成员,子类中也是公共的,类外可以访问//cout<<"pwd = "<<s1.pwd<<endl;               //继承的受保护的权限下成员,子类中也是受保护的,类外无法访问//cout<<"money = "<<s1.money<<endl;          //继承的私有成员,子类中无法访问,类外也无法访问cout<<"sex = "<<s1.sex<<endl;               //子类中的公共权限下的成员类外可以访问//cout<<"score = "<<s1.score<<endl;            //子类受保护的成员,类外无法访问//cout<<"age = "<<s1.age<<endl;                 //子类的私有成员,类外无法访问return 0;
}

1.5 继承过程中的成员

1> 子类会继承父类中的所有成员,包括私有成员,只不过不能子类中不能使用父类的私有成员。如果非要使用父类的私有成员,需要在父类中提供public或者protected类型的接口函数完成对私有成员的操作。

2> 想要完成对 子类从父类中继承下来的成员的初始化工作,需要在子类的初始化列表中,显性调用父类的构造函数来完成。如果没有显性调用父类的有参构造,那么系统会自动调用父类的无参构造,来完成对继承下来的成员的初始化工作。

在这个过程中,虽然调用的父类的构造函数,但是并没有实例化父类对象,最终对象的个数只有一个

3> 类与类之间的关系模型

1、继承关系:is a 模型,是特殊的包含关系(has a模型)

2、包含关系:has a模型,在一个类中,有另一个类的成员子对象

3、友元关系:use a模型,在一个类中,使用另一类中的内容

#include <iostream>using namespace std;//定义父类
class Person
{
public:string name = "张三";        //公共成员姓名
protected:int pwd = 666666;            //受保护成员 银行卡密码
private:int money = 888888;           //私有成员 私房钱public:Person() {cout<<"Person::无参构造"<<endl;}Person(string n, int p, int m):name(n),pwd(p),money(m) {cout<<"Person::有参构造"<<endl;}~Person() {cout<<"Person::析构函数"<<endl;}void show(){cout<<"name = "<<name<<endl;         //父类中的公共权限下的成员,父类中可以访问cout<<"pwd = "<<pwd<<endl;         //父类中的受保护权限下的成员,父类中可以访问cout<<"money = "<<money<<endl;         //父类中的私有权限下的成员,父类中可以访问}
};//基于一个已有的父类,定义一个新类
class Stu : public Person        //公共继承
//class Stu : protected Person       //受保护继承
//class Stu :  Person       //私有继承
{
public:string sex = "男";         //性别
protected:int score = 99;          //受保护成员分数
private:int age = 18;            //私有成员 年龄public:Stu() {cout<<"Stu::无参构造"<<endl;}Stu(string n, int p, int m,string s, int s1, int a):sex(s), score(s1), age(a) {cout<<"Stu::有参构造"<<endl;}~Stu() {cout<<"Stu::析构函数"<<endl;}//定义show函数void show(){cout<<"name = "<<name<<endl;         //从父类中继承的的公共权限下的成员,子类中可以访问cout<<"pwd = "<<pwd<<endl;         //从父类中继承的受保护权限下的成员,子类中可以访问//cout<<"money = "<<money<<endl;         //从父类中继承的私有权限下的成员,子类中不可以访问cout<<"sex = "<<sex<<endl;            //子类中的公共权限下的成员,子类中可以访问cout<<"score = "<<score<<endl;            //子类中的受保护权限下的成员,子类中可以访问cout<<"age = "<<age<<endl;            //子类中的私有权限下的成员,子类中可以访问}};int main()
{cout << sizeof (Person) << endl;cout << sizeof (Stu) << endl;//使用子类实例化对象Stu s1("zhangpp", 111111, 100, "男", 99, 18);    //有参构造//s1.show();cout<<"name = "<<s1.name<<endl;           //继承的公共权限下的成员,子类中也是公共的,类外可以访问//cout<<"pwd = "<<s1.pwd<<endl;               //继承的受保护的权限下成员,子类中也是受保护的,类外无法访问//cout<<"money = "<<s1.money<<endl;          //继承的私有成员,子类中无法访问,类外也无法访问cout<<"sex = "<<s1.sex<<endl;               //子类中的公共权限下的成员类外可以访问//cout<<"score = "<<s1.score<<endl;            //子类受保护的成员,类外无法访问//cout<<"age = "<<s1.age<<endl;                 //子类的私有成员,类外无法访问cout<<"**********************************************"<<endl;Person p1;cout<<"&p1 = "<<&p1<<"     &p1.name = "<<&p1.name<<endl;cout<<"**********************************************"<<endl;cout<<"&s1 = "<<&s1<<"     &s1.name = "<<&s1.sex<<endl;cout<<"**********************************************"<<endl;return 0;
}

1.6 子类继承父类的过程

1> 继承步骤

1、全盘吸收父类的内容:子类会继承父类的所有成员,包括私有成员

2、更改父类中的成员属性(using):子类可以更改从父类中继承下来的能够访问的成员的权限

3、拓展新成员:子类也可以定义独属于子类自己的成员

2> 子类中可以定义与父类中同名的相关成员,如果通过子类对象进行调用时,默认使用的是子类的成员,如果非要使用父类中的成员,需要使用父类名和作用域限定符来找到父类的内容进行访问

#include <iostream>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 = "<<num<<endl;cout<<"Father::value = "<<value<<endl;cout<<"Father::key = "<<key<<endl;}
};//子类继承父类
class Son:public Father
{//子类是可以更改继承下来的父类中能访问的成员的
public:using Father::value;       //把从父类中继承下来的value成员变量的权限由protected改成publicstring name;                  //子类中拓展的成员private:using Father::num;          //将从父类中继承下来的num的权限由public改成privatepublic:Son(){}Son(int n, int v, int k, string name):Father(n,v,k), name(name){}~Son(){}void show(){cout<<"Son::num = "<<num<<endl;cout<<"Son::value = "<<value<<endl;//cout<<"Son::key = "<<key<<endl;cout<<"Son::name = "<<name<<endl;}};int main()
{Son s(520, 1313, 666, "zhasngpp");s.show();                  //默认调用的是自己的成员函数cout<<"**********************************"<<endl;s.Father::show();           //调用父类提供的同名的函数cout<<"**********************************"<<endl;s.Father::num = 888;s.show();                  //子类和父类中使用的num是同一个cout<<"**********************************"<<endl;return 0;
}

1.7 继承过程中的特殊成员函数

1> 构造函数:父子类中拥有不同的构造函数,在构造子类时,需要显性调用父类的有参构造完成对子类从父类中继承下来成员的初始化工作,如果没有显性调用,系统会自动调用父类的无参构造完成对成员的初始化工作

构造顺序:先构造父类再构造子类

2> 析构函数:父子类中拥有不同的析构函数,在析构子类时,不需要显性调用父类的析构函数,系统会自动调用父类的析构函数,来完成对子类从父类中继承下来成员的空间回收

析构顺序:先析构子类再析构父类

3> 拷贝构造函数:父子类中拥有不同的拷贝构造函数,需要在子类的拷贝构造函数的初始化列表中显性调用父类的拷贝构造函数来完成对子类从父类继承下来成员的初始化工作,如果没有显性调用父类的拷贝构造函数,那么系统会自动调用父类的无参构造来完成对那些成员的初始化工作。

4> 拷贝赋值函数:父子类中拥有不同的拷贝赋值函数,需要在子类的拷贝赋值函数的函数体内,显性调用父类的拷贝赋值函数来完成对子类从父类中继承的成员的赋值工作,如果没有显性调用父类的拷贝赋值函数,那么系统不会自动调用其他函数。当前对象中父类从子类中继承下来的成员的值保持不变。

#include <iostream>using namespace std;class Father
{
public:int num;
protected:int value;
private:int key;public:Father() {cout<<"Father::无参构造"<<endl;}Father(int n, int v, int k):num(n), value(v), key(k) {cout<<"Father::有参构造"<<endl;}~Father() {cout<<"Father::析构函数"<<endl;}Father(const Father&other):num(other.num), value(other.value), key(other.key){cout<<"Father::拷贝构造"<<endl;}//拷贝赋值函数Father &operator=(const Father&other){if(this!=&other){this->num = other.num;this->key = other.key;this->value = other.value;}cout<<"Father::拷贝赋值函数"<<endl;return *this;}void show(){cout<<"Father::num = "<<num<<endl;cout<<"Father::value = "<<value<<endl;cout<<"Father::key = "<<key<<endl;}
};//子类继承父类
class Son:public Father
{//子类是可以更改继承下来的父类中能访问的成员的
public:using Father::value;       //把从父类中继承下来的value成员变量的权限由protected改成publicstring name;                  //子类中拓展的成员private:using Father::num;          //将从父类中继承下来的num的权限由public改成privatepublic:Son(){cout<<"Son::无参构造"<<endl;}Son(int n, int v, int k, string name):Father(n,v,k), name(name){cout<<"Son::有参构造"<<endl;}~Son(){cout<<"Son::析构函数"<<endl;}Son(const Son&other):Father(other),name(other.name){cout<<"Son::拷贝构造"<<endl;}//定义子类的拷贝赋值函数Son &operator=(const Son&other){if(this != &other){//显性调用父类的拷贝赋值函数this->Father::operator=(other);this->name = other.name;}cout<<"Son::拷贝赋值函数"<<endl;return *this;}void show(){cout<<"Son::num = "<<num<<endl;cout<<"Son::value = "<<value<<endl;//cout<<"Son::key = "<<key<<endl;cout<<"Son::name = "<<name<<endl;}};int main()
{{Son s(520, 1313, 666, "zhasngpp");s.show();                  //默认调用的是自己的成员函数cout<<"**********************************"<<endl;s.Father::show();           //调用父类提供的同名的函数cout<<"**********************************"<<endl;s.Father::num = 888;s.show();                  //子类和父类中使用的num是同一个cout<<"**********************************"<<endl;Son s1 = s;s1.show();cout<<"**********************************"<<endl;Son s2;         //无参构造cout<<"**********************************"<<endl;s2 = s1;        //拷贝构造s2.show();cout<<"**********************************"<<endl;}cout<<"**********************************"<<endl;return 0;
}

1.8 多级继承

1> C++中允许多级继承,子类继承自父类,孙子类继承自子类

2> 当前子类会用于祖先类的所有成员,包括私有成员

1.9 多重继承

1> C++的面向对象是支持多重继承的

2> 允许一个子类由多个父类共同派生出来,当前子类会继承所有父类的成员

3> 继承格式

class  子类名:继承方式1  父类1, 继承方式2 父类2,。。。,继承方式n 父类n
{
    子类拓展成员
}

4> 需要在子类的构造函数的初始化列表中,显性调用所有父类的构造函数来完成对从不同父类中继承下来成员的初始化工作,如果没有显性调用有参构造,系统会自动调用对应父类的无参构造完成初始化工作

5> 子类中调用父类的构造函数的顺序跟继承顺序有关,跟构造函数的摆放顺序无关

6> 产生问题:多个父类中可能会出现同名的成员,子类对象访问起来就会产生歧义。

解决办法:需要使用对应的父类名和作用域限定符来指定

#include <iostream>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::有参构造"<<endl;}};class B
{
public:string name;int value_b;
public:B() {}B(string n, int vb):name(n), value_b(vb) {cout<<"B::有参构造"<<endl;}
};//定义子类继承自上面两个类
class C:public A, public B
{
public:C(){}C( string n2, int vb,string n1, int va):A(n1, va),B(n2,vb){cout<<"C::有参构造"<<endl;}
};int main()
{C c1("zhangs",520, "李四",1314);//cout<<c1.name<<endl;               //访问由多个父类继承下来的同名成员时,容易产生歧义cout<<c1.A::name<<endl;              //可以在成员名前加父类名和作用域限定符cout<<c1.B::name<<endl;return 0;
}

1.10 菱形继承问题

1> 问题模型

                                A                     公共基类/     \B          C               中间子类\    /D                     汇聚子类

2> 问题阐述

中间子类会正常继承公共基类的所有成员,但是,使用多个中间子类共同派生出一个汇聚子类时,该汇聚子类中,就会同时拥有多份公共基类的成员,会造成汇聚子类类体膨胀,访问数据也比较麻烦。这个问题成为菱形基础问题也称钻石继承。

1.11 虚继承

1> 虚继承是为了解决菱形继承问题引入的

2> 继承格式:在生成中间子类时,在继承方式前加关键字 virtual ,那么该继承方式就是虚继承

3> 汇聚子类中,仅仅只保留一份公共基类的成员

4> 由于传递给公共基类的数据只有一份,但是有多个父类的构造函数。原则上来说,从父类中继承的成员需要调用直接父类来进行初始化操作,但是这一份成员,不能确定是由哪一个父类来进行构造。索性,直接父类的构造函数全部都不用,直接由公共基类进行构造。

5> 需要在汇聚子类的构造函数初始化列表中,显性调用公共基类的有参构造,来完成对汇聚子类中从公共基类中继承的成员的初始化工作,如果没有显性调用公共基类的有参构造,则系统会自动调用公共基类的无参构造来完成。

#include <iostream>using namespace std;class A
{
protected:int value;
public:A() {cout<<"A::无参构造"<<endl;}A(int v): value(v) {cout<<"A::有参构造"<<endl;}~A() {cout<<"A::析构函数"<<endl;}
};//生成中间子类:使用的是虚继承
class B:virtual public A
{
protected:int value_b;
public:B() {cout<<"B::无参构造"<<endl;}B(int v, int b):A(v), value_b(b) {cout<<"B::有参构造"<<endl;}~B() {cout<<"B::析构函数"<<endl;}
};class C:virtual public A
{
protected:int value_c;
public:C() {cout<<"C::无参构造"<<endl;}C(int v, int c):A(v), value_c(c) {cout<<"C::有参构造"<<endl;}~C() {cout<<"C::析构函数"<<endl;}
};//通过中间子类,共同生成汇聚子类
class D:public B, public C
{
protected:int value_d;
public:D() {cout<<"D::无参构造"<<endl;}D(int a1, int a2, int b, int c, int d):A(520), B(a1,b),C(a2,c), value_d(d) {cout<<"D::有参构造"<<endl;}~D() {cout<<"D::析构函数"<<endl;}void show(){cout<<"value = "<<value<<endl;          //数据只有一份cout<<"value = "<<B::value<<"   &value = "<<&(B::value)<<endl;        //说明是从B中传下来的valuecout<<"value = "<<C::value<<"   &value = "<<&(C::value)<<endl;        //说明从C中传下来的valuecout<<"value_b = "<<value_b<<endl;cout<<"value_c = "<<value_c<<endl;cout<<"value_d = "<<value_d<<endl;}
};int main()
{//cout << sizeof(D) << endl;D d1(1,2,3,4,5);d1.show();return 0;
}

二、多态

面向对象的三大特征:封装、继承、多态

多态:就是多种状态,能够实现“一物多用”,是实现泛型编程的重要途径

2.1 多态的实现

父类的指针或引用可以指向子类的对象,进而调用子类中重写的父类的虚函数。

2.2 函数重写(override)

1> 函数重写是发生在父子类中

2> 要求在子类中定义与父类中函数原型相同的函数

原型相同:返回值类型、函数名、参数列表都相同

2.3 虚函数

1> 定义格式:在定义函数前加关键字 virtual,此时的函数就是虚函数

2> 需要在父类中定义虚函数,子类中可以进行重写也可以不重写

3> 当一个类中,如果定义了虚函数,那么该类会增加一个指针的大小,这个指针指向虚函数表

4> 如果一个类中的某个函数定义成虚函数,那么该类的子子孙孙类中的该函数都是虚函数,即使没有加virtual

#include <iostream>using namespace std;class Animal
{
public:string name;      //名称
public:Animal() {}Animal(string n):name(n) {}~Animal() {}//定义虚函数virtual void voice(){cout<<"~~~~~~~~"<<endl;}
};//定义羊类
class Sheep:public Animal
{
public:int leg;       //腿的个数
public:Sheep(){}Sheep(string n, int l):Animal(n), leg(l){}~Sheep(){}//重写父类的函数void voice() override{cout<<"咩咩咩~~~~~"<<endl;}//定义子类自己的函数void show(){cout<<"我是:"<<name<<"   有"<<leg<<"条腿"<<endl;}};int main()
{Sheep s1("喜羊羊", 2);s1.voice();            //调用的是自己的s1.Animal::voice();      //调用父类的s1.show();//定义一个父类的引用,目标为子类Animal &r1 = s1;r1.voice();               //调用父类的//r1.show();return 0;
}

 2.4    使用多态实现的实例

 #include <iostream>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<<hero.name<<"攻击了暴君,目前暴君血量为:"<<hero.boss_blood<<endl;
}int main()
{Assassin h1("李白", 70, 300);    //实例化刺客Master h2("妲己", 30, 250);         //实例化法师fun(h1);fun(h2);return 0;
}

练习:

定义一个图形类(shape)类,包含成员函数输出图像的周长和面积

定义一个圆形类(Circle),继承自图形类,有私有成员半径,重写父类的输出周长和面积函数

定义一个矩形类(Rectangle),继承自图形类,有私有成员宽(width)和高(height),重写父类中输出周长和面积函数

定义一个全局函数,要求传入任意图形,都可以输出该图形的周长和面积

#include <iostream> // 引入输入输出流库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 虚函数的底层实现

2.6 重载(voerload)、重写(override)和隐藏(hide)的区别

1> 函数重载:函数名相同,形参列表必须不同

1、作用域相同

2、函数名相同

3、参数列表必须不同(个数、类型)

4、有无 virtual 都无所谓

5、跟返回值没有关系

2> 函数重写:子类中重写父类的虚函数

1、作用域发生在父子类中

2、函数原型相同(返回值、参数个数、参数类型、函数名)

3、父类中的函数必须要有 virtual 关键字

3> 函数隐藏:子类中定义与父类同名的函数

1、作用域发生在父子俩中

2、函数名相同

3、返回值可以不同

4、参数相同

5、没有virtual修饰

2.7 纯虚函数(抽象类)

1> 对于有些类而言,类中的相关成员函数没有实现的意义,主要是让子类来完成重写操作的

2> 以便于使用父类的指针或引用指向子类对象,调用子类中重写的父类的虚函数

3> 我们就可以将这样的函数设置成纯虚函数

4> 定义格式: virtual 返回值类型 函数名(形参列表) = 0;

5> 要求子类中必须对这些纯虚函数进行重写

6> 抽象类:包含纯虚函数的类叫做抽象类,抽象类是不能实例化对象的

7> 如果包含纯虚函数的子类中没有重写其虚函数,那么其子类也是抽象类,子类中的该函数也还是纯虚函数

#include <iostream>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"<<endl;cout<<"面积="<<radius*radius<<"pi"<<endl;}
};class Rectangle:public shape
{
private:double width;double height;
public:Rectangle():width(0),height(0){}Rectangle(double w,double h):width(w),height(h){}~Rectangle(){}void output(){cout<<"周长="<<2*(width+height)<<endl;cout<<"面积="<<width*height<<endl;}
};int main()
{//shape s;                //抽象类不能实例化对象Circle c1(2.5);Rectangle r1(3.5,4.2);//定义父类指针shape *ptr = &c1;       //定义父类指针指向子类对象ptr->output();           //父类?子类?cout<<"************************"<<endl;//ptr->shape::output();return 0;
}

2.8 虚析构函数

1> 当使用父类指针指向子类对象时,构造时会正常先构造父类后构造子类,但是在使用delete释放内存空间时,由于父类指针的作用域,只作用在子类的父类空间内,所以,只会调用父类的析构函数,子类自己的空间就泄露了

2> 此时可以使用虚析构函数来解决:定义析构函数时,在函数头前面加关键字virtual即可

3> 虚析构函数能正确引导delete关键字,在释放父类空间时,把子类的空间一并释放

4> 如果父类的析构函数为虚析构函数,那么该类的子子孙孙类中的析构函数都是虚析构函数

#include <iostream>using namespace std;class shape
{
public:double perimeter;double area;public:
shape(){cout<<"shape ::构造函数"<<endl;}
virtual ~shape(){cout<<"shape ::析构函数"<<endl;}         //定义虚析构函数
virtual void output() = 0;};class Circle:public shape
{
private:double radius;
public:Circle():radius(0){}Circle(double r):shape(),radius(r){}~Circle(){}void output(){cout<<"周长="<<2*radius<<"pi"<<endl;cout<<"面积="<<radius*radius<<"pi"<<endl;}
};class Rectangle:public shape
{
private:double width;double height;
public:Rectangle():width(0),height(0){}Rectangle(double w,double h):shape(),width(w),height(h){cout<<"rectangle::构造函数"<<endl;}~Rectangle(){cout<<"rectangle::析构函数"<<endl;}void output(){cout<<"周长="<<2*(width+height)<<endl;cout<<"面积="<<width*height<<endl;}
};int main()
{shape *ptr = new Rectangle(3,5);         //在堆区申请一个子类的对象,用父类的指针指向ptr->output();         //正常输出delete ptr;return 0;
}

作业

自己封装 栈和队列

#include <iostream> // 引入输入输出流库
#include <vector>   // 引入向量库template <typename T> // 定义一个模板类,用于实现栈和队列的功能
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<T> data; // 使用vector作为栈的底层数据结构
};template <typename T> // 定义一个模板类,用于实现栈和队列的功能
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<T> data; // 使用vector作为队列的底层数据结构
};int main() {Stack<int> stack; // 创建一个整数类型的栈对象stack.push(1);     // 向栈中压入元素1stack.push(2);     // 向栈中压入元素2stack.push(3);     // 向栈中压入元素3std::cout << "栈顶元素: " << stack.top() << std::endl; // 输出栈顶元素stack.pop();       // 从栈中弹出元素std::cout << "栈顶元素: " << stack.top() << std::endl; // 输出栈顶元素Queue<int> queue; // 创建一个整数类型的队列对象queue.enqueue(1); // 向队列中插入元素1queue.enqueue(2); // 向队列中插入元素2queue.enqueue(3); // 向队列中插入元素3std::cout << "队首元素: " << queue.front() << std::endl; // 输出队首元素queue.dequeue();  // 从队列中移除队首元素std::cout << "队首元素: " << queue.front() << std::endl; // 输出队首元素return 0; // 程序正常结束
}

相关文章:

day47——面向对象特征之继承

一、继承&#xff08;inhert&#xff09; 面向对象三大特征&#xff1a;封装、继承、多态 继承&#xff1a;所谓继承&#xff0c;是类与类之间的关系。就是基于一个已有的类&#xff0c;来创建出一个新类的过程叫做继承。主要提高代码的复用性。 1.1 继承的作用 1> 实现…...

启动 Spring Boot 项目时指定特定的 application.yml 文件位置

java -jar your-spring-boot-app.jar --spring.config.locationfile:/path/to/your/config/application.yml your-spring-boot-app.jar 是你的 Spring Boot 应用的 JAR 文件名。file:/path/to/your/config/application.yml 是配置文件的绝对路径。 如果你有多个配置文件&#…...

Hive 本地启动时报错 Persistence Manager has been closed

Hive 本地启动时报错 Persistence Manager has been closed 2024-09-07 17:21:45 ERROR RetryingHMSHandler:215 - Retrying HMSHandler after 2000 ms (attempt 2 of 10) with error: javax.jdo.JDOFatalUserException: Persistence Manager has been closedat org.datanucle…...

多模态在京东内容算法上的应用

多模态在京东内容算法上的应用 作者&#xff1a;京东零售技术 2024-09-04 北京 本文字数&#xff1a;5226 字 阅读完需&#xff1a;约 17 分钟 本文作者唐烨参与 DataFunsummit2024&#xff1a;推荐系统架构峰会&#xff0c;在专题【多模态推荐论坛】中分享了多模态算法在京…...

SSM+Ajax实现广告系统

文章目录 1.案例需求2.编程思路3.案例源码(这里只给出新增部分的Handler和ajax部分&#xff0c;需要详情的可以私信我)4.小结 1.案例需求 使用SSMAjax实现广告系统&#xff0c;包括登录、查询所有、搜索、新增、删除、修改等功能&#xff0c;具体实现的效果图如下&#xff1a;…...

项目实战 ---- 商用落地视频搜索系统(6)---UI 结构及与service互动

目录 背景 技术问题 描述 Jinja2 概述 特性 问题解决手段 问题1 问题2 问题3 代码实现 前端代码 python代码 解释 页面展示 home 上传视频 搜索视频 背景 通过1-5 我们已经搭建好完整的后台功能,service,及准备与UI 交互的路由及接口。下面就是UI 部分的搭…...

双头BFS

牛客月赛100 D题&#xff0c;过了80%数据&#xff0c;调了一下午。。。烦死了。。。 还是没调试出来&#xff0c;别人的代码用5维的距离的更新有滞后性&#xff0c;要在遍历之前要去重。。。 #include<bits/stdc.h> using namespace std; const int N2e310; char g[N][…...

使用Spring Boot拦截器实现时间戳校验以防止接口被恶意刷

使用Spring Boot拦截器实现时间戳校验以防止接口被恶意刷 在开发Web应用程序时&#xff0c;接口被恶意刷请求&#xff08;例如DDoS攻击或暴力破解&#xff09;是一个常见的安全问题。为了提高接口的安全性&#xff0c;我们可以在服务端实现时间戳校验&#xff0c;以确保请求的…...

第10讲 后端2

主要目标&#xff1a;理解滑动窗口法、位姿图优化、带IMU紧耦合的优化、掌握g2o位姿图。 第9讲介绍了以为BA为主的图优化。BA能精确优化每个相机位姿与特征点位置。不过在更大的场景中&#xff0c;大量特征点的存在会严重降低计算效率&#xff0c;导致计算量越来越大&#xff0…...

统计学习方法与实战——统计学习方法概论

统计学习方法概论 文章目录 统计学习方法概论前言章节目录导读 实现统计学习方法的步骤统计学习方法三要素模型模型是什么? 策略损失函数与风险函数常用损失函数ERM与SRM 算法 模型评估与模型选择过拟合与模型选择 正则化与交叉验证泛化能力生成模型与判别模型生成方法判别方法…...

人体红外传感器简介

人体红外传感器的工作原理是利用热释电效应&#xff0c;将人体发出的特定波长的红外线转化为电信号&#xff0c;从而实现对人体的检测和感知。 具体来说&#xff0c;人体红外传感器主要由滤光片、热释电探测元和前置放大器组成。滤光片的作用是使特定波长的红外辐…...

【JAVA入门】Day35 - 方法引用

【JAVA入门】Day35 - 方法引用 文章目录 【JAVA入门】Day35 - 方法引用一、方法引用的分类1.引用静态方法2.引用成员方法2.1 引用其他类的成员方法2.2 引用本类和父类的成员方法2.3 引用构造方法2.4 使用类名引用成员方法2.5 引用数组的构造方法 二、方法引用的例题 方法引用就…...

集合及映射

1、集合类图 1&#xff09;ArrayList与LinkedList 区别 LinkedList 实现了双向队列的接口&#xff0c;对于数据的插入速度较快&#xff0c;只需要修改前后的指向即可&#xff1b;ArrayList对于特定位置插入数据&#xff0c;需要移动特定位置后面的数据&#xff0c;有额外开销 …...

软考基础知识之计算机网络

目录 前言 网络架构与协议 网络互联模型 1、OSI/RM 各层的功能 2、TCP/IP 结构模型 常见的网络协议 1、应用层协议 2、传输层协议 3、网络层协议 IPv6 前言 从古代的驿站、 八百里快马&#xff0c; 到近代的电报、 电话&#xff0c; 人类对于通信的追求从未间断&…...

云手机怎样简化海外社媒平台运营

随着越来越多的卖家希望拓展海外市场&#xff0c;运营TikTok、Facebook等社交媒体平台已经成为吸引流量和促进销售的重要手段。然而&#xff0c;在管理海外社媒账号的过程中&#xff0c;许多人会面临网络连接的问题。这时&#xff0c;使用一款高效便捷的云手机工具就显得尤为便…...

创业者必读!选择拍卖源码还是自建开发,哪种方案更安全?

在当今数字化时代&#xff0c;拍卖平台作为一种独特的电子商务模式&#xff0c;正逐渐成为人们关注的焦点。随着互联网技术的发展&#xff0c;网络安全问题变得越来越突出。如何保障用户数据安全&#xff0c;防止信息泄露及攻击事件的发生&#xff0c;已经成为拍卖软件开发者面…...

Spring Cloud Gateway整合基于STOMP协议的WebSocket实战及遇到问题解决

本实例介绍了Spring Cloud Gateway整合基于STOMP协议的WebSocket的实现。开发了聊天功能,和用户在线状态。解决了协议gateway整合websocket出现的问题 技术点 Spring Cloud GatewayNacosWebSocketSTOMPWebSocket与STOMP协议详解 1. WebSocket WebSocket 是一种通信协议,提…...

软考高级:系统架构设计师——软件架构设计 Chapter 笔记

软考高级&#xff1a;系统架构设计师——软件架构设计 1 软件架构设计—基本概念架构所处的位置架构发展历程架构的“41”视图例题 架构描述语言&#xff08;ADL&#xff09;例题 2软件架构设计—架构风格数据流风格调用/返回 风格独立构件风格虚拟机风格仓库风格&#xff08;以…...

PageHelper组件 实现前端分页查询功能

Hi~&#xff01;这里是一颗小谷粒&#xff0c;很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~~&#x1f4a5;&#x1f4a5;个人主页&#xff1a;一颗小谷粒&#x1f4a5;&#x1f4a5;所属专栏&#xff1a;Web前端开发 &#x1f4a5;&#x1f4a5;博主…...

线性回归与逻辑回归在模型参数优化上的比较

概述 线性回归和逻辑回归是两种基础且广泛应用的预测模型。尽管它们在很多方面有相似之处&#xff0c;如都使用梯度下降算法来优化模型参数&#xff0c;但在优化目标和方法上存在一些关键差异。本文将探讨这两种模型在参数优化上的差异&#xff0c;并提供相应的代码示例。 线…...

JavaWeb JavaScript 10.日程管理 第一期

自我消耗&#xff0c;敏感是我&#xff0c; 明媚是我&#xff0c; 我横跳在不同的情绪中 —— 24.8.31 一、登录页及校验 1.校验账号格式 // 校验账号格式function checkUsername(){// 定义正则表达式表示字符串规则var usernameReg /^[a-zA-Z0-9]{5,10}$/;// 获取用户名输入…...

redis为什么快

春内存访问&#xff0c;相比数据库访问磁盘要快单线程&#xff0c;避免上下文切换带来的cpu开销渐进式Rehash。减少阻塞网络模型多路复用&#xff0c;reactor模型 常用基本数据类型 5个基本数据类型2个高级数据结构&#xff08;bitmaps、hyperlog&#xff09; redis高级功能…...

十分钟学会Kubernetes(K8S) 部署SpringBoot3.0

1、十分钟学会Kubernetes(K8S) 部署SpringBoot3.0 本课程以 Java 后端开发的视角&#xff0c;带着大家从零基础入门 k8s 实战&#xff0c;掌握企业级容器化管理平台的各种实战应用&#xff0c;以及 Prometheus 监控告警、ELK 日志收集、DevOps 等众多实战课程内容&#xff0c;大…...

顺序表的插入与删除

一.插入&#xff1a;插入前先移动后面的元素 1.图解&#xff1a; 在b和d之间插入c&#xff0c;此时就需要把d&#xff0c;e&#xff0c;f都向后移一位&#xff0c;腾出一个位置后插入c。 2.代码实现&#xff1a; #include<stdio.h> #define MaxSize 10 //定义最大长度…...

FFMPEG -- 音频开发

1&#xff1a;前言 在进行音频开发之前需要先知道一些基础知识&#xff0c;一些有必要的指导的概念。 1.1 声音的产生、获取和转换 声音的产生的本质是靠震动&#xff0c;声音的传播需要借助媒介&#xff0c;比如空气、液体、固体等媒介。在自然界中声音的可视化为音波的形式&…...

lxml官方入门教程(The lxml.etree Tutorial)翻译

lxml官方入门教程&#xff08;The lxml.etree Tutorial&#xff09;翻译 说明&#xff1a; 首次发表日期&#xff1a;2024-09-05官方教程链接&#xff1a; https://lxml.de/tutorial.html使用KIMI和豆包机翻水平有限&#xff0c;如有错误请不吝指出 这是一个关于使用lxml.et…...

string详解

Golang详解string 文章目录 Golang详解stringGolang中为什么string是只读的&#xff1f;stirng和[]byte的转化原理[]byte转string一定需要内存拷贝吗&#xff1f;字符串拼接性能测试 Golang中为什么string是只读的&#xff1f; 在Go语言中&#xff0c;string其实就是一个结构体…...

基于约束大于规范的想法,封装缓存组件

架构&#xff1f;何谓架构&#xff1f;好像并没有一个准确的概念。以前我觉得架构就是搭出一套完美的框架&#xff0c;可以让其他开发人员减少不必要的代码开发量&#xff1b;可以完美地实现高内聚低耦合的准则;可以尽可能地实现用最少的硬件资源&#xff0c;实现最高的程序效率…...

自动化测试面试真题(附答案)

一、编程语法题 1 、 python 有哪些数据类型 python 数据类型有很多&#xff0c;基本数据类型有整型&#xff08;数字&#xff09;、字符串、元组、列表、字典和布尔类型等 2 、怎么将两个字典合并 调用字典的 update 方法&#xff0c;合并 2 个字典。 3 、 json.l python …...

云原生架构概念

云原生架构概念 云原生架构&#xff08;Cloud Native Architechtrue&#xff09;作为一种现代软件开发的革新力量&#xff0c;正在逐渐改变企业构建、部署和管理应用程序的方式。它的核心优势在于支持微服务架构&#xff0c;使得应用程序能够分解为独立、松耦合的服务&#xf…...