学习C++:C++基础(二)----继承、封装、多态

目录

1.1 继承

1.1.1 为什么需要继承

1.1.2 继承基本语法

1.1.3 继承方式

1.1.3 C++的初始化列表

1.1.4 继承中的构造与析构函数

1.1.5 继承中同名非静态成员处理

1.1.6 继承中同名静态成员处理 

1.1.7 多继承

1.2 多态

1.2.1 多态基本概念

1.2.2 静态联编和动态联编

1.2.3 虚函数原理剖析

1.2.4  多态实现--计算器

1.2.5 纯虚函数

1.2.6 虚析构与纯虚析构

1.2.7 向上向下的类型转换

1.2.8 重写、重载、重定义

1.2.8 练习:电脑安装

1.3 封装


1.1 继承

1.1.1 为什么需要继承

        生活中有许多交通工具:车、畜牲、飞机等;就拿车来说,每辆车都有相同的属性:比如车牌号、生产厂商是车都有的。但每辆车又有不同的属性,比如有的车是自动档就没有换档的零部件、有些车加汽油就没有充电插槽。

        要设计每个型号的汽车类,既有所有汽车共有的属性,也有自己特有的属性。那是否可以把所有属性全写进一个类,可以但是没必要,本节将介绍如何解决这个问题。

1.1.2 继承基本语法

class CarBase
{
public:
    string carName;
    int carNumber;
};

class Tesla : public CarBase
{
public:
    string chargeTools;
};

void test01()
{
    Tesla tesla;
    tesla.carName = "特斯拉" ;
    tesla.carNumber = 66666;
    tesla.chargeTools = "electric";
    cout << "carName is : " << tesla.carName << endl << "car number is : " << tesla.carNumber << endl << "chaege ways is : " << tesla.chargeTools << endl;
}
/home/lhw/桌面/C++/day6/cmake-build-debug/day6
carName is : 特斯拉
car number is : 66666
chaege ways is : electric

进程已结束,退出代码为 0

1.继承语法:class + 子类(派生类) + : + 继承方式 + 父类(基类)

2.优势:提高代码复用性

1.1.3 继承方式

1.继承方式简介

 下面我们根据代码验证一下。

2.公共继承

class CarBase
{
public:
    string carName;
    int carNumber;
protected:
    int brockentimes;
private:
    int bornDate;
};

class Tesla : public CarBase
{
public:
    void ChangeInfo()
    {
        this -> carName = "特斯拉" ;      //父类公共权限子类变成公共权限
        this -> carNumber = 6666 ;       //父类公共权限子类变成公共权限
        this -> brockentimes = 3;        //父类保护权限子类变成保护权限
        //this -> bornDate = 990311 ;     父类私有成员子类不可访问,因为被继承下来了但是不可访问
    }
};

void test01()
{
    Tesla tesla;
    tesla.carName = "捷达" ;         //在tesla中,carame是公共权限,类外可以访问
    tesla.carNumber = 66665 ;       //在tesla中,carnumber是公共权限,类外可以访问。
    //tesla.brockentimes = 6;       //在tesla中,brockentimes是保护权限,类外不可以访问。
}

父类公共权限子类变成公共权限

父类保护权限子类变成保护权限

父类私有成员子类不可访问,因为被继承下来了但是不可访问

在子类对象中,carame是公共权限,类外可以访问

在子类对象中,brockentimes是保护权限,类外不可以访问

3. 保护继承

