完整阅读C++ Primer Plus
系统重新学习C++语言部分,记录重要但易被忽略的,关键但易被遗忘的。
内存模型和名称空间
1、C++标准允许每个编译器设计人员以他认为合适的方式实现名称修饰,因此由不同编译器创建的二进制模块很可能无法正确的链接。也就是说,两个编译器将为同一个函数生成不同的修饰名称。名称的不同使链接器无法将一个编译器生成的函数调用与另一个编译器生成的函数定义匹配。在链接编译模块时,需要确保对象文件或库都是由同一个编译器生成的。如果有源代码,通常可以用自己的编译器重新编译源代码来消除所有链接错误。
2、C++使用三种(C++11使用四种)不同的方存储数据。
自动存储持续性(函数内的局部变量以及函数参数)
静态存储持续性(static以及函数外定义的变量)
动态存储持续性(new,delete)
线程存储持续性(C++11):多核处理器常见,这些CPU可同时处理多个执行任务。这让程序能够将计算放在可并行处理的不同线程中,如果变量使用关键字thread_local声明的,则其生命周期与所属线程一样长。
3、C++中,关键字register已经失去了它的意义,它只是显示地指出变量是自动的。
4、在默认情况下,全局变量的链接性为外部的,但const全局变量的链接性为内部的。在C++看来,全局const就像使用了static一样,这就是为什么常量定义可以放在头文件中,因为每一个包含了这份头文件的代码文件,都会有单独的一组常量。
5、内联函数不受单定义规则的约束,这也是可以将内联函数的定义放在头文件中的原因,这样,包含了头文件的每个代码文件都有内联函数的定义,然而,C++要求同一个函数的所有内联定义都必须相同
6、定位new运算符
通常,new负责在堆中找到足以满足要求的内存块,new运算符还有一种变体,被称为new定位运算符,能够指定要使用的位置,这样便将分配内存的工作交到了程序员的手上。
1 #include<new> 2 struct chaff 3 { 4 char dross[20]; 5 int slag; 6 }; 7 char buffer1[50]; 8 char buffer2[500]; 9 int main() 10 { 11 char * p1,p2; 12 int * p3,p4; 13 p1 = new chaff; // 使用堆 14 p3 = new int[20]; // 使用堆 15 p2 = new (buffer1) chaff; // 使用buffer1 16 p4 = new (buffer2) int[20]; // 使用buffer2 17 ...... 18 }
值得关注的是,第一,定位new运算符的工作原理只是返回传递给它的地址,并将其强制类型转换为void *。第二,定位运算符不跟踪哪些地址已被使用,也不查找哪些未被使用,可以给它提供一个偏移量(buffer1 + n)指定地址,第三,不能使用delete释放上例中的内存,同时如果使用定位new运算符创建对象时,在代码块结束时,也不会自动调用析构函数(有时需要显示调用,这也是为数不多显式调用析构函数的地方)。
7、定位new函数不可替换,但可以重载,它至少接受两个参数,第一个总是std::size_t,指定请求的字节数。
8、函数中的using编译指令将名称空间的名称视为在函数之外声明的,但它不会使得该文件中的其他函数能够使用这些名称。
9、假设名称空间和声明区域定义了相同的名称,如果使用using声明指令将名称空间的名称导入声明区域,则这两个名称会发生冲突,如果使用using编译指令将该名称空间的名称导入该声明区域,则局部的版本将隐藏名称空间版本。
10、可以给名称空间创建别名
1 namespace myspace = std;
11、通过省略名称空间的名字来创建未命名的名称空间,同时,不能在未命名名称空间所属文件之外的其他文件中,使用该名称空间的名称,这为链接性为内部的静态变量提供了替代品。
12、如果名称空间的函数被重载,那么一个using声明将导入所有的版本。
对象和类
13、其定义位于类声明内部的函数都将自动成为内联函数。
14、对于下面两个初始化一个类的语法,C++标准允许编译器有两种方式去执行第二个语法。
1 Stock stock1("aaa", 1, 2.1); // #1 2 Stock stock2 = Stock("bbb", 2, 3.1); // #2
其中一种是与第一个语法完全相同,另一种方式是调用构造函数创建一个临时对象,然后将该临时对象赋值给要创建的对象,编译器可能会立刻调用析构函数去删除这个临时对象,但也可能等一段时间
15、在成员函数定义和声明后加const,它保证了成员函数不会修改对象中的数据。对于const对象,无法调用没有被const修饰的成员函数。
16、可以用构造函数初始化对象数组中的每个元素,如果类包含多个构造函数,则可以对不同元素使用不同构造函数:
1 const int STKS = 10; 2 Stock stocks[STKS] = { 3 Stock("aaa", 12, 13.1), 4 Stock(), 5 Stock("bbb", 12.1, 13), 6 };
对于没有显示调用构造函数的数组元素将使用默认的构造函数。
17、最初的UNIX实现使用C++前端cfront将C++程序转换为C程序。
1 void Stock::show() const; 2 void show(const Stock * this);
将Stock::限定符转换为函数参数(指向Stock的指针),用指针来访问类成员。
1 top.show(); 2 show(&top);
将调用对象的地址赋给this指针。
18、除了全局(文件)作用域和局部(代码块)作用域外,C++新增了类作用域,在类中定义的名称作用域为整个类。
19、在类内想要使用const去创建一个常量是行不通的,类声明只是描述了类的形式,并没有为其分配存储空间,一种代替方式是使用枚举,枚举只是创建了符号常量,并不会成为类的数据成员,可以使用枚举代替整数常量。另一种方式是使用static修饰常量,该常量将于其他静态变量存储在一起,而不存储在对象中。
20、C++11提供了一种新的枚举,防止类作用域内的枚举名发生冲突。
1 enum class egg {small, medium, large, jumbo}; 2 enum class t_shirt {small, medium, large, xlarge}; 3 egg choice = egg::large; 4 t_shirt floyd = t_shirt::large;
21、C++11提高了作用域内枚举的类型安全,普通枚举一般会自动转换为整型(如将其赋值给int变量或用于比较表达式时),但类作用域内的枚举无法进行隐式类型转换,在必要时可进行显示地类型转换。
22、C++98中的枚举使用底层整数类型,具体选择取决于实现。C++11对于作用域内枚举消除了这种依赖性,可以使用如下语法:
1 enum class : short pizza {small, medium, large, xlarge};
:short将底层类型指定为short类型,底层类型必须为整型,C++11也支持这种语法来指定常规枚举底层类型。