今天开始拜读大神的《Effective C++》,虽说是编程的注意事项,但是读起来一点都不轻松,也许正说明自己的C++不深厚,由此可以得到提升和巩固吧!今天主要学习的第一章《Accustoming Yourself to C++》,介绍了一些使用C++时基本却很重要的问题。一个明确的理念是作为一种多重范型编程语言,C++功能强大,可以看成C、C wiht classes、C with templates和STL的集合,因此不存在放之四海而皆准的准则,必须根据实际使用的编程方法来灵活使用准则,这就要求我们必须在学习的时候明白为什么会有这条准则和何时使用。
一、首先关于#define
这是C中非常常用的预编译处理,自己用的最多的就是常量定义和宏。但是由于是预编译处理,因此该符号有可能并没有列入symbol table表中,容易导致后续的编译错误且不易查找;另一方面由于实行简单地代码替换,因而容易造成目标代码中较多的重复代码,降低效率。所以Scott建议我们使用const/enum和inline来替换掉#define的使用。
inline是比较容易理解的,声明为inline函数可以在调用时直接展开代码,而不是开辟栈,提高了利用率,所以可以使用inline来取代#define的宏定义。使用常量替换#define时要留意const自身的特性。const作为一种左结合类型符,其永远与左侧符号结合,比如:
const char* const authorname = "Scott Meyers"'
其中第一个const定义了变量authorname指向一个常量,第二个const与*结合说明authorname是一个常指针,即本身也是一个常量。
另一个需要注意的是当用static类型替换#define类型时,在类中的声明不能取代定义,对于static变量成员一般要求类外单独的显示定义,但是有时在类中声明的同时要求必须赋值,这时可以使用enum hack的方法,即利用“一个属于枚举类型的数值可权充ints被使用”。如:
class GamePlayer {
private :
enum {NumTurns = 5}; //声明时提供初值
int scores[NumTurns];
... ...
}
所以我们有结论:
1. 对于单纯的常量,最好用const对象或enums替换#define
2. 对于形似函数的宏,最好改用inline函数替代
二、关于const的使用
const作为类型修饰符,可以说明其后被修饰的对象不可更改。这里自己想重点谈谈常成员和常函数。对于类中的成员而言,常成员一般意味着其所有的数据成员的值不可变,即常说的物理/比特常量性(physical/bitwise constness)。为了保证这些常成员不可变,我们要求把所有不改变成员对象的函数统统声明为const函数(const标志位于参数表和函数体之间)。这样编译器就会替我们进行检查。常成员对象只能调用常函数以防止数据发生改变。即使一个函数没有修改数据但是没有声明为const函数被常成员对象调用时依然会报错。但是有时bitwise constness只能保证类成员数据不变,但是不负责指针或引用指向的对象。并且有时我们需要常函数修改某些变量,为了突破编译器的检查,我们可以声明需要被修改的变量为mutable属性。
三、确保对象使用前皆被初始化
使用C时就必须警惕声明定义一个指针后立刻初始化,避免使用未定义指针造成程序严重错误。C++中最好将所有对象在使用前都进行初始化。一类是C++内建数据类型(如int\double等),必须显示的赋值初始化;另一类是用户自定义数据类型,如class,这部分会由C++提供的默认constructor负责。其中类中的对象应当由constructor的成员初始化列表完成。因为C++规定成员变量的初始化应当在构造函数体执行前完成。因此以下两种方式是不同的:
class GamePlayer {
public :
GamePlayer(string& User, string& Country)
{
_User = User; //此时C++已经调用了default constructor完成了_User和_Country的初始化
_Country = Country;
}
... ...
private :
string _User;
string _Country;
}
GamePlayer(string &User, string Country) : _User(User), _Country(Country) {} //成员初始化列表形式
这部分另一个需要特别注意的是,当有多个源文件进行编译,但是文件A中的global(即为static)对象被B中的static对象调用时,C++同时编译不能确定二者之间的初始化顺序,必须人为采用reference-returning形式显示调用成员函数实现。如:
A: FileSystem& tfs()
{
static FileSystem fs;
return fs;
}
B: std::size_t disks = fs().numDisks();
//static类型对象一旦定义,即存在于整个程序运行期间,无论是global还是local
所以我们有结论:
1. 内置型对象需要手工初始化,因为C++不保证初始化它们;
2. 构造函数(Constructor)最好使用成员初始化列表,初值列列出的成员变量,排列次序应该与class中声明次序相同(初始化顺序由class中声明次序决定);
3. 为避免多个源文件编译时global static的初始化次序,应当以local static替换non-local static
阅读(528) | 评论(0) | 转发(0) |