嵌入式学习(十七)

(复习课)
一、面向对象的三大特征

封装、继承和多态

静态多态:函数重载

动态多态:

泛式/泛型编程:以不变的代码,实现多种功能。

二、多态

【1】函数重写(override)

复习重载:

函数名相同

作用域相同

参数不同

函数重写发生在父子类之间,指的是,在子类中重写父类的函数,

要求父子类中的函数:函数名相同,参数列表也相同------>函数声明相同。

只有虚函数能进行重写(virtual)

【2】虚函数(virtual)

在定义成员函数时,在函数前面加上virtual,那么该函数就是虚函数

只要父类中某个函数被定义为了虚函数,后面所有继承自该类的该成员函数都是虚函数

虚函数可以在子类中对父类继承下来的虚函数进行重写,如果不重写使用的还是父类中的函数

体现出函数重写的条件(父类的指针指向子类的成员)

虚指针,是从父类继承下来的,虚指针指向虚函数表

虚函数表:只要类中有虚函数都会给该类提供一个虚函数表,每个类有不同的虚函数表

#include <iostream>

using namespace std;

class Person

{

    //对于有虚函数的类,会提供一个虚指针,指向虚函数表

private:

    string name;

    int age;

public:

    Person(string name,int age):name(name),age(age){}

    virtual void show()

    {

        cout << "name=" << name << "\t" << "age=" << age << endl;

    }

};

class Stu:public Person

{

private:

    //string name;

    //int age;

    int score;

public:

    Stu(int s,string name,int age):Person(name,age),score(s){}

    void show()override

    {

        cout << "score= " << score << endl;

    }

};

int main()

{

    Stu s1(100,"zhangsan",18);

    //父类的指针指向子类的对象

    Person *p = &s1;

    p->show();  //如果不加virtual访问到的是Person中的show

    //如果加上virtual关键字,访问到的是Stu中的show

//    cout << sizeof(Person) << endl;

//    cout << sizeof(Stu) << endl;

    s1.Person::show();

    return 0;

}

练习:

1、定义一个基类Shape和派生类Circle(圆)、Rec(长方形),基类有一个虚函数void show_area(),要求子类重写该虚函数,实现输出对应类型图形的面积,给每一个类提供构无参构造和有参构造。(写一个全局函数,实现根据传进来的图形不同,调用不同图形类中的函数)

#include <iostream>

using namespace std;

class Shape

{

private:

    string name;

public:

    Shape(){}

    Shape(string name):name(name){}

    virtual void show_area()

    {}

};

class Rec:public Shape

{

private:

    int len;

    int wid;

public:

    Rec(){}

    Rec(string name,int len,int wid):Shape(name),len(len),wid(wid){}

    //重写父类的虚函数

    void show_area()override

    {

        cout << "Rec的面积=" << len*wid << endl;

    }

};

class Circle:public Shape

{

private:

    float r;

    float PI;

public:

    Circle(){}

    Circle(string name,float PI,float r):Shape(name),PI(PI),r(r){}

    //重写

    void show_area()

    {

        cout << "Cir的面积=" << r*r*PI << endl;

    }

};

//全局函数,使用父类的指针接收子类的对象

void fun(Shape *p)

{

    p->show_area();

}

//父类的引用接收子类的对象

void fun(Shape &p)

{

    p.show_area();

}

int main()

{

    Rec r1("chang",8,9);

    fun(&r1);

//    r1.show_area();

    Shape *p = &r1;

    Circle c1("yuan",2,3.14);

    fun(c1);

//    c1.show_area();

    return 0;

}

作业:

全局变量,int monster = 10000;定义英雄类hero,受保护的属性string name,int hp,int attck;公有的无参构造,有参构造,虚成员函数 void Atk(){blood-=0;},法师类继承自英雄类,私有属性 int ap_atk=50;重写虚成员函数void Atk(){blood-=(attck+ap_atk);};射手类继承自英雄类,私有属性 int ac_atk = 100;重写虚成员函数void Atk(){blood-=(attck+ac_atk);}实例化类对象,判断怪物何时被杀死。

