C++沙海拾遗(一)

1. 关于一些基本概念的

  1. main()函数是所有程序都应该提供的一个默认的全局函数,所有C/C++程序都应该从main()函数开始执行。
  2. main并不是C++/C的关键字,理论上,它可以作为类、名字空间或者成员函数的名字。但请不要这么做。
  3. 一些程序框架如MFC,源码中没有main()函数并不意味着它不存在,实际上它们把main函数给隐藏起来了。
  4. main()函数原型已经形成标准了,请参看ISO/IEC14882:1998 3.6.1节。标准中描述main的函数原型为
int main(){/*...*/}

或者

int main(int argc,char *argv[]){/*...*/}
  1. 不要省略main的返回语句return。返回为0表示程序正常结束,返回任何非0值表示错误或非正常退出,返回值会被系统的exit()函数调用。
  2. main()不能重载,不能内联,不能定义为静态的,不能取地址,不能用户自己调用。
  3. main的参数不是程序启动时命令行参数,程序启动时的命令行参数由启动程序打包成字符串数组传递给main()的形参argv,而包括命令字(可执行文件的名称)在内的所有参数的个数被传递给形参argc。
  4. 程序中用户定义的标识符(函数、变量、类型及名字空间等)会被转换为内部名称,不同的系统转换规则不同,如C中的main内部名称为_main,C++中规则要复杂些,可能包含作用域名称,参数类型和个数等等,这么做是可以让同名的类函数、重载函数在C++/C环境内部避免了命名冲突。
  5. 内部名称没有统一规则,各家有各家的做法,所以不同的编译器编译后的东西连接时不会兼容。
  6. C++和C中的全局变量(extern或static的)是存放在静态数据区中的,程序进入main之前创建,main结束之后销毁,进入main前它们如果没有初值,系统会用默认的全局初始化器0对其进行初始化。
  7. 函数内的static局部变量和类的static数据成员也会被移到静态数据区,如果没有提供初值,也会默认初始化为0。自动变量的初始化必须由编程者完成,系统不会给默认值。
  8. 要区分初始化和赋值的区别,前者发生在对象(变量)创建的同时,后者是对象创建后进行的。
  9. 程序员有程序员责任,编译器有编译器的责任,编译器的责任包括:全局变量的初始化、数据类型隐式转换、类的隐含成员初始化等。程序员的责任包括:局部变量的初始化,强制类型转换、类的非静态成员初始化等,不要把程序员的责任推给编译器。
  10. 在一个编译单元(文件)中定义的全局变量的初值不要依赖定义于另一个编译单元的全局变量的初始值。因为编译器无法决定两个编译单元连接在一起时哪个全局变量会先进行初始化。
  11. 要区分“编译时”和“运行时”的区别。编译伪指令、类(型)定义、外部对象声明、函数原型、标识符、各种修饰符号(const,static等)及类成员访问说明符(public、private等)以及连接规范、调用规范是在编译时发挥作用,可执行程序中不存在这些。而容器越界访问、虚函数动态决议、函数动态连接、动态内存分配、异常处理和RTTI等则在运行时发挥作用。
  12. 每一个源代码文件(源文件即递归包含的所有头文件展开)就是一个最小的编译单元,可以独立编译而不需要知道其他编译单元的存在和编译结果。因此,如果两个编译单元都定义了同名的extern全局变量或全局函数,单独编译都能通过,但连接时会出错。

