zoukankan      html  css  js  c++  java
  • C++ 类与对象

     类与对象是C与C++的最大区别之一,也是从面向过程转为面向对象的一个转折点

    以下分为多部分介绍

    1.1 类,结构体的扩展

    1.2 公有和私有

    1.3 构造函数

    1.4 类的继承

    1.5 多态

    1.1类,结构体的扩展

    我们先从熟悉的结构体struct开始,一步一步进入面向对象的世界。

    //使用结构体的方法如下

    struct Car
    {
        string s_strName;//名字
        int s_iWheelNumber;//轮子数量  
    };
    int main(void)
    {
        struct Car car;
        car.s_strName="Benz";
        car.s_iWheelNumber=4;
        cout<<"名字:"<<car.s_strName<<endl;
        system("pause");
        return 0;
    }

    在结构体中,我们可以定义变量成员、枚举成员和其他结构体成员,但却没有办法定义函数体,在C++中类的诞生为我们解决了这一问题。

    类(class)从使用上可以理解为结构体(struct)的扩展,类中除了可以包含变量、还可以包括函数体等内容。

    在上述的struct例子中,改为class,加入了move_forward() 函数、move_back()函数,那么,在实例化类的对象以后,就可以通过car.move_forward(),car.move_back()来调用类中的成员函数了。

    class Car
    {
    public://这个关键字在1.2介绍
        string m_strName;
        int m_iWheelNumber;
        void move_forward(void);
        void move_back(void);
    };
    void Car::move_forward(void)
    {
        cout<<"前进"<<endl;
    
    }
    
    void Car::move_back(void)
    {
        cout<<"后退"<<endl;
    }
    
    //要使用类,与结构体类似,需要将类进行实例化:
    int main(void)
    {
        Car car;//实例化;Car为类,car为对象
        car.m_strName="Benz";//调用car中的m_strName(到此步骤基本与struct相同)
        car.move_forward();//调用car中的move_forward方法
        cout<<"汽车的名字:"<<car.m_strName<<endl;
        system("pause");//暂停控制台
        return 0;
    }

    1.2公有和私有

    在类中,public关键字下为公有成员、函数。这个关键词修饰下面的内容在类的外部可以调用 。private下则为私有,仅为类的内部函数才能使用。

    把1.1中的例子修改成下面,在实例化后,我们不能通过car.m_strName访问 m_strName这个成员,因为它在private关键词下,类外不能访问,那么要如何操作呢?请看例子

    #include<iostream>
    #include<string>
    using namespace std;
    class Car
    {
    public:
        void setName(string _name)
        {
            m_strName = _name;
        }
        string getName(void)
        {
            return m_strName;
        }
        void move_forward(void);
        void move_back(void);
    private:
        string m_strName;
        int m_iWheelNumber;
    };
    void Car::move_forward(void)
    {
        cout<<"前进"<<endl;
    
    }
    void Car::move_back(void)
    {
        cout<<"后退"<<endl;
    }
    int main(void)
    {
        Car car;//实例化Car为类,car为对象
        //  car.m_strName="Benz";//private中的变量在外部不能直接调用
        car.setName("Benz");//调用car中的setName()来改变m_strName
        car.move_forward();//调用car中的move_forward方法。
        cout<<"汽车的名字:"<<car.getName()<<endl;
        system("pause");//暂停控制台
        return 0;
    }

    虽然m_strName为pravite不能在外部直接操作,但可以使用类中的方法setName()可以改变m_strName的值,getName()方法可以返回m_strName的值,把类中的成员封装起来通过函数调用,可以防止误操作改变成员,是面向对象的基本思想之一。

    1.3构造函数与析构函数

    C++的类作为结构体第三大升级,类具有构造函数析构函数。分别在类的创建(实例化)和消除的时候自动调用。

    1.3.1构造函数

    构造函数名字与类名相同,在实例化的时候自动调用,一般用于对类中的成员赋予初始值。

    继续使用上面的例子改造

    class Car
    {
    public:
        Car(){m_strName ="Benz",m_iWheelNumber=4;};//构造函数
        void move_forward(void);
        void move_back(void);
        void setName(string _name)
        {
        m_strName = _name;
        }
        string getName(void)
        {
        return m_strName;
        }
    private:
        string m_strName;
        int m_iWheelNumber;
    };
    void Car::move_forward(void)
    {
        cout<<"前进"<<endl;
    }
    void Car::move_back(void)
    {
        cout<<"后退"<<endl;
    }
    int main(void)
    {
        Car car;//实例化;Car为类,car为对象,在实例化时,会调用构造函数Car();
        // car.setName("Benz");//不需要调用这句,构造函数已经为成员赋初值
        car.move_forward();//调用car中的move_forward方法。
        cout<<"汽车的名字"<<car.getName()<<endl;
        system("pause");//暂停控制台
        return 0;
    }

    *注意:

    • 即是没有写构造函数,C++也会自动为类添加一个空的构造函数

    构造函数可以带有参数,参数还可以有默认值:

      Car(string _name,int _wheelnumber=4){m_strName =_name,m_iWheelNumber=_wheelnumber;};//构造函数

    基本构造函数还可以重载(关于重载在多态中详细介绍),一个方法可以有多个基本构造函数,将上面两种构造函数放在同一个类中:

    class Car
    {
    public:
        Car(){m_strName ="Benz",m_iWheelNumber=4;cout<<"1"<<endl;};//构造函数1
        Car(string _name,int _wheelnumber=4){m_strName =_name,m_iWheelNumber=_wheelnumber;};//构造函数2
        ...
    }

    汽车的轮子是固定的,所以我们可能会使用const来修饰(表示是一个常量,不可改变),那么,如何对const类型赋初值呢?如果直接在构造函数内赋值则提示不可变的参数,这时候需要用到初始化列表  

     

    初始化列表是在构造函数的(){}之间插入:类内string变量("xxx"),类内整形变量(4)的方式,例子如下

    class Car
    {
    public:
        Car():m_strName("Benz"),m_iWheelNumber(4){}//构造函数+初始化列表
        void move_forward(void);
        void move_back(void);
        void setName(string _name)
        {
            m_strName = _name;
        }
        string getName(void)
        {
            return m_strName;
        }
        const int getWheelNum(void)
        {
            return m_iWheelNumber;
        }
    private:
        string m_strName;
        const int m_iWheelNumber;
    };

    1.3.2 拷贝构造函数

    除了基本构造函数,类中还默认存在一个拷贝构造函数,也就是拷贝的时候调用的构造函数,比如Car c1; Car c2=c1或者Car c2(c1);则会调用拷贝构造函数。

    #include<iostream>
    #include<string>
    using namespace std;
    class Car
    {
    public:
        Car(){m_strName ="Benz",m_iWheelNumber=4;cout<<"1"<<endl;};//普通构造函数
        Car(const Car &car){cout<<"2"<<endl;}//拷贝构造函数
        void move_forward(void);
        void move_back(void);
        void setName(string _name)
        {
        m_strName = _name;
        }
        string getName(void)
        {
        return m_strName;
        }
    private:
        string m_strName;
        int m_iWheelNumber;
    };
    void Car::move_forward(void)
    {
        cout<<"前进"<<endl;
    }
    void Car::move_back(void)
    {
        cout<<"后退"<<endl;
    }
    int main(void)
    {
        Car car;//实例化Car为类,car为对象 触发构造函数 打印出 1
        Car car2(car);//car2拷贝car,触发了拷贝构造函数(不触发普通构造函数),会打印出 2
        car.move_forward();//调用car中的move_forward方法。
        cout<<"汽车的名字"<<car.getName()<<endl;
        system("pause");//暂停控制台
        return 0;
    }

    *注意:

    • 拷贝构造函数如果没有写系统会自动分配一个默认拷贝构造函数,默认的拷贝构造函数会把成员全部拷贝。
    • 对象在函数传参的时候会发生拷贝,此时也会触发拷贝构造函数。

    1.3.3析构函数

      析构函数是对象在消除的时候自动调用的函数,其名字与类相同,并在前加“~”符号,当用户没有定义析构函数时,C++会自动生成一个空的析构函数。

      上述Car的例子,编写析构函数:

    class Car
    {
    public:
      Car(){m_strName ="Benz",m_iWheelNumber=4;cout<<"1"<<endl;};//构造函数1
      Car(string _name,int _wheelnumber=4){m_strName =_name,m_iWheelNumber=_wheelnumber;};//构造函数2
      ~Car(){cout<<"析构函数"<<endl;};//析构函数
      ...
    
    }

      *注意:

    • 析构函数必须无输入参数,且不能重载。

     1.4 类的继承

      面向对象的三大特性之一,类可以继承

      什么是继承?

      例如,Car(汽车)类中包括1、轮子数量 2、时速 两个成员 

         BMW(宝马)类中包括 1、轮子数量 2、时速 3、外形 三个成员。

      那么我们说 宝马(类) 是一种 汽车(类)

      为了方便,我们不必把宝马类的全部成员重写一遍,只需要从汽车类中继承。下面的例子介绍如何定义宝马类。

    1.4.1公有继承

      使用public关键字继承,Car是基类,也叫父类,BMW是Car中继承的类,叫派生类,也叫子类

      *在公有继承中,父类public的成员也成为子类public成员

               父类protected的成员成为子类protected成员

               父类private的成员不会出现在子类中(不发生继承)

      也就是说 ,因为BMW继承了Car类,Car类中protected关键字下的m_iWheelNumber和m_iSpeed会自动复制到BMW类中:

      下面是一个公有继承的例子  

    class Car
    {
    public:
        Car(){m_iWheelNumber=4;};//普通构造函数
        void move_forward(void);
    protected://可以发生继承的private形式
        int m_iWheelNumber;
        int m_iSpeed;
    private://private中的成员不会发生继承
        string m_strName;
    };
    class BMW:public Car//宝马类继承小车类
    {
    public:
        BMW(){}
    protected:
        string m_strLook;
    };

    在上面这个例子中 ,BMW类继承了Car类,那么,BMW类中除了m_strLook成员外,还具有m_iWheelNumber和m_iSpeed成员,实际它的成员如下

    class BMW:public Car
    {
    public:
        BMW(){}
        void move_forward(void);//继承来的函数成员,不用写,默认存在
    protected:
        int m_iWheelNumber;//继承来的成员,不用写,默认存在
        int m_iSpeed;//继承来的成员,不用写,默认存在
        string m_strLook;
    private:
    //
    };

    1.4.2 保护继承

      *在公有继承中,父类public的成员也成为子类protected成员

               父类protected的成员成为子类protected成员

               父类private的成员不会出现在子类中(不发生继承)

      上面的例子改为保护继承,那么实际上BMW类中从Car继承来的成员都移到了protected下:

    class BMW:protected Car
    {
    public:
        BMW(){}
    protected:
        void move_forward(void);//继承来的函数成员,不用写,默认存在 父类public->子类protected
        int m_iWheelNumber;//继承来的成员,不用写,默认存在
        int m_iSpeed;//继承来的成员,不用写,默认存在
        string m_strLook;
    private//
    };

    1.4.2 私有继承

      *在公有继承中,父类public的成员也成为子类private成员

               父类protected的成员成为子类private成员

               父类private的成员不会出现在子类中(不发生继承)

    上面的例子改为私有继承,那么实际上BMW类中从Car继承来的成员都移到了private下:

    class BMW:private Car
    {
    public:
        BMW(){}
    protected:
    //
    privatevoid move_forward(void);//继承来的函数成员,不用写,默认存在 父类public->子类private
        int m_iWheelNumber;//继承来的成员,不用写,默认存在,父类protected->子类private
        int m_iSpeed;//继承来的成员,不用写,默认存在,父类protected->子类private
        string m_strLook;
    };

    1.4.2 多继承

      一个子类从多个父类中发生继承,称为多继承

      举个例子:

      有Car(小车)和TOY(玩具)两个类

      其中,Car类有  1、轮子数量 2、速度 两个成员

         TOY类有 1、尺寸  2、颜色 两个成员

      现在我们要新建一个玩具车类,它需要有Car类和TOY类的所有成员,那么我们可以这样写:

    class Car//父类1
    {
    public:
        Car(){m_iWheelNumber=4;};//普通构造函数
        void move_forward(void);
    protected:
        int m_iWheelNumber;
        int m_iSpeed;
    };
    
    class Toy//父类2
    {
    public:
        Toy(){};
    protected:
        int m_iSize;
        int m_iColor;  
    };
    
    class ToyCar:public Car,publicToy
    {
    pulic:
        TopCar(){};
    };
    
    //同样的,ToyCar类默认拥有了Car 和 Toy的public和protected的成员
    
    class ToyCar:public Car,publicToy
    {
    pulic:
        TopCar(){};
    protected:
        int m_iWheelNumber;
        int m_iSpeed;
        int m_iSize;
        int m_iColor; 
    };

    1.5 多态

      多态,表示同名的参数产生不同的结果,他们包括

      一般多态:

    • 参数多态:(模板
    • 包含多态:不同类的同一个函数发生不同的结果(覆盖

      特殊多态:

    • 重载多态:表示同一个函数不同调用发生的不同结果(重载
    • 强制多态:类型的强制转换(强转

      上述这些名词是没有意义的,我们真正是要学会使用他们,下面我们展示它们的意义和实现方法

    1.5.1 重载

    1.5.1 函数重载

      我们继续以Car为例子:Car具有两个成员  名字m_strName 和 速度m_iSpeed  ,并且设置了默认前进函数move_forward(void),调用它会给m_iSpeed默认赋值100的。但是为了能够准确的设置前进速度,我们另外写了一个同名函数move_forward(int speed),这两个同名函数互为重载。当我们调用car.move_forward()会调用第一个,当传入参数时候,调用第二个前进函数,并赋予传入参数的m_iSpeed。

    class Car
    {
    public:
        Car(){m_strName = "BMW";m_iSpeed=0;};//默认构造函数
        Car(string name,int speed){m_strName =name;m_iSpeed = speed;}//有参构造函数 与 默认构造函数互为重载
        void move_forward(void);
        void move_forward(int speed);//输入前进速度的move_forward函数
        int getSpeed(void);
        string getName(void);
    private:
        string m_strName;
        int m_iSpeed;//行驶速度
    };
    
    void Car::move_forward(void)
    {
        m_iSpeed = 100;
    };
    void Car::move_forward(int speed)
    {
        m_iSpeed = speed;
    }
    int Car::getSpeed(void)
    {
        return m_iSpeed;
    }
    string Car::getName (void)
    {
        return m_strName;
    }
    
    int main(void)
    {
        Car car1;//自动使用默认构造函数Car(), car1是宝马BMW
        Car car2("Lambo",0);//自动使用有参的构造函数Car(string name,int speed) car2是兰博基尼
        
        cout<<"car1 name:"<<car1.getName()<<endl;
        cout<<"car2 name:"<<car2.getName()<<endl;
    
        car1.move_forward();//自动调用move_forward(void),设置速度为100
        cout<<"car1 speed:"<<car1.getSpeed()<<endl;
    
        car1.move_forward(500);//自动调用重载的move_forward(int speed) 设置速度为500
        cout<<"car1 speed:"<<car1.getSpeed()<<endl;
    
        system("pause");
        return 0;
    
    }

    1.5.2 运算符重载

    运算符重载意思是把常用的运算符赋予新的功能,比如“+、-、*、/、++、--、[] ”等等。当运算符被重载后,特定的情况下会赋予运算符不一样功能

    比如,继续以Car类为例子,当一个car的对象与另一个car的对象相加的时候,我们希望他们的轮子数量相加,并且名字改为变形金刚。car3=car1+car2,car3就是变形金刚,且轮子数为8,但类中是不支持加法的,那我们就需要在Car类中重载运算符"+"

    在接触运算符重载之前,我们现需要了解一个新关键词:this

    1.5.2.2 关键词this

    直接看例子

    (此处插入Car类程序)

    this实际是一个指针 ,它指向这个类实例化成对象后的第一个地址。

    例如 Car c1;此时this指向c1的首地址。

    那么,this和运算符重载有什么关系呢?这里需要补充说明,在所有的类内函数中,默认传递参数 this指针也就是说,Car类实际上是这样的

    (此处插入程序)

    在了解了this以后,就可以真正进入运算符重载了:

    1.5.2.1 关键词operator

     operator 是重载运算符的关键词,当我们想重载哪一个运算符,我们使用operator后面紧跟符号

    下面的例子,是重载“+”,用于一个Car类的对象与另一个Car类的对象相加。当大家看了下面两个问题就能理解运算符重载的操作了。

    问:Car operator+(Car &car);传入的参数有几个 ?
    答:有两个,第一个是C++默认传参this指针,第二个是写出来的Car &car

    问:car3=car1+car2;这句话相当于什么意思?
    答:相当于car3=car1.operatro+(car2);
    #include<iostream>
    #include<string>
    using namespace std;
    class Car
    {
    public:
        Car(){m_strName = "BMW";m_iWheel=4;};//默认构造函数
        Car(string name,int wheel){m_strName =name;m_iWheel = wheel;}//有参构造函数 与 默认构造函数互为重载
        int getWheel(void);
        string getName(void);
        void setWheel(int wheel);
        void setName(string name);
        Car operator+(Car &car);
    private:
        string m_strName;
        int m_iWheel;
    };
    
    int Car::getWheel(void)
    {
        return m_iWheel;
    }
    string Car::getName (void)
    {
        return m_strName;
    }
    void Car::setName(string name)
    {
        m_strName=name;
    }
    void Car::setWheel(int wheel)
    {
        m_iWheel = wheel;
    }
    Car Car::operator+(Car &car)
    {
        Car out;
        out.setName("变形金刚");
        out.setWheel(this->m_iWheel+car.getWheel());
        return out;
    }
    int main(void)
    {
        Car car1("BMW",4);//自动使用默认构造函数Car(), car1是宝马BMW
        Car car2("Lambo",4);//自动使用有参的构造函数Car(string name,int speed) car2是兰博基尼
        Car car3;
        car3=car1+car2;
    
        cout<<"car1:"<<car1.getName()<<"  "<<car1.getWheel()<<endl;
        cout<<"car2:"<<car2.getName()<<"  "<<car2.getWheel()<<endl;
        cout<<"car3:"<<car3.getName()<<"  "<<car3.getWheel()<<endl;
        system("pause");
        return 0;
    
    }

    1.5.2 覆盖

      覆盖、要从虚函数说起,在类的成员函数前加virtual关键字表示这是一个虚函数,当这个类发生继承的时候,子类可以重写虚函数,重写后父类的虚函数被覆盖,也就是父类的同名方法不会再被调用。

      例如,在上述的Car类中加入一个漂移的函数virtual void drift(void);但是不是所有类型的小车都能漂移,比如宝马可以漂移,大众漂移会翻车。那么我们就在Car的子类宝马BMW中重新实现漂移这个函数。

    class Car
    {
    public:
        Car(string name ,int size=4):m_strName(name),m_iSize(size){cout<<"Car()"<<endl;}
        virtual ~Car(){cout<<"~Car()"<<endl;}
        virtual void drift(void)
        {
            cout<<"Car类不能漂移"<<endl;
        }
        string getName(void)
        {
            return m_strName;
        }
    protected:
        string m_strName;
        int m_iSize;
    };
    class BMW:public Car { public: BMW():Car("BMW",4){cout<<"BMW()"<<endl;} virtual~BMW(){cout<<"~BMW()"<<endl;} virtual void drift(void) { cout<<"漂移~"<<endl; } }; int main(void) { BMW *b=new BMW; cout<<"名字:"<<b->getName()<<endl; b->drift(); delete b; b=NULL; system("pause"); return 0; }

    上面这个例子打印如下

    Car() 

    BMW()

    名字:BMW

    漂移~//关键词virtual使得父类drift()已经被覆盖了,所以打印的是子类的drift();

    ~BMW()

    ~Car()//析构函数作为虚函数是例外,它的父类函数不会被覆盖,会一起被调用

    *注意

    • virtual声明的成员函数在子类中仍然是virtual ,即是没有人为写上。
    •      virtual声明的析构函数不会发生覆盖,而是在对象销毁时,子类的析构函数被调用后、父类的析构函数接着调用

    1.5.3 模板

    模板分为类模板和函数模板,他们的使用方法基本一样,在接触这一章节中,我们会接触到一个有趣的关键字 template,typename T代表类型:

    template<typename T>

    例子:

    以下实现的a、b数据交换,可以输入任意的类型。

    template <typename T>
    void swapnum(T &a,T &b)
    {
        T c;
        c=a;
        a=b;
        b=c;
    }
    int main()
    {
        float a=1.1;
        float b=2.2;
        int a1=1;
        int b1=2;
        swapnum<float>(a,b);
        cout<<"a:"<<a<<endl<<"b:"<<b<<endl;
        cout<<"a1:"<<a1<<endl <<"b1:"<<b1<<endl;
        system("pause");
        return 0;
    }
  • 相关阅读:
    js上传图片预览
    Android 调用QQ登录
    未开启HugePages ORACLE session剧增时引起的一次悲剧
    脱了裤子放屁之std::string
    [Python爬虫] Selenium自己主动訪问Firefox和Chrome并实现搜索截图
    tomcat启动报错,找不到相应的 queue,从而引发内存泄漏
    LeetCode: Binary Tree Postorder Traversal [145]
    素数打表法。
    linux 抓包 tcpdump 简单应用
    Linux命令之kill
  • 原文地址:https://www.cnblogs.com/HongYi-Liang/p/7092005.html
Copyright © 2011-2022 走看看