c深度剖析.docx

上传人:b****0 文档编号:9341728 上传时间:2023-05-18 格式:DOCX 页数:19 大小:23.95KB
下载 相关 举报
c深度剖析.docx_第1页
第1页 / 共19页
c深度剖析.docx_第2页
第2页 / 共19页
c深度剖析.docx_第3页
第3页 / 共19页
c深度剖析.docx_第4页
第4页 / 共19页
c深度剖析.docx_第5页
第5页 / 共19页
c深度剖析.docx_第6页
第6页 / 共19页
c深度剖析.docx_第7页
第7页 / 共19页
c深度剖析.docx_第8页
第8页 / 共19页
c深度剖析.docx_第9页
第9页 / 共19页
c深度剖析.docx_第10页
第10页 / 共19页
c深度剖析.docx_第11页
第11页 / 共19页
c深度剖析.docx_第12页
第12页 / 共19页
c深度剖析.docx_第13页
第13页 / 共19页
c深度剖析.docx_第14页
第14页 / 共19页
c深度剖析.docx_第15页
第15页 / 共19页
c深度剖析.docx_第16页
第16页 / 共19页
c深度剖析.docx_第17页
第17页 / 共19页
c深度剖析.docx_第18页
第18页 / 共19页
c深度剖析.docx_第19页
第19页 / 共19页
亲,该文档总共19页,全部预览完了,如果喜欢就下载吧!
下载资源
资源描述

c深度剖析.docx

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

c深度剖析.docx

c深度剖析

✧1.指针可以进行强制类型转换;

✧非指针类型必须要先取变量地址然后再类型转换最后取值

✧Sizeof(int)*p

✧Void*

 

✧在C语言中,可以给无参数的函数传送任意类型的参数,

✧但是在C++编译器中编译同样的代码则会出错。

在C++中,不能向无参数的函数传送任何

✧参数,出错提示“'fun':

functiondoesnottake1parameters”。

✧所以,无论在C还是C++中,若函数不接受任何参数,一定要指明参数为void。

 

✧按照ANSI(AmericanNationalStandardsInstitute)标准,不能对void指针进行算法操作,

✧即下列操作都是不合法的:

✧void*pvoid;

✧pvoid++;//ANSI:

错误

✧pvoid+=1;//ANSI:

错误

✧ANSI标准之所以这样认定,是因为它坚持:

进行算法操作的指针必须是确定知道其指

✧向数据类型大小的。

也就是说必须知道内存目的地址的确切值。

 

✧典型的如内存操作函数memcpy和memset的函数原型分别为:

✧void*memcpy(void*dest,constvoid*src,size_tlen);

✧void*memset(void*buffer,intc,size_tnum);

✧这样,任何类型的指针都可以传入memcpy和memset中,这也真实地体现了内存操作

✧函数的意义,因为它操作的对象仅仅是一片内存,而不论这片内存是什么类型。

 

✧void不能代表一个真实的变量。

✧因为定义变量时必须分配内存空间,定义void类型变量,编译器到底分配多大的内存呢。

✧return语句不可返回指向“栈内存”的“指针”,因为该内存在函数体结束时

✧被自动销毁。

 

✧const是constant的缩写,是恒定不变的意思,也翻译为常量、常数等。

很不幸,正是

✧因为这一点,很多人都认为被const修饰的值是常量。

这是不精确的,精确的说应该是只读

✧的变量,其值在编译时不能被使用,因为编译器在编译时不知道其存储的内容。

或许当初

✧这个关键字应该被替换为readonly

✧节省空间,避免不必要的内存分配,同时提高效率

✧编译器通常不为普通const只读变量分配存储空间,而是将它们保存在符号表中,这使

✧得它成为一个编译期间的值,没有了存储与读内存的操作,使得它的效率也很高。

 

✧//////////////////////////////////////////////////////////////////////////

✧#include

✧voidmain()

✧{

✧constinta=100;

✧intb[a];

✧b[0]=1;

✧printf("%d",b[0]);

✧}

