C深度解剖.docx

上传人:b****0 文档编号:9259636 上传时间:2023-05-17 格式:DOCX 页数:94 大小:65.44KB
下载 相关 举报
C深度解剖.docx_第1页
第1页 / 共94页
C深度解剖.docx_第2页
第2页 / 共94页
C深度解剖.docx_第3页
第3页 / 共94页
C深度解剖.docx_第4页
第4页 / 共94页
C深度解剖.docx_第5页
第5页 / 共94页
C深度解剖.docx_第6页
第6页 / 共94页
C深度解剖.docx_第7页
第7页 / 共94页
C深度解剖.docx_第8页
第8页 / 共94页
C深度解剖.docx_第9页
第9页 / 共94页
C深度解剖.docx_第10页
第10页 / 共94页
C深度解剖.docx_第11页
第11页 / 共94页
C深度解剖.docx_第12页
第12页 / 共94页
C深度解剖.docx_第13页
第13页 / 共94页
C深度解剖.docx_第14页
第14页 / 共94页
C深度解剖.docx_第15页
第15页 / 共94页
C深度解剖.docx_第16页
第16页 / 共94页
C深度解剖.docx_第17页
第17页 / 共94页
C深度解剖.docx_第18页
第18页 / 共94页
C深度解剖.docx_第19页
第19页 / 共94页
C深度解剖.docx_第20页
第20页 / 共94页
亲,该文档总共94页,到这儿已超出免费预览范围,如果喜欢就下载吧!
下载资源
资源描述

C深度解剖.docx

《C深度解剖.docx》由会员分享,可在线阅读,更多相关《C深度解剖.docx(94页珍藏版)》请在冰点文库上搜索。

C深度解剖.docx

C深度解剖

第一章节

本内容由凯幽网【痴情流氓】制作,要更多请到手机网下载

第一章关键字

每次讲关键字之前,我总是问学生:

C语言有多少个关键字?

sizeof怎么用?

它是函数

吗?

有些学生不知道C语言有多少个关键字,大多数学生往往告诉我sizeof是函数,因为

它后面跟着一对括号。

当投影仪把这32个关键字投到幕布上时,很多学生表情惊讶。

有些

关键字从来没见过,有的惊讶C语言关键字竟有32个之多。

更有甚者,说大学老师告诉他

们sizeof是函数,没想到它居然是关键字!

由此可想而知,大学的计算机教育是多么失败!

表(1.1)C语言标准定义的32个关键字

关键字意义

auto声明自动变量,缺省时编译器一般默认为auto

int声明整型变量

double声明双精度变量

long声明长整型变量

char声明字符型变量

float声明浮点型变量

short声明短整型变量

signed声明有符号类型变量

unsigned声明无符号类型变量

struct声明结构体变量

union声明联合数据类型

enum声明枚举类型

static声明静态变量

switch用于开关语句

case开关语句分支

default开关语句中的“其他”分支

break跳出当前循环

register声明寄存器变量

const声明只读变量

volatile说明变量在程序执行中可被隐含地改变

typedef用以给数据类型取别名(当然还有其他作用)

下面的篇幅就一一讲解这些关键字。

但在讲解之前先明确两个概念:

什么是定义?

什么是声明?

它们有何区别?

举个例子:

A)inti;

B)externinti;(关于extern,后面解释)

哪个是定义?

哪个是声明?

或者都是定义或者都是声明?

我所教过的学生几乎没有一

人能回答上这个问题。

这个十分重要的概念在大学里从来没有被提起过!

什么是定义:

所谓的定义就是(编译器)创建一个对象,为这个对象分配一块内存并给它

取上一个名字,这个名字就是我们经常所说的变量名或对象名。

但注意,这个名字一旦和

这块内存匹配起来(可以想象是这个名字嫁给了这块空间,没有要彩礼啊。

^_^),它们就同

生共死,终生不离不弃。

并且这块内存的位置也不能被改变。

一个变量或对象在一定的区

域内(比如函数内,全局等)只能被定义一次,如果定义多次,编译器会提示你重复定义

同一个变量或对象。

什么是声明:

有两重含义,如下:

第一重含义:

告诉编译器,这个名字已经匹配到一块内存上了(伊人已嫁,吾将何去何

从?

何以解忧,唯有稀粥),下面的代码用到变量或对象是在别的地方定义的。

声明可以出

现多次。

第二重含义:

告诉编译器,我这个名字我先预定了,别的地方再也不能用它来作为变量

名或对象名。