class BWM : protected CarBase
{
public:
    void ChangeInfo()
    {
        this -> carName = "特斯拉" ;      //父类公共权限子类变成保护权限
        this -> carNumber = 6666 ;       //父类公共权限子类变成保护权限
        this -> brockentimes = 3;        //父类保护权限子类变成保护权限
        //this -> bornDate = 990311 ;     父类私有成员子类不可访问,因为被继承下来了但是不可访问
};

void test02()
{
    BWM bwm;
    //tesla.carName = "捷达" ;         //在BWM中,carame是保护权限,类外不可以访问
    //tesla.carNumber = 66665 ;       //在BWM中,carnumber是保护权限,类外不可以访问。
    //tesla.brockentimes = 6;         //在BWM中,brockentimes是保护权限,类外不可以访问。
}

父类公共权限子类变成保护权限

父类保护权限子类变成保护权限

父类私有成员子类不可访问,因为被继承下来了但是不可访问

在子类对象中,carame是保护权限,类外不可以访问

在子类对象中,carnumber是保护权限,类外不可以访问

在子类对象中,brockentimes是保护权限,类外不可以访问

4.私有继承

class bicycle : private CarBase
{
public:
    void ChangeInfo()
    {
        this -> carName = "特斯拉" ;      //父类公共权限子类变成私有权限
        this -> carNumber = 6666 ;       //父类公共权限子类变成私有权限
        this -> brockentimes = 3;        //父类保护权限子类变成私有权限
        //this -> bornDate = 990311 ;     父类私有成员子类不可访问,因为被继承下来了但是不可访问
};

void test03()
{
    bicycle bicycle1;
    //bicycle1.carName = "捷达" ;         //在bicycle中,carame是私有权限,类外不可以访问
    //bicycle1.carNumber = 66665 ;       //在bicycle中,carnumber是私有权限,类外不可以访问。
    //bicycle1.brockentimes = 6;         //在bicycle中,brockentimes是私有权限,类外不可以访问。
}

父类公共权限子类变成私有权限

父类保护权限子类变成私有权限

父类私有成员子类不可访问,因为被继承下来了但是不可访问

在子类对象中,carame是私有权限,类外不可以访问

在子类对象中,carnumber是私有权限,类外不可以访问

在子类对象中,brockentimes是私有权限,类外不可以访问

5.总结:一个博主的总结,觉得总结的不错

在成员访问模式中:

    public 表示共有;类的数据成员和函数可以被该类对象和派生类访问。
    private 私有型;自己的类可以访问,但派生类不能访问。
    protected 保护型;自身类和派生类可以访问相当于自身的private型成员,它同private的区别就是在对待派生类的区别上。

C++中 public,protected, private 访问标号小结
第一:private, public, protected 访问标号的访问范围。

    private:只能由1.该类中的函数、2.其友元函数访问。不能被任何其他访问,该类的对象也不能访问。
    protected:可以被1.该类中的函数、2.子类的函数、以及3.其友元函数访问。但不能被该类的对象访问。
    public:可以被1.该类中的函数、2.子类的函数、3.其友元函数访问,也可以由4.该类的对象访问。
    注:友元函数包括3种:设为友元的普通的非成员函数;设为友元的其他类的成员函数;设为友元类中的所有成员函数。

第二:类的继承后方法属性变化。