2. 关于基本数据类型的

  1. 每种数据类型都对应特定的字节数。如32位系统int为4字节,double为8字节。使用sizeof()可以确定数据类型在不同系统上的大小。
  2. 字节是内存编址的最小单位,C和C++语言支持对每个变元进行取址运算(&),前提是这个地址必须使有效的内存单元地址。
  3. 最小的对象(包括空对象)也至少会占据1字节的内存空间。
  4. C语言支持的基本类型包括int、long、float、double、char、void以及它们和signed、unsigned、*、&等的组合。C++中增加了bool为基本类型同时增加了两个内置的符号常量:TRUE和FALSE。
  5. void是”空“类型,大小无法确定,不存在void类型的对象,也不能对它用sizeof()运算符。
  6. 注意区分void类型指针和NULL指针的区别。NULL指针是可以赋给任何类型指针的值0。C中NULL的类型为void*,而在C++中就是整数0,这是因为在C++中允许从0到任何指针类型的隐式转换。
  7. C中int为默认类型,如果不明确指定函数形参类型或返回值类型,它们的类型为int。C++中不支持默认类型。
  8. 无论是C还是C++,请不要使用默认数据类型,一定要明确指出函数每一个形参的类型和返回值类型。

3. 关于类型转换的

  1. 本质上,C++/C不会直接对两个不同类型的操作数进行运算,如果操作数类型不同,编译器会试图运用隐式转换规则或按照用户要求的强制转换类型转换。类型转换并不是改变原来的类型和值,而是生成了新的临时变元,其类型为目标类型。
  2. 基本类型之间隐式转换:char-int,int-long,long-float,float-double
  3. 低级数据类型(占据字节数少的类型)的对象总是优先转换为能够容纳得下它的最大值的、占用内存最少的高级类型对象(占据字节数多的类型)。
  4. 可以直接将派生类对象隐式转换为基类对象,虽然会发生内存截断,但这种转换是安全的。
  5. C中允许任何非void类型指针和void类型指针之间进行直接的相互转换。但在C++中,可以把任何类型的指针直接派给void类型的指针,反过来不行,除非进行强制转换。
  6. 强制类型转换可能会导致一些安全问题,如内存扩张,内存截断等。基类和派生类之间的指针强制转换也同样存在安全隐患。
  7. 不可以把基类对象直接转换为派生类对象,无论是直接赋值还是强制转换。
  8. 基本类型的强制转换要区分值值的截断和内存截断的不同
  9. 如果坚持强制转换,必须同时确保内存访问的安全性和转换结果的安全性。
  10. 尽量使用显式的(即强制的)数据类型转换,可以让别人知道发生了什么事。
  11. 尽量避免违反编译器类型安全原则和数据保护原则的事,如有符号和无符号数之间的转换,或者把const对象的地址指派给给非const对象指针等。

4. 关于标识符

  1. 标识符是由字母、数字和下划线组成的字符序列,标识一个程序元素,如变量、函数、宏、类型名称等。
  2. 原则上,标识符长度任意,但C语言只取前31个字符为有效标识符,C++取前255个字符。
  3. 请避免使用前导“_”和“__”定义自己的标识符,系统会使用它定义一些内部名称或预定义的宏,如果你也用它,有命名冲突的风险。
  4. 请给标识符一个有意义的名字。如果是变量,最好能体现出它的值的类型。
  5. 长的标识符名字并不会增大可执行代码的体积,因此不要使用过于简单的名字,但也不要过长,应遵循“用最短的名字包含最多的信息量”这一原则。

5. 关于转义序列

  1. 转义序列是由“\”跟一个特定的转义字符组成,用来把一些具有特殊意义的字符,如%,?,“等进行输出或在字符串中处理。不用转义序列也可以用ASCII码实现同样的效果。
  2. "\n"表示换行,“\r”表示回车,它们的ASCII码不同,另外,换行一般用于文件,换行还用于输出,让终端从新行开始。而回车是键盘功能,用于输入控制,不能输出。记住:输出换行,输入回车。有些函数可以把回车自动转换为换行返回,如getchar()。
  3. 字符串中使用“\0OOO”或“\xHH”来引用ASCII码表中的任何一个字符。“OOO”和"HH"表示该字符的八进制数据和十六进制数据。