✧//////////////////////////////////////////////////////////////////////////

✧C语言里面这样定义是错的

 

✧//////////////////////////////////////////////////////////////////////////

✧#include

✧voidmain()

✧{

 

✧constinta=0,b=1,c=2;

✧intn;

✧scanf("%d",&n);

✧switch(n)

✧{

✧casea:

printf("good");break;

✧caseb:

printf("well");break;

✧casec:

printf("bad");break;

✧default:

break;

✧}

✧printf("%d",b[0]);

✧}

✧//////////////////////////////////////////////////////////////////////////

✧同样是错的但是c++里面都是对的

 

✧最易变的关键字----volatile

✧volatile是易变的、不稳定的意思。

很多人根本就没见过这个关键字,不知道它的存在。

✧也有很多程序员知道它的存在,但从来没用过它。

我对它有种“杨家有女初长成,养在深闺

✧人未识”的感觉。

✧volatile关键字和const一样是一种类型修饰符,用它修饰的变量表示可以被某些编译器

✧未知的因素更改,比如操作系统、硬件或者其它线程等。

遇到这个关键字声明的变量,编

✧译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。

✧volatile

✧inti=10;

✧intj=i;//(3)语句

✧intk=i;//(4)语句

✧volatile关键字告诉编译器i是随时可能发生变化的,每次使用它的时候必须从内存中取出i

✧的值,因而编译器生成的汇编代码会重新从i的地址处读取数据放在k中。

 

struct关键字

struct是个神奇的关键字,它将一些相关联的数据打包成一个整体,方便使用。

在网络协议、通信控制、嵌入式系统、驱动开发等地方,我们经常要传送的不是简单

的字节流(char型数组),而是多种数据组合起来的一个整体,其表现形式是一个结构体。

经验不足的开发人员往往将所有需要传送的内容依顺序保存在char型数组中,通过指针偏

移的方法传送网络报文等信息。

这样做编程复杂,易出错,而且一旦控制方式及通信协议

有所变化,程序就要进行非常细致的修改,非常容易出错。

这个时候只需要一个结构体就

能搞定。

平时我们要求函数的参数尽量不多于4个,如果函数的参数多于4个使用起来非

常容易出错(包括每个参数的意义和顺序都容易弄错),效率也会降低(与具体CPU有关,ARM

芯片对于超过4个参数的处理就有讲究,具体请参考相关资料)。

这个时候,可以用结构体

压缩参数个数。

 

大小端问题:

windows是小段;unix是大端

 

每个枚举常量的列举是用逗号,非分好

enum_type_name是自定义的一种数据数据类型名,而enum_variable_name为

enum_type_name类型的一个变量,也就是我们平时常说的枚举变量。

enumenum_type_name

{

ENUM_CONST_1,

ENUM_CONST_2,

...

ENUM_CONST_n

}enum_variable_name;

下面再看看枚举与#define宏的区别:

1),#define宏常量是在预编译阶段进行简单替换。

枚举常量则是在编译的时候确定其值。

2),一般在编译器里,可以调试枚举常量,但是不能调试宏常量。

3),枚举可以一次定义大量相关的常量,而#define宏一次只能定义一个。

4),sizof(enum)大小为整形的字节数4;

 

注释:

不允许嵌套

y=x/*p

y=x/*p,这是表示x除以p指向的内存里的值,把结果赋值为y?

我们可以在编译器

上测试一下,编译器提示出错。

实际上,编译器把/*当作是一段注释的开始,把/*后面的内容都当作注释内容,直到出

现*/为止。

这个表达式其实只是表示把x的值赋给y,/*后面的内容都当作注释。

但是,由

于没有找到*/,所以提示出错。

我们可以把上面的表达式修改一下

 

汇编语言是以分号为始开始翻译的:

 

a.对于全局数据(全局变量、常量定义等)必须要加注释。

b.当代码比较长,特别是有多重嵌套时,应当在一些段落的结束处加注释,便于阅读。

c.注释的缩进要与代码的缩进一致。

