C语言深度剖析(第二部分)

社区服务
高级搜索
猴岛论坛电脑百科C语言深度剖析(第二部分)
发帖 回复
倒序阅读 最近浏览的帖子最近浏览的版块
2个回复

C语言深度剖析(第二部分)

楼层直达
猴岛︶ㄣㄨ林

ZxID:8787498

等级: 少尉
我去上学我没有时间玩的理想的天空我现在心是 痛苦的但我不想离
举报 只看楼主 使用道具 楼主   发表于: 2010-01-16 0
1.4.1,数据类型与“模子”
short、int、long、char、float、double 这六个关键字代表C 语言里的六种基本数据类型。
怎么去理解它们呢? 举个例子:见过藕煤球的那个东西吧?(没见过?煤球总见过吧)。那个
东西叫藕煤器,拿着它在和好的煤堆里这么一咔,一个煤球出来了。半径12cm,12 个孔。
不同型号的藕煤器咔出来的煤球大小不一样,孔数也不一样。这个藕煤器其实就是个模子。
现在我们联想一下,short、int、long、char、float、double 这六个东东是不是很像不同
类型的藕煤器啊?拿着它们在内存上咔咔咔,不同大小的内存就分配好了,当然别忘了给
它们取个好听的名字。在32 位的系统上short 咔出来的内存大小是2 个byte;int 咔出来的
内存大小是4 个byte;long 咔出来的内存大小是4 个byte;float 咔出来的内存大小是4 个byte;
double 咔出来的内存大小是8 个byte;char 咔出来的内存大小是1 个byte。(注意这里指一
般情况,可能不同的平台还会有所不同,具体平台可以用sizeof 关键字测试一下)
很简单吧?咔咔咔很爽吧?是很简单,也确实很爽,但问题就是你咔出来这么多内存块,
你总不能给他取名字叫做x1,x2,x3,x4,x5…或者长江1 号,长江2 号…吧。它们长得这么像(不
是你家的老大,老二,老三…),过一阵子你就会忘了到底哪个名字和哪个内存块匹配了(到
底谁嫁给谁了啊?^_^)。所以呢,给他们取一个好的名字绝对重要。下面我们就来研究研究
取什么样的名字好。
1.4.2,变量的命名规则
一般规则:
【规则1-1】命名应当直观且可以拼读,可望文知意,便于记忆和阅读。
标识符最好采用英文单词或其组合,不允许使用拼音。程序中的英文单词一般不要太复
杂,用词应当准确。
【规则1-2】命名的长度应当符合“min-length && max-information”原则。
C 是一种简洁的语言, 命名也应该是简洁的。例如变量名MaxVal 就比
MaxValueUntilOverflow 好用。标识符的长度一般不要过长,较长的单词可通过去掉“元音”
形成缩写。
另外,英文词尽量不缩写,特别是非常用专业名词,如果有缩写,在同一系统中对同一
单词必须使用相同的表示法,并且注明其意思。
【规则1-3】当标识符由多个词组成时,每个词的第一个字母大写,其余全部小写。比如:
int CurrentVal;
这样的名字看起来比较清晰,远比一长串字符好得多。
【规则1-4】尽量避免名字中出现数字编号,如Value1,Value2 等,除非逻辑上的确需要编
号。比如驱动开发时为管脚命名,非编号名字反而不好。
初学者总是喜欢用带编号的变量名或函数名,这样子看上去很简单方便,但其实是一颗
颗定时炸弹。这个习惯初学者一定要改过来。
【规则1-5】对在多个文件之间共同使用的全局变量或函数要加范围限定符(建议使用模块名
(缩写)作为范围限定符)。(GUI_ ,etc)
标识符的命名规则:
【规则1-6】标识符名分为两部分:规范标识符前缀(后缀) + 含义标识。非全局变量可以
不用使用范围限定符前缀。
【规则1-7】作用域前缀命名规则。
【规则1-8】数据类型前缀命名规则。
No. 标识符类型作用域前缀
1 Global Variable g
2 File Static Variable(native) n
3 Function Static Variable f
4 AutoVariable a
5 Global Function g
6 Static Function n
No. Prefix Suffix Data Type Example Remark
1 bt bit Bit btVariable;
2 b boolean boolean bVariable;
3 c char char cVariable;
4 i int int iVariable;
5 s short[int] short[int] sVariable;
6 l long[int] long[int] lVariable;
7 u unsigned[int] unsigned[int] uiVariable;
8 d double double dVariable;
9 f float float fVariable;
【规则1-9】含义标识命名规则,变量命名使用名词性词组,函数命名使用动词性词组。
例如:
变量含义标识符构成:目标词+ 动词(的过去分词)+ [状语] + [目的地];
10 p pointer void *vpVariable; 指针前缀
11 v void void vVariable;
13 st enum enumA stVariable;
14 st struct struct A stVariable;
15 st union union A stVariable;
16 fp function
point
void(* fpGetModeFuncList_a[])( void )
17 _a array of char cVariable_a[TABLE_MAX];
18
_st
_pst
typedef
enum/struct/u
nion
typedef struct SM_EventOpt
{
unsigned char
unsigned int
char
}SM_EventOpt_st,*SM_EventOpt_pst;
当自定义
结构数据
类型时使
用_st 后
缀;
当自定义
结构数据
类型为指
针类型时
使用_pst
后缀;
No 变量名目标词动词(的过去分词) 状语目的地含义
1 DataGotFromSD Data Got From SD 从SD 中取
得的数据
2 DataDeletedFromSD Data Deleted From SD 从SD 中删
除的数据
N
o
变量名动词(一般现时) 目标词状语目的地含义
1 GetDataFromSD Get Data From SD 从SD 中取
得数据
2 DeleteDataFromSD Delete Data From SD 从SD 中删
除数据
函数含义标识符构成:动词(一般现时)+目标词+[状语]+[目的地];
【规则1-10】程序中不得出现仅靠大小写区分的相似的标识符。
例如:int x, X; 变量x 与X 容易混淆
void foo(int x); 函数foo 与FOO 容易混淆
void FOO(float x);
这里还有一个要特别注意的就是1(数字1)和l(小写字母l)之间,0(数字0)和o
(小写字母o)之间的区别。这两对真是很难区分的,我曾经的一个同事就被这个问题折腾
了一次。
【规则1-11】一个函数名禁止被用于其它之处。
例如:
#include "c_standards.h"
void foo(int p_1)
{
int x = p_1;
}
void static_p(void)
{
int foo = 1u;
}
【规则1-12】所有宏定义、枚举常数、只读变量全用大写字母命名,用下划线分割单词。
例如:
const int MAX_LENGTH = 100; //这不是常量,而是一个只读变量,具体请往后看
#define FILE_PATH “/usr/tmp”
【规则1-13】考虑到习惯性问题,局部变量中可采用通用的命名方式,仅限于n、i、j 等作
为循环变量使用。
一定不要写出如下这样的代码:
int p;
char i;
int c;
char * a;
一般来说习惯上用n,m,i,j,k 等表示int 类型的变量;c,ch 等表示字符类型变量;a 等表
示数组;p 等表示指针。当然这仅仅是一般习惯,除了i,j,k 等可以用来表示循环变量外,别
的字符变量名尽量不要使用。
【规则1-14】定义变量的同时千万千万别忘了初始化。定义变量时编译器并不一定清空了
这块内存,它的值可能是无效的数据。
这个问题在内存管理那章有非常详细的讨论,请参看。
【规则1-15】不同类型数据之间的运算要注意精度扩展问题,一般低精度数据将向高精度
数据扩展
1.5,最冤枉的关键字----sizeof
1.5.1,常年被人误认为函数
sizeof 是关键字不是函数,其实就算不知道它是否为32 个关键字之一时,我们也可以
借助编译器确定它的身份。看下面的例子:
int i=0;
A),sizeof(int); B),sizeof(i); C),sizeof int; D),sizeof i;
毫无疑问,32 位系统下A),B)的值为4。那C)的呢?D)的呢?
在32 位系统下,通过Visual C++6.0 或任意一编译器调试,我们发现D)的结果也为4。
咦?sizeof 后面的括号呢?没有括号居然也行,那想想,函数名后面没有括号行吗?由此轻
易得出sizeof 绝非函数。
好,再看C)。编译器怎么怎么提示出错呢?不是说sizeof 是个关键字,其后面的括号
可以没有么?那你想想sizeof int 表示什么啊?int 前面加一个关键字?类型扩展?明显不
正确,我们可以在int 前加unsigned,const 等关键字但不能加sizeof。好,记住:sizeof 在
计算变量所占空间大小时,括号可以省略,而计算类型(模子)大小时不能省略。一般情况下,
咱也别偷这个懒,乖乖的写上括号,继续装作一个“函数”,做一个“披着函数皮的关键字”。
做我的关键字,让人家认为是函数去吧。
1.5.2,sizeof(int)*p 表示什么意思?
sizeof(int)*p 表示什么意思?
留几个问题(讲解指针与数组时会详细讲解),32 位系统下:
int *p = NULL;
sizeof(p)的值是多少?
sizeof(*p)呢?
int a[100];
sizeof (a) 的值是多少?
sizeof(a[100])呢?//请尤其注意本例。
sizeof(&a)呢?
sizeof(&a[0])呢?
int b[100];
void fun(int b[100])
{
sizeof(b);// sizeof (b) 的值是多少?
}
1.4,signed、unsigned 关键字
我们知道计算机底层只认识0、1.任何数据到了底层都会变计算转换成0、1.那负数怎么
存储呢?肯定这个“-”号是无法存入内存的,怎么办?很好办,做个标记。把基本数据类
型的最高位腾出来,用来存符号,同时约定如下:最高位如果是1,表明这个数是负数,其
值为除最高位以外的剩余位的值添上这个“-”号;如果最高位是0,表明这个数是正数,
其值为除最高位以外的剩余位的值。
这样的话,一个32位的signed int类型整数其值表示法范围为:- 231~231 -1;8 位的
char类型数其值表示的范围为- 27~27 -1。一个32位的unsigned int类型整数其值表示法
范围为:0~ 232 -1;8位的char类型数其值表示的范围为0~28 -1。同样我们的signed 关
键字也很宽恒大量,你也可以完全当它不存在,编译器缺省默认情况下数据为signed 类型
的。
上面的解释很容易理解,下面就考虑一下这个问题:
intmain()
{
char a[1000];
int i;
for(i=0; i<1000; i++)
{
a = -1-i;
}
printf("%d",strlen(a));
return 0;
}
此题看上去真的很简单,但是却鲜有人答对。答案是255。别惊讶,我们先分析分析。
for 循环内,当i 的值为0 时,a[0]的值为-1。关键就是-1 在内存里面如何存储。
我们知道在计算机系统中,数值一律用补码来表示(存储)。主要原因是使用补码,可
以将符号位和其它位统一处理;同时,减法也可按加法来处理。另外,两个用补码表示的数
相加时,如果最高位(符号位)有进位,则进位被舍弃。正数的补码与其原码一致;负数的
补码:符号位为1,其余位为该数绝对值的原码按位取反,然后整个数加1。
按照负数补码的规则,可以知道-1 的补码为0xff,-2 的补码为0xfe……当i 的值为127
时,a[127]的值为-128,而-128 是char 类型数据能表示的最小的负数。当i 继续增加,a[128]
的值肯定不能是-129。因为这时候发生了溢出,-129 需要9 位才能存储下来,而char 类型
数据只有8 位,所以最高位被丢弃。剩下的8 位是原来9 位补码的低8 位的值,即0x7f。
当i 继续增加到255 的时候,-256 的补码的低8 位为0。然后当i 增加到256 时,-257 的补
码的低8 位全为1,即低八位的补码为0xff,如此又开始一轮新的循环……
按照上面的分析,a[0]到a[254]里面的值都不为0,而a[255]的值为0。strlen 函数是计
算字符串长度的,并不包含字符串最后的‘\0’。而判断一个字符串是否结束的标志就是看
是否遇到‘\0’。如果遇到‘\0’,则认为本字符串结束。
分析到这里,strlen(a)的值为255 应该完全能理解了。这个问题的关键就是要明白char
类型默认情况下是有符号的,其表示的值的范围为[-128,127],超出这个范围的值会产生溢
出。另外还要清楚的就是负数的补码怎么表示。弄明白了这两点,这个问题其实就很简单了。
留三个问题:
1),按照我们上面的解释,那-0 和+0 在内存里面分别怎么存储?
2),int i = -20;
unsigned j = 10;
i+j 的值为多少?为什么?
3), 下面的代码有什么问题?
unsigned i ;
for (i=9;i>=0;i--)
{
printf("%u\n",i);
}
1.6,if、else 组合
if 语句很简单吧。嗯,的确很简单。那我们就简单的看下面几个简单的问题:
1.6.1,bool 变量与“零值”进行比较
bool 变量与“零值”进行比较的if 语句怎么写?
bool bTestFlag = FALSE;//想想为什么一般初始化为FALSE 比较好?
A), if(bTestFlag == 0); if(bTestFlag == 1);
B), if(bTestFlag == TRUE); if(bTestFlag == FLASE);
C), if(bTestFlag); if(!bTestFlag);
哪一组或是那些组正确呢?我们来分析分析:
A)写法:bTestFlag 是什么?整型变量?如果要不是这个名字遵照了前面的命名规范,
肯怕很容易让人误会成整型变量。所以这种写法不好。
B)写法:FLASE 的值大家都知道,在编译器里被定义为0;但TRUE 的值呢?都是1
吗?很不幸,不都是1。Visual C++定义为1,而它的同胞兄弟Visual Basic 就把TRUE 定义
为-1.那很显然,这种写法也不好。
大家都知道if 语句是靠其后面的括号里的表达式的值来进行分支跳转的。表达式如果
为真,则执行if 语句后面紧跟的代码;否则不执行。那显然,本组的写法很好,既不会引
起误会,也不会由于TRUE 或FLASE 的不同定义值而出错。记住:以后写代码就得这样写。
1.6.2, float 变量与“零值”进行比较
float 变量与“零值”进行比较的if 语句怎么写?
float fTestVal = 0.0;
A), if(fTestVal == 0.0); if(fTestVal != 0.0);
B), if((fTestVal >= -EPSINON) && (fTestVal <= EPSINON)); //EPSINON 为定义好的
精度。
哪一组或是那些组正确呢?我们来分析分析:
float 和double 类型的数据都是有精度限制的,这样直接拿来与0.0 比,能正确吗?明显
不能,看例子:  的值四舍五入精确到小数点后10位为:3.1415926536,你拿它减去
0.00000000001 然后再四舍五入得到的结果是多少?你能说前后两个值一样吗?
EPSINON 为定义好的精度,如果一个数落在[0.0-EPSINON,0.0+EPSINON] 这个闭区间
内,我们认为在某个精度内它的值与零值相等;否则不相等。扩展一下,把0.0 替换为你想
比较的任何一个浮点数,那我们就可以比较任意两个浮点数的大小了,当然是在某个精度
内。
同样的也不要在很大的浮点数和很小的浮点数之间进行运算,比如:
10000000000.00 + 0.00000000001
这样计算后的结果可能会让你大吃一惊。
1.6.3,指针变量与“零值”进行比较
指针变量与“零值”进行比较的if 语句怎么写?
int* p = NULL;//定义指针一定要同时初始化,指针与数组那章会详细讲解。
A), if(p == 0); if(p != 0);
B), if(p); if(!p);
C) , if(NULL == p); if(NULL != p);
哪一组或是那些组正确呢?我们来分析分析:
A)写法:p 是整型变量?容易引起误会,不好。尽管NULL 的值和0 一样,但意义不同。
B)写法:p 是bool 型变量?容易引起误会,不好。
C)写法:这个写法才是正确的,但样子比较古怪。为什么要这么写呢?是怕漏写一个
“=”号:if(p = NULL),这个表达式编译器当然会认为是正确的,但却不是你要表达的意思。
所以,非常推荐这种写法。
1.6.4,else 到底与哪个if 配对呢?
else 常常与if 语句配对,但要注意书写规范,看下面例子:
if(0 == x)
if(0 == y) error();
else{
//program code
}
这个else 到底与谁匹配呢?让人迷糊,尤其是初学者。还好,C 语言有这样的规定:else
始终与同一括号内最近的未匹配的if 语句结合。虽然老手可以区分出来,但这样的代码谁
都会头疼的,任何时候都别偷这种懒。关于程序中的分界符‘{’和‘}’,建议如下:
【建议1-16】程序中的分界符‘{’和‘}’对齐风格如下:
注意下表中代码的缩进一般为4 个字符,但不要使用Tab 键,因为不同的编辑器Tab 键定义
的空格数量不一样,别的编辑器打开Tab 键缩进的代码可能会一片混乱。
提倡的的风格不提倡的风格
void Function(int x)
{
//program code
}
void Function(int x){
//program code
}
if (condition)
{
//program code
}
if (condition){
//program code
}else{
//program code
1.6.5,if 语句后面的分号
关于if-else 语句还有一个容易出错的地方就是与空语句的连用。看下面的例子:
if(NULL != p) ;
fun();
这里的fun()函数并不是在NULL != p 的时候被调用,而是任何时候都会被调用。问题就出
在if 语句后面的分号上。在C 语言中,分号预示着一条语句的结尾,但是并不是每条C 语
言语句都需要分号作为结束标志。if 语句的后面并不需要分号,但如果你不小心写了个分号,
编译器并不会提示出错。因为编译器会把这个分号解析成一条空语句。也就是上面的代码实
际等效于:
if(NULL != p)
{
else
{
//program code
}
}
或:
if (condition)
//program code
else
//program code
或:
if (width < height) dosomething();
for (initialization; condition; update)
{
//program code
}
for (initialization;condition; update){
//program code
}
while (condition)
{
//program code
}
while (condition){
//program code
}
do
{
//program code
}
while (condition);
do{
//program code
}while (condition);
;
}
fun();
这是初学者很容易犯的错误,往往不小心多写了个分号,导致结果与预想的相差很远。所
以建议在真正需要用空语句时写成这样:
NULL;
而不是单用一个分号。这就好比汇编语言里面的空指令,比如ARM 指令中的NOP 指令。
这样做可以明显的区分真正必须的空语句和不小心多写的分号。
1.6.6,使用if 语句的其他注意事项
【规则1-17】先处理正常情况,再处理异常情况。
在编写代码是,要使得正常情况的执行代码清晰,确认那些不常发生的异常情况处理
代码不会遮掩正常的执行路径。这样对于代码的可读性和性能都很重要。因为,if 语句总是
需要做判断,而正常情况一般比异常情况发生的概率更大(否则就应该把异常正常调过来
了),如果把执行概率更大的代码放到后面,也就意味着if 语句将进行多次无谓的比较。另
外,非常重要的一点是,把正常情况的处理放在if 后面,而不要放在else 后面。当然这也
符合把正常情况的处理放在前面的要求。
【规则1-18】确保if 和else 子句没有弄反。
这一点初学者也容易弄错,往往把本应该放在if 语句后面的代码和本应该放在else 语
句后面的代码弄反了。__

会继续发帖,望大家共同学习,共同进步!
本帖de评分: 1 条评分 DB +10
DB+10

谢谢分享

iamcoolmanliu

ZxID:8401501

等级: 准尉
爷就是哥爷只是个传说请迷恋爷
举报 只看该作者 沙发   发表于: 2010-01-16 0
顶你一下
嘎嘎
iamcoolmanliu

ZxID:8401501

等级: 准尉
爷就是哥爷只是个传说请迷恋爷
举报 只看该作者 板凳   发表于: 2010-01-16 0
顶一下 路过而已
嘎嘎
« 返回列表
发帖 回复