《软件研究室培训》由会员分享,可在线阅读,更多相关《软件研究室培训(40页珍藏版)》请在金锄头文库上搜索。
1、C语言,主要内容,一、 学习编程语言的一些建议 二、 指针 三、 预处理器 四、 结构、联合和枚举 五、 sizeof 六、 参考书籍和资料,(1)多动手 学习编程唯一方法是编写调试程序。看再多的书,效果也比不上设计调试一个简单程序。 (2)养成良好的编程习惯 养成好习惯很难。所以良好的编程习惯要从一开始培养。 (3)注意细节 不应漏过任何一个技术细节。只有细节的日积月累,才能有技术上的提高。 (4)了解开发环境 在学习编程语言的同时,也需要了解编程语言的开发、使用环境。如硬件平台(51、MSP430、PC)、操作系统(uCOS、Linux、DOS)、集成开发环境(Keil C,Borland
2、 C)等。 (5)多看书、勤思考 很多国外的经典书籍是由拥有几十年经验的质深程序员编写的。这些书里最重要的是融入了作者多年的编程经验。多看书,再结合自身平时的积累,不断思考总结是提高编程技术最有效的办法。,第一章 学习编程语言的一些建议,第二章 指 针,2.1 指针的基本概念 2.1.1 指针是什么? 指针是一个变量,它的值是另外一个变量的地址。 例1,2.1.2 指针的类型 指针所存储的那个变量类型,就称为指针的类型。 例 2 有三个不同类型的指针: int I2, *pI = ,2.1.3 指针的三个要素 指针指向的地址(指针的内容); 指针指向的地址上的内容; 指针本身的地址。 例 3:
3、 int A, *pA, *ppA; pA = 在复杂的指针都可以通过下表来分析: &ppA ppA *ppA *ppA &pA pA *pA &A A,第二章 指 针,第二章 指 针,2.1.4 指针的大小(指针变量占用的内存空间) 与所用的CPU寻址空间大小和类型有关,而与指针类型无关。 8位CPU的指针长度为12个字节(51单片机的情况较为复杂,是13个字节); 16位CPU的指针长度为2个字节(如MSP430); 32位CPU的指针长度为4个字节(如Intel 80386)。 上面所述是通常情况,并不是全部符合。,第二章 指 针,2.1.5 指针的初始化 变量在没有赋值之前,其值不定的
4、。对于指针变量,可以表述为:指向不明。,一个指向不明的指针,是非常危险的!,因此,指针在使用前一定要初始化;在使用前一定要确定指针是非空的!,第二章 指 针,2.2 数组与指针 对于数组的两个概念: 1. C语言中只有一维数组,数组的大小必须在编译时作为一个常数确定下来。数组的元素可以是任何类型,甚至是数组,由此可以方便地得到多维数组; 2. 数组的任何操作,即使采用数组下标进行的运算都等于对应的指针运算。可以用指针行为替代数组下标的运算。 例4 : int a4, *p; p = a; /等价于 p =,但数组不同于指针: 数组名 a是指向数组起始位置的“常量”。 因此,不能对数组名进行赋值
5、操作。 例 5: int a4, *p; p = a; /正确 a = p; /错误 p+; /正确 a+; /错误,第二章 指 针,第二章 指 针,2.3 空指针与通用指针 (1). 空指针 是个特殊指针值,也是唯一对任何指针类型都合法的指针值。一个指针变量具有空指针值,表示它当时没指向有意义的东西,处于闲置状态。空指针值用0 表示,这个值绝不会是任何程序对象的地址。给一个指针赋值0 就表示要它不指向任何有意义的东西。为了提高程序的可读性,标准库定义了一个与0 等价的符号常量NULL,程序里可以写: p = NULL; /注意不要与空字符NUL混淆,NUL等价于0 或者: p = 0; 注意
6、: 在编程时,应该将处于闲置的指针赋为空指针; 在调用指针前一定要判断是否为空指针,只有在非空情况下才能调用。,(2).通用指针 通用指针可以指向任何类型的变量。通用指针的类型用(void *)表示,因此也称为void 指针。 下面的第三行定义了两个通用指针: int n, *p; double *q; void *gp1, *gp2; 可以直接把任何变量的地址赋给通用指针。 例如,有了上面定义,下面赋值是合法的: gp1 = (void *) ,第二章 指 针,第二章 指 针,2.4 函数指针 2.4.1 函数指针的定义 函数指针即指向函数地址的指针。利用该指针可以知道函数在内存中的位置。因
7、此也可以利用函数指针调用函数。 函数指针的定义方法: (* )() 例如: int (*func)(void ) 这里,func就是一个函数指针。 注意:int *func(void)和int (*func)(void)的区别 int *func(void); /这是返回一个整型指针的函数 int (*func)(void); /这是一个函数指针,2.4.2 函数指针的使用 例6 :假定有下面的函数声明 int ptr; int fn(int); int (*fp)(int); 指出下面的语句是否合法?,为什么?。 fp = fn; /正确,将函数fn的地址赋给fp fp = fn(5); /
8、错误,返回给fp的结果不是一个函数地址。 fp = &ptr; / 错误,ptr的地址不在程序代码区,两种数据类型不能转换。 从上面的例子可以看出: (1) 不能将普通变量的地址赋给函数指针; (2) 不能将函数的调用赋给函数指针 (3) 可以将函数名赋给一个函数指针,第二章 指 针,第二章 指 针,2.4.2 函数指针的用途 一旦函数可以通过指针被传递、被记录,这开启了许多应用,特别是下列三者: 多态 (polymorphism):指用一个名字定义不同的函数,这函数执行不同但又类似的操作,从而实现“一个接口,多种方法”。 多线程 (multithreading):将函数指针传进负责建立多线程
9、的 API 中:例如 Win32 的 CreateThread(.pF.)。 回调 (call-back):所谓的回调机制就是:当发生某事件时,自动呼叫某段程序代码。事件驱动 (event-driven) 的系统经常透过函数指针来实现回调机制,例如 Win32 的 WinProc 其实就是一种回调,用来处理窗口的讯息。,2.4.3 函数指针数组 例7 :在一个计算器的例子中,有如下一些语句: switch(oper) case ADD: result=add(op1,op2); break; case SUB: result=sub(op1,op2); break; . 对于一个复杂的计算器,
10、switch语句将非常长。我们可以用函数指针数组来完成。 double add(double,double); double sub(double,double); . double (*oper_func)(double,double)=add,sub,.; 第2个步骤是用下面语句替换前面整条switch语句: result=oper_funcoper(op1,op2); oper从数组中选择正确的函数指针,而函数调用操作符将执行这个函数。,第二章 指 针,第三章 预处理器,3.1 预处理器的作用 可以将预处理器看作为一个文本编辑器。它只对程序的文本起作用,而不会进行其他任何依赖于C语言语法
11、的工作。它负责在正式编译前将源程序进行转换,替换宏定义、删去无需编译的代码、插入包含的文件等等。 出于几个主要原因,预处理器为我们提供了一些简化的途径: 首先,可以通过改变一个数字并重新编译程序来改变一个特殊量的所有实例。 其次,可以定义一些东西,它们看起来象函数但没有函数调用所需的运行开销。例如,putchar()和getchar()通常实现为宏以避免对每一个字符的输入输出都要进行函数调用。,第三章 预处理器,3.2 #define 3.2.1 带参数的宏定义 当需要有一个程序块,看上去和用起来像函数一样,但是没有函数调用时的开销时,可以采用宏定义。宏定义可以象函数一样带参数,如: 例 8:
12、 #define Add(a,b) (a)+(b) 注意: (1) 括号的作用 预防由运算符优先级引起有关的问题。 例 9: #define Add(a,b) a+b Average = Add(Num1, Num2) / 2; 例中,运算式最终由预处理器展开为: Average = Num1+Num2 / 2;,(2) 不要忽视空格的作用 例10: #define f (x) (x)-1) Num = f(Num); 上面这个语句会被展开成什么,a还是b,为什么? a. Num = (Num)-1); b. Num = (x) (x)-1)(Num); (3) 宏不是类型定义 例11:#de
13、fine PWORD int * typedef PBYTE char *; PWORD pA, pB; /pB是什么类型? PBYTE pC, pD; /pD是什么类型?,第三章 预处理器,3.3 #include 和 #include “ ” 有什么区别? #include : 在使用时,编译器将会到指定的系统或标准头文件目录查找头文件。通常用于标准或系统提供的头文件。 #include “ ”: 在使用“ ”时,编译器将会到用户创建的项目文件所在目录查找头文件。通常用于程序自己的头文件。 例12: #include #include #include “disp.h” #include
14、“output.h”,第三章 预处理器,第三章 预处理器,3.4 条件编译 (1). 条件编译可以按照不同的编译条件,组织成不同的代码。 例13: #define MCS51 #ifdef MCS51 Baud = 9600; #elif MSP430 Baud = 4800; #elif PC104 Baud = 115200; #endif ,(2). 条件编译可用于调试。将用于调试的语句放入条件编译中。 例14: #define DEBUG TotalNum = a + b; #ifdef DEBUG printf(“The total num is: %d”,TotalNum); #e
15、ndif 当程序调试结束后,只需将 #define DEBUG一句取消,即可将程序中所有的 调试语句删去。,第三章 预处理器,(3). 在通用的模块中,可以通过条件编译实现功能的可裁减。 例14: #define TMR_SET_EN #ifdef TMR_SET_EN void TMR_Set(char tmr_init, char tmr_mod, char tmr_fnct) #endif 如果不需要TMR_Set功能,只需将 #define TMR_SET_EN语句删除即可。轻 松实现TMR模块的可裁减特性。,第三章 预处理器,(4). 在宏定义时,避免重复定义。 例15: #ifndef TRUE #define TRUE 1 #endif #ifndef FALSE #define FALSE 0 #endif,第三章 预处理器,第四章 结构、联合和枚举,4.1 结构体 结构是由若干(可不同类型的)数据项组合而成的复合数据对象,这些数据 项称为结构的成分或成员。 (1) 字段 C 语言的结构还提供了一种定义字段的机制,使人在需要时能把几个结构成 员压缩到一个基本