比如你在图书馆自习室的某个座位上放了一本书,表明这个座位已经有人预

订,别人再也不允许使用这个座位。

其实这个时候你本人并没有坐在这个座位上。

这种声

明最典型的例子就是函数参数的声明,例如:

voidfun(inti,charc);

好,这样一解释,我们可以很清楚的判断:

A)是定义;B)是声明。

那他们的区别也很清晰了。

记住,定义声明最重要的区别:

定义创建了对象并为这个

extern声明变量是在其他文件正声明(也可以看做是引用变量)

return子程序返回语句(可以带参数,也可不带参数)

void声明函数无返回值或无参数,声明空类型指针

continue结束当前循环,开始下一轮循环

do循环语句的循环体

while循环语句的循环条件

if条件语句

else条件语句否定分支(与if连用)

for一种循环语句(可意会不可言传)

goto无条件跳转语句

sizeof计算对象所占内存空间大小

对象分配了内存,声明没有分配内存(一个抱伊人,一个喝稀粥。

^_^)。

1.1,最宽恒大量的关键字----auto

auto:

它很宽恒大量的,你就当它不存在吧。

编译器在默认的缺省情况下,所有变量

都是auto的。

1.2,最快的关键字----register

register:

这个关键字请求编译器尽可能的将变量存在CPU内部寄存器中而不是通过内

存寻址访问以提高效率。

注意是尽可能,不是绝对。

你想想,一个CPU的寄存器也就那么

几个或几十个,你要是定义了很多很多register变量,它累死也可能不能全部把这些变量放

入寄存器吧,轮也可能轮不到你。

1.2.1,皇帝身边的小太监----寄存器

不知道什么是寄存器?

那见过太监没有?

没有?

其实我也没有。

没见过不要紧,见过就

麻烦大了。

^_^,大家都看过古装戏,那些皇帝们要阅读奏章的时候,大臣总是先将奏章交

给皇帝旁边的小太监,小太监呢再交给皇帝同志处理。

这个小太监只是个中转站,并无别

的功能。

好,那我们再联想到我们的CPU。

CPU不就是我们的皇帝同志么?

大臣就相当于我们

的内存,数据从他这拿出来。

那小太监就是我们的寄存器了(这里先不考虑CPU的高速缓

存区)。

数据从内存里拿出来先放到寄存器,然后CPU再从寄存器里读取数据来处理,处理

完后同样把数据通过寄存器存放到内存里,CPU不直接和内存打交道。

这里要说明的一点

是:

小太监是主动的从大臣手里接过奏章,然后主动的交给皇帝同志,但寄存器没这么自觉,

它从不主动干什么事。

一个皇帝可能有好些小太监,那么一个CPU也可以有很多寄存器,

不同型号的CPU拥有寄存器的数量不一样。

为啥要这么麻烦啊?

速度!

就是因为速度。

寄存器其实就是一块一块小的存储空间,只

不过其存取速度要比内存快得多。

进水楼台先得月嘛,它离CPU很近,CPU一伸手就拿到

数据了,比在那么大的一块内存里去寻找某个地址上的数据是不是快多了?

那有人问既然

它速度那么快,那我们的内存硬盘都改成寄存器得了呗。

我要说的是:

你真有钱!

1.2.2,使用register修饰符的注意点

虽然寄存器的速度非常快,但是使用register修饰符也有些限制的:

register变量必须是

能被CPU寄存器所接受的类型。

意味着register变量必须是一个单个的值,并且其长度应小

于或等于整型的长度。

而且register变量可能不存放在内存中,所以不能用取址运算符“&”

来获取register变量的地址。

1.3,最名不符实的关键字----static

不要误以为关键字static很安静,其实它一点也不安静。

这个关键字在C语言里主要有

两个作用,C++对它进行了扩展。

1.3.1,修饰变量

第一个作用:

修饰变量。

变量又分为局部和全局变量,但它们都存在内存的静态区。

静态全局变量,作用域仅限于变量被定义的文件中,其他文件即使用extern声明也没法

使用他。

准确地说作用域是从定义之处开始,到文件结尾处结束,在定义之处前面的那些

代码行也不能使用它。

想要使用就得在前面再加extern***。

恶心吧?

要想不恶心,很简单,

直接在文件顶端定义不就得了。

静态局部变量,在函数体里面定义的,就只能在这个函数里用了,同一个文档中的其他

函数也用不了。

由于被static修饰的变量总是存在内存的静态区,所以即使这个函数运行结