    private 属性不能够被继承。
    使用private继承,父类的protected和public属性在子类中变为private;
    使用protected继承,父类的protected和public属性在子类中变为protected;
    使用public继承,父类中的protected和public属性不发生改变;如下所示:
    public: protected: private:
    public继承 public protected 不可用
    protected继承 protected protected 不可用
    private继承 private private 不可用
    protected继承和private继承能降低访问权限。
————————————————
版权声明:本文为CSDN博主「浮鱼浮鱼」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_50849959/article/details/116595077

1.1.3 C++的初始化列表

我们将解释观察下面的代码:

class InitList
{
public:
    InitList():m_a(10),m_b(20),m_c(30){}
public:
    int m_a;
    int m_b;
    int m_c;
};

void test06()
{
    InitList a;
    cout << "a.m_a = " << a.m_a << endl << "a.m_b = " << a.m_b << endl << "a.m_c = " << a.m_c << endl ;
}

初始化列表是将第一个属性初始化为.....,第二个属性初始化为.....,第三个属性初始化为.....,但这样不是将属性值写死了吗?我们正常这么写:

class InitList
{
public:
    InitList(int a,int b,int c):m_a(a),m_b(b),m_c(c){}
public:
    int m_a;
    int m_b;
    int m_c;
};

void test06()
{
    InitList a(70,80,90);
    cout << "a.m_a = " << a.m_a << endl << "a.m_b = " << a.m_b << endl << "a.m_c = " << a.m_c << endl ;
}

我们看这么一段代码

class InitList
{
public:
    InitList(int a,int b,int c):m_a(a),m_b(b),m_c(c){}
public:
    int m_a;
    int m_b;
    int m_c;
};

void test06()
{
    InitList a(70,80,90);
    cout << "a.m_a = " << a.m_a << endl << "a.m_b = " << a.m_b << endl << "a.m_c = " << a.m_c << endl ;
}

我们理清它的逻辑,我们在test06中通过调用类InitList的有参构造函数来对其初始化,然后70,80,90这三个值传到 int a,int b,int c三个形参中,在传递给右面的初始化列表,将m_a属性赋值70......

1.1.4 继承中的构造与析构函数

1.结论

只介绍结论

子类也会调用父类构造函数,创建子类时先调用父类的构造,再调用自身的构造,析构函数相反。

另外,还记得如果一个类中有其他类的成员,是先构造其他类。

但如果在子类有其他类,是先调用父类还是其他类呢?先走父类构造,在走子类成员构造,再调用自身构造。

2.一些容易犯错的点

我们看下面一段代码:

class Base1
{
public:
    Base1(int a)
    {
        cout << "Base1 struct function is running !" << endl;
    }
};

class son1 : public Base1
{
public:
    son1()
    {
        cout << "son1 struct function is running !" << endl;
    }
};

这段代码会报错:

为什么呢?

父类函数有有参构造函数,则父类就没有默认构造函数了。那么根据结论,调用子类构造函数前要调用父类的默认构造,此时父类函数没有默认构造,编译器报错。

解决1:提供默认构造函数

class Base1
{
public:
    Base1()
    {
    }
    Base1(int a)
    {
        cout << "Base1 struct function is running !" << endl;
    }
};

class son1 : public Base1
{
public:
    son1()
    {
        cout << "son1 struct function is running !" << endl;
    }
};

解决2:利用初始化列表

class Base1
{
public:
    Base1(int a)
    {
        this->m_a = a;
        cout << "Base1 struct function is running !" << endl;
    }
    int m_a;
};

class son1 : public Base1
{
public:
    son1() : Base1(1000)
    {
        cout << "son1 struct function is running !" << endl;
    }
};

void test05()
{
    //son1 s(100);
    son1 s2;
    //cout << "s::m_a = :" << s.m_a << endl;
    cout << "s2::m_a = :" << s2.m_a << endl;
}

我们看这段代码,我们利用了初始化列表,显示的调用父类的其他(有参)构造函数,我们用这种方法,使得执行son1 s2时,执行Base1的构造函数传值10,即调用了父类的有参构造函数,使得m_a获取了10这个值。

class Base1
{
public:
    Base1(int a)
    {
        this->m_a = a;
        cout << "Base1 struct function is running !" << endl;
    }
    int m_a;
};

class son1 : public Base1
{
public:
    son1(int a = 1000) : Base1(a)
    {
        cout << "son1 struct function is running !" << endl;
    }
};

void test05()
{
    son1 s(100);
    son1 s2;
    cout << "s::m_a = :" << s.m_a << endl;
    cout << "s2::m_a = :" << s.m_a << endl;
}

int main() {
    test05();
    return 0;
}

我们这么改,增加了代码的通用性,我们在执行到s(100)时,将参数传给自身的构造函数,构造函数丢掉默认值,选择100传给继承来的Base1属性,Base1属性初始化m_a的值。

/home/lhw/桌面/C++/day6/cmake-build-debug/day6
Base1 struct function is running !
son1 struct function is running !
Base1 struct function is running !
son1 struct function is running !
s::m_a = :100
s2::m_a = :1000

观察结果!我们发现其实是先调用子类的构造函数,不过没执行完就把控制权交给父类了。

1.1.5 继承中同名非静态成员处理

猜猜下段代码会发生什么

class Father
{
public:
    Father()
    {
        m_A = 10;
    }
    int m_A;
};

class Child : public Father
{
public:
    Child()
    {
        m_A = 100;
    }
    int m_A;
};

void test07()
{
    Child child;
    cout << "clild's m_A = " << child.m_A << endl;
}
/home/lhw/桌面/C++/day6/cmake-build-debug/day6
clild's m_A = 100

说明这是就近原则。优先使用自己的。但如何找父类的同名属性呢?

利用作用域。

class Father
{
public:
    Father()
    {
        m_A = 10;
    }
    int m_A;
};

class Child : public Father
{
public:
    Child()
    {
        m_A = 100;
    }
    int m_A;
};

void test07()
{
    Child child;
    cout << "clild's m_A = " << child.m_A << endl;
    cout << "Father's m_A is : " << child.Father::m_A << endl;
}
/home/lhw/桌面/C++/day6/cmake-build-debug/day6
clild's m_A = 100
Father's m_A is : 10

函数也是如此。

但如果父类发生了重载呢?我们看下面一段代码?

假设我们现在的类是这样的

class Father
{
public:
    Father()
    {
        m_A = 10;
    }
    void func()
    {
        cout << "Father's funcc" << endl;
    }
    void func(int a)
    {
        cout << "Father's funcc(int a)" << endl;
    }
    int m_A;
};

class Child : public Father
{
public:
    Child()
    {
        m_A = 100;
    }
    void func()
    {
        cout << "child's funcc" << endl;
    }
    int m_A;
};
void test07()
{
    Child child;
    child.func();
}

我们这么调用代码,根据上文描述,是调用的子类的func函数。事实也如此。

但我们如果这么调用,则编译器会报错。

