文章目录
-
- 一、视C++为一个语言联邦
-
- 1、C++不同于C的部分
- 2、可以将C++分为4个层次
- 二、尽量以const,enum,inline替换#define
-
- 1、#define存在的问题
- 2、const,enum,inline的好处
- 3、请记住
- 三、尽可能使用const
-
- 1、const修饰变量
- 2、const修饰函数参数
- 3、const成员函数
- 4、函数返回const
- 5、请记住
- 四、确定对象使用前已被初始化
-
- 1、成员初始化列表
- 2、以local static对象替换non-local static对象
- 3、请记住
一、视C++为一个语言联邦
C是面向过程的语言,它的侧重点在于算法和数据结构。编写C代码,侧重点在于通过输入数据,设计一定的算法过程,得到输出。C++是面向对象的语言,它的侧重点在于抽象出对象模型,使这个模型和问题像契合。通过对对象状态的控制,来解决问题。
1、C++不同于C的部分
class:虽然C语言也有结构体struct,但是它更多的侧重于数据机构,侧重数据的组织。虽然struct在++中也支持class的个各种特性,但是很少用struct去替代class。它们两个一个不同在于class默认成员的访问是private,而struct是public。
template:模板属于泛型编程,泛型编程使得代码独立于特定的数据类型,可以大大减少代码量。
overload:重载是C语言中没有的,在一些代码中经常看到external “C",这是表示以C语言方式编译。因为重载是通过编译时,在函数明后加上函数参数类型来生成函数名实现的,而C语言则不是,所以如果要给C调用,就要加上extern “C”。
2、可以将C++分为4个层次
- C:C++实在C语言的基础上发展而来的。
- Object-Oriented C++:这是C++中不同于C的部分,这里主要指面向对象。
- Template C++:C++中的泛型编程。
- STL:这是一个标准模板库,它用模板实现了很多容器、迭代器和算法,使用STL往往事半功倍。
二、尽量以const,enum,inline替换#define
1、#define存在的问题
【不容易定位错误】:
在预处理时, 所有使用PI的地方都将被替换,之后编译器在编译时从未看到过PI。这时如果遇到错误,报错时给出的是3.1415926,而不是PI,因为PI从未进入到符号表,这将导致错误难以理解。一个替换的方法是如下定义:
【class专属常量】:
专属于class作用域的常量。专属于class的常量将这个常量限定在class的作用域内,而#define定义的常量没有作用域的限制,一旦在某一处有个宏定义,在其后面都有效(除非#undef)。这意味着#define不仅不能够用来定义class专属常量,也不能提供任何封装性。
class GamePlayer
{
static const int NumTurns=5; // 所有实例共享一份
int scores[NumTurns];
};
2、const,enum,inline的好处
- const的好处:
- define直接常量替换,出现编译错误不易定位(不知道常量是哪个变量);
- define没有作用域,const有作用域提供了封装性。
- enum的好处:
- 提供了封装性;
- 编译器肯定不会分配额外内存空间(其实const也不会)。
- inline的好处:
- define宏函数容易造成误用。
3、请记住
- 对于单纯常量,最好以const对象或者enum替换#define。
- 对于形似函数的宏,最好改用inline函数替换#define。
三、尽可能使用const
1、const修饰变量
// 表示a是一个常量。
const T a;
//表示a是一个指向常量的指针,*a是不能被修改的,但是a可以被修改。
const T *a;
// 表示a是一个指向整数的常指针,a是不能被修改的,但是*a是可以被修改的。
T * const a;
// 表示a是一个指向常量的常指针,*a是不能被修改的,a也是不能被修改的。
const T * const a;
2、const修饰函数参数
传递过来的参数在函数内不能改变,与修饰变量性质一样。
void display(const int a, int b)
{
a=5; // 错误。
}
3、const成员函数
const成员函数的目的是确认该函数可以用到const对象上。const成员函数使得:
- class接口更加容易理解,确认哪些接口可以修改class成员。
- 使操作const对象成为可能。
关于第2点,是因为const对象只能调用const成员函数,但是非const对象既可以调用普通成员函数,也可以调用const成员函数。这是因为this指针可以转换为const this,但是const this不能转换为非const this。
【Note】:
(1)一个函数是不是const是可以被重载的。
(2)如果某些成员变量可能总是会被修改,即使是在const成员函数中,可以对这一类变量使用mutable关键字。
4、函数返回const
若函数的返回值是指针,且用const修饰,则函数返回值指向的内容是常数,不可被修改,此返回值仅能赋值给const修饰的相同类型的指针。令函数返回一个常量值,往往可以降低客户错误而造成的意外,又不至于放弃安全性和高效性。
Rational a, b, c;
(a * b) = c; // 避免无意义的赋值操作。
5、请记住
- 将某些东西声明为const可帮助编译器侦测出错误用法。const可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体。
- 编译器强制实施bitwise constness,但你编写程序时应该使用“概念上的常量性”(conceptual constness)。
- 当const 和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复。
四、确定对象使用前已被初始化
1、成员初始化列表
在一些语境下会初始化为0,但在另一些语境下可能就不会初始化,例如:
class Point
{
int x, y;
}
在构造函数中完成初始化时,要区分赋值和初始化。在构造函数体内的是赋值,在初始化列表中的才是初始化。
class Point
{
public:
Point(int x_, int y_):x(x_), y(y_) { }
private:
int x, y;
}
初始化效率往往高于赋值。赋值是先定义变量,在定义的时候可能已经调用了变量的构造函数,之后赋值是调用了赋值操作符;而初始化是直接调用了复制构造函数。
【Note】:
(1)初始化顺序要和声明顺序一致。
(2)在一些情况下必须使用初始化方式,有些遍历在定义时就要求初始化,例如const和引用。
2、以local static对象替换non-local static对象
将每个non-local static对象搬到自己的专属函数内(该对象在此函数内被声明为static)。这些函数返回一个reference指向它所含的对象。然后用户调用这些函数,而不是直接指涉这些对象。换句话说,non-local static对象被local static对象替换了。
class FileSystem{
/..../
std::size_t numDisks() const;
/..../
};
FileSystem& tfs()
{
static FileSystem fs;
return fs;
}
class Directory {
/..../
Directory (params);
/..../
};
Directory::Directory(params)
{
/..../
// 如果是tfs.numDisks()则无法保证tfs在tempDir之前被初始化。
std::size_t disks = tfs().numDisks();
/..../
}
Directory& tempDir()
{
static Directory td;
return td;
}
3、请记住
- 为内置型对象进行手工初始化,因为C++不保证初始化它们(C part of C++)。
- 构造函数最好使用成员初始化列表,而不要在构造函数本体内使用赋值操作。初值列列出的成员变量,其排列次序应该和它们在class中的声明次序相同。
- 为避免“跨编译单元之初始化次序”问题,请以local static 对象替换 non-local static对象。