束,这个静态变量的值还是不会被销毁,函数下次使用时仍然能用到这个值。

staticintj;

voidfun1(void)

{

staticinti=0;

i++;

}

voidfun2(void)

{

j=0;

j++;

}

intmain()

{

for(k=0;k<10;k++)

{

fun1();

fun2();

}

return0;

}

i和j的值分别是什么,为什么?

1.3.2,修饰函数

第二个作用:

修饰函数。

函数前加static使得函数成为静态函数。

但此处“static”的含义

不是指存储方式,而是指对函数的作用域仅局限于本文件(所以又称内部函数)。

使用内部函

数的好处是:

不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件

中的函数同名。

关键字static有着不寻常的历史。

起初,在C中引入关键字static是为了表示退出一个

块后仍然存在的局部变量。

随后,static在C中有了第二种含义:

用来表示不能被其它文件

访问的全局变量和函数。

为了避免引入新的关键字,所以仍使用static关键字来表示这第二

种含义。

当然,C++里对static赋予了第三个作用,这里先不讨论,有兴趣的可以找相关资料研

究。

1.4,基本数据类型----short、int、long、char、float、double

C语言包含的数据类型如下图所示:

C

基本类型

构造类型

指针类型

空类型void

数值类型

字符类型char

枚举类型enum

浮点型

单精度型float

双精度型double

短整型short

长整型long

整型int

数组

结构体struct

共用体union

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】当标识符由多个词组成时,每个词的第一个字母大写,其余全部小写。

比如:

intCurrentVal;

这样的名字看起来比较清晰,远比一长串字符好得多。

【规则1-4】尽量避免名字中出现数字编号,如Value1,Value2等,除非逻辑上的确需要编

号。

比如驱动开发时为管脚命名,非编号名字反而不好。

初学者总是喜欢用带编号的变量名或函数名,这样子看上去很简单方便,但其实是一颗

颗定时炸弹。

这个习惯初学者一定要改过来。

【规则1-5】对在多个文件之间共同使用的全局变量或函数要加范围限定符(建议使用模块名

(缩写)作为范围限定符)。

(GUI_,etc)

标识符的命名规则:

【规则1-6】标识符名分为两部分:

规范标识符前缀(后缀)+含义标识。

非全局变量可以

不用使用范围限定符前缀。

【规则1-7】作用域前缀命名规则。

【规则1-8】数据类型前缀命名规则。

No.标识符类型作用域前缀

1GlobalVariableg

2FileStaticVariable(native)n

3FunctionStaticVariablef

4AutoVariablea

5GlobalFunctiong

6StaticFunctionn

No.PrefixSuffixDataTypeExampleRemark

1btbitBitbtVariable;

2bbooleanbooleanbVariable;

3ccharcharcVariable;

4iintintiVariable;

5sshort[int]short[int]sVariable;

6llong[int]long[int]lVariable;

7uunsigned[int]unsigned[int]uiVariable;

8ddoubledoubledVariable;

9ffloatfloatfVariable;

【规则1-9】含义标识命名规则,变量命名使用名词性词组,函数命名使用动词性词组。

例如:

变量含义标识符构成:

目标词+动词(的过去分词)+[状语]+[目的地];

10ppointervoid*vpVariable;指针前缀

11vvoidvoidvVariable;

13stenumenumAstVariable;

14ststructstructAstVariable;

15stunionunionAstVariable;

16fpfunction

point

void(*fpGetModeFuncList_a[])(void)

17_aarrayofcharcVariable_a[TABLE_MAX];

18

_st

_pst

typedef

enum/struct/u

nion

typedefstructSM_EventOpt

{

unsignedchar

unsignedint

char

}SM_EventOpt_st,*SM_EventOpt_pst;

当自定义

结构数据

类型时使

用_st后

缀;

当自定义

结构数据

类型为指

针类型时

使用_pst

后缀;

No变量名目标词动词(的过去分词)状语目的地含义

1DataGotFromSDDataGotFromSD从SD中取

得的数据

2DataDeletedFromSDDataDeletedFromSD从SD中删

除的数据

N

o

变量名动词(一般现时)目标词状语目的地含义

1GetDataFromSDGetDataFromSD从SD中取

得数据

2DeleteDataFromSDDeleteDataFromSD从SD中删

除数据

函数含义标识符构成:

动词(一般现时)+目标词+[状语]+[目的地];

【规则1-10】程序中不得出现仅靠大小写区分的相似的标识符。

例如:

intx,X;变量x与X容易混淆

voidfoo(intx);函数foo与FOO容易混淆

