《C++ Prime Plus》(5)

同一类的所有对象共享同一组类方法

 也就是说,对于一个类,创建多个对象时会申请各自的内存空间用于存放内部变量和成员,但是类方法(成员函数)只有一个副本,多个对象在使用类方法时实际上访问的是同一个内存块。

对象无法调用构造函数

 道理是显然的,对象本就是由构造函数创建出来的,在构造函数被调用之前对象是不存在的,因此对象无法调用构造函数。如果想要改变私有成员的值,应该通过类成员函数来实现,而无法通过构造函数实现。

类的默认构造函数

 默认构造函数有两种,第一种为Student(){};,第二种为Student(type1 para1 = 0, type2 para2 = 1, ···){};。一个类必须要有默认构造函数,其目的是保证在实例化一个对象时,这个对象的各个私有成员都被初始化。当程序员不提供构造函数时,编译器会自动提供上述第一种默认构造函数,它会为私有成员提供一个随机的初始值。当程序员提供构造函数,但没有像上述第二种默认构造函数那样指定每一个私有成员的默认值(如:Student(int ID, string name = "Tom", double scores = 0){};,则在使用此类实例化一个对象时,编译器拒绝以下的语句:Student stu1;。总而言之,编译器着力避免在构造一个类对象时出现某个私有成员未被初始化的情形。

对象间赋值

 用类实例化两个对象stu1stu2,在使用stu1 = stu2;语句后,stu1中的内存单元将被来自stu2的相应内存单元的数据所覆盖。这一赋值语句的特点是它直接在内存间复制数据,而不考虑类私有成员的private特性。

const成员函数

 在使用C++时,一个需要遵守的原则是,当传入参数主观上不存在修改意图时,应该使用const修饰符来保护传入数据。对于类成员以及成员函数的操作也是如此。

 C++拒绝编译类似这样的语句:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Student{
int m_ID;
string m_name;
double scores;
public:
Student(int ID, string name, double scores){
m_ID = ID, m_name = name, m_double = double;
}
void print_info(void){
cout << m_ID << " " << m_name << " " << m_scores << endl;
}
};

const Student stu1(1, "Tom", 95.5);
stu1.print_info(); // errors!

 拒绝的理由是,stu1是个const Student型的变量,它在参与程序运行时不能被改变。而print_info()只是一个普通函数,无法保证调用它的对象在调用过程中数据的一致性。正确的做法是将print_info()的定义改为:void print_info const(void){···}

this指针

 this指针用于在成员函数在中指代该成员函数所属的对象。this是当前对象的地址,*this就是当前对象本身。在一些成员函数中,需要返回该成员函数所属的对象本身,此时可以用this充当被返回的对象从而避免无物可用的窘迫。

类对象数组

 要创建类对象数组,这个类必须要有默认构造函数。

在类中定义常量

 需要注意的是,下面的语句是不被C++编译器接受的:

1
2
3
4
class Student{
private:
const int grade = 7; // rejected
};

 道理如下,在类的声明阶段它是不占有内存空间的,只有当一个类被实例化为对象后,编译器才为这个对象分配存储空间。而const常量要求占用一块固定的存储空间,这两者之间就存在矛盾。为了在类中声明常量,可以采用以下两种解决办法:

 (1)借助枚举类型。使用枚举类型创建符号常量,编译器不需要为其分配地址,在编译时编译器将所有遇到的符号常量名替换成其对应的值。这种方法局限性在于只能定义整型常量。

1
enum {grade = 7};   // accepted

 (2)借助static。使用static关键字定义的常量将与其他静态变量存储在一起,而不必要求对象拥有内存。

1
static const int grade = 7; // accepted

操作符重载

 以时间加法为例,通过操作符+实现两个时间类相加,其中小时为24进制,分钟为60进制,秒钟为60进制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// 首先定义一个时间类,将类定义和原型放在头文件中
/***** <time.h> *****/
#ifndef TIME_H_
#define TIME_H_

class Time{
private:
int m_hour, m_minute, m_second;
public:
Time(); // 默认构造函数,有这个函数可以在实例化对象时暂不赋初值
Time(int hour, int minute, int second); // 构造函数
Time operator+(Time const & t) const; // +操作符重载函数
void show(void) const; // 以便于阅读的格式打印最终时间
};

#endif

// 将函数实现放在同名cpp文件中
/***** <time.cpp> *****/
#include <iostream> // 需要用到输入输出流
#include "time.h" // 必要操作:包含原型头文件
using namespace std;

Time::Time(){} // 默认构造函数实现

Time::Time(int hour, int minute, int second){ // 构造函数实现
m_hour = hour, m_minute = minute, m_second = second;
}

Time Time::operator+(Time const & t) const{ // 操作符重载函数实现
Time time;
time.m_second = m_second + t.m_second;
time.m_minute = m_minute + t.m_minute + time.m_second / 60;
time.m_second %= 60;
time.m_hour = m_hour + t.m_hour + time.m_minute / 60;
time.m_minute %= 60;
time.m_hour %= 24;
return time;
}

void Time::show(void) const{ // 打印时间函数实现
cout << m_hour << ":" << m_minute << ":" << m_second << endl;
}

// 在主程序中实例化类和使用重载操作符
/***** <MainFile.cpp> ******/
#include <iostream>
#include "time.h" // 必要操作:包含原型头文件
using namespace std;

int main(int argc, char const *argv[])
{
Time time1(23, 0, 1), time2(1, 59, 59);
Time time3 = time1 + time2;
time3.show(); // time3: 1:0:0
return 0;
}

对操作符进行重载的注意事项

 (1)重载操作符至少有一个操作数的类型为用户定义的类型;

 (2)重载操作符不能违反原操作符的句法规则(如双目运算符不能重载为单目运算符),不能改变操作符的运算顺序;

 (3)不能定义一种C++原先没有的操作符。

 (4)有一些操作符不能被重载。

C++关键字explicit

 在类的声明中,用explicit修饰构造函数会使得构造函数内只能进行显式强制类型转换而不再进行隐式类型转换。


转载请注明来源:©Tinshine