6. 关于运算符

  1. 运算符包括算术运算符、逻辑运算符、关系运算符和其他运算符。
  2. 运算符的优先级不用死记硬背,活用括号确定计算顺序。
  3. 条件运算符“?:”是C++/C中唯一的三元运算符。
  4. 单独对一个变量使用++,- -运算符时,前置版本和后置版本效果一样。只有在较复杂的表达式中,前置版本和后置版本才具有不同的效果。

7. 关于表达式

  1. 一个单独的标识符是一个表达式,由表达式和运算符按照语法规则构成的更加复杂的表达式也是表达式。任何表达式都是有值的。
  2. 不要把数学中的表达式和程序中的表达式混淆,a<b<c在数学上和在程序中的意义不同。
  3. 常量表达式全部由常量和运算符组成,编译器在编译时就进行求值。
  4. 有些东西在编译时就会求值,但不会分配内存空间,比如基本类型的字面常量、枚举常量、sizeof()、常量表达式等,这些东西也没有存储类型。但字符串常量、const常量(尤其是构造类型的const对象)都会分配运行时的内存空间。有特定的存储类型;
  5. 运算符“&&”的表达式中,要尽量把最有可能为false的子表达式放在左边;运算符“||”的表达式中,要尽量把最有可能为true的子表达式放在左边。因为C++/C对逻辑表达式的判断采取“突然死亡法”。这么做可以提高效率。
  6. 不要编写太复杂的复合表达式,不利于程序理解。所谓复合表达式就是一个表达式中既有算术的又有逻辑和关系的。

8. 关于程序基本控制结构

  1. 多层if/else嵌套结构中,要尽可能把为true概率较高的条件判断放在前面,有利于提高性能。
  2. 布尔值false可以隐性转换为整数0,true隐性转换为什么却没有标准,大多数环境会转换为1,但不排除有些软件会转换为-1,比如VB。所以不要直接将布尔变量与true、1、-1、0做比较。
  3. 布尔变量与零值比较请使用以下方法:
if(flag)		
if(!flag)
  1. 整型变量与零做比较时不要写成
if(value)		
if(!value)

虽然语法和执行效果没问题,但会让人误以为value是布尔变量。应该写成

if(value == 0)		
if(value != 0)
  1. 不能直接判断浮点数与 0是否相当,或者说,不能直接判断两个浮点数是否相等,而应该判断两个浮点数之差的绝对值是否小于一个足够小的量。
if(abs(x - y) <= EPSILON)				//x等于y时
if(abs(x - y) > EPSILON)				//x不等于y时
  1. 指针变量跟零值比较请使用下面这个方式:
if(p == NULL)		
if(p != NULL)

虽然下面这些写法也没有错误,程序也能正常运行,

if(p == 0)						//会让人误解p是整型变量		
if(p != 0)
if(p)								//会让人误解p是布尔变量		
if(!p)

但容易让人误解。
60. switch语句没有自动跳出功能,因此在每个case子句的结尾,请不要忘了加上break。还有不要忘了在最后那个default子句。即使程序真的不需要default处理,也请保留default:break;这句。不仅是出于程序清晰型和完整性的考虑,也可以防止别人看你的程序时误以为你忘了default处理。
61. 一个循环结构,如果循环次数是确定的,使用for结构,否则使用while结构,至于do/while结构…还是少用吧。
62. C和C++允许在for语句内声明(定义)变量,它的作用范围仅限该for语句块。不过,有些语言可能没有遵循这条规则,比如VC++,它会把这样声明的变量挪到for语句外,让变量的作用域与for语句一样,在for语句结束后仍有效。
63. for语句计数器如果从0开始,建议循环控制变量取值采用“前闭后开区间”的写法。另外,要防止出现“差1”错误。

for(int x = 0; x < N; x++)
{
......
}
  1. 对于利用for遍历多维数组的问题,由于C++/C语言对多维数组的存储采取的是“先行后列”的方式,因此遍历方法也尽量采取“先行后列”的方式。这种遍历方式的效率一定高于“先列后行”的遍历方式。
  2. 一定要慎用goto语句,每使用一次goto语句都有可能带来隐患。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值