voidFOO(floatx);

这里还有一个要特别注意的就是1(数字1)和l(小写字母l)之间,0(数字0)和o

(小写字母o)之间的区别。

这两对真是很难区分的,我曾经的一个同事就被这个问题折腾

了一次。

【规则1-11】一个函数名禁止被用于其它之处。

例如:

#include"c_standards.h"

voidfoo(intp_1)

{

intx=p_1;

}

voidstatic_p(void)

{

intfoo=1u;

}

【规则1-12】所有宏定义、枚举常数、只读变量全用大写字母命名,用下划线分割单词。

例如:

constintMAX_LENGTH=100;//这不是常量,而是一个只读变量,具体请往后看

#defineFILE_PATH“/usr/tmp”

【规则1-13】考虑到习惯性问题,局部变量中可采用通用的命名方式,仅限于n、i、j等作

为循环变量使用。

一定不要写出如下这样的代码:

intp;

chari;

intc;

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个关键字之一时,我们也可以

借助编译器确定它的身份。

看下面的例子:

inti=0;

A),sizeof(int);B),sizeof(i);C),sizeofint;D),sizeofi;

毫无疑问,32位系统下A),B)的值为4。

那C)的呢?

D)的呢?

在32位系统下,通过VisualC++6.0或任意一编译器调试,我们发现D)的结果也为4。

咦?

sizeof后面的括号呢?

没有括号居然也行,那想想,函数名后面没有括号行吗?

由此轻

易得出sizeof绝非函数。

好,再看C)。

编译器怎么怎么提示出错呢?

不是说sizeof是个关键字,其后面的括号

可以没有么?

那你想想sizeofint表示什么啊?

int前面加一个关键字?

类型扩展?

明显不

正确,我们可以在int前加unsigned,const等关键字但不能加sizeof。

好,记住:

sizeof在

计算变量所占空间大小时,括号可以省略,而计算类型(模子)大小时不能省略。

一般情况下,

咱也别偷这个懒,乖乖的写上括号,继续装作一个“函数”,做一个“披着函数皮的关键字”。

做我的关键字,让人家认为是函数去吧。

1.5.2,sizeof(int)*p表示什么意思?

sizeof(int)*p表示什么意思?

留几个问题(讲解指针与数组时会详细讲解),32位系统下:

int*p=NULL;

sizeof(p)的值是多少?

sizeof(*p)呢?

inta[100];

sizeof(a)的值是多少?

sizeof(a[100])呢?

//请尤其注意本例。

sizeof(&a)呢?

sizeof(&a[0])呢?

intb[100];

voidfun(intb[100])

{

sizeof(b);//sizeof(b)的值是多少?

}

1.4,signed、unsigned关键字

我们知道计算机底层只认识0、1.任何数据到了底层都会变计算转换成0、1.那负数怎么

存储呢?

肯定这个“-”号是无法存入内存的,怎么办?

很好办,做个标记。

把基本数据类

型的最高位腾出来,用来存符号,同时约定如下:

最高位如果是1,表明这个数是负数,其

值为除最高位以外的剩余位的值添上这个“-”号;如果最高位是0,表明这个数是正数,

其值为除最高位以外的剩余位的值。

这样的话,一个32位的signedint类型整数其值表示法范围为:

-~-1;8位的312312

char类型数其值表示的范围为-~-1。

一个32位的

unsignedint类型整数其值表示法7272

范围为:

0~-1;8位的char类型数其值表示的范围为0~-1。

同样我们的signed关

32282

键字也很宽恒大量,你也可以完全当它不存在,编译器缺省默认情况下数据为signed类型

的。

上面的解释很容易理解,下面就考虑一下这个问题:

intmain()

{

chara[1000];

inti;

for(i=0;i<1000;i++)

{

a[i]=-1-i;

}

printf("%d",strlen(a));

return0;

}

此题看上去真的很简单,但是却鲜有人答对。

答案是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),inti=-20;

unsignedj=10;

i+j的值为多少?

为什么?

3),下面的代码有什么问题?

unsignedi;

for(i=9;i>=0;i--)

{

printf("%u\n",i);

}

1.6,if、else组合

if语句很简单吧。

嗯,的确很简单。

那我们就简单的看下面几个简单的问题:

1.6.1,bool变量与“零值”进行比较

bool变量与“零值”进行比较的if语句怎么写?

boolbTestFlag=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。

VisualC++定义为1,而它的同胞兄弟VisualBasic

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 医药卫生

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2