C语言深度解析笔记

读《C语言深度解析》笔记   

2011-10-12

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 //用以给数据类型取别名
extern //声明变量是在其他文件正声明(也可以看做是引用变量)
return //子程序返回语句(可以带参数,也可不带参数)
void //声明函数无返回值或无参数,声明空类型指针
continue //结束当前循环,开始下一轮循环
do //循环语句的循环体
while //循环语句的循环条件
if //条件语句
else //条件语句否定分支(与if连用)
for //一种循环语句
goto //无条件跳转语句
sizeof //计算对象所占内存空间大小

2.定义与声明的区别?
定义创建了对象并为这个对象分配了内存
声明没有分配内存

3.关键字(register)
register:这个关键字请求编译器尽可能的将变量存在CPU内部寄存器中而不是通过内存寻址访问以提高效率。

数据从内存里拿出来先放到寄存器,然后CPU再从寄存器里读取数据处理,处理完后同样把数据通过寄存器存放到内存里,CPU不直接和内存打交道。

register变量必须是能被CPU寄存器所接受的类型。即register变量必须是一个单个的值,并且其长度应小于或等于整型的长度。而且register变量可能不存放在内存中,所以不能用取址运算符"&"来获取register变量的地址。

4.关键字(static)
//此关键字在C语言中有两个作用,C++对它进行了扩展

1.修饰变量
//变量又分为局部和全局变量,但它们都存在于内存的静态区。
//静态全局变量,作用域仅限于变量被定义的文件中,其他文件即使用extern声明也没法使用。
准确地说作用域是从定义之处开始,到文件结尾处结束,在定义之处前面的代码行也不可以使用。
//静态局部变量,在函数体里面定义的,就只能在这个函数里用,同一个文档中的其他函数也用不了。
由于被static修饰的变量松狮存在内存的静态区,所以即使这个函数运行结束,
这个静态变量的值还是不会被销毁,函数下次使用时仍然能用。

2.修饰函数
//函数前加static使得函数成为静态函数。
但此处static的含义不是指存储方式,而使指对函数的作用域仅局限于本文件(所以又称内部函数)。
使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其他文件的函数同名。

//在C中引入关键字static是为了表示退出一个块后仍然存在的局部变量。
//随后,static在C中有了第二种含义:用来表示不能被其他文件访问的全局变量和函数。
为了避免引入新的关键字,所以仍然使用static关键字来表示这第二种含义。

5.关键字(sizeof)
//sizeof是关键字不是函数
sizeof在计算变量所占空间大小时,括号可以省略,而计算类型大小时不能省略。
定义int i=0;则:
sizeof(int)//=4
sizeof(i)//=4
sizeof int//错误语句
sizeof i//=4

//sizeof (int)*p,表示sizeof((int)*p)

32位系统下,定义:
int *p=NULL;
int a[100];
那么:
sizeof(p)//=4
sizeof(*p)//=4
sizeof(a)//=400
sizeof(a[100])//=4
sizeof(&a)//=4
sizeof(&a[0])//=4

void fun(int a[100])
{
sizeof(a)//=4
}


2011-10-13

6.基本数据类型(short.int.long.char.float.double)
//C数据类型

{
//基本数据类型
{
//数值类型
{
//整型
{
短整型short
整型int
长整型long
}

//浮点型
{
单精度型float
双精度型double
}
}

字符类型char
}

//构造类型
{
数组
结构体struct
共用体union
枚举类型enum
}

指针类型

空类型void
}

7.关键字(signed、unsigned)
//32位的signed int类型整数其值表示范围为:-2^31~2^31-1
//8位的signed char类型其值表示范围为:-2^7~2^7-1
//32位的unsigned int类型整数其值表示范围为:0~2^32-1
//8位的unsigned char类型其值表示范围为:0~2^8-1

思考下面的strlen(a)?
int main()
{
char a[1000];
int i;
for(i=0;i<1000;i++)
{
a[i]=-1-i;
}
printf("%d",strlen(a));
return 0;
}
//for循环内,当i=0时,a[0]=-1。问题的关键在于-1在内存里如何存储。
在计算机系统中,数值一律用补码表示(存储)。
主要原因是使用补码,可以将符号位和其它位统一处理;
同时,减法也可以按加法来处理;
另外,两个用补码表示的数相加时,如果最高位(符号位)有进位,则进位被舍弃。
////整数的补码与其原码一致
////负数的补码:符号位为1,其余为该数绝对值的原码按位取反,然后整个数加1。

8.if、else组合
//与“零值”比较
bool bTestFlag=FALSE;
if(bTestFlag) or if(!bTestFlag);

int *p=NULL;
if(NULL==p) or if(NULL!=p)

//if语句注意事项
先处理正常情况,再处理异常情况

9.switch、case组合
//if、else一般表示两个分支或是嵌套表示少量的分支,但如果分支很多,则使用switch、case组合更合适

