把C++看作语言的联邦。C++包括了C、 Object-Oriented C++(C with class)、 Template C++、 STL。
C++高效守则视情况而变化,取决于你使用C++的哪一部分。
#define不被视作源码的一部分,在源码被编译器处理之前就被预处理器处理了。所以当#define出现错误时,会很难去追踪这个错误。最好的处理就是使用const常量代替宏。在替换时有两点需要注意:
由于常量定义通常放在头文件内,所以指针也必须时const类型,因此在定义时必须const两次。eg:
const char* const authorName = "xxxx";
由于常量必须限制在类中,为了确保常量最多只有一份实体,所以它必须成为一个静态成员。eg:
class GamePlayer{
private:
static const int NumTurns = 5;//声明
int scores[NumTurns];
...
};
如果需要使用这个类的专属常量的地址,需要在该文件中提供该常量的定义。eg:
const int GamePlayer::NumTurns;//由于在声明时已经获得了初值,所以在定义的时候可以不用给予初值。
理论基础:一个属于枚举类型的数值可权充int被使用。
class GamePlayer{
private:
enum(NumTurns = 5); //"the enum hack"令NumTurns成为5的一个记号名称
int scores[NumTurns];
}
枚举可以阻止别人通过指针或者引用指向你的某个整数常量。即别人无法通过指针或者引用访问枚举的常量。
#define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b))
template<typename T>
inline void callWithMax(const T& a, const T& b)
{
f(a > b ? a : b);
}
可以用const在class外部修饰global或者namespace作用域中的常量,或修饰文件、函数、或区块作用域中被声明为static的对象。你也可以用它修饰class内部的static和non-static成员变量。而对于指针,你也可以指出指针自身、指针所指物,或者二者都是const。
const虽然变化多,但并不困难。如果const在星号左边,则表示被指物为常量,如果在星号右边,则表示指针自身是常量。如果出现在星号两边,则说明指针和被指物都是常量。
将const实施于成员函数的目的,是为了确认该成员函数可作用于const对象身上。
许多人漠视一件事实:两个成员函数如果只是常量性不同,可以被重载
class TextBlock{
public:
...
const char& operator[](std::size_t position)const
{
return test[position];}
char &operator[](std::size_t position)const
{
return test[position];}
private:
std::string test;
}
int main()
{
TextBlock tb("HELLO WORLD!");
const TextBlock ctb("hello world!");
std::cout<<tb[0]<<std::endl;
std::cout<<ctb[0]<<std::endl;
return 0;
}
1、将某些东西声明为const可帮助编译器侦测出错误用法。const可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体。
2、编译器强制实施“bitwise constness”,但编写程序时应该使用概念上的常量
3、当const和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复。
需要手动为内置型对象进行初始化,因为C++不保证初始化它们。
int t;
class Point{
int x, y;
};
Point p;
在某些情况下x 以及p的成员会被初始化,但有时候不会。这会导致初始值不明确。
通常如果你使用C part of C++(见条款1)而且初始化可能招致运行期成本,那么就不保证发生初始化。一旦进入non-C parts of C++,规则有些变化。这就很好地解释了为什么 array(来自C part of C++)不保证其内容被初始化,而vector(来自 STL part of C++)却有此保证。
最好的处理办法就是:永远在使用对象之前将他初始化。对于无任何成员的内置类型,必须手工完成。
对于内置类型意外的任何东西,初始化责任就放在了构造函数身上。规则很简单:确保每一个构造函数都将对象的每一个成员初始化。
构造函数的一个较佳的写法是,使用member initialization list(成员初始化列表)替换复制动作。
class PhoneNumber{
...};
class ABEntry{
public:
ABEntry(std::string name, std::string address)
:theName(name), theAddress(address), numTimesConsulted(0){
};
private:
std::string theName;
std::string theAddress;
int numTimesConsulted;
};
如果在定义时,没有给参数,我们可以再额外重构一个无参数的构造函数。
class PhoneNumber{
...};
class ABEntry{
public:
ABEntry(std::string name, std::string address)
:theName(name), theAddress(address), numTimesConsulted(0){
};
ABEntry():theName(), theAddress(), numTimesConsulted(0){
};
private:
std::string theName;
std::string theAddress;
int numTimesConsulted;
};
有些情况下即使面对的成员变量属于内置类型,也一定要使用初始化列表,比如成员变量为const 或 references,他们必须有初值,而且不能被赋值。为避免需要记住成员变量何时需要在成员初始化列中初始化,何时不需要。最简单的做法就是:使用成员初始化列表,这样做有时候绝对必要,而且比赋值更加高效。
C++ 有着固定的成员初始化次序,次序总是相同:base ckasses 更早于其derived ckass被初始化。而class的成员变量总是以声明次序被初始化。
static对象指其寿命从被构造出来直到程序结束位置。这种对象包括global对象、定义于namespace作用域内的对象、在class内、函数内、以及file作用域内被声明的static对象。函数内的static称为local static对象,其他static对象称为non-local static对象。程序结束时static对象会自动销毁。
编译单元指产出单一目标文件的那些源码。基本上它是单一源码加上其所含入的头文件。
所以本部分主要涉及至少两个源码文件,每个内都至少含有一个non-local static对象。问题在于如果某编译单元内的某个non-local static对象的初始化作用使用了另一个编译单元内的某个non-local static对象,它所用到的这个对象可能尚未被初始化。因为C++对“定义于不同编译单元内的non-local static对象”的初始化次序并无明确定义。
解决办法:将每个non-local static对象搬到自己的专属函数内(该对象在此函数内被声明为static),这些函数返回一个reference指向它所含的对象。然后用户调用这些函数,而不直接指涉这些对象。换句话说:non-local static对象被local static对象替换了。(单例模式)
1、为内置型对象进行手工初始化,因为C++不保证初始化它们。
2、构造函数最好使用成员初始化列表,而不要在构造函数内使用赋值操作,初始列列出的成员变量,其排列次序应该和它们在class中的声明次序相同。
3、为避免“跨编译单元之初始化次序”问题,请以local static对象替换non-local static对象。