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: //空 private: void 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; }