//使用switch、case规则
1.每个case语句结尾加break,否则多个分支会重叠
2.最后必须使用default分支

//case关键字后面的值有什么要求
case后面只能是整型或字符型的常量或常量表达式

//case语句的排列顺序
1.按字母或数字顺序排列各条case语句
2.把正常情况放在前面,而把异常情况放在后面
3.按执行频率排列case语句

//使用case语句其他事项
1.简化每种情况对应的操作
2.不要为了使用case语句而可以制造一个变量
3.把default子句只用于检查真正的默认情况

10.do、while、for关键字
//C语言的三种循环语句:while、do-while、for
while:先判断while后面括号里的值,如果为真则执行其后面的代码;否则不执行
do-while:先执行do后面的代码,然后再判断while后面括号里的值,如果为真,循环开始;否则,循环不开始。用法和while没有区别,但相对较少用。
for:可以很容易的控制循环次数,多用于事先知道循环次数的情况。

//循环语句的注意点
1.在多重循环中,尽量将最长的循环放在最内层,最短的循环放在最外层,以减少CPU跨切循环层的次数
2.for语句的循环控制变量的取值采用"半开半闭区间"写法
//半开半闭区间:for(n=0;n<10;n++)
//闭区间:for(n=0;n<=9;n++)
3.不能在for循环体内修改循环变量,防止循环失控
4.循环要尽可能的短,使代码清晰
5.循环嵌套控制在3层内

//break与continue的区别
break表示终止本层循环
continue表示终止本次循环

11.关键字(void)
void作用:
1.对函数返回的限定
2.对函数参数的限定

//void修饰函数返回值和参数
1.如果函数没有返回值,那么应声明为void类型。在C语言中,凡不加返回值类型限定的函数,就会被编译器作为返回整型值处理。
2.如果函数无参数,那么应声明其参数为void

//void指针
1.谨慎使用void指针类型。ANSI规定,不能对void指针进行算法操作。进行算法操作的指针必须是确定知道其指向数据类型大小的。也就是说必须知道内存目的地址的确切值。
2.如果函数的参数可以是任意类型指针,那么应该声明其参数为void *
//void *memcpy(void *dest, const void *src, size_t len);
//void *memset(void *buffer, int c, size_t num);

//void不能代表一个真实的变量
void的出现只是一种抽象的需要,如果正确地理解了面向对象中“抽象基类”的概念,也容易理解void数据类型。正如不能给抽象基类定义一个实例,我们也不能定义一个void变量。

12.关键字(return)
//return用来终止一个函数并返回其后面跟着的值
return (value); //此括号可以省略。但一般不省略,尤其在返回一个表达式的值时。

return可以返回什么?例子
char *Func(void)
{
char str[30];
...
return str;
}
str属于局部变量,位于栈内存中,在Func结束的时候被释放,所以返回str将导致错误。
//return语句不可返回指向栈内存的指针,因为该内存在函数体结束时被自动销毁。

13.关键字(const)
//关键字const也许该被替换为readonly
const修饰的是只读的变量,其值在编译时不能被使用,因为编译器在编译时不知道其存储的内容。
const推出的初始目的,正是为了取代预编译指令,消除它的缺点,同时继承它的优点。

//cosnt与define宏的区别
1.const修饰的只读变量,并且在定义的同时进行初始化
2.const可以节省空间,避免不必要的内存分配,同时提高效率
//编译器通常不为普通const只读变量分配存储空间,而使将它们保存在符号表中,
//这使得它成为一个编译器件的值,没有了存储与读内存的操作,使得它的效率也很高。
//例如:
// #define M 3 //宏常量
// const int N=5; //此时并未将N放入内存中
// int i=N; //此时为N分配内存,以后不再分配
// int I=M; //预编译期间进行宏替换,分配内存
// int j=N; //没有内存分配
// int J=M; //再次宏替换,又一次分配内存
const定义的只读变量从汇编的角度看,只是给出了对应的内存地址,而不是像define一样给出的立即数,
所以,const定义的只读变量在程序运行过程中只有一份拷贝(因为它是全局的只读变量,存放在静态区),
而define定义的宏常量在内存中有若干个拷贝。
define宏是在预编译阶段进行替换,而const修饰的只读变量是在编译的时候确定其值。
define宏没有类型,而const修饰的只读变量具有特定的类型。
3.修饰一般变量,一般变量是指简单类型的只读变量。
//这种制度变量在定义时,修饰符const可以用在类型说明符前,也可以用在类型说明符后。如:int const i=2 或 const int i=2;
4.修饰数组。如int const a[5]={1,2,3,4,5} 或 const int a[5]={1,2,3,4,5};
5.修饰指针
//const int *p; //p可变,p指向的对象不可变
//int const *p; //p可变,p指向的对象不可变
//int *const p; //p不可变,p指向的对象可变
//const int *const p; //指针p和p指向的对象都不可变
6.修饰函数的参数,当不希望这个参数值被函数体内意外改变时使用。
如:void Fun(const int i);告诉编译器i在函数体中不能改变,从而防止了使用者的一些误操作。
7.修饰函数的返回值,表示返回值不可被改变。
如:const int Fun(void);
8.在另一连接文件中引用const只读变量:
extern const int i; //正确的声明
extern const int j=10; //错误,只读变量的值不能改变,注意声明和定义的区别。

