分类: C/C++
2011-08-18 20:58:43
《C 语言深度解剖》前言:如果本书上面的问题能真正明白 80%,作为一个应届毕业生,肯怕没有一家大公司会拒绝你。
第一章 关键字
什么是定义?什么是声明?
什么是定义:所谓的定义就是(编译器)创建一个对象,为这个对象分配一块内存并给它取上一个名字,这个名字就是我们经常所说的变量名或对象名。但注意,这个名字一旦和这块内存匹配起来,它们就同生共死,终生不离不弃。并且这块内存的位置也不能被改变。一个变量或对象在一定的区域内(比如函数内,全局等)只能被定义一次,如果定义多次,编译器会提示你重复定义同一个变量或对象。
什么是声明:有两重含义,如下:
第一重含义:告诉编译器,这个名字已经匹配到一块内存上了,下面的代码用到变量或对象是在别的地方定义的。声明可以出现多次。
第二重含义:告诉编译器,我这个名字我先预定了,别的地方再也不能用它来作为变量名或对象名。
定义声明最重要的区别:定义创建了对象并为这个对象分配了内存,声明没有分配内存
C语言标准定义的 32个关键字
关键字 意 义
auto 声明自动变量,缺省时编译器一般默认为 auto
int 声明整型变量
double 声明双精度变量
long 声明长整型变量
char 声明字符型变量
float 声明浮点型变量
short 声明短整型变量
这六个关键字代表 C 语言里的六种基本数据类型
在 32位的系统上 short内存大小是2 个byte;int内存大小是4个byte;long内存大小是4个byte;float内存大小是4个byte;double内存大小是 8 个byte;char内存大小是 1 个 byte。(注意这里指一般情况,可能不同的平台还会有所不同,具体平台可以用 sizeof关键字测试一下)
signed 声明有符号类型变量
unsigned 声明无符号类型变量
正负数:最高位如果是 1,表明这个数是负数。如果最高位是 0,表明这个数是正数
struct 声明结构体变量
不要认为结构体内不能放函数\
union 声明联合数据类型
union只配置一个足够大的空间以来容纳最大长度的数据成员
enum 声明枚举类型
成员都是常量,也就是我们平时所说的枚举常量(常量一般用大写)。enum变量类型还可以给其中的常量符号赋值,如果不赋值则会从被赋初值的那个常量开始依次加 1,如果都没有赋值,它们的值从 0开始依次递增 1
static 声明静态变量
第一个作用:修饰变量。
1、静态全局变量,作用域仅限于变量被定义的文件中,其他文件即使用 extern声明也没法使用他
2、静态局部变量,在函数体里面定义的,就只能在这个函数里用。由于是存在内存的静态区,所以即使这个函数运行结束,这个静态变量的值还是不会被销毁,函数下次使用时仍然能用到这个值。
第一个作用:修饰函数
函数前加 static使得函数成为静态函数。但此处“static”的含义不是指存储方式,而是指对函数的作用域仅局限于本文件(所以又称内部函数)。使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名。
switch 用于开关语句
case 开关语句分支
default 开关语句中的“其他”分支
break 跳出当前循环
如果分支很多……请用switch、case
case 后面只能是整型或字符型的常量或常量表达式【C语言中,字符常量的字面值是整型,表达式运算时,字符型也会自动提升为整型。这也就是说,在switch或case中写的表达式,其值是整型:】
register 声明寄存器变量
这个关键字请求编译器尽可能的将变量存在 CPU 内部寄存器中而不是通过内存寻址访问以提高效率。注意是尽可能,不是绝对
register变量必须是能被 CPU寄存器所接受的类型。意味着 register变量必须是一个单个的值,并且其长度应小于或等于整型的长度
const 声明只读变量
volatile 说明变量在程序执行中可被隐含地改变
用它修饰的变量表示可以被某些编译器未知的因素更改,比如操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。
typedef 用以给数据类型取别名(当然还有其他作用)
extern 声明变量是在其他文件正声明(也可以看做是引用变量)
return 子程序返回语句(可以带参数,也可不带参数)
void 声明函数无返回值或无参数,声明空类型指针
void* 任何类型的指针都可以直接赋值给它,无需进行强制类型转换
continue 结束当前循环,开始下一轮循环
do 循环语句的循环体
while 循环语句的循环条件
for 一种循环语句(可意会不可言传)
if 条件语句
else 条件语句否定分支(与 if 连用)
C 语言有这样的规定:else始终与同一括号内最近的未匹配的 if语句结合
goto 无条件跳转语句
goto 语句可以灵活跳转,如果不加限制,它的确会破坏结构化设计风格;其次,goto 语句经常带来错误或隐患。它可能跳过了变量的初始化、重要的计算等语句
sizeof 计算对象所占内存空间大小
sizeof是关键字不是函数。sizeof在计算变量所占空间大小时,括号可以省略,而计算类型(模子)大小时不能省略
第二章 符号
国际 C 语言乱码大赛(IOCCC)这是IOCCC 1988年获奖作品,作者是 IanPhillipps。
#include
main(t,_,a)char*a;{return!0<t?t<3?main(-79,-13,a+main(-87,1-_,
main(-86,0,a+1)+a)):1,t<_?main(t+1,_,a):3,main(-94,-27+t,a)&&t==2?_<13?
main(2,_+1,"%s%d%d\n"):9:16:t<0?t<-72?main(_,t,
"@n'+,#'/*{}w+/w#cdnr/+,{}r/*de}+,/*{*+,/w{%+,/w#q#n+,/#{l+,/n{n+,/+#n+,/#\
;#q#n+,/+k#;*+,/'r :'d*'3,}{w+Kw'K:'+}e#';dq#'l\
q#'+d'K#!/+k#;q#'r}eKK#}w'r}eKK{nl]'/#;#q#n'){)#}w'){){nl]'/+#n';d}rw' i;#\
){nl]!/n{n#';r{#w'r nc{nl]'/#{l,+'K {rw' iK{;[{nl]'/w#q#n'wknw'\
iwk{KK{nl]!/w{%'l##w#'i;:{nl]'/*{q#'ld;r'}{nlwb!/*de}'c \
;;{nl'-{}rw]'/+,}##'*}#nc,',#nw]'/+kd'+e}+;#'rdq#w!nr'/ ') }+}{rl#'{n'')# \
}'+}##(!!/")
:t<-50?_==*a?putchar(31[a]):main(-65,_,a+1):main((*a=='/')+t,_,a+1)
:0<t?main(2,2,"%s"):*a=='/'||main(0,main(-61,*a,
"!ek;dci@bK'(q)-[w]*%n+r3#l,{}:\nuwloca-O;m.vpbks,fxntdCeghiry"),a+1);}
接续符和转义符
C语言里以反斜杠(\)表示断行。编译器会将反斜杠剔除掉,跟在反斜杠后面的字符自动接续到前一行。但是注意:反斜杠之后不能有空格,反斜杠的下一行之前也不能有空格。
花括号
char a[10]= {“abcde”};[正确] char a[10]{ =“abcde”}; [错误]
用花括号是为了把一些语句或代码打个包包起来,使之形成一个整体,并与外界绝缘,
++、--操作符
inti =3;
(++i)+(++i)+(++i);
表达式的值为多少?15吗?16吗?18吗?其实对于这种情况,C语言标准并没有作出规定。有点编译器计算出来为 18,因为 i经过 3次自加后变为 6,然后3 个6 相加得18;而有的编译器计算出来为 16(比如 VisualC++6.0),先计算前两个i 的和,这时候i 自加两次,2 个 i的和为 10,然后再加上第三次自加的i 得 16。其实这些没有必要辩论,用到哪个编译器写句代码测试就行了。但不会计算出 15的结果来的。
贪心法
C 语言有这样一个规则:每一个符号应该包含尽可能多的字符。也就是说,编译器将程序分解成符号的方法是,从左到右一个一个字符地读入,如果该字符可能组成一个符号,那么再读入下一个字符,判断已经读入的两个字符组成的字符串是否可能是一个符号的组成部分;如果可能,继续读入下一个字符,重复上述判断,直到读入的字符组成的字符串已不再可能组成一个有意义的符号。这个处理的策略被称为“贪心法”。需要注意到是,除了字符串与字符常量,符号的中间不能嵌有空白(空格、制表符、换行符等)
按照这个规则可能很轻松的判断 a+++b表达式与 a+++b一致
第三章 预处理
ANSI 标准定义的 C 语言预处理指令:
另外 ANSI 标准 C还定义了如下几个宏:
_LINE_ 表示正在编译的文件的行号
_FILE_ 表示正在编译的文件的名字
_DATE_ 表示编译时刻的日期字符串,例如: "25 Dec 2007"
_TIME_ 表示编译时刻的时间字符串,例如: "12:30:55"
_STDC_ 判断该文件是不是定义成标准 C程序
如果编译器不是标准的,则可能仅支持以上宏的一部分,或根本不支持。当然编译器也有可能还提供其它预定义的宏名。注意:宏名的书写由标识符与两边各二条下划线构成。
第四章 指针和数组
三个问题:
A),什么是指针?
B),什么是数组?
C),数组和指针之间有什么样的关系?
指针
一个基本的数据类型(包括结构体等自定义类型)加上“*”号就构成了一个指针类型的模子。这个模子的大小是一定的,与“*”号前面的数据类型无关。“*”号前面的数据类型只是说明指针所指向的内存里存储的数据类型。所以,在32 位系统下,不管什么样的指针类型,其大小都为4byte。可以测试一下sizeof(void *)
如何将数值存储到指定的内存地址
int *p = (int *)0x12ff7c; 需要注意的是将地址0x12ff7c 赋值给指针变量p 的时候必须强制转换
左值和右值
简单而言,出现在赋值符“=”右边的就是右值,出现在赋值符“=”左边的就是左值
C 语言引入一个术语-----“可修改的左值”。意思就是,出现在赋值符左边的符号所代
表的地址上的内容一定是可以被修改的。换句话说,就是我们只能给非只读变量赋值
指针与数组
A),char *p = “abcdef”;
B),char a[] = “123456”;
例子A)定义了一个指针变量p,p 本身在栈上占4 个byte,p 里存储的是一块内存的首地址。这块内存在静态区,其空间大小为7 个byte,这块内存也没有名字。对这块内存的访问完全是匿名的访问
1)以指针的形式:*(p+4)。
2)以下标的形式:p[4]
以下标的形式访问在本质上与以指针的形式访问没有区别,只是写法上不同
对指针进行加1 操作,得到的是下一个元素的地址,而不是原有地址值直接加1。所以,一个类型为T 的指针的移动,以sizeof(T) 为移动单位
指针数组和数组指针
指针数组:首先它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身决定。它是“储存指针的数组”的简称。
数组指针:首先它是一个指针,它指向一个数组。在32 位系统下永远是占4 个字节,至于它指向的数组占多少字节,不知道。它是“指向数组的指针”的简称。
下面到底哪个是数组指针,哪个是指针数组呢:
A),int *p1[10];
B),int (*p2)[10];
这里需要明白一个符号之间的优先级问题。“[]”的优先级比“*”要高
P1是一个数组,其包含10 个指向int 类型数据的指针,即指针数组
p2 是一个指针,它指向一个包含10 个int 类型数据的数组,即数组指针
二级指针
char **p;
A),p = NULL;
B),char *p2; p = &p2;
给p 赋值没有问题,但怎么使用p 呢?这就需要我们前面多次提到的钥匙(“*”)。
第一步:根据p 这个变量,取出它里面存的地址。
第二步:找到这个地址所在的内存。
第三步:用钥匙打开这块内存,取出它里面的地址,*p 的值。
第四步:找到第二次取出的这个地址。
第五步:用钥匙打开这块内存,取出它里面的内容,这就是我们真正的数据,**p 的值。
我们在这里用了两次钥匙(“*”)才最终取出了真正的数据。也就是说要取出二级指针所真正指向的数据,需要使用两次两次钥匙(“*”)。
第五章 内存管理
栈、堆和静态区
对于程序员,一般来说,我们可以简单的理解为内存分为三个部分:静态区,栈,堆
静态区:保存自动全局变量和static 变量(包括static 全局和局部变量)。静态区的内容在总个程序的生命周期内都存在,由编译器在编译的时候分配。
栈:保存局部变量。栈上的内容只在函数的范围内存在,当函数运行结束,这些内容也会自动被销毁。其特点是效率高,但空间大小有限。
堆:由malloc 系列函数或new 操作符分配的内存。其生命周期由free 或delete 决定。在没有释放之前一直存在,直到程序结束。其特点是使用灵活,空间比较大,但容易出错。
内存泄漏
会产生泄漏的内存就是堆上的内存(这里不讨论资源或句柄等泄漏情况),也就是说由malloc 系列函数或new 操作符分配的内存。如果用完之后没有及时free 或delete,这块内存就无法释放,直到整个程序终止。
C语言深度解剖.pdf
C语言深度解剖学习笔记
Lzy 2011-8-18
欢迎转载!