    child.func(3);

他不会调用父类对func的重载。事实上,当子类重新定义了父类中的同名成员函数,子类的成员函数会隐藏掉父类中所有重载版本的同名成员,但可以通过作用域来访问。

void test07()
{
    Child child;
    child.Father::func(3);
}
/home/lhw/桌面/C++/day6/cmake-build-debug/day6
Father's funcc(int a)

进程已结束,退出代码为 0

1.1.6 继承中同名静态成员处理 

class Father
{
public:

    static int m_A;
};

int Father::m_A = 20;

class Child : public Father
{
public:

    void func()
    {
        cout << "child's funcc" << endl;
    }
    static int m_A;
};

int Child::m_A = 10;

void test07()
{
    Child child;
    cout << "m_a = " << child.m_A << endl ;
    cout << "Father's m_a = " << child.Father::m_A << endl;
}

我们运行上面一段代码:

/home/lhw/桌面/C++/day6/cmake-build-debug/day6
m_a = 10
Father's m_a = 20

好像和非静态的没有什么区别。

因为是静态变量也可直接用类名来访问,下面是代码以及执行结果。

    cout << "Child's m_A is " << Child::m_A << endl;
    cout << "Father's m_A is " << Father::m_A << endl;
    cout << "Father's m_A is " << Child::Father::m_A << endl;
Child's m_A is 10
Father's m_A is 20
Father's m_A is 20

对于静态成员函数,我们要知道,静态成员函数是无法访问类中的变量的。

也可以通过类名和对象访问。结论和非静态成员函数一样。

1.1.7 多继承

1.用途

