C++类的定义和使用

C++ 中,类的定义方法如下:

class  类名{
访问范围说明符:
    成员变量1
    成员变量2
    成员函数声明1
    成员函数声明2

访问范围说明符:
    更多成员变量
    更多成员函数声明
    ...
};

类的定义要以;结束。

“访问范围说明符”一共有三种,分别是 public、private 和 protected。三者的区别后面会详细介绍,目前暂且都用 public。“访问范围说明符”可以出现任意多次。

“成员变量”的写法与普通的变量定义相同。称其为成员变量,是因为这些变量是一个类的成员。

同样地,“成员函数声明”的写法也与普通的函数声明相同。

一个类的成员函数之间可以互相调用。类的成员函数可以重载,也可以设定参数的默认值。

以前所学的函数不是任何类的成员函数,可称为“全局函数”。

成员变量就代表对象的“属性”,成员函数就代表对象的“方法”。成员变量和成员函数出现的先后次序没有规定。

成员函数的实现可以位于类的定义之外,格式如下:

返回值类型  类名:函数名()
{
    函数体
}


定义类之后,就可以定义对象了。定义对象的基本方法如下:

类名  对象名;

此处,“对象名”的命名规则和普通变量相同。对象也可以看作“类变量”。

类的示例程序剖析

下面来看一个用面向对象的方法进行 C++ 程序设计的例子。

例题:编写一个程序,输入矩形的宽度和高度,输出其面积和周长。

这个程序比较简单,实际上根本不需要用面向对象的方法进行设计,这里只是为了使读者更容易理解类和对象的概念。

首先要做的事情是分析问题中有哪些“对象”。比较明显,只有“矩形”这种对象。然后要进行“抽象”,即概括“矩形”这种对象的共同特点。

矩形的属性就是宽度和高度。因此需要两个变量,分别代表宽度和高度。

一个矩形可以有哪些方法(即可以对矩形进行哪些操作)呢?在本程序中,矩形可以有设置宽度和高度、计算面积和计算周长三种行为,这三种行为可以各用一个函数实现,它们都会用到宽度和高度这两个变量。

“抽象”完成后,就可以用 C++ 提供的语法特性写出一个“矩形类”,将矩形的属性和方法“封装”在一起。程序如下:
#include <iostream>
using namespace std;
class CRectangle
{
public:
    int w, h; //成员变量,宽和高
    void init( int w_,int h_ ); //成员函数,设置宽和高
    int area(); //成员函数, 求面积
    int perimeter(); //成员函数,求周长
}; //必须有分号
void CRectangle::init( int w_,int h_ )
{
    w = w_;  h = h_;
}
int CRectangle::area()
{
    return w * h;
}
int CRectangle::perimeter()
{
    return 2 * ( w + h);
}
int main( )
{
     int w,h;
     CRectangle  r;  //r是一个对象
     cin >> w >> h;
     r.init( w,h);
     cout << "It's area is " << r.area() << endl;
     cout << "It's perimeter is " << r. perimeter();
       cout << sizeof(CRectangle) << endl;
     return 0;
}
第 3~10 行定义了一个类,类的名字就是 CRectangle。在 C++ 中,每个类的名字都是一种类型,可以像 int、double 等基本类型那样来使用。因此,CRectangle 也是一种程序员自己定义的类型的名字。

CRectangle 类的定义中声明了它有哪些成员变量和成员函数。第 5行 的 public 就是类成员的访问范围说明符,它说明了下面的成员都是公有成员(具体含义稍后解释)。

第 11~22 行是 CRectangle 类各个成员函数的具体实现。

定义 CRectangle 类后,就可以定义 CRectangle 类型的变量。在 C++ 中,通过类定义出来的变量称为“对象”。例如,第 26 行定义了一个 CRectangle 类型的变量 r,由于 CRectangle 是一个类,因此将 r 称为一个“对象”。

一般来说,在 C++ 中,一个对象占用的内存空间的大小等于其成员变量所占用的内存空间的大小之和。例如,一个 CRetangle 对象占用 8 个字节,因为它的两个成员变量 w 和 h 分别 占用 4 个字节。sizeof(CRectangle) 表达式的值是 8。

每个对象都有各自的存储空间。一个对象的某个成员变量被改变后,不会影响另一个对象。成员函数并非每个对象各自存有一份。成员函数和普通函数一样,在内存中只有一份,但是它可以作用于不同的对象。

使用类的成员变量和成员函数可以用对象名.成员名的方式。例如:
CRectangle r1, r2;
r1.w = 5;
r2.init(5, 4);
r1.w = 5;这条语句直接对 r1 对象的 w 成员变量进行赋值。它不会影响 r2。

对象名.成员函数名的方式可以调用成员函数,并且被调用成员函数是作用在该对象上的。例如,r2.init(5, 4);这条语句就调用了 CRectangle 类的 init 成员函数,该成员函数执行时是作用在 r2 这个对象上的。

所谓成员函数作用在某个对象上,指的是进入该成员函数时,函数中访问到的成员变量是属于该对象的。例如,r2.init(5, 4);这条语句在 init 函数执行期间访问的 w 和 h 就是属于 r2 这个对象的。执行r2.init(5, 4);不会影响 r1。

同理,在上面的程序中,第 29 行和第 30 行的 area 和 perimeter 成员函数都是作用在 r 对象上 的,返回的就是 r 的面积和周长。

访问对象的成员

除了前面提到的对象名.成员名方法,还可以用指针->成员名的方式来访问对象的成员。例如:
CRectangle rl, r2;
CRectangle* p1 = &r1;
CRectangle* p2 = &r2;
p1 -> w = 5;  //此处的 w 属于 p1 指向的对象
p2 -> init(5, 4);  //init函数作用在 p2 指向的对象上

还可用引用名.成员名的方式访问对象的成员。例如:
CRectangle r2;
CRectangle & rr = r2;
rr.w = 5;
rr. init(5, 4);  //rr的值改变,r2的值也改变

调用类的成员函数时,必须指明其所作用的对象。对于上面的 CRectangle 类来说,如果只写init(2, 4);这条语句是不能编译通过的。因为编译器不清楚这个 init 函数是作用在哪个对象上面的,进入 init 函数以后,其中访问的 w 和 h 的内存地址在哪里无从知晓,因而也就无法编译了。

类还有另外一种写法,就是将 class 关键字换成 struct。例如:
struct CRectangle{
public:
    int w, h;  //成员变量,宽度和高度
    void init(int w_, int h_);  //成员函数,设置宽度和高度
};
没有成员函数的 struct 还是称作“结构”,结构变量不是对象;有成员函数的 struct 就是类。

写成 struct 的类和写成 class 的类只在类成员的可访问范围方面有一点很小的差别,后面很快会提到。

和结构变量一样,对象之间可以用互相赋值,但不能用 ==、!=、<、>、<=、>= 进行比较运算,除非这些运算符经过适当的“重载”。运算符重载是后面要学的知识,此处不予解释。