#include <iostream>

using namespace std;

//怪兽的血量

int blood = 10000;

class Hero

{

protected:

    string name;

    int hp;

    int attck;

public:

    Hero(){}

    Hero(string name,int hp,int attck):name(name),hp(hp),attck(attck){}

    virtual ~Hero(){cout << "Hero的析构函数" << endl;}

    virtual void Atk()

    {

      blood-=0;

    }

};

class Mage:public Hero

{

    int ap_atk = 50;

public:

    Mage(){}

    Mage(string name,int hp,int attck):Hero(name,hp,attck){}

    void Atk()override

    {

        blood-=(attck+ap_atk);

    }

};

//射手类

class Shooter:public Hero

{

    int ac_atk=100;

public:

    Shooter(){}

    Shooter(string name,int hp,int attck):Hero(name,hp,attck){}

    //重写父类的Atk函数

    void Atk()override

    {

        blood-=(attck+ac_atk);

        cout << "射手的成员函数" << endl;

    }

    ~Shooter()

    {cout << "Shooter的析构函数" << endl;}

};

int main()

{

//    //实例化法师类对象

//    Mage m1("daji",100,1000);

//    //实例化射手类对象

//    Shooter s1("houyi",100,2000);

//    int count = 0;

//    while(blood>0)

//    {

//        m1.Atk();

//        if(blood>0)

//        {

//          s1.Atk();

//        }

//        count++;

//    }

    //体现出函数重写

    Hero *p = new Shooter("liyuanf",2000,90);

    p->Atk();

    delete p;

    return 0;

}

整理思维导图

【3】虚析构函数

函数重写的现象是通过父类指针,指向子类成员得到的。

当释放父类指针时,父类指针能访问的空间只有父类的部分,不能访问子类的空间,所以在使用delete释放时,默认只能释放调父类的空间,会造成内存泄漏

给父类的析构函数添加关键字virtual,把父类的析构函数编程虚析构函数,在释放空间时,父类的析构函数就会引导编译器释放掉子类的空间。

只要父类的析构函数是虚析构函数,派生类/子类的虚构函数也都是虚析构函数

所以建议大家,在写析构函数时,都直接写成虚析构函数。

三、纯虚函数和抽象类

【1】纯虚函数

前面写的虚函数:virtual void show(){}

纯虚函数:virtual void show()=0;  //直接让虚函数=0就是纯虚函数

【2】抽象类

含有纯虚函数的类就叫抽象类,抽象类不能实例化类对象

纯虚函数在子类中必须重写,如果不重写,子类也无法实例化类对象

#include <iostream>

using namespace std;

//animal类不能实例化类对象

class animal

{

    string colour;

    int age;

public:

    virtual void show() = 0;

};

class Cat:public animal

{

public:

    //对父类的纯虚函数重写,但是函数体没有意义

    void show()

    {

    }

};

int main()

{

    //animal a1;  包含纯虚函数的类是抽象类不能实例化对象。

    Cat c1;

    c1.show();

    return 0;

}

练习

1、定义包含纯虚函数的抽象类animal,私有成员string name、int age,公有的纯虚函数speak(),派生子类Cat、Dog,并重写父类中的speak函数,实现输出他们的叫声,要求(使用全局函数实现对不同类中speak函数的调用)

四、C++中的异常处理

【1】异常概念

C++中的异常指的是程序在运行阶段出现的问题,没有语法错误,存在逻辑问题。

遇到异常时,可以手动抛出异常的现象,(throw)。

【2】异常处理

抛出异常  throw ----->抛出异常的位置一定要在发生异常之前

尝试捕获和处理异常  try catch

注意:

抛出异常的位置一定要在发生异常之前

try···catch中存放的是所有可能发生异常的代码

catch可以直接通过抛出异常的类型来判断是哪一种异常情况

如果同一类型有多种情况,可以在catch里,对具体获取到的异常的数值再进行判断

#include <iostream>

using namespace std;

void fun(int a,int b)