 我们可以从多个类去继承。

2.语法

class ChildClass : public Father1,public Father2

3.菱形继承

I .为什么需要菱形继承:

两个派生类继承同一个基类而某个类又同时继承两个派生类时,这时发生了菱形继承。

比如

而年龄只要一份就够了,要两份没什么用呀!

羊继承了动物的数据和函数,驼也继承了动物的数据和函数,当草泥马使用两者的函数或者数据时,会产生二义性。

草泥马继承动物的数据两份,事实上我们需要一份就够了。

Ⅱ.建立模型

class Animal
{
public:
    int age;
};

class Sheep : public Animal
{
};

class tuo : public Animal
{
};

class caonima : public Sheep,public tuo
{

};

Ⅲ.修改方法一:制定作用域但存在奇义

 我们可以发现,直接赋值会报错:编译器并不知道要赋值给谁

Non-static member 'age' found in multiple base-class subobjects of type 'Animal': class caonima -> class Sheep -> class Animal class caonima -> class tuo -> class Animal member found by ambiguous name lookup

void test08()
{
    caonima cao;
    cao.Sheep::age = 10 ;
    cao.tuo::age = 15;
    cout << "Sheep's age is : " << cao.Sheep::age << endl ;
    cout << "tuo's age is : " << cao.tuo::age << endl ;
}

我们这么修改代码可以给sheep和tuo的age属性赋值,但现在cao的age是多少,其实不管访问与否都没有什么意义。

Ⅳ.修改方法二:虚继承

class Animal
{
public:
    int age;
};

class Sheep : virtual public Animal
{
};

class tuo : virtual public Animal
{
};

class caonima : public Sheep,public tuo
{

};

void test08()
{
    caonima cao;
    cao.Sheep::age = 10 ;
    cao.tuo::age = 15;
    cout << "Sheep's age is : " << cao.Sheep::age << endl ;
    cout << "tuo's age is : " << cao.tuo::age << endl ;
}

先说一个概念:

虚继承:在继承的前面写个关键字virtual,这时animal类叫做虚基类

我们再来执行这个程序:

/home/lhw/桌面/C++/day6/cmake-build-debug/day6
Sheep's age is : 15
tuo's age is : 15

现在说明在内存中只有一份age了,我们可以这样访问age。

void test08()
{
    caonima cao;
    cao.age = 10 ;
    cout << "age is : " << cao.age << endl;
}

Ⅴ.虚继承的内部实现

当发生虚继承时,sheep和tuo继承的是父类的vbptr(虚基类)指针,子类对于该属性不分配内存空间,仅仅有指向父类该属性的指针,这时看这两行代码

    cao.Sheep::age = 10 ;
    cao.tuo::age = 15;

也就理解了为什么最终数据是15吧。

1.2 多态

1.2.1 多态基本概念