d.注释代码段时应注重“为何做(why)”,而不是“怎么做(how)”。

e.数值的单位一定要注释。

f.对变量的范围给出注释。

g.对于函数的入口出口数据给出注释

 

左移和右移

左移运算符“<<”是双目运算符。

其功能把“<<”左边的运算数的各二进位全部左移若干

位,由“<<”右边的数指定移动的位数,高位丢弃,低位补0。

右移运算符“>>”是双目运算符。

其功能是把“>>”左边的运算数的各二进位全部右移若

干位,“>>”右边的数指定移动的位数。

但注意:

对于有符号数,在右移时,符号位将随同

移动。

当为正数时,最高位补0;而为负数时,符号位为1,最高位是补0或是补1取决

于编译系统的规定。

TurboC和很多系统规定为补1

0x01<<2+3;

结果为7吗?

测试一下。

结果为32?

别惊讶,32才是正确答案

 

加法优先级高于移位运算符

逻辑运算符》条件运算符》赋值运算符;

 

左移和右移的位数不能大于数据

的长度,不能小于

左移和右移的位数不能大于数据

的长度,不能小于0。

求余取模

最重要的一点,我们希望q*b+r==a,因为这是定义余数的关系。

2,如果我们改变a的正负号,我们希望q的符号也随之改变,但q的绝对值不会变。

3,当b>0时,我们希望保证r>=0且r

大多数编程语言选择了放弃第三条,而改为要求余数与

被除数的正负号相同。

这样性质1和性质2就可以得到满足。

大多数C语言编译器也都是

 

逗号运算符:

只能连接连个表达式,语句不可以!

 

3.1.3,用define宏定义注释符号?

不能定义注释符号

上面对define的使用都很简单,再看看下面的例子:

#defineBSC//

#defineBMC/*

#defineEMC*/

D),BSCmysingle-linecomment

E),BMCmymulti-linecommentEMC

D)和E)都错误,为什么呢?

因为注释先于预处理指令被处理,当这两行被展开成//…或

/*…*/时,注释已处理完毕,此时再出现//…或/*…*/自然错误.因此,试图用宏开始或结束一段

注释是不行的。

 

#undef

#undef是用来撤销宏定义的,用法如下:

#define

PI

3.141592654

//code

#undef

PI

 

3.4,#error预处理

#error预处理指令的作用是,编译程序时,只要遇到#error就会生成一个编译错误提

示消息,并停止编译。

其语法格式为:

#errorerror-message

注意,宏串error-message不用双引号包围。

遇到#error指令时,错误信息被显示,可能同时

还显示编译程序作者预先定义的其他内容。

关于系统所支持的error-message信息,请查找

相关资料,这里不浪费篇幅来做讨论。

3.5,#line预处理

#line的作用是改变当前行数和文件名称,它们是在编译程序中预先定义的标识符

命令的基本形式如下:

#linenumber["filename"]

其中[]内的文件名可以省略。

例如:

#line30a.h

其中,文件名a.h可以省略不写。

这条指令可以改变当前的行号和文件名,例如上面的这条预处理指令就可以改变当前的行号

为30,文件名是a.h。

初看起来似乎没有什么用,不过,他还是有点用的,那就是用在编译

器的编写中,我们知道编译器对C源码编译过程中会产生一些中间文件,通过这条指令,

可以保证文件名是固定的,不会被这些中间文件代替,有利于进行分析。

3.6,#pragma预处理

在所有的预处理指令中,#pragma指令可能是最复杂的了,它的作用是设定编译器的

状态或者是指示编译器完成一些特定的动作。

#pragma指令对每个编译器给出了一个方法,

在保持与C和C++语言完全兼容的情况下,给出主机或操作系统专有的特征。

依据定义,编译

指示是机器或操作系统专有的,且对于每个编译器都是不同的。

其格式一般为:

#pragmapara

其中para为参数,下面来看一些常用的参数。

3.6.1,#pragmamessage

message参数:

Message参数是我最喜欢的一个参数,它能够在编译信息输出窗

