C++类成员函数定义方法详解
类成员函数定义与常规函数类似,除特殊情况外,它们有一个包含返回类型(可能为 void)、函数名和形参列表(可能为空)的函数头。执行函数动作的语句包含在一对大括号中,跟在函数头后面。
我们在《类对象的创建和使用》一节定义 Circle 类时,已经在类声明本身中定义了它的两个成员函数。当一个类函数定义在类声明中时,它被称为内联函数。
内联函数提供了在类声明中包含函数信息的便捷方式,但只能在函数体非常短(通常是单行)时使用。当函数体更长时,则可以在函数的类声明中放置一个原型,而不是函数定义本身。然后,将函数定义放在类声明之外,要么跟在它后面,要么放在一个单独的文仵中。
虽然 Circle 类中的两个函数足够短,可以写成内联函数,但是也可以将它们重写为常规函数,定义在类声明之外。在类声明中,函数将被以下原型所替代:
成员函数 getLength 和 getWidth 都是访问器函数或 get 函数。通常使用单词 get 命名一个访问器,后面跟着将被获取其值的成员变量的名称。函数 getLength 返回存储在 length 成员变量中的值,而 getWidth 则返回存储在 width 成员变量中的值。
最后一个成员函数 getArea 也是一个访问器函数,但并不改变存储在类变量中的任何值,它用于返回计算的结果,而不仅仅是检索存储在类变量中的值。
前面提到过,在设计类时,通常的做法是使所有成员变量为 private,并为访问这些变量提供公共的 set 和 get 函数,这样可以保护数据。类之外的函数只能通过调用公共成员函数来访问成员数据,并且这些函数可以被写入,以防止数据被破坏或修改,从而可能会对类的对象的行为产生不利影响。
注意,程序中写入 set 函数一定要滤出无效数据,而不是允许将无效值存储在成员变量中,如果传递给它们的数据不可接受,则它们将使用默认值。
有人可能会奇怪,为什么矩形的面积没有存储在成员变量中。该面积未被存储,是因为它可能会变得陈旧。当某个项目的值取决于其他数据,并且当其他数据被更改而该项目未及时更新时,就可以说该项目已经变得陈旧。如果矩形的面积存储在成员变量中,则只要 length 或 width 成员变量发生变化,那么它的值就会变得不正确。
在设计类时,一般不要使用成员变量来存储可能变得过时的计算值,相反,应该提供使用最新数据计算值的成员函数,然后返回计算结果。
每次调用常规函数时,都会在幕后进行许多操作。一些特殊的项目,如函数执行完成时返回的地址和函数实参的值,都必须存储在名为栈(Stack)的内存部分中;另外,还需要创建局部变量并保留一个位置来保存函数的返回值。所有这些函数调用阶段的设置开销都会占用 CPU 时间,虽然所需的时间很小,但是如果一个函数被调用多次(例如在某个循环中),那么它是会被累加的。
另一方面,内联函数根本不是传统意义上的内联,相反,在所谓内联扩展的过程中,编译器将使用函数本身的实际代码替换对函数的每次调用。这意味着,如果从程序中的多个地方调用该函数,则其代码的整个主体将被多次插入,从而增加程序的大小,这就是为什么只有寥寥几行代码的函数才能写成一个内联函数。
实际上,如果函数太大而使得内联扩展不可行,那么编译器将忽略以这种方式处理函数的请求。但是,当一个成员函数很小的时候,把它写成一个内联函数确实可以提高性能,因为当没有进行实际的函数调用时,它的开销更少。
我们在《类对象的创建和使用》一节定义 Circle 类时,已经在类声明本身中定义了它的两个成员函数。当一个类函数定义在类声明中时,它被称为内联函数。
内联函数提供了在类声明中包含函数信息的便捷方式,但只能在函数体非常短(通常是单行)时使用。当函数体更长时,则可以在函数的类声明中放置一个原型,而不是函数定义本身。然后,将函数定义放在类声明之外,要么跟在它后面,要么放在一个单独的文仵中。
虽然 Circle 类中的两个函数足够短,可以写成内联函数,但是也可以将它们重写为常规函数,定义在类声明之外。在类声明中,函数将被以下原型所替代:
void setRadius(double);
double getArea();
void Circle::setRadius(double r) { radius = r; } double Circle::getArea() { return 3.14 * pow(radius, 2); }可以看到,以上函数实现语句和普通函数看起来是一样的,区别在于,在函数返回类型之后函数名之前,放置了类名和双冒号(::)。:: 符号称为作用域解析运算符。它可以用来指示这些是类成员函数,并且告诉编译器它们属于哪个类。
注意,类名和作用域解析运算符是函数名的扩展名。当一个函数定义在类声明之外时,这些必须存在,并且必须紧靠在函数头中的函数名之前。
以下示例通过对比,清晰说明了当类函数定义在类声明之外时,该如何使用作用域解析运算符:
double getArea () //错误!类名称和作用域解析运算符丢失
Circle :: double getArea () //错误!类名称和作用域解析运算符错位
double Circle :: getArea () //正确
// This program demonstrates a simple class with member functions defined outside the class declaration. #include <iostream> #include <cmath> using namespace std; //Circle class declaration class Circle { private: double radius; // This is a member variable. public: void setRadius(double); // These are just prototypes double getArea(); // for the member functions. }; void Circle::setRadius(double r) { radius = r; } double Circle::getArea() { return 3.14 * pow(radius, 2); } int main() { Circle circle1,circle2; circle1.setRadius(1); // This sets circle1's radius to 1.0 circle2.setRadius(2.5); // This sets circle2's radius to 2.5 cout << "The area of circle1 is " << circle1.getArea() << endl; cout << "The area of circle2 is " << circle2.getArea() << endl; return 0; }程序输出结果为:
The area of circle1 is 3.14
The area of circle2 is 19.625
类成员函数的命名约定
下面的程序提供了使用类和对象的另一个示例。它声明并实现了一个具有两个私有成员变量和 5 个公共成员函数的 Rectangle 类:#include <iostream> using namespace std; // Rectangle class declaration class Rectangle { private: double length; double width; public: void setLength(double); void setWidth(double); double getLength(); double getWidth(); double getArea(); }; //Member function implementation section void Rectangle::setLength(double len) { if (len >= 0.0) length = len; else { length = 1.0; cout << "Invalid length. Using a default value of 1.0\n"; } } void Rectangle::setWidth(double w) { if (w >= 0.0) width = w; else { width = 1.0; cout << "Invalid width. Using a default value of 1.0\n"; } } double Rectangle::getLength() { return length; } double Rectangle::getWidth() { return width; } double Rectangle::getArea() { return length * width; } int main() { Rectangle box; // Declare a Rectangle object double boxLength, boxWidth; //Get box length and width cout << "This program will calculate the area of a rectangle.\n"; cout << "What is the length?"; cin >> boxLength; cout << "What is the width?"; cin >> boxWidth; //Call member functions to set box dimensions box.setLength(boxLength); box.setWidth(boxWidth); //Call member functions to get box information to display cout << "\nHere is the rectangle's data:\n"; cout << "Length: " << box.getLength() << endl; cout << "Width : " << box.getWidth () << endl; cout << "Area : " << box.getArea () << endl; return 0; }程序运行结果:
This program will calculate the area of a rectangle.
What is the length?10.1
What is the width?5
Here is the rectangle's data:
Length: 10.1
Width : 5
Area : 50.5
成员函数 getLength 和 getWidth 都是访问器函数或 get 函数。通常使用单词 get 命名一个访问器,后面跟着将被获取其值的成员变量的名称。函数 getLength 返回存储在 length 成员变量中的值,而 getWidth 则返回存储在 width 成员变量中的值。
最后一个成员函数 getArea 也是一个访问器函数,但并不改变存储在类变量中的任何值,它用于返回计算的结果,而不仅仅是检索存储在类变量中的值。
前面提到过,在设计类时,通常的做法是使所有成员变量为 private,并为访问这些变量提供公共的 set 和 get 函数,这样可以保护数据。类之外的函数只能通过调用公共成员函数来访问成员数据,并且这些函数可以被写入,以防止数据被破坏或修改,从而可能会对类的对象的行为产生不利影响。
注意,程序中写入 set 函数一定要滤出无效数据,而不是允许将无效值存储在成员变量中,如果传递给它们的数据不可接受,则它们将使用默认值。
避免陈旧数据
在 Rectangle 类中,getLength 和 getWidth 成员函数返回存储在 length 和 width 成员变量中的值,但 getArea 成员函数返回计算结果。有人可能会奇怪,为什么矩形的面积没有存储在成员变量中。该面积未被存储,是因为它可能会变得陈旧。当某个项目的值取决于其他数据,并且当其他数据被更改而该项目未及时更新时,就可以说该项目已经变得陈旧。如果矩形的面积存储在成员变量中,则只要 length 或 width 成员变量发生变化,那么它的值就会变得不正确。
在设计类时,一般不要使用成员变量来存储可能变得过时的计算值,相反,应该提供使用最新数据计算值的成员函数,然后返回计算结果。
内联函数详解
在设计一个类时,将需要确定哪些成员函数在类声明中作为内联函数来编写,哪些函数在类之外定义。编译器对内联函数的处理方式与常规函数完全不同,了解这种差异将有助于程序员决定使用这两种函数的时机。每次调用常规函数时,都会在幕后进行许多操作。一些特殊的项目,如函数执行完成时返回的地址和函数实参的值,都必须存储在名为栈(Stack)的内存部分中;另外,还需要创建局部变量并保留一个位置来保存函数的返回值。所有这些函数调用阶段的设置开销都会占用 CPU 时间,虽然所需的时间很小,但是如果一个函数被调用多次(例如在某个循环中),那么它是会被累加的。
另一方面,内联函数根本不是传统意义上的内联,相反,在所谓内联扩展的过程中,编译器将使用函数本身的实际代码替换对函数的每次调用。这意味着,如果从程序中的多个地方调用该函数,则其代码的整个主体将被多次插入,从而增加程序的大小,这就是为什么只有寥寥几行代码的函数才能写成一个内联函数。
实际上,如果函数太大而使得内联扩展不可行,那么编译器将忽略以这种方式处理函数的请求。但是,当一个成员函数很小的时候,把它写成一个内联函数确实可以提高性能,因为当没有进行实际的函数调用时,它的开销更少。