        多态性提供接口与具体实现的一种隔离,改善了代码的可读性和组织性,同时又让生成的程序有一定量的扩展性。项目不仅在创建初期可以拓展,在需要有新功能添加时也能拓展。

1.2.2 静态联编和动态联编

函数重载和运算符重载是一种静态多态。前面已经介绍,不再阐述。

Ⅰ.对于有父子关系的两个类指针或者引用是可以相互转换的

class Animal
{
public:
    void speak()
    {
        cout << "animals are speaking!" << endl;
    }
};

class Cat : public Animal
{
public:
    void speak()
    {
        cout << "Cat is speaking!" << endl;
    }
};

void Speak(Animal & animal)
{
    animal.speak();
}

void test01()
{
    Cat cat;
    Speak(cat);
}

我们向父类传进一个子类对象,编译器并没有报错,我们运行以下看看结果。

/home/lhw/桌面/C++/day7/cmake-build-debug/day7
animals are speaking!

这是为什么呢?

我们在dospeak函数中定义了animal的引用,因此函数执行地址早就绑定到了animal那个类的初始地址了。因此不管我们调用猫还是狗或者其他动物,都会输出animal下的speak方法。

地址早就绑定好了,成为静态联编。

Ⅱ.那如果想调用小猫在说话呢?是不是地址就不能早就绑定好了勒!

我们选择在运行时绑定,成为动态联编

我们在父类中的speak前面加上virtual关键字,构成虚函数。这时候再来运行

class Animal
{
public:
    virtual void speak()
    {
        cout << "animals are speaking!" << endl;
    }
};

class Cat : public Animal
{
public:
    void speak()
    {
        cout << "Cat is speaking!" << endl;
    }
};

void Speak(Animal & animal)
{
    animal.speak();
}

void test01()
{
    Cat cat;
    Speak(cat);
}
/home/lhw/桌面/C++/day7/cmake-build-debug/day7
Cat is speaking!

我们看看编译器内部发生了什么。

这时候调用地址是在运行时根据传入对象(Cat类对象)的不同而去绑定。

Ⅲ.动态多态的产生条件

首先要有继承关系

父类中有虚函数

子类中重写了虚函数

父类的指针或引用指向子类对象

1.2.3 虚函数原理剖析

我们知道,正常创建一个类内函数是不会占用类的空间的。但我们将其加上Virtual关键字,类内就会多出来一个指针vfptr(虚函数指针)指向虚函数表(vftable),虚函数表记录虚函数的函数入口首地址(&Animal:speak),如果创建了子类继承了父类,那么子类也会创建一个vfptr和一个vftable,vftable也会有animal成员函数speak的首地址(&Animal:speak)。如果我们发生了重写(子类重写父类中的虚函数:返回值参数名形参列表必须相同),会覆盖到自己的虚函数表中,即把父类的speak函数(&Animal:speak)覆盖,只有自己的(&Cat:speak),但不改变父类的虚函数表。

我们执行这段代码

Animal * animal = new Cat;

父类指针指向子类对象,这是一个cat对象,切记!

animal -> speak();

这时候会从子类cat中寻找speak的入口。

1.2.4  多态实现--计算器

class CalculatorBase
{
public:
    virtual int getResult()
    {
    }
    int m_A;
    int m_B;
};

class AddCalculator : public CalculatorBase
{
public:
    virtual int getResult()
    {
        return m_A + m_B;
    }
};

class SubCalculator : public CalculatorBase
{
public:
    virtual int getResult()
    {
        return m_A - m_B;
    }
};

class MulCalculator : public CalculatorBase
{
public:
    virtual int getResult()
    {
        return m_A * m_B;
    }
};

void test03()
{
    CalculatorBase * calculatorBase = new AddCalculator;
    calculatorBase->m_A = 100;
    calculatorBase->m_B = 200;
    cout << "M_A + M_B = " << calculatorBase->getResult() << endl ;
    delete calculatorBase;
    calculatorBase = new SubCalculator;
    calculatorBase->m_A = 50 ;
    calculatorBase->m_B = 130;
    cout << "M_A - M_B = " << calculatorBase->getResult() << endl ;
}
/home/lhw/桌面/C++/day7/cmake-build-debug/day7
M_A + M_B = 300
M_A - M_B = -80

1.2.5 纯虚函数

1.用处

Ⅰ.用处

如果父类中的实现没有任何意义,可以写成一个纯虚实现。

Ⅱ.语法

2.语法

virtual int getResult() = 0;

Ⅲ.注意

3.注意

1.如果一个类含有纯虚函数,那么这个类无法实例化对象了,这个类我们称之为抽象类。

class CalculatorBase
{
public:
    virtual int getResult() = 0;
    int m_A;
    int m_B;
};

2.抽象类的子类必须重写父类中的纯虚函数,否则子类也属于抽象类。

1.2.6 虚析构与纯虚析构

1.一个案例

class Animal
{
public:
    Animal()
    {
        cout << "Animal's construct" << endl ;
    }
    virtual void speak()
    {
        cout << "animals are speaking!" << endl;
    }
    ~Animal()
    {
        cout << "Animal's end" << endl ;
    }
};

class Cat : public Animal
{
public:
    Cat(char * name)
    {
        this -> m_Name = new char[strlen(name) +1];
        strcpy(this->m_Name,name);
        cout << "cat's construct" << endl ;
    }
    void speak()
    {
        cout << "Cat " << this->m_Name <<"is speaking!" << endl;
    }
    ~Cat()
    {
        if(this->m_Name)
        {
            delete [] this->m_Name;
            this->m_Name = NULL;
        }
        cout << "cat's end" << endl ;
    }
    char * m_Name;
};



void test01()
{
    Animal * animal = new Cat("xiaohu");
    animal->speak();
}

我们看这一段代码的执行结果:

/home/lhw/桌面/C++/day7/cmake-build-debug/day7
Animal's construct
cat's construct
Cat xiaohu is speaking!

那么问题来了,为什么没有释放。

animal:new出来的东西创建在堆区,需要手动释放。修改test01函数即可:

void test01()
{
    Animal * animal = new Cat("xiaohu");
    animal->speak();
    delete animal;
}
/home/lhw/桌面/C++/day7/cmake-build-debug/day7
Animal's construct
cat's construct
Cat xiaohuis speaking!
Animal's end

那么为什么 cat的析构函数没有被调用呢?

这是因为不会运行子类的析构函数的原因,因为delete的是animal而不是cat。

解决:虚析构

如果子类中有指向堆区的属性(类似释放堆的代码),那么要利用虚析构技术,在delete的时候调用子类的析构函数。

完整的修改完的代码及运行结果如下:

class Animal
{
public:
    Animal()
    {
        cout << "Animal's construct" << endl ;
    }
    virtual void speak()
    {
        cout << "animals are speaking!" << endl;
    }
    virtual ~Animal()
    {
        cout << "Animal's end" << endl ;
    }
};

class Cat : public Animal
{
public:
    Cat(char * name)
    {
        this -> m_Name = new char[strlen(name) +1];
        strcpy(this->m_Name,name);
        cout << "cat's construct" << endl ;
    }
    void speak()
    {
        cout << "Cat " << this->m_Name <<"is speaking!" << endl;
    }
    ~Cat()
    {
        if(this->m_Name)
        {
            delete [] this->m_Name;
            this->m_Name = NULL;
        }
        cout << "cat's end" << endl ;
    }
    char * m_Name;
};



void test01()
{
    Animal * animal = new Cat("xiaohu");
    animal->speak();
    delete animal;
}
/home/lhw/桌面/C++/day7/cmake-build-debug/day7
Animal's construct
cat's construct
Cat xiaohuis speaking!
cat's end
Animal's end

纯虚析构与纯虚函数类似,但不同的是,纯虚析构其要有声明也要有实现,类内声明类外实现,代码如下。

class Animal
{
public:
    Animal()
    {
        cout << "Animal's construct" << endl ;
    }
    virtual void speak()
    {
        cout << "animals are speaking!" << endl;
    }
    virtual ~Animal() = 0;

};

Animal::~Animal()
{
    cout << "Animal's end" << endl ;
}

class Cat : public Animal
{
public:
    Cat(char * name)
    {
        this -> m_Name = new char[strlen(name) +1];
        strcpy(this->m_Name,name);
        cout << "cat's construct" << endl ;
    }
    void speak()
    {
        cout << "Cat " << this->m_Name <<"is speaking!" << endl;
    }
     ~Cat()
    {
        if(this->m_Name)
        {
            delete [] this->m_Name;
            this->m_Name = NULL;
        }
        cout << "cat's end" << endl ;
    }
    char * m_Name;
};



void test01()
{
    Animal * animal = new Cat("xiaohu");
    animal->speak();
    delete animal;
}

1.2.7 向上向下的类型转换

1.向下类型转换