口中输出相应的信息,这对于源代码信息的控制是非常重要的。

其使用方法为:

#pragmamessage(“消息文本”)

当编译器遇到这条指令时就在编译输出窗口中将消息文本打印出来。

当我们在程序中定义了许多宏来控制源代码版本的时候,我们自己有可能都会忘记有没有

正确的设置这些宏,此时我们可以用这条指令在编译的时候就进行检查。

假设我们希望判

断自己有没有在源代码的什么地方定义了_X86这个宏可以用下面的方法

#ifdef_X86

#Pragmamessage(“_X86macroactivated!

”)

#endif

当我们定义了_X86这个宏以后,应用程序在编译时就会在编译输出窗口里显示“_

X86macroactivated!

”。

我们就不会因为不记得自己定义的一些特定的宏而抓耳挠腮了

3.6.2,#pragmacode_seg

另一个使用得比较多的pragma参数是code_seg。

格式如:

#pragmacode_seg(["section-name"[,"section-class"]])

它能够设置程序中函数代码存放的代码段,当我们开发驱动程序的时候就会使用到它。

3.6.3,#pragmaonce

#pragmaonce(比较常用)

只要在头文件的最开始加入这条指令就能够保证头文件被编译一次,这条指令实际上在

VisualC++6.0中就已经有了,但是考虑到兼容性并没有太多的使用它。

3.6.4,#pragmahdrstop

#pragmahdrstop表示预编译头文件到此为止,后面的头文件不进行预编译。

BCB可以

预编译头文件以加快链接的速度,但如果所有头文件都进行预编译又可能占太多磁盘空间,

所以使用这个选项排除一些头文件。

有时单元之间有依赖关系,比如单元A依赖单元B,所以单元B要先于单元A编译。

你可以用#pragmastartup指定编译优先级,如果使用了#pragmapackage(smart_init),BCB

就会根据优先级的大小先后编译。

3.6.5,#pragmaresource

#pragmaresource"*.dfm"表示把*.dfm文件中的资源加入工程。

*.dfm中包括窗体

外观的定义。

3.6.6,#pragmawarning

#pragmawarning(disable:

450734;once:

4385;error:

164)

等价于:

#pragmawarning(disable:

450734)//不显示4507和34号警告信息

#pragmawarning(once:

4385)

//4385号警告信息仅报告一次

#pragmawarning(error:

164)

//把164号警告信息作为一个错误。

同时这个pragmawarning也支持如下格式:

#pragmawarning(push[,n])

#pragmawarning(pop)

这里n代表一个警告等级(1---4)。

#pragmawarning(push)保存所有警告信息的现有的警告状态。

#pragmawarning(push,n)保存所有警告信息的现有的警告状态,并且把全局警告

等级设定为n。

#pragmawarning(pop)向栈中弹出最后一个警告信息,在入栈和出栈之间所作的

一切改动取消。

例如:

#pragmawarning(push)

#pragmawarning(disable:

4705)

#pragmawarning(disable:

4706)

#pragmawarning(disable:

4707)

//.......

#pragmawarning(pop)

在这段代码的最后,重新保存所有的警告信息(包括4705,4706和4707)。

3.6.7,#pragmacomment

#pragmacomment(...)

该指令将一个注释记录放入一个对象文件或可执行文件中。

常用的lib关键字,可以帮我们连入一个库文件。

比如:

#pragmacomment(lib,"user32.lib")

该指令用来将user32.lib库文件加入到本工程中。

linker:

将一个链接选项放入目标文件中,你可以使用这个指令来代替由命令行传入的或

者在开发环境中设置的链接选项,你可以指定/include选项来强制包含某个对象,例如:

#pragmacomment(linker,"/include:

__mySymbol")

3.6.8,#pragmapack

这里重点讨论内存对齐的问题和#pragmapack()的使用方法。

什么是内存对齐?

先看下面的结构:

structTestStruct1

{

charc1;

shorts;

charc2;

inti;

};

