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;
}
原文地址:https://www.cnblogs.com/HongYi-Liang/p/7092005.html