{

    if(b==0)

    {

        //throw typename(数值)

        //throw 数据  ----->throw后面直接加数据,编译器会根据数据来自动匹配类型

        throw double(1);

    }

    else if(b==4)

    {

        throw double(2);

    }

    else if(b==2){

        throw string("1");

    }else {

        cout << a/b << endl;

    }

}

//try···catch

int main()

{

    //try是写在主调函数中,

    //所有可能发生异常的情况都要写在try中,如果某一条语句遇到异常,try后面的其他语句不会执行

    try

    {

        fun(1,0); //如果这条语句发生异常,try后面的语句都不会执行

        fun(8,3);

    }catch (double a)  //能够捕获到所有的double类型的异常,可以通过异常的数值再做不同的处理

    {

        if(a==1)

        {

            cout << "除数不能为0" << endl;

        }else if(a==2)

        {

            cout << "这是个测试" << endl;

        }

    }catch (string)  //可以捕获到所有string类型的异常fun(1,2) ---->b==2抛出string异常

    {

        cout << "这是个测试" << endl;

    }

    return 0;

}

五、模板(template)

模板也可以实现多态,是泛式编程的一种

实现的是,静态多态,编译时多态。

泛式编程:用相同的代码,实现不同的功能

【1】模板函数

函数重载: ----->实现(编译时)多态

函数重写: ----->实现(运行时)多态

模板函数

实际开发的过程中,对于同一个函数,往往可以传多个不同的数据类型,需要实现多个,使用模板函数可以解决这个问题

模板函数根据调用时传的参数,来具体实现函数的功能

格式:template  ----->定义了一个模板,模板可以接收两个不同的数据类型

模板函数的调用方式:1、隐式调用(不给出模板中数据类型的调用方式)。2、显式调用(调用时给出模板中的数据类型) 函数名<数据类型1,数据类型2···>(形参)

一个模板只能定义一个模板函数

函数的返回值类型,并不能获取到模板中

i)模板函数需要显性调用的时机

当模板提供了两种数据类型,但是模板函数只有一种数据类型时,需要在函数调用处显性调用模板函数:函数名<数据类型1,数据类型2···>(形参)  -----> <>对应的是模板,()对应参数

对于实参是字符串常量,想要不强转实现函数调用,需要显性调用模板函数

#include <iostream>

using namespace std;

//int add(int a,int b)

//{

//    return a+b;

//}

//定义了一个模板

template <typename T1>

//T1 add(T1 a,T2 b)  解决方式1

T1 add(T1 a,T1 b)

{

    return a+b;

}

//一个模板只对一个函数生效

template <typename T1>

T1 arg(T1 a,T1 b)

{

    return (a+b)/2;

}

int main()

{

    int x = 10,y = 90;

    //cout << add<int,double>(x,y) << endl;

    //add<int,int>(x,y);  //解决办法2:显性调用

    //cout << add<double,int>(10.9,6) << endl;

    //对于add函数不能直接传"hello",因为识别成const char*,不能相加,需要显性强转成string

    //cout << add(string("hello"),string(" world")) << endl;

    //如果项直接在模板函数中,使用字符串作为参数传递还可以显性调用模板函数

    cout << add<string>("hello ","world") << endl;

    arg(10.8,4.0);

    arg(10,4);

    return 0;

}

练习:

1、使用模板函数,实现两个数求平均值的操作,要求:函数返回平均值,在主函数内输出结果

2、使用模板函数,实现两数求最大值的操作,直接在函数内输出结果,不需要返回。

//一个模板只对一个函数生效

template <typename T1,typename T2>

T2 arg(T1 a,T1 b)

{

    return (a+b)/2.0;

}

template<typename T1>

void max_(T1 a,T1 b)

{

    cout << (a>b?a:b) << endl;

}

ii)模板函数的特化

#include <iostream>

using namespace std;

//定义一个模板

template <typename T1,typename T2>

//实现特化的模板函数---->指明部分数据类型

double arg(int a,double b)

{

    return (a+b)/2.0;

}

//模板函数的特化,先在模板的位置,就把数据类型特化出来了

template <typename T1 = int ,typename T2>

int fun(int a,T2 b)

{

    return a+b;

}