14.关键字(volatile)
volatile修饰的变量可以被某些编译器未知的因素更改,比如操作系统,硬件或者其他线程等。
遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。
例如:
int i=10;
int j=i;
int k=i;
这时候编译器对代码进行优化,因为在后两条语句中,i没有被用作左值。
这时候编译器认为i的值没有发生改变,所以从内存中取出i赋给j之后,这个值并没有被丢掉,而是继续用这个值给k赋值。
编译器不会生成处汇编代码重新从内存里取i值,这样提高了效率。但是要注意在多次赋值中间i没有被用作左值。

例子:
volatile int i=10;
int j=i;
int k=i;
volatile告诉编译器i是随时可能发生变化的,每次使用它的时候必须从内存中取出i的值,因而编译器生成的汇编代码会重新从i的地址处读取数据放在k中。

so。如果i是一个寄存器变量或者表示一个端口数据或者是多个线程的共享数据,就容易出错,所以说volatile可以保证对特殊地址的稳定访问。

15.关键字(extern)
extern可以修饰变量或者函数,以表示变量或者函数的定义在别的文件中。
从而提示编译器遇到此变量和函数时在其他模块中寻找其定义。

16.关键字(struct)
struct将一些相关联的数据打包成一个整体,方便使用。
//空结构体有多大?
结构体所占的内存大小是其成员所占内存之和。如:
sturct student
{

}stu;
sizeof(stu)=1,为什么?
假设结构体内只有一个char型的数据成员,那其大小为1byte。
也就是说非空结构体类型数据最少需要占一个字节的空间,而空结构体类型数据总不能比最小的非空结构体数据所占的空间小,
所以编译器为每个结构体类型数据至少预留1个byte的空间,则空结构体的大小就定位1个byte。

//柔性数组
C99中,结构中的最后一个元素允许是未知大小的数组,这就叫做柔性数组成员,但结构中的柔性数组成员前面必须至少一个其他成员。
柔性数组允许结构中包含一个大小可变的数组。sizeof返回的这种结构大小不包括柔性数组的内存。
包含柔性数组成员的结构用malloc()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。

typedef struct st_type
{
int i;
int a[];
}type_a;
这样就定义一个可变长的结构体,用sizeof(type_a)得到的只有4,就是sizeof(i)=sizeof(int)。
那个0个元素的数组没有占有空间,而后可以进行变长操作。通过下面表达式给结构体分配内存:
type_a *p=(type_a*)malloc(sizeof(type_a)+100*sizeof(int));
这样为结构体指针p分配了一块内存。用p->item[n]就能简单地访问可变长元素。
但是这个时候再用sizeof(*p)测试结构体大小,发现仍然为4.
因为在定义结构体的时候,以及确定不包含柔性数组的内存大小。所以柔性数组其实与结构体没有关系,算不得结构体的正式成员。

//struct和class的区别
在C++里struct关键字与class关键字一般可以通用,只有一个小区别。
struct的成员默认情况下属性是public,而class是private

17.关键字(union)
//union与struct用法类似
union维护足够的空间来置放多个数据成员中的“一种”,而不是为每一个数据成员配置空间,在union中所有的数据成员共用一个空间,同一时间只能储存其中一个数据成员,所有的数据成员具有相同的其实地址。
union StateMachine
{
char character;
int number;
char *str;
double exp;
};
一个union只配置一个足够大的空间来容纳最大长度的数据成员,以上例而言,最大长度是double,所以StateMachine的空间大小就是double数据类型大小。

在C++里,union的成员默认属性也是public。
union主要用来压缩空间。如果一些数据不可能在同一时间同时被用到,则可以使用union。

//大小端模式对union类型数据的影响
union
{
int i;
char a[2];
}*p,u;
p=&u;
p->a[0]=0x39;
p->a[1]=0x38;
那么p.i=?
这里需要考虑存储模式:大端模式和小端模式
大端模式(Big_endian):字数据的高字节存储在低地址中,而字数据的低字节存放在高地址中。
小端模式(Little_endian):子数据的低字节存储在低地址中,而子数据的高字节存放在高地址中。
union型数据所占的空间等于其最大的成员所占的空间。对union型的成员的存取都是相对于该联合体基地址的偏移量为0处开始,也就是联合体的访问不论对那个变量的存取都是从union的首地址位置开始。



posted @ 2011-10-12 19:19  JK00  阅读(566)  评论(0编辑  收藏  举报