870920 Menu

SwingCoder之C++备忘录·1

 学习编程,必须亲自编码,而不是死记硬背。多练少看

 研究类库最有效的办法依然是多看(文档和源码)多练

 必须注重架构、设计、类图、伪代码编程、单元测试和完善注释

编码不是编程。编程除了编码之外,还包括很多要做的工作。实际编码之前,必须要有详细的思考、架构与设计,哪怕再简单的代码,也务必要三思并反复完善后再开始动手。动手的第一步也不是写实际代码,而是确定写哪些类、类有什么功能和接口,理清类与类之间的关系和交互,画出类图,每个类都要一层层的由粗到细写伪代码,反复斟酌和迭代,直到改无可改。之后再按照伪代码写实际代码。写完之后,及时进行单元测试、修改BUG,完善注释和文档,最后是模块集成、系统集成与黑盒测试。反复这个过程,而后开始下一轮编程。

要给类的设计、画类图、写伪代码、代码测试、完善注释和文档等不可或缺的步骤分配更多的时间。也只有这些步骤做到尽善尽美之后,实际编码才会更轻松。

编程是个无比繁琐、麻烦和劳神费力的工作,它以脑力活动(思考、设计和斟酌)为主,而不是动手编码这种体力活动为主。既然选择这个行业,就不要怕麻烦,不要忽视编程中的每个环节,不要想着短时间内就见到成效。慢慢来,不能急,持之以恒,滴水石穿,不断摸索,逐步累积。

1.1.1 声明与可见性

变量、常量、栈对象、堆对象、函数等共有3种声明方式,对应3种不同的内存分配与存储机制(对象的3种形态):

 普通声明:又称“自动变量”,分配栈内存,离开作用域时系统自动回收,所占内存空间视数据类型而定,由声明的名称指代。

 new声明:分配堆内存,系统无法自动回收,必须delete显式销毁并回收所分配的内存。该内存中的数据无名,可使用保存了其地址的指针变量来访问(指针变量也要占内存空间,大小与int相同)。

 static声明:分配静态内存,只初始化一次,整个程序的运行期内一直存在,这部分内存无法显式回收,数据有名字。

由此可知,一个C++程序在运行时所占据的内存空间分为3部分:

 栈空间(stack):存储自动变量和函数参数,程序运行期该空间的大小会发生变化。

 堆空间(heap):存储由new、malloc等运算符所分配的数据,运行期该空间的大小会发生变化。

 静态空间(static):存储程序的文本和静态数据,程序运行期该空间的大小固定不变。

new运算符与声明堆对象: MyType* ptrObj = new MyType();

该语句其实同时声明并分配了两个内存空间,一个是指针变量ptrObj,位于栈内存中,由系统自动回收;第二个是堆内存,该内存保存了一个类型为MyType的无名数据,该数据所使用的内存无法自动回收。赋值运算符使指针变量ptrObj保存了该数据的内存首字节地址并可自动确定该内存块大小。原理是:

 声明指针变量时,给出了它所指向的数据的类型,因此编译器可确定该数据的类型和大小。

 new运算符其实是函数重载,该函数的返回值为一个内存地址,即该数据在内存中的首字节地址。

由于new运算符为某个类型分配堆中数据后只能返回该数据的内存地址,而无法为其命名,因此,所有运行期建立的堆数据全部无名,要使用该数据,必须通过保存了其内存首地址的指针变量ptrObj来进行。要销毁和回收,则delete “指向它的指针”。这是C++语言的内部机制和硬性规定,也是指针技术的最本质原理。

变量或对象的可见性(链接性)与作用域密不可分,不使用extern关键字的默认情况下,它们只能被同一作用域内的函数所访问。可见性和变量的类型无关(变量的类型指全局变量,还是数据成员变量,一个全局变量的可见性仅限于文件作用域,默认为内部链接,但可以使用extern关键字使其具有外部链接属性,此时,其它编译单元可访问之,但该变量不得为static)。cpp文件中使用static关键字修饰的函数,仅具有该文件范围内的内部链接属性。

C++有4种作用域类型:局部作用域,函数作用域,文件作用域,类作用域。

1.1.2 内置数据类型

 字符型(char、unsigned char、wchar_t)。比如:’a’。字符型实为有效范围0~255的整数。

 整型(int、unsigned int、size_t、short、unsigned short、long、unsigned long、long long)。后缀u或U:无符号int。后缀l或L:long int。

 整型数据以0开头为8进制数值。整型数据以0x开头则为16进制数值。

 浮点型(float、double、long double)。比如:float f = 0.25f; double d = 1.50;

 浮点型数据后缀f或F强制为float型。后缀l或L强制为long double型。

 布尔型(bool)。true或false。比如:bool isSelected = false;

 常量字串(const char*):以’\0’为结束符。默认自动连接:”any” “one”相当于:”anyone”

 void类型:无类型,void*则为任意类型。

 内存地址类型(类型*) :指针变量,保存变量、对象或函数的内存地址。“*”前面无数据类型,后面是指针变量,相当于对该指针解引用,获取它“指向”的内存所存储的实际数据。“*”置于数据类型之后和变量名之前,相当于声明该类型的指针变量,此指针变量用来存储该类型的变量在内存中的地址。“&”前置于变量之前(前面无数据类型),相当于获取该变量的内存地址。“**”置于数据类型之后和变量名之前,相当于声明“指向”该类型的数组的指针,即“指向”指针的指针(大多数情况下是声明一个位于堆内存中的二维数组)。“&&”为C++ 11新增的右值引用,可获取临时变量或临时对象的值(内部机制为转移,而非复制)。

 以上类型的字节数和数值的有效范围。32位Windows和OSX系统下,常用内置类型的字节数和有效值范围(64位系统,字节数翻倍。指针变量所占字节,与int相同,指针的值可为nullptr):