//在模板声明中,class的作用表示,该数据类型可以使用默认值

//typename应用场合比较多

template <class T1=int,typename T2=double>

int fun_c(T1 a,T1 b)

{

    return a+b;

}

int main()

{

    //必须显性的调用特化的模板函数

    cout << arg<int,double>(12,3.0) << endl;

    int x = 9;

    cout << fun(10,x) << endl;

    cout << fun_c<int,double>(12,9) << endl;

    //显性调用特化过的模板函数arg

    cout << arg<int,double>(10,4.0) << endl;

    return 0;

}

【2】模板类

在开发过程中,对于同一功能的类,由于数据类型的不同,可能会出现重复定义的现象(链表中的节点)

可以通过模板解决

如果定义模板类,只能显性调用

如果模板类中的函数,想要实现类内声明,类外定义,需要在定义函数的位置重新写一遍模板

#include <iostream>

using namespace std;

//模板可以实现泛式编程:用相同的代码实现不同的功能

template<typename T1>

class A

{

    T1 age;

public:

    A(T1 age):age(age){}

    //类内声明

    void show();

};

//类外定义,需要再次写出模板并显性调用

template<typename T1>

void A<T1>::show()

{

  cout << age << endl;

}

int main()

{

    A<int> a1(100);

    a1.show();

    return 0;

}

【3】思考,模板函数和模板类实现的机制

顺序结构

分支结构

循环结构

二次编译/延时编译

在编译过程中,第一次遇到模板时,之进行语法分析,并不实现,在继续编译到模板调用时,会根据得到的类型,再次对模板部分的代码编译,并实现模板类和模板函数。

六、C++中的类型转换

reinterpret_cast(原有的变量)---->通配格式

C++中仍然支持C语言的强制类型转换

const_cast,把常量指针的常属性取消

static_cast,类型转换,基本所有的类型都可以使用,类似于C中的类型转换

dynamic_cast,把父类指针,转换成子类指针 ---->依托于多态实现,一定发生在父类和子类的指针之间,转换失败返回空地址

reinterpret_cast,重新解释数据的类型,不建议使用,不会检查任何类型匹配问题

#include <iostream>

using namespace std;

//怪兽的血量

int blood = 10000;

class Hero

{

protected:

    string name;

    int hp;

    int attck;

public:

    Hero(){}

    Hero(string name,int hp,int attck):name(name),hp(hp),attck(attck){}

    virtual ~Hero(){cout << "Hero的析构函数" << endl;}

    virtual void Atk()

    {

      blood-=0;

    }

};

class Mage:public Hero

{

    int ap_atk = 50;

public:

    Mage(){}

    Mage(string name,int hp,int attck):Hero(name,hp,attck){}

    void Atk()override

    {

        blood-=(attck+ap_atk);

    }

};

//射手类

class Shooter:public Hero

{

    int ac_atk=100;

public:

    Shooter(){}

    Shooter(string name,int hp,int attck):Hero(name,hp,attck){}

    //重写父类的Atk函数

    void Atk()override

    {

        blood-=(attck+ac_atk);

        cout << "射手的成员函数" << endl;

    }

    ~Shooter()

    {cout << "Shooter的析构函数" << endl;}

};

int main()

{

//    //实例化法师类对象

//    Mage m1("daji",100,1000);

//    //实例化射手类对象

//    Shooter s1("houyi",100,2000);

//    int count = 0;

//    while(blood>0)

//    {

//        m1.Atk();

//        if(blood>0)

//        {

//          s1.Atk();

//        }

//        count++;

//    }

    //体现出函数重写

    //Hero *p = new Shooter("liyuanf",2000,90);

//    Hero *p = new Hero("liyuanf",2000,90);

//    cout << "p=" << p << endl;    //0x48

//    Shooter *p1 = static_cast<Shooter *>(p);

//    cout << "p1=" << p1 << endl;  //0x48

//    Shooter *p2 = dynamic_cast<Shooter *>(p);

//    cout << "p2=" << p2 << endl;  //0

    //因为dynamic_cast可以判断是否能成功的强转,如果强转失败,dynamic_cast返回空地址

    const int a = 90;

    int a2 = a;  //不存在类型的问题,因为不涉及到对常量a的修改

    int *p3 = const_cast<int *>(&a);  //const_cast取消常属性


    //reinterpret_cast<new type>(原有的变量)

    char *p4 = reinterpret_cast<char *>(a);  //reinterpret_cast重新解释数据类型       

    return 0;

}