        我们直到,子类占用的空间一定比父类大。我们考虑这么一段代码。

Animal * animal = new Animal;
Cat * cat = (Cat*)animal;

这段代码是不安全的,因为Animal寻址范围小,如果强转成cat*的话,使其寻址范围变大,可能访问到不该访问的东西。

 2.向上类型转换

安全!

即子类指针转换成父指针可以,反之不可以。

且如果发生了多态,转换永远是安全的

Animal * animal = new Cat;
Cat * cat = (Cat*)animal;

1.2.8 重写、重载、重定义

1.重载

同一个作用域

参数个数、参数顺序、参数类型不同

和函数返回值没有关系

const也可以作为重载条件 //do(const teacher & t)   do(teacher & t)

2.重定义(覆盖)

有继承

子类重新定义父类的同名成员(非virtual函数)

3.重写(隐藏)

有继承

子类重写父类的virtual函数

函数返回值、函数名字、函数参数必须和基类中的虚函数一致

4.code

class A
{
public:
    //同一作用域下func1重载
    void func1(){};
    void func1(int a){};
    void func1(int a,int b){};
    void func2(){};
    virtual void fun3(){};
};

class B : public A
{
public:
    //重定义基类的func2,隐藏了基类的func2方法
    void func2(){};
    //重写基类的fun3函数,也可以是覆盖基类的func3
    virtual void func3(){};
};

1.2.8 练习:电脑安装

class CPU
{
public:
    virtual void calculate() = 0;
};

class Videocard
{
public:
    virtual void display() = 0;
};

class Memory
{
public:
    virtual void storage() = 0;
};

class Computer
{
public:
    Computer(CPU * cpu1,Videocard * videocard1,Memory * memory)
    {
        this ->cpu = cpu1;
        this ->videocard = videocard1;
        this ->memory = memory;
    }
    void dowork()
    {
        this -> cpu -> calculate();
        this ->memory ->storage();
        this ->videocard ->display();
    }
    CPU * cpu;
    Videocard * videocard;
    Memory * memory;