1字节 bool:true或false(非零即真)

1字节 char:-128 ~ 127

1字节unsigned char:0 ~ 255 注意:非0~256

2字节 short:-32768 ~ 32767

2字节 unsigned short:0 ~ 65535 注意:非0~65536

4字节 int:-2,147,483,648 ~ 2,147,483,647(正负21.478亿)

4字节 unsigned int:0 ~ 4,294,967,295(42.949亿)

4字节 float:1.E-36 ~ 1.E+36(不存在unsigned无符号float)

8字节 double:1.E-303 ~ 1.E+303(不存在unsigned无符号double)

 编程时,经常需要对32位int和unsigned int的数值范围有个直观的认识。以毫秒为例,int类型的最大表示范围为:正负596.523小时(正负24.855天),unsigned int的最大表示范围:0~1193.046小时(49.710天)。

 浮点数的精度有限,永远不要尝试比较两个不确定精度的浮点数是否相等。double的精度为16位有效数字,float的精度为7位有效数字。所谓有效数字是指:从左边第一个不为0的数字开始,右边的数字均为有效数字(并不考虑小数点的位置)。关于float的精度,直观的理解见下表:

实际值 float所表示的值(精度为7位有效数字)
0.123456789 0.1234567XX (X意味着该值不确定,是随机的,下同)
0.000000123456789 0.0000001234567XX
1.23456789 1.234567XX
1234567.89 1234567.XX(此时整个小数位都是随机的)
表 1 1 float精度表

 不同类型的数值混合计算时,C++自动进行类型转换(隐式强转且向上转换,类型“小于”整型的数据进行运算时一律提升为整型)。可利用这一点简化代码,比如:

int i = 5;

float f = i + 0.25f; // i自动进行隐式类型提升,计算时按float对待

 避免向下强转(比如将float或double转换为int,无符号数据转为有符号数据),除非有意舍弃小数位的数据,或者冒数据溢出的风险。这么做时,要进行显式类型转换,比如:

double d = 21.25;

int i = int(d / 2) + 15; // 注意:d /2,是按double进行计算的

 两个整数相除,结果依然为整数,舍弃小数(整数相除,求商不求余)。这一点务必要谨记。

 如需得到两个整数相除的余数,采用%运算符,该运算求余不求商。前者小于后者时返回前者。

 目前流行的64位操作系统,int数据为8个字节,而不是32位机器上的4字节。

 计算机中,负数采用补码的形式来表示,内部原理为:逐位取反,而后加1。减法运算的原理:

a – b 实则:a + (~b + 1)

1.1.3 基本运算与表达式

 算术运算(加、减、乘、除、模),返回数值结果。整数的算术运算,结果依然为整数,舍弃小数。模运算只能是整数运算。不同类型的算术运算,计算之前,C++会向上提升参与计算的变量的类型。

 位运算(位与&、位或|、位异或^、位取反~、左移位<<、右移位>>),返回数值结果。

位与运算规则: 0 & 0 == 0; 0 & 1 == 0; 1 & 0 == 0; 1 & 1 == 1;

位或运算规则: 0 | 0 == 0; 0 | 1 == 1; 1 | 0 == 1; 1 | 1 == 1;

 位与运算常用于“位掩码”操作。对两个有效范围为0~65535的unsigned short计算时,位与和位或运算非常重要。用十六进制来表示时,有一个很有用的定律:位与运算:两者的高位字节和低位字节,哪组较小返回哪组。位或运算:两者的高位字节和低位字节,哪组较大返回哪组:
unsigned short a = 0xff00;
unsigned short b = 0x0111;
a & b == 0x0100; // 位与运算:返回高位字节和低位字节的较小者
a | b == 0xff11; // 位或运算,返回高位字节和低位字节的较大者

 左移位和右移位运算符可替代乘除运算符,特别是乘除2的n次幂时:

x << n; // 等同于:x * 2n x >> n; // 等同于:x / 2n

 算术运算与位运算均有复合运算符,比如+=、*=、/=、&=、<<=、%=、|=等等。

 关系运算(大于>、小于<、等于==、不等于!=、大于等于>=、小于等于<=),返回true或false。

 逻辑运算(与&&、非!、或||),返回true或false。

逻辑与运算规则: false && false == false; false && true == false;

true && false == false; true && true == true;

逻辑非运算规则: !true == false; !false == true;

逻辑或运算规则: false || flase == false; false || true == true;

true || false == true; true || true == true;

德摩根定理(逻辑表达式的转换法则,A、B为逻辑表达式或真假值):

原表达式 等价表达式
!A && !B !(A || B)
!A || !B !(A && B)
A && B !(!A || !B)
A || B !(!A && !B)
表 1 2 逻辑表达式的转换法则

 前置于变量或对象之前的一元运算符“&”为取地址,“*”为解引用(取值)。

 整数除运算,结果为整数(商),丢弃小数部分。如果除数小于被除数,结果永远为0。

 浮点数除运算,结果的精度受限。

 “%”模运算只能用于整数,结果为整数余数。如果模数小于被模数,结果永远为模数。

 表达式中的“,”运算符确保操作数的求值顺序为从左到右,其优先级最低。

 位运算符的优先级也较低,关系表达式或逻辑表达式中混合了位运算时,要注意使用圆括号。

 函数参数表中的逗号不同于逗号运算符,它并非运算符,只起分隔作用,逗号两侧的求值顺序并不确定。不同的编译器,参数的求值顺序不同。因此,要避免函数实参中的互相依赖。

 sizeof()可获取变量、对象或类型的大小(单位为char字节数。大多数平台下1byte = 8bit)。