七、lambda表达式

lambda表达式是C++11开始支持的特性

lambda表达式,多用于实现轻量级的函数

定义:[]()mutable->返回值{函数体};

[捕获列表](参数列表)mutable->返回值{函数体};

1、[捕获列表]:获取外部数据

    [=]:按值捕获外部的所有变量

    [&]:按引用捕获外部的所有变量

    [=,&变量名]:引用捕获指定变量,其余的都按值捕获

    [&,变量名] :值捕获指定变量,其余按引用捕获

    [num1,&num2,num3]:num1和num3按值捕获,num2引用捕获

2、(参数列表):和普通的函数一致

3、->返回值类型:和普通的函数一致

4、函数体:和普通的函数一致

应用:

#include <iostream>

using namespace std;

int main()

{

    //auto类型接收lambda的结果

    //使用lambda表达式,实现求最大值的代码

    auto fun=[](double a,double b)->int{return a>b?a:b;};

    //fun(3,8)调用lambda表达式

    cout << fun(3.8,8.9) << endl;

    int num1 = 90,num2 = 79,num3=100,num4=78;

    auto fun1=[&,num3]()mutable->int{

        //num1++;  捕获列表获取外部变量时,默认自带常属性

        num1++;

        return num1+num2;};


    cout << fun1() << endl;

    cout << num1 << endl;

    return 0;

}

八、STL标准模板库

【1】C++中的标准模板库STL

C++ Standard Template Library

C++ STL (Standard Template Library标准模板库) 是通用类模板和算法的集合,它提供给程序员一些标准的数据结构的实现如queues(队列),lists(链表), 和stacks(栈)等.

C++ STL 提供给程序员以下三类数据结构的实现:

顺序结构

C++ Vectors

C++ Lists

C++ Double-Ended Queues

容器适配器

C++ Stacks

C++ Queues

C++ Priority Queues

联合容器

C++ Bitsets

C++ Maps

C++ Multimaps

C++ Sets

C++ Multisets

程序员使用复杂数据结构的最困难的部分已经由STL完成.

【2】Vectors

Vectors 包含着一系列连续存储的元素,其行为和数组类似。访问Vector中的任意元素或从末尾添加元素都可以在常量级时间复杂度内完成,而查找特定值的元素所处的位置或是在Vector中插入元素则是线性时间复杂度。

类似于数据结构中的顺序表

使用Vectors标准模板库,需要导头文件#include

常用的函数:

1、vector(); 

vector( size_type num, const TYPE &val ); 

vector( const vector &from ); 

vector( input_iterator start, input_iterator end );

2、求实际的大小:size_type size();

3、求最大容量:size_type capacity();

4、弹出队尾元素:void pop_back();

5、插入元素:void push_back(const TYPE &val );

6、访问:TYPE at( size_type loc );  at会检查越界问题,并且at返回的是指定位置元素的引用

7、使用insert进行插入:iterator insert( iterator loc, const TYPE &val );

8、返回第一个元素的迭代器:iterator begin();

9、清空所有元素:void clear();

10、对Vector的判空:bool empty();

11、返回起始位置的引用:TYPE front();

12、返回末尾位置的引用:TYPE back();

13、返回末尾位置下一个位置的迭代器:  iterator end();

14、对Vector中的元素赋值,使用数据范围赋值: void assign( input_iterator start, input_iterator end );

代码:

#include <iostream>

#include <vector>

using namespace std;

int main()

