嵌入式学习---C++学习---C++入手引言4

1 什么是多态?  

    定义:“多种状态”,指的是相同对象收到不同消息(静态多态),或者不同对象收到相同消息(动态多态),产生不同的结果。

 1)静态多态:指相同对象收到不同消息,产生不同的结果--》函数重载

 2)动态多态:指的是不同对象收到相同消息,产生不同的结果-->虚函数

    代码实现:

#include <iostream>

using namespace std;
class base
{
public:
    int num1;
    int num2;
    base(int a,int b):num1(a),num2(b)
    {
        cout<<"base()--------------\r\n";
    }
    ~base()
    {
        cout<<"~base()---------------\r\n";
    }
};

class sub:public base
{
public:
    int num3;
    sub(int a,int b,int c):base(a,b),num3(c)
    {
        cout<<"sub()------------\r\n";
    }
    ~sub()
    {
        cout<<"~sub()------------\r\n";
    }
};

int main()
{
    base  obj1(1,2);
    sub   obj2(10,20,30);
    base  *p=&obj1;
    cout<<"p->num1="<<p->num1<<endl;
    cout<<"p->num2="<<p->num2<<endl;
     cout<<"--------------------\r\n";
     //基类的指针在不进行显示类型转换的情况下可以指向派生类对象,
     //通过基类的指针可以访问派生类继承自基类的成员,但是不能访问派生类新增的成员
    p=&obj2;
    cout<<"p->num1="<<p->num1<<endl;//10
    cout<<"p->num2="<<p->num2<<endl;//20
   // cout<<"p->num3="<<p->num3<<endl;

    //基类的引用在不进行显示类型转换的情况下可以引用派生类对象,
    //通过基类的引用可以访问派生类继承自基类的成员,但是不能访问派生类新增的成员
    base &obj=obj2;
    cout<<"obj.num1="<<obj.num1<<endl;
    cout<<"obj.num2="<<obj.num2<<endl;
    //cout<<"obj.num3="<<obj.num3<<endl;

    //sub *q=&obj1;//子类的指针无法指向基类的对象
    //sub &obj3=obj1;//子类的引用无法引用基类的对象


    return 0;
}

2 多态是如何实现的?

c++的动态多态性是通过虚函数实现的

        *什么是虚函数?

        指在基类的函数声明前加virtual关键字,在派生类中可以对继承过来的。该虚函数进行函数重写,运行函数时,系统会根据对象的实际类型来调用相应的函数。如果对象是派生类对象,就调用派生类的重写该虚函数;如果对象是基类对象,就调用基类的虚函数。

        *虚函数的工作原理?

        含有虚函数的类,类的大小会比不含虚函数的类的大小多出4个字节,多出的4个字节存放的是虚函数表指针。每一个含有虚函数的类都有一个虚函数表。

        *虚函数表指针(vptr)?

指向类中虚函数表的指针

        *虚函数表:

        是存放类中所有虚函数地址的一张表(存放顺序和虚函数的声明顺序一致)c++编译器会给含有虚函数的类编译生成一张虚函数表,并生成一个虚函表指针指向该虚函数表。当我们该类定义对象时,对象的地址会自动赋给虚函数表指针,所以对象可以直接调用虚函数;当我们用基类的指针指向派生类对象时,基类指针的值就是派生类对象的地址,所以基类指针p可以通过vptr访问子类对象的虚函数(重写后的)。

         *虚函数virtual的使用限制:

1)virtual不能修饰类外的普通函数,只能修饰类中的成员函数(普通成员函数和析构函数);

2)不能修饰构造函数;

3)不能修饰静态成员函数;

代码实现:

#include <iostream>

using namespace std;
class B
{
public:
    int b;
    void func()
    {
        cout<<"B::func()--------------\r\n";
    }
};

class B1:virtual public B
{//虚基类指针4
private:
    int b1;
};

class B2:virtual public B
{
private:
    int b2;
};

class C:public B1,public B2
{
private:
    int c;
    void func2()
    {
        B1::func();//加作用域
        cout<<"C::func2()------------\r\n";
    }
#if 0
public:
    void func() //同名覆盖
    {
        cout<<"C::func()------------\r\n";
    }
#endif
};

int main()
{
    C obj;
    obj.B2::func();//B::func()  加作用域
    obj.func();//B::func()

    cout<<"B:size="<<sizeof(B)<<endl;//4
    cout<<"B1:size="<<sizeof(B1)<<endl;//12
    cout<<"B2:size="<<sizeof(B2)<<endl;//12
    cout<<"C:size="<<sizeof(C)<<endl;//24 12+12+4-4=24
    return 0;
}