假设这个结构的成员在内存中是紧凑排列的,假设c1的地址是0,那么s的地址就应该

是1,c2的地址就是3,i的地址就是4。

也就是c1地址为00000000,s地址为00000001,c2

地址为00000003,i地址为00000004。

可是,我们在VisualC++6.0中写一个简单的程序:

structTestStruct1a;

printf("c1%p,s%p,c2%p,i%p\n",

(unsignedint)(void*)&a.c1-(unsignedint)(void*)&a,

(unsignedint)(void*)&a.s-(unsignedint)(void*)&a,

(unsignedint)(void*)&a.c2-(unsignedint)(void*)&a,

(unsignedint)(void*)&a.i-(unsignedint)(void*)&a);

运行,输出:

c100000000,s00000002,c200000004,i00000008。

为什么会这样?

这就是内存对齐而导致的问题。

 

#运算符

#也是预处理?

是的,你可以这么认为。

那怎么用它呢?

别急,先看下面例子:

#defineSQR(x)

printf("Thesquareof

x

is%d.\n",((x)*(x)));

如果这样使用宏:

SQR(8);

则输出为:

Thesquareof

x

is64.

注意到没有,引号中的字符x被当作普通文本来处理,而不是被当作一个可以被替换的语言

符号。

假如你确实希望在字符串中包含宏参数,那我们就可以使用“#”,它可以把语言符号转

化为字符串。

上面的例子改一改:

#defineSQR(x)

printf("Thesquareof

"#x"

is%d.\n",((x)*(x)));

//重点

Intmain()

{

inta[4]={1,2,3,4};

int*ptr1=(int*)(&a+1);

int*ptr2=(int*)((int)a+1);

printf("%x,%x",ptr1[-1],*ptr2);

return0;

}

无法把指针变量本身传递给一个函数

这很像孙悟空拔下一根猴毛变成自己的样子去忽悠小妖怪。

所以fun函数实际运行时,

用到的都是_p2这个变量而非p2本身。

如此,我们看下面的例子:

voidGetMemory(char*p,intnum)

{

p=(char*)malloc(num*sizeof(char));

}

intmain()

{

char*str=

NULL;

GetMemory(str,10);

strcpy(str,”hello”);

free(str);//free并没有起作用,内存泄漏

return0;

}

 

*(int*)&p----这是什么?

也许上面的例子过于简单,我们看看下面的例子:

voidFunction()

{

printf("Call

Function!

\n");

}

intmain()

{

void

(*p)();

*(int*)&p=(int)Function;

(*p)();

return0;

}

 

静态区:

保存自动全局变量和static变量(包括static全局和局部变量)。

静态区的内容

在总个程序的生命周期内都存在,由编译器在编译的时候分配。

栈:

保存局部变量。

栈上的内容只在函数的范围内存在,当函数运行结束,这些内容

也会自动被销毁。

其特点是效率高,但空间大小有限。

堆:

由malloc系列函数或new操作符分配的内存。

其生命周期由free或delete决定。

在没有释放之前一直存在,直到程序结束。

其特点是使用灵活,空间比较大,但容易出错

 

函数名与返回值类型在语义上不可冲突。

违反这条规则的典型代表就是C语言标准库函数getchar。

几乎没有一部名著没有提到

getchar函数,因为它实在太经典,太容易让人犯错误了。

所以,每一个有经验的作者都

会拿这个例子来警示他的读者,我这里也是如此:

charc;

c=getchar();

if(EOF==c)

{

}

按照getchar名字的意思,应该将变量c定义为char类型。

但是很不幸,getchar函数的

返回值却是int类型,其原型为:

intgetchar(void);

由于c是char类型的,取值范围是[-128,127],如果宏EOF的值在char的取值范围之外,

EOF的值将无法全部保存到c内,会发生截断,将EOF值的低8位保存到c里。

这样if语

句有可能总是失败。

这种潜在的危险,如果不是犯过一次错,肯怕很难发现。

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

当前位置:首页 > 农林牧渔 > 林学

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

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