{

    //创建了一个int类型的vector

    vector<int> vec1(5,1);

//    vec1.clear();

    //vector的判空函数

//    if(vec1.empty())

//    {

//        cout << "1" ;

//    }

//    else {

//        cout << "!" << endl;

//    }

    cout << "实际的大小" << vec1.size() << endl;

    cout << "最大容量" << vec1.capacity() << endl;

    //使用pop_back函数实现,弹出最后一个元素

    vec1.pop_back();

    cout << "实际的大小" << vec1.size() << endl; //4

    cout << "最大容量" << vec1.capacity() << endl; //5

    //添加元素,不额外开空间

    vec1.push_back(90);

    int i;

    for(i=0;i<static_cast<int>(vec1.size());i++)

    {

        //at返回指定位置的引用

        cout << vec1.at(i) << endl;

    }

    //begin的返回值就是一个迭代器

//    vec1.insert(vec1.begin(),78);

//    vec1.insert(vec1.begin(),3,78);

//    cout << vec1.size() << endl;

    int ret = vec1.back();

    cout << "第一个元素" << ret << endl;

    vec1.erase((vec1.end()-1));

//    for(i=0;i<static_cast<int>(vec1.size());i++)

//    {

//        //at返回指定位置的引用

//        cout << vec1.at(i) << endl;

//    }

    int arr[10]= {12,9,8,7,5,4,0};

    vector<int> vec2;

    //使用assign函数给vec2赋值

    vec2.assign(arr,arr+5);

        for(i=0;i<=static_cast<int>(vec2.size());i++)

        {

            //at返回指定位置的引用

            cout << vec2.at(i) << endl;

        }

    return 0;

}

九、迭代器

C++ Iterators(迭代器)

迭代器可被用来访问一个容器类的所包函的全部元素,其行为像一个指针。举一个例子,你可用一个迭代器来实现对vector容器中所含元素的遍历。有这么几种迭代器如下:

迭代器描述

input_iterator提供读功能的向前移动迭代器,它们可被进行增加(++),比较与解引用(*)。

output_iterator提供写功能的向前移动迭代器,它们可被进行增加(++),比较与解引用(*)。

forward_iterator可向前移动的,同时具有读写功能的迭代器。同时具有input和output迭代器的功能,并可对迭代器的值进行储存。

bidirectional_iterator双向迭代器,同时提供读写功能,同forward迭代器,但可用来进行增加(++)或减少(--)操作。

random_iterator随机迭代器,提供随机读写功能.是功能最强大的迭代器, 具有双向迭代器的全部功能,同时实现指针般的算术与比较运算。

reverse_iterator如同随机迭代器或双向迭代器,但其移动是反向的。(Either a random iterator or a bidirectional iterator that moves in reverse direction.)(我不太理解它的行为)

每种容器类都联系于一种类型的迭代器。第个STL算法的实现使用某一类型的迭代器。举个例子,vector容器类就有一个random-access随机迭代器,这也意味着其可以使用随机读写的算法。既然随机迭代器具有全部其它迭代器的特性,这也就是说为其它迭代器设计的算法也可被用在vector容器上。

cppreference提示:通过对一个迭代器的解引用操作(*),可以访问到容器所包含的元素。

作业:

整理思维导图

自己实现Vector容器的功能,封装成类

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 一、继承过程中的特殊成员函数 1.1 构造函数 啥是构造函数?为啥要构造函数?所谓构造函数,其实就为后面实例化对象...
    彬粉世界阅读 1,045评论 0 2
  • 一、类 在C++中,类是面向对象的基础特征封装的实现,我们可以将实现某事物的所有属性(成员变量)、行为(成员函数)...
    彬粉世界阅读 1,238评论 0 2
  • 由于C++的类没学好。这是在另一个班学习的笔记 一、面向对象的三大特征 封装、继承和多态 二、类之间的继承 【1】...
    彬粉世界阅读 861评论 0 2
  • 我们学习了单链表、双向链表等数据存储结构。经过这些天的揣摩,事实告诉我们,如果不是应对考试,不管什么什么链...
    彬粉世界阅读 1,189评论 0 2
  • 今天要学三个知识点:引用、内存分配及重載 一、引用 & 引用是C++对C的一个非常重要的扩充,引用的引入,...
    彬粉世界阅读 2,241评论 0 1

友情链接更多精彩内容