前言:C++并不是一个纯粹的面向对象的语言,而是一种基于过程和面向对象的混合型语言。今天就来回顾一下C++面向对象中的几个注意的问题。(距离我上一次写c++已经过去了一个世纪)
1、概述
面向对象程序设计有4个主要特点:抽象、封装、继承、多态
C++的类对象体现了抽象性和封装的特点,在此基础上再利用继承和多态性,就称为真正的面向对象程序设计。
(1)封装
封装(encapsulate)指两方面含义:一是将有关的数据和操作代码封装在一个对象中,形成一个基本的单位,各个对象之间相对独立,互不干扰;二是将对象的某些部分对外隐蔽,隐藏其中细节,只留下少量接口,以便与外界联系。
这种对外隐蔽的做法称为信息隐蔽。
(2)抽象
抽象(abstract)的作用是表示同一类事物的本质。类是对象的抽象,而对象则是类的特例。即类的具体表现形式。
(3)继承于重用
C++提供了继承机制,采用继承的方法可以方便的利用一个已有的类建立一个新的类。
(4)多态
如果有几个相似而不完全相同的对象,有时人们要求在向他们发出同一个消息时,他们的反应各不相同,分别执行不同的操作,这种情况就叫做多态现象。
在c++中,所谓多态(polymorphism)是指由继承而产生的不同的派生类,其对象对同一消息会做出不同的响应。
2. 构造函数和析构函数
类和对象的栗子:
#include <iostream>
using namespace std;
class Box
{
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度
};
int main( )
{
Box Box1; // 声明 Box1,类型为 Box
Box Box2; // 声明 Box2,类型为 Box
double volume = 0.0; // 用于存储体积
// box 1 详述
Box1.height = 5.0;
Box1.length = 6.0;
Box1.breadth = 7.0;
// box 2 详述
Box2.height = 10.0;
Box2.length = 12.0;
Box2.breadth = 13.0;
// box 1 的体积
volume = Box1.height * Box1.length * Box1.breadth;
cout << "Box1 的体积:" << volume <<endl;
// box 2 的体积
volume = Box2.height * Box2.length * Box2.breadth;
cout << "Box2 的体积:" << volume <<endl;
return 0;
}
类的构造函数
#include <iostream>
using namespace std;
class Line{
public:
void setLength(double len);
double getLength();
Line();
private:
double length;
};
Line::Line(){
cout << "Object is being created" << endl;
}
void Line::setLength(double len){
length = len;
}
double Line::getLength()
{
return length;
}
int main()
{
Line line;
line.setLength(8.0);
cout << "Length of line : " << line.getLength() <<endl;
return 0;
}
3.运算符重载和函数的重载
重写和重载的区别:
重载:函数名相同,参数列表不同;
重写:子类重新定义父类中相同名称的参数的虚函数。在继承关系之间,C++用虚函数实现多态。
函数的重载
#include <iostream>
using namespace std;
class printData
{
public:
void print(int i) {
cout << "整数为: " << i << endl;
}
void print(double f) {
cout << "浮点数为: " << f << endl;
}
void print(char c[]) {
cout << "字符串为: " << c << endl;
}
};
int main(void)
{
printData pd;
// 输出整数
pd.print(5);
// 输出浮点数
pd.print(500.263);
// 输出字符串
char c[] = "Hello C++";
pd.print(c);
return 0;
}
运算符的重载
您可以重定义或重载大部分 C++ 内置的运算符。这样,您就能使用自定义类型的运算符。
重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。与其他函数一样,重载运算符有一个返回类型和一个参数列表。
Box operator+(const Box&);
4. 继承与派生
面向对象程序设计中最重要的一个概念是继承。继承允许我们依据另一个类来定义一个类,这使得创建和维护一个应用程序变得更容易。这样做,也达到了重用代码功能和提高执行时间的效果。
当创建一个类时,您不需要重新编写新的数据成员和成员函数,只需指定新建的类继承了一个已有的类的成员即可。这个已有的类称为基类,新建的类称为派生类
#include <iostream>
using namespace std;
// 基类
class Shape
{
public:
void setWidth(int w)
{
width = w;
}
void setHeight(int h)
{
height = h;
}
protected:
int width;
int height;
};
// 派生类
class Rectangle: public Shape
{
public:
int getArea()
{
return (width * height);
}
};
int main(void)
{
Rectangle Rect;
Rect.setWidth(5);
Rect.setHeight(7);
// 输出对象的面积
cout << "Total area: " << Rect.getArea() << endl;
return 0;
}
继承类型:
当一个类派生自基类,该基类可以被继承为 public、protected 或 private 几种类型。继承类型是通过上面讲解的访问修饰符 access-specifier 来指定的。
我们几乎不使用 protected 或 private 继承,通常使用 public 继承。当使用不同类型的继承时,遵循以下几个规则:
- 公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问。
- 保护继承(protected): 当一个类派生自保护基类时,基类的公有和保护成员将成为派生类的保护成员。
- 私有继承(private):当一个类派生自私有基类时,基类的公有和保护成员将成为派生类的私有成员。
多继承
C++ 类可以从多个类继承成员,语法如下:
class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,…
{
<派生类类体>
};
栗子:
#include<iostream>
using namespace std;
// 基类 Shape
class Shape{
public:
void setWidth(int w)
{
width = w;
}
void setHeight(int h)
{
height = h;
}
protected:
int width;
int height;
}
// 基类 PaintCost
class PaintCost{
public:
int getCost(int area)
{
return area * 70;
}
}
// 派生类
class Rect: public Shape, public PaintCost{
public:
int getArea(){
return (width*height);
}
}
int main(){
Rect rect;
int area;
rect.setWidth(5);
rect.setHeight(7);
area = rect.getArea();
// 输出对象的面积
cout << "Total area: " << area << endl;
// 输出总花费
cout << "Total paint cost: $" << rect.getCost(area) << endl;
return 0;
}
5. 多态与虚函数
首先来看一个栗子:
#include <iostream>
using namespace std;
//定义基类
class Shape{
protected:
int w,h;
public:
Shape(int x,int y){
w=x;
h=y;
}
int area(){
cout<<"这个shape方法被调用了"<<endl;
return w*h;
}
};
//Rect派生自基类
class Rect:public Shape{
public:
Rect(int x, int y):Shape(x,y){}
};
//Triangle派生自基类
class Triangle :public Shape{
public:
Triangle(int x,int y):Shape(x,y){}
int area(){
cout<<"这个Triangle方法被调用了"<<endl;
return w*h*1/2;
}
};
int main()
{
cout << "Hello world!" << endl;
Rect r1(2,3);
Triangle t1(2,3);
cout<<r1.area()<<endl; // 这个shape方法被调用了 6
cout<<t1.area()<<endl; //这个Triangle方法被调用了 3
Shape *shape;
//储存矩形的地址
shape=&r1;
//调用矩形的求面积函数
cout<<shape->area()<<endl; //这个Triangle方法被调用了 6
//储存三角形的地址
shape=&t1;
//调用矩形的求面积函数
cout<<shape->area()<<endl; //这个Triangle方法被调用了 6
return 0;
}
导致错误输出的原因是,调用函数 area() 被编译器设置为基类中的版本,这就是所谓的静态多态,或静态链接 - 函数调用在程序执行前就准备好了。有时候这也被称为早绑定,因为 area() 函数在程序编译期间就已经设置好了。
但现在,让我们对程序稍作修改,在 Shape 类中,area() 的声明前放置关键字 virtual,如下所示:
class Shape{
protected:
int w,h;
public:
Shape(int x,int y){
w=x;
h=y;
}
virtual int area(){
cout<<"这个shape方法被调用了"<<endl;
return w*h;
}
};
这时候的输出就会变得正确。
此时,编译器看的是指针的内容,而不是它的类型。因此,由于 tri 和 rec 类的对象的地址存储在 *shape 中,所以会调用各自的 area() 函数。
正如您所看到的,每个子类都有一个函数 area() 的独立实现。这就是多态的一般使用方式。有了多态,您可以有多个不同的类,都带有同一个名称但具有不同实现的函数,函数的参数甚至可以是相同的。
虚函数
虚函数时在基类中使用关键字virtual声明的函数,在派生类中重新定义基类的虚函数时,会告诉编译器不需要静态连接到该函数。
我们想要的是在城中可以任意给句所调用的对象类型来选择调用的函数,这种操作被称为动态连接或者后期绑定。
纯虚函数
您可能想要在基类中定义虚函数,以便在派生类中重新定义该函数更好地适用于对象,但是您在基类中又不能对虚函数给出有意义的实现,这个时候就会用到纯虚函数。
我们可以把基类中的虚函数 area() 改写如下:
class Shape {
protected:
int width, height;
public:
Shape( int a=0, int b=0)
{
width = a;
height = b;
}
// pure virtual function
virtual int area() = 0;
}
= 0 告诉编译器,函数没有主体,上面的虚函数是纯虚函数。