    ~Computer()
    {
        if(this->cpu!=NULL)
        {
            delete this->cpu;
            this->cpu = NULL;
        }
        if(this->videocard!=NULL)
        {
            delete this->videocard;
            this->videocard = NULL;
        }
        if(this->memory!=NULL)
        {
            delete this->memory;
            this->memory = NULL;
        }
    }
};

class intelCPU : public CPU
{
public:
    void calculate()
    {
        cout << "INTEL CPU is calculating now" << endl ;
    }
};

class intelVideoCard : public Videocard
{
public:
    void display()
    {
        cout << "INTEL Videocard is displaying now" << endl ;
    }
};

class intelMemory : public Memory
{
public:
    void storage()
    {
        cout << "INTEL Memory is storage now" << endl ;
    }
};

class levonoCPU : public CPU
{
public:
    void calculate()
    {
        cout << "levono CPU is calculating now" << endl ;
    }
};

class levonoVideoCard : public Videocard
{
public:
    void display()
    {
        cout << "levono Videocard is displaying now" << endl ;
    }
};

class levonoMemory : public Memory
{
public:
    void storage()
    {
        cout << "levono Memory is storage now" << endl ;
    }
};

void test06()
{
    cout << "第一台电脑组装" << endl ;
    CPU * intelcpu = new intelCPU;
    Videocard * lenovoVideocard = new levonoVideoCard;
    Memory * lenovomemory = new levonoMemory;
    Computer c1(intelcpu,lenovoVideocard,lenovomemory);
    c1.dowork();

}
/home/lhw/桌面/C++/day7/cmake-build-debug/day7
第一台电脑组装
INTEL CPU is calculating now
levono Memory is storage now
levono Videocard is displaying now

1.3 封装

1.封装的思想

①:该隐藏的数据私有化,该公开的公有化(接口)(private、public);
②:目的就是为了分工合作,有助于使用的方便和安全性;
③:防止不必要的扩展。

2.代码解析

class A
{
public:
    void changea(int a)
    {
        this -> m_a = a;
    }
    int geta()
    {
        return m_a;
    }
private:
    int m_a;
};

void test()
{
    A a;
    a.changea(5);
    cout << "a = " << a.geta() << endl ;
}

封装的思想就是把变量私有化,通过对外界提供接口去访问或者赋值。

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

APS2023

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值