一、默认情况下,const对象仅在文件内有效,多个文件出现同名const变量,视为不同文件分别定义的独立变量。若要在不同文件间共享const对象,需加extern关键字。
二、指向常量的指针或引用(所谓low-level const)可以绑定到一个非常量上。
三、copy时,两个对象必须具有相同的low-level const资格,或能进行相关转换(窄向转换,对对象的转换朝限制更严格的方向)。
四、当使用类型别名时,注意数据的基本类型:
typedef char* pstring;
const pstring cstr;//此处cstr是指向char的常量指针,const是顶层const
//并不能直接按展开来看,此处基本数据类型是pstring
//即char*
五、auto类型符会忽略top-level const,会保留low-level const 。
六、引用总是作为其所指对象的同义词出现,但是在decltype处是个例外:
const int ci = 0,cj = 1,
&cr = ci; decltype(cr) x = cj;//此处x类型为引用,必须初始化
七、decltype((variable))
(双层括号)结果永远是引用。
decltype作用于一个表达式,若表达式求值结果是左值,则得到引用类型:
int a = 1;
int* p = &a;
decltype(*p) q = a; //*p求值结果是左值,decltype结果是int&
八、vector初始化:当使用花括号初始化但提供的初始值又不能用来列表初始化,编译器会推断用这样的值来构造合适的对象。
vector<string> v1{10};//10个空字符串
vector<string> v2{10,"hi"};//10个hi
//正常构造使用圆括号
九、#include<iterator>
用于数组的两个标准库函数begin()和end(),以数组名作为 参数,返回指向数组首元素和尾后元素的指针:
int* beg = begin(intarr);
int* ed = end(intarr);
十、内置下标的索引不是无符号类型,标准库类型必须是无符号类型:
int arr[] = {1,2,3,4,5}
int *p = arr[2];
int i = p[-2]; //i = arr[0]
十一、使用范围for循环处理多维数组,除了最内层循环,其他所有外层循环的控制变量都应该是引用类型,这样做是为了避免数组被自动转换为指针
int ia[row][col];
for(auto row : ia) //此处row被转换成int*
for(auto col : row) //error,内层循环不合法
{ //正确写法是auto& row
//...
}
十二、range-for中预存了尾后迭代器,如果需要添加/删除变量,可能会使迭代器失效,所以不能这样做。
十三、重载函数实参类型转换优先级:
- 1.精确匹配、数组/函数->指针、增删顶层const
- 2.const转换
- 3.类型提升
- 4.算术类型转换
- 5.类类型转换
十四、定义了指向重载函数的指针,指针类型必须与重载函数中的某一个精确匹配,不能有实参或返回类型的转换。
十五、在类中定义类型的成员必须先定义后使用,这一点与普通成员有别。因为如果成员使用了外层作用域定义的一个类型名,则类在之后不能重新定义该名字,所以类型名的定义一般放在累的开始处。
//typedef int pos;
class A{
public:
//..
typedef int pos; //之后不能再重新定义pos。
//假如类外已经定义pos类型,
//则在类内重新定义pos是错误的,尽管编译器经常忽略此错误
//..
};
十六、当一个数据成员被定义成mutable,则在const成员函数内也能改变它的值:
class A
{
private:
mutable int a;
public:
void f() const //本来const成员函数内的数据成员是不允许被改变的
{ //但是a被mutable修饰后是可以改变的
a++;
}
//..
};
十七、避免头文件的循环引用:
- 头文件做好 include guard,例如 pragma once。
- 尽量使用 forward declaration,namespace 中的 class 都可以前向声明,模板也可以前向声明,例如
class Foo; typedef std::shared_ptr<Foo> FooPtr
。但是 nested class无法前向声明。 - 保证每个构成interface的头文件都独立可用,例如 class A 的 cpp 文件第一个包含的头文件应该是 class A的头文件,以此类推。尽量 cpp/头文件 配对:尽量每一个 class 有一个头文件和一个 cpp文件,文件名与类名相同,仅扩展名不同。(类模板除外,没办法。)
- include 头文件使用绝对路径,从VCS的根开始。
- cpp 文件和头文件中写 include时按一定的原则分组(例如本项目、本公司、第三方C++库、C++标准库、第三方C库,libc库),每组以内按字母顺序排列头文件。做到第3点之后,用简单的脚本就能生成头文件的包含关系图(Doxygen也行),然后就很容易看出循环依赖。也不难自动检测。
- 头文件里不要埋地雷,比如修改 struct 的默认对齐方式,修改编译器的优化级别或警告级别等等。
十八、类负责控制自己的友元类和友元函数,友元不存在传递性。
十九、友元影响的是访问权限,并非真正意义上的声明,即使在类内部定义的友元函数,也必须要在外部提供相应的声明以确保其可见:
class Sample
{
friend void fun() {} //内部定义的友元函数
public:
void f();
};
void fun(); //外部声明是必要的
void Sample::f()
{
fun();
}
二十、类的两阶段编译:
- 编译所有成员的声明。
- 直到类全部可见再编译函数体。
声明中使用的名字(包括返回类型和参数列表)仍然要确保使用前可见:
typedef double Money;
string bal;
class Acc{
public:
Money balance() {return bal;} //balance编译时,Money在外层作用域可见
//当编译bal时,类内bal已经可见,此处bal是类内的
private:
Money bal;
// ..
};