《C++QT设计模式(第二版)》读书笔记
Alan Ezust
Paul Ezust
好习惯:
定义类,应将它的定义放到一个与这个类同名的头文件中,并添加预处理宏来避免多次包含。例:aclass.h
|
|
再将这个类成员函数的具体实现写入一个单独的实现文件中去,命名为同名的cpp文件:
|
|
默认情况下,类的成员是private类型
类的友元
友元可以是一个类,另外一个类的成员函数,或者任何一个非成员函数。
破坏封装性可能会危害程序的课维护性,因此应该尽量少的使用友元,不得已使用时候也要倍加小心。
通常而言,为了达到以下两个目的才是用友元函数:
- 为了使用工厂方法,此时需要对某个类强制实施某些创建规则。(具体待补充)
- 为了使用全局运算符函数,比如operator<<()
析构函数
如果某个类满足以下条件,则无需提供析构函数:
- 只拥有非指针的简单类型成员
- 拥有已经适当定义的析构函数的类成员
- 它是满足某些条件的一种QT类(待补充)
stactic
全局命名空间污染:将名称添加到全局作用域中(例如,声明全局变量或者全局函数)
这是不好的编程风格
相对于全局变量,我们更倾向于使用static成员(且一般可以代替全局变量),因为static成员不会在全局命名空间中增加不必要的名称
每个static数据成员都必须在类定义之外的文件中被初始化(定义),一般在对应的类的实现文件中初始化。
.cpp中
int Thing::s_count = 0;
类的声明和定义
例:
egg.h:
|
|
chicken.h:
|
|
一个头文件包含另外一个头文件,另一个又包含回来了,预处理器不允许这样的循环依赖。
这两个头文件都不需要包含另一个头文件,因为这样做会不必要的在头文件之间建立一种强依赖性。
在正确的情况下,C++允许使用前置类声明,而不必包含某个特定的头文件。
例:egg.h
|
|
注意:cpp文件中可以包含多个头文件,而不会导致它们之间的循环依赖性。.cpp文件对两个头文件具有依赖性,但头文件之间彼此不存在依赖性。
例:egg.cpp
|
|
因此,前置类声明使得定义双向关系成为可能,
转换构造函数
当一个构造函数只有一个参数,而且该参数又不是本类的const引用时,这种构造函数称为转换构造函数。
用转换构造函数可以将一个指定类型的数据转换为类的对象,但是不能反过来将一个类的对象转换为一个其他类型的数据。 类型转换函数的作用是将一个类的对象转换成另一个类型的数据。
转换构造函数的作用是将某种类型的数据转换为类的对象,当一个构造函数只有一个参数,而且该参数又不是本类的const引用时,这种构造函数称为转换构造函数。试验了一下
|
|
结果是输出2和3
实际上这是由隐式转换机制造成的,如果不想要这种效果,可以在构造函数前加上explicit声明。加上之后上面的代码就会编译出错,提示
无法从“int”转换为“A”。
既然能将数据转换为类型,类型也能转换为数据。c++的类型转换函数可以将一个类的对象转换为一个指定类型的数据。
类型转换函数的一般形式为 :
operator 类型名()
{实现转换的语句}
测试代码:
|
|
结果输出5和6
const成员函数
当将关键字const应用到一个(非static的)类成员函数时,其含义是比较特殊的。如果放置在参数之后,const就成为函数签名的一部分,从而保证此函数不会改变主题对象的状态。
要理解只一点,好方法就是意识到每一个非static成员函数都有一个称为this的隐式参数,this是一个指向主题对象的指针。当将一个成员函数声明为const时,就相当于告诉编译器只要此函数被调用,this就是一个指向const的指针。
可以看一下原始的C++到C语言的预处理器是如何处理成员函数的。因为C语言不支持重载函数或成员函数,预处理器会将这些函数翻译成具有“重整名称”的C语言函数,例:
|
|
|
|
用QT实现Unicode显示
QtextStream能够针对Unicode的QString和其它类型的Qt起作用
所以配合QString的QtextStream可以顺利实现打印中文字符
|
|
C++中避免使用数组
在C++中,数组被看成是“邪恶的”,
下面是应该避免在C++中使用数组的一些理由:
- 编译器和运行时系统都不会检查数组的下标是否位于正确范围之内
- 使用数组的程序员有责任编写额外的范围检查代码
- 数组的大小可以是固定不变的,或者必须使用堆中的动态内存
- 对于堆数组,程序员有责任确保所有情况下正确释放内存
- 为此需要深入理解C++已经异常,特别是发生异常时的底层处理机制。向数组插入、预分配或追加元素都是费时的操作(再运行时和开发时都是如此)。
QStringList与迭代
例子:
|
|
运算符重载
例:
|
|
例子中声明的运算符都是二元的(接收两个操作数)。对成员函数而言,却只有一个形式参数,因为第一个(左边的)操作数都是隐式的:*this
虽然可以将运算符重载为成员函数或者全局函数,但需要注意,它们的主要差异是调用它们的方式。特别地,成员函数运算符要求有一个用作左操作数的对象,而全局函数允许对任何一个操作数进行某种类型转换。
例:
|
|
所以重载操作符应该视情况重载全局的运算符,将全局运算符重载函数声明成非成员友元函数。
从函数返回应用
有时候将函数设计成返回引用是非常有用的。例如,可以将多个操作“链”起来:
cout<<thing1<<thing2<<thing3...;
返回引用(尤其是*this)通常用来给成员函数提供左值行为。
采用引用参数,就可以通过将对象的别名指定成const来保护返回的引用。
例:
|
|
注意:要小心,不要让函数返回一个临时(局部)对象的引用。当函数返回时,所有局部变量都销毁了。
inline函数的一些规则
- inline函数必须在被调用之前定义(仅仅声明它是不够的)。
- 在一个源代码模块中只能有一次inline定义。
- 如果类成员函数的定义出现在类定义之内,则成员函数就是隐含inline的。
如果函数太复杂,或者编译器选项改变了,则编译器可能会忽略inline指令。大多数编译器会拒绝包含如下语句的inline函数:
- while,for,do-while语句
- switch语句
- 超过一定数量的代码行
继承和多态
没有virtual时候*p1和*p2的具体类型靠函数指针类型来判断
:
|
|
有virtual时候*p1和*p2的具体类型通过指向类型判断
,虚函数有虚函数表,可以确定数据类型:
|
|