3 多态的作用

作用:对外提供高度统一的接口,实现接口复用,提高代码的可扩展性和可维护性

4 实现多态的条件

条件:1) 必须要有继承;

           2) 在派生类中重写继承自基类的虚函数;

           3) 通过基类的指针或引用指向派生类对象,通过基类指针或引用调用虚函数;

5 函数重写:

        就是重新定义,子类继承了基类的虚函数,子类可以对该虚函数进行重新定义,子类的该虚函数的函数头和基类的函数头一致,但是函数体和基类的不一样。

代码实现:

#include <iostream>

using namespace std;
class base
{
public:
    base()
    {
        cout<<"base()------------\r\n";
    }
    ~base()
    {
        cout<<"~base()--------------\r\n";
    }
    virtual void func()
    {
        cout<<"base::func()--------------\r\n";
    }
};

class sub:public base
{
public:
    sub()
    {
        cout<<"sub()--------------\r\n";
    }
    ~sub()
    {
        cout<<"~sub()-------------\r\n";
    }
    void func()
    {
        cout<<"sub::func()-----------\r\n";
    }
};

int main()
{
    sub obj;
    obj.func();//调用的是重写后的
    //obj.base::func();//子类函数重写后,继承的基类的该虚函数还隐式存在,要使用必须加作用域显示调用

    base *p=&obj;
    p->func();//一定调用的是重写后的



    return 0;
}

6 关于函数重载 函数重写 函数隐藏相关概念

*函数重载:是指函数名相同,参数不同(个数、类型),与返回值值无关

        函数重载特点:A 相同的作用域 B 函数名相同 C 参数不同 D 没有virtual关键字E 返回值可以相同,也可以不同

*函数重写:成员函数覆盖(指派生类重新定义继承自基类的虚函数)

        函数重写特点:A 不同的作用域(分别位于基类和派生类中) B 函数名相同 C 参数相同 D 函数体不同 E 基类的函数前必须有virtual关键字,子类的该函数可有可无 F 返回值相同 G 重写函数的访问限定符可以和基类的访问限定符不同

*函数隐藏:同名覆盖,指的是成员函数隐藏

        函数隐藏特点:A 不同的作用域(分别位于基类和派生类中) B 函数名相同,函数名前没有virtual关键字 C 返回值可相同,也可以不同 D 参数可以相同,也可以不同。

7 纯虚函数和抽象类

  *纯虚函数:基类中的虚函数,只有声明,没有函数体。例如:virtual void move()=0;

  *抽象类:含有纯虚函数的类就是抽象类,一般抽象类作为接口类使用,一般用于基类。抽象类无法实例化对象。

8 虚析构函数

        基类的指针指向派生类对象,当通过基类的指针要释放派生类对象内存的时候(delete p),系统只会调用基类的析构函数去析构子类对象中的基类部分,不会调用子类的析构函数去析构子类新增部分。那么子类成员所占的内存空间就不会被释放,会造成内存泄漏或堆内存空间浪费。那么如何解决这个问题呢?用虚析构函数,将基类的析构函数设计成虚析构函数。(在基类的析构函数前加virtual关键字)。

    注意:只要将基类的析构函数设计成虚析构函数,通过基类的指针去释放子类对象的内存时,就能将子类对象的所有空间释放掉

建议:若要将一个类作为基类,那么就把该类的析构函数设计成虚析构函数

代码实现:

#include <iostream>

using namespace std;
class base
{
private:
    int bnum;
public:
    base(int val=0)
    {
        bnum=val;
        cout<<"base()----------\r\n";
    }
    virtual ~base()
    {
        cout<<"~base()------------\r\n";
    }
};

class sub:public base
{
private:
    int snum;
public:
    sub(int a,int b):base(a),snum(b)
    {
        cout<<"sub()-------------\r\n";
    }
    ~sub()
    {
        cout<<"~sub()--------------\r\n";
    }
};

int main()
{
    cout<<"sub size="<<sizeof(sub)<<endl;//8
   base *p=new sub(1,2);
   delete p;

    return 0;
}

9 c++关键字

9.1 explicit关键字

        作用:用来防止单参数的构造函数发生隐式类型转化(把基本数据类型转化成类类型)

        特点:1) 只能修饰类中的构造函数 ;2) explicit关键字只对一个参数的构造函数有效,对多参数的构造函数无效(多参数的构造函数本身也不会发生隐式类型转化)

代码示例:

#include <iostream>

using namespace std;
class demo
{
public:
    explicit demo(int val=0)
    {
        ival=val;
        cout<<"demo()----------------\r\n";
    }
    ~demo()
    {
        cout<<"~demo()-------------\r\n";
    }
    void getint()
    {
        cout<<"getint():ival="<<ival<<endl;
    }
private:
    int ival;
};

int main()
{
   // demo obj=10;//隐式类型转换 等价于 demo a(10);demo obj=a;
    obj.getint();

    return 0;
}

9.2 override关键字

        作用:若在父类中定义了虚函数,在子类中要对继承的该虚函数做函数重写,override关键字会帮你检查重写的虚函数名和基类的虚函数名是否一致?若不一致,编译会报错。

代码示例:

#include <iostream>

using namespace std;
class base
{
public:
    virtual void func()
    {
        cout<<"base::func()----------\r\n";
    }
    virtual void func1()
    {
        cout<<"base::func1()------------\r\n";
    }
};

class sub:public base
{
public:
    virtual void func() override
    {
        cout<<"sub:func()-----------\r\n";
    }
};

int main()
{
    base *p=new sub;
    p->func();
    return 0;
}

9.3 final关键字

        作用:当不希望某个类被继承,或者不希望某个虚函数被重写,可以在类名和虚函数后添加final关键字,添加后,此类就是一个final类(final类不能派生子类),此虚函数就是一个final虚函数(final虚函数不能被重写)。

代码示例:

#include <iostream>

using namespace std;
class base
{
public:
    void func()
    {
        cout<<"base()----------\r\n";
    }
};

class sub:public base
{
public:
    void func1()
    {
        cout<<"sub::func1()-------\r\n";
    }
};

class sua final:public sub
{
public:
    void func2()
    {
        cout<<"sua::func2---------\r\n";
    }
};
#if 0
class suc:public sua   //final类不能派生新的子类
{
public:
    void func3()
    {
        cout<<"suc::func3()-----------\r\n";
    }
};
#endif

class A
{
public:
    virtual void func()
    {
        cout<<"A::func()---------\r\n";
    }
};

class B:public A
{
public:
    virtual void func() final  //最后一次被重写
    {
        cout<<"B::func()----------\r\n";
    }
};

class C:public B
{
public:
    void func()
    {
        cout<<"C::func()--------------\r\n";
    }
};

int main()
{
    sua obj;
    obj.func();
    return 0;
}

9.4 inline关键字

        作用:inline用来修饰c++类中的函数,被修饰的函数称为内联函数,内联函数是c++的函数宏在c++中,常量宏用const表示,函数宏用内联函数表示(使用函数宏比使用函数的系统开销小,所以建议将简短的频繁调用的成员函数设计成内联函数)

        Inline内联函数的特点:1) 函数体功能简单、代码简短、调用频繁; 2) 内联函数是宏函数,是宏,使用时只做宏替换,所以系统开销小;

注意:

        1) inline必须和函数的定义一起使用,如果只是和函数的声明放一起,则inline不起作用;

        2) 如果函数体代码过长或函数体内有循环语句、if语句或switch语句或递归算法语句,那么不宜将此函数设计成内联函数;

         3) 内联函数只是对编译的一个建议,是否能真正内联,取决于编译器;

代码示例:

#include <iostream>

using namespace std;

class demo
{
public:
#if 0
    inline void func()  //内联函数
    {
        cout<<"demo::func()-----------\r\n";
    }
#endif
     inline void func();
};


 inline void demo::func()
{
    cout<<"demo::func()-----------\r\n";
}

int main()
{
   demo obj;
   obj.func();
    return 0;
}

10 异常处理

  标准异常处理机制:异常处理分成三部分:try检查异常-------throw抛出异常------catch捕获异常。

语法格式为:

try

{

//检查语句

if(错误)

throw 异常

}

catch(异常类型比较)

{处理异常语句;}

catch(异常类型)

{处理异常语句;}

。。。。  

代码示例:

#include <iostream>
#include <stdexcept>

using namespace std;

int func(int a,int b)
{
    if(0==b)
    {
        throw(invalid_argument("div is 0"));
    }
    return a/b;
}

int main()
{
    try
    {
        int a=10,b=0;
        int ret=func(a,b);
        cout<<"ret="<<endl;
    }
    catch(invalid_argument&err)
    {
        cout<<"error  "<<err.what()<<endl;//打印err的错误信息
    }
    catch(int err)
    {
        cout<<"......"<<endl;
    }

    return 0;
}

11 转化函数

    c++提供类型转化函数,用来将一个类的对象转化成另一类型的数据。转化函数的本质就是运算符重载,只是重载的不是内置的运算符,而是类名这个特殊的数据类型。

有两种转换函数:

1>自定义转化函数

        语法格式:operator 类型名(){实现转化的语句块;}

转化函数的基本规则:

1) 转换函数只能是类的成员函数,没有返回值,没有入参;

2) 转化函数不能将类类型转化成void类型,也不能转化成数组和函数;

3) 转化函数通常定义成const形式,原因是它并不该变数据成员的值;

2> 标准转化函数

    c++标准库提供了标准转化函数:

1) reinterpret_cast:可以做不同类型对象之间的转化,还可以将指针类型转化成整型类型(可以做不同数据类型之家的指针和引用之间的转化);

2) const_cast:可以将常指针或常引用转化成非常指针或非常引用;

3) static_cast:可以做基本数据类型之间的转化,也可以做具有继承关系的对象之间的转化;

4) dynamic_cast:可以做具有继承关系的指针或引用的转化;

代码示例:

#include <iostream>

using namespace std;
class demo
{
public:
    demo(int a):ival(a)
    {
        cout<<"demo()------------\r\n";
    }
    ~demo()
    {
        cout<<"~demo()-----------\r\n";
    }
    operator int() const
    {
        return this->ival;
    }

private:
    int ival;
};

class demo1
{
public:
    demo1(int a,int b):val(a),val2(b)
    {
        cout<<"demo1()--------------------\r\n";
    }
    ~demo1()
    {
        cout<<"~demo1()--------------------\r\n";
    }
    operator double() const
    {
        return ((double)val/val2);
    }

private:
    int val;
    int val2;
};

int main()
{
    demo obj(100);
    int tmp=obj;
    cout<<"tmp==="<<tmp<<endl;//100

    demo1 obj1(3,5);
    double d=5+obj1;
    cout<<"d==="<<d<<endl;//5.6
    return 0;
}

12 智能指针(代表一个类作用域)

12.1为什么要使用智能指针?

c++没有自动内存回收机制,当我们写一个new语句时,一般也会把delete语句写上,单我们不能避免程序还为执行到delete时就跳转了 或函数还没有执行到delete时就返回了,那么此时就会造成内存泄露。所以为了避免内存泄露,引入了智能指针,因为智能指针就是一个类作用域,当超过了类的作用域时,系统会自动调用析构函数来释放内存资源。

12.2本质

让智能指针的生命周期和堆内存的生命周期保持一致,释放智能指针时,自动释放堆内存空间    

    12.3 c++提供了4种常见的智能指针(类模板)

        1)auto_ptr:自动智能指针(新版c++已废弃);

2)shared_ptr:共享性智能指针,可以允许多个智能指针指向同一块内存空间,可以自动把堆内存空间释放(最后一个智能指针没了,空间才会被释放);

        3)weak_ptr:弱型智能指针,不能单独存在,必须和共享型智能指针一起使用(依附 型智能指针),弱型智能指针的增加和消亡不会影响堆内存的释放;

        4)unique_ptr:独享型智能指针,同一时刻只能有一个独享型智能指针指向一块内存;

注意: reset()函数释放智能指针(让智能指针不在指向new demo);expired函数检查new的空间是否被释放了?若释放了,返回真;若未释放,返回假。

代码示例:

#include <iostream>
#include <memory>

using namespace std;

class demo
{
public:
    demo()
    {
        cout<<"demo()------------\r\n";
    }
    ~demo()
    {
        cout<<"~demo()----------\r\n";
    }
};


int main()
{
    shared_ptr<demo> p(new demo);//p是智能共享性指针,指向new demo
    shared_ptr<demo> q=p;//q也指向new demo

    cout<<q.use_count()<<endl;//2 查看堆内存空间的所有者个数
    p.reset();//释放智能指针P,p不再指向new demo

    weak_ptr<demo> r=q;//r是弱性智能指针,r指向new demo
    cout<<q.use_count()<<endl;//1
    q.reset();//释放智能指针q
    if(r.expired())
    {
        cout<<"堆内存空间被释放了----\r\n";
    }
    else
    {
        cout<<"堆内存空间未被释放-------\r\n";
    }
    return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

一条小白码

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

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

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

打赏作者

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

抵扣说明:

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

余额充值