C++ 与“类”有关的注意事项总结(一)
时间:2011-04-04 来源:charley_yang
1. 除了静态 static 数据成员外,数据成员不能在类体中被显式地初始化。
例如 :
class First {
int memi = 0; // 错误
double memd = 0.0; // 错误
};
类的数据成员通过类的构造函数进行初始化。
2. 我们可以声明一个类但是并不定义它.
例如:
class Screen; // Screen 类的声明
这个声明向程序引入了一个名字 Screen,指示 Screen为一个类类型。
但是我们只能以有限的方式使用已经被声明但还没有被定义的类类型,如果没有定义类那么我们就不能定义这类类型的对象。因为类类型的大小不知道,编译器不知道为这种类类型的对象预留多少存储空间。
但是,我们可以声明指向该类类型的指针或引用,允许指针和引用是因为它们都有固定的大小, 这与它们指向的对象的大小无关,但是,因为该类的大小和类成员都是未知的,所以要等到完全定义了该类,我们才能将解引用操作符( * )应用在这样的指针上,或者使用指针或引用来指向某一个类成员。
只有已经看到了一个类的定义,我们才能把一个数据成员声明成该类的对象。在程序文本中还没有看到该类定义的地方,数据成员只能是该类类型的指针或引用。例如下面是类StackScreen的定义,它有一个数据成员是指向 Screen类的指针,这里 Screen只有声明没有定义:
class Screen; // 声明
class StackScreen {
int topStack;
// ok: 指向一个 Screen 对象
Screen *stack;
void (*handler)();
};
因为只有当一个类的类体已经完整时,它才被视为已经被定义,所以一个类不能有自身类型的数据成员,但是,当一个类的类头被看到时,它就被视为已经被声明了,所以一个类可以用指向自身类型的指针或引用作为数据成员。例如 :
class LinkScreen {
LinkScreen iLinkScreen; // 错误:一个类不能有自身类型的数据成员
LinkScreen *next;
LinkScreen *prev;
};
3. inline和非inline成员函数
在类体中定义的成员函数默认是inline函数,可以显示的加上inline;
通常,在类体外定义的成员函数不是 inline 的,但是,这样的函数也可以被声明为 inline函数。可以通过显式地在类体中出现的函数声明上使用关键字 inline 或者通过在类体外出现的函数定义上显式使用关键字 inline,或者两者都用。
4. const 成员函数
通常,程序中任何试图修改 const 对象的动作都会被标记为编译错误
const char blank = ' ';
blank = '\n'; // 错误
但是,程序通常不直接修改类对象,又是在必须修改类的对象时,才调用公有成员函数集来完成,为尊重类对象的常量性,编译器必须区分不安全与安全的成员函(即区分试图修改类对象与不试图修改类对象的函数 ),例如:
const Screen blankScreen;
blankScreen.display(); // 读类对象
blankScreen.set( '*' ); // 错误: 修改类对象
类的设计者通过把成员函数声明为 const 以表明它们不修改类对象,例如 :
class Screen {
public:
char get() const { return _screen[_cursor]; }
// ...
}
只有被声明为 const 的成员函数才能被一个const 类对象调用,关键字 const 被放在成员函数的参数表和函数体之间,对于在类体之外定义的const 成员函数,我们必须在它的定义和声明中同时指定关键字 const。
把一个修改类数据成员的函数声明为 const 是非法的,例如,在如下简化的 Screen定义中:
class Screen {
public:
int ok() const { return _cursor; }
void error( int ival ) const { _cursor = ival; }
// ...
private:
string::size_type _cursor;
// ...
};
ok()的定义是一个有效的 const 成员函数定义,因为它没有改变_cursor 的值,但是 error() 的定义修改了_cursor 的值,因此它不能被声明为一个 const 成员函数,这个函数定义将导致下面的编译器错误消息:
error: cannot modify a data member within a const member function
但是,把一个成员函数声明为const 并不能阻止程序员可能做到的所有修改动作,把一个成员函数声明为 const 可以保证这个成员函数不修改类的数据成员 。但是,如果该类含有指针,那么在 const 成员函数中就能修改指针所指的对象,编译器不会把这种修改检测为错误,例如 :
#include <cstring>
class Text {
public:
void bad( const string &parm ) const;
private:
char *_text;
};
void Text::bad( const string &parm ) const
{
_text = parm.c_str(); // 错误: 不能修改 _text
for ( int ix = 0; ix < parm.size(); ++ix )
_text[ix] = parm[ix]; // 不好的风格, 但不是错误的
}
尽管_text 不能被修改,但是_text 的类型是 char* 在类 Text 的 const 成员函数中可以修改_text 指向的字符,成员函数 bad()反映了一种不良的程序设计风格。
const 成员函数可以被相同参数表的非 const 成员函数重载,例如:
class Screen {
public:
char get(int x, int y);
char get(int x, int y) const;
// ...
};
在这种情况下 类对象的常量性决定了调用哪个函数 :
int main() {
const Screen cs;
Screen s;
char ch = cs.get(0,0); // 调用 const 成员
ch = s.get(0,0); // 调用非 const 成员
}
构造函数和析构函数是两个例外,即使构造函数和析构函数不是const 成员函数,const 类对象也可以调用它们。当构造函数执行结束,类对象已经被初始化时,类对象的常量性就被建立起来了,析构函数一被调用,常量性就消失。所以一个 const 类对象从构造完成时刻到析构开始时刻,这段时间内被认为是const。