电脑爱好者,提供IT资讯信息及各类编程知识文章介绍,欢迎大家来本站学习电脑知识。 最近更新 | 联系我们 RSS订阅本站最新文章
电脑爱好者
站内搜索: 
当前位置:首页>> c语言>>[转]C语言笔记:

[转]C语言笔记

来源:kingwei | 2006-4-28 | (有5941人读过)

这篇是寒假的时候写的,本来设想是要写C的很多方面,结果开学以后就再没动过。

对于派生类型的介绍,个人认为还是可以一看的,参考了一些书籍。



1 概述
1.1 发展历史
C语言是在70年代初问世的。一九七八年由美国电话电报公司(AT&T)贝尔实验室正式发表了C语言。同时由B.W.Kernighan和D.M.Ritchit合著了著名的“THE C PROGRAMMING LANGUAGE”一书。通常简称为《K&R》,也有人称之为《K&R》标准。但是,在《K&R》中并没有定义一个完整的标准C语言,后来由美国国家标准学会在此基础上制定了一个C 语言标准,于一九八三年发表。通常称之为ANSI C。
C语言是一种结构化语言。它层次清晰,便于按模块化方式组织程序,易于调试和维护。由于C语言实现了对硬件的编程操作,因此C语言集高级语言和低级语言的功能于一体,因此也有人称它为中级语言。此外,C语言还具有效率高,可移植性强等特点。因此广泛地移植到了各类计算机上,从而形成了多种版本的C语言。
目前最流行的C语言有以下几种:
•Microsoft C 或称 MS C
•Borland Turbo C 或称 Turbo C
•AT&T C

1.2 程序结构举例
本文档中列举的代码均在Dev-C++中运行通过。
下面这个程序先重定向标准输入输出流,然后,从文件in.txt中读取数据,调用标准库函数qsort,升序排序后输出到文件out.txt。

#include <stdio.h> /* 包含头文件 */
#include <stdlib.h>

#define MAX_LENGTH 100 /* 宏定义 */

int length; /* 定义全局变量 */
int list[MAX_LENGTH];

int Comp(const void *p1, const void *p2) /* 定义函数体 */
{
int e1 = *((int *)p1); /* 定义局部变量 */
int e2 = *((int *)p2);
if (e1 > e2) /* 分支结构(if-else) */
return 1;
else if (e1 < e2)
return -1;
else
return 0; /* 向主调函数返回值 */
}

int main() /* 主函数 */
{
int i;

freopen("in.txt", "r", stdin); /* 顺序结构 */
freopen("out.txt", "w", stdout);

while (scanf("%d", &length) != EOF) /* 循环结构(while) */
{
for (i=0; i<length; i++) /* 循环(for)控制输入 */
scanf("%d", &list[i]);
qsort((void *)list, length, sizeof(int), Comp);
for (i=0; i<length; i++) /* 循环(for)控制输出 */
printf("%d ", list[i]);
printf("\n");
}

return 0; /* 进程向系统返回值 */
}

1.3 编译、连接
任何一种计算机语言都要从某种人们容易理解的形式(源代码)转化成计算机可以执行的形式(机器指令)。对于C语言程序,首先需要对源代码作预处理,预处理器(preprocessor)对预编译指令进行处理,生成的代码通常存放在一个中间文件中。然后使用编译器(compiler),将中间文件编译成为目标文件。最后经过连接器(linker),将目标模块和库进行连接,并添加启动模块,才能最终成为系统可以加载和执行的程序。
开发环境 编译(compile) 连接(link)
(工具) C源文件 --------------> 目标文件 ------------>最终文件

Windows *.c *.obj *.exe 可执行文件
(VC) *.lib 静态库文件
*.dll 动态库文件

Linux *.c *.o *.--- 可执行文件
(KDevelop) *.a 静态库文件
*.so 动态库文件

因此,将TC、VC等程序设计软件称为编译器是不恰当的,这些软件是集成了编辑、预处理、编译、连接、调试等工具的集成开发环境。以Dev-C++为例,它提供了一个窗口界面的编辑环境和一些配置选项,但内部编译器仍然是gcc/g++、内部调试工具是gdb.exe。

2 数据类型
C语言中,变量和函数有两个属性,数据类型和存储类别。

变量声明和定义的格式为:
[存储类别] 数据类型 变量名; 如:
extern unsigned char g_lumQuantTable[DCT_SIZE][DCT_SIZE];

函数声明的格式为:
[存储类别] 数据类型 函数名(形参列表...); 如:
extern void DCT_8x8(double dct[][DCT_SIZE], const double pic[][DCT_SIZE]);

函数定义的格式为:
[存储类别] 数据类型 函数名(形参列表...) {
...
}
如:
static void DCT_AAN(double dct[], const double pic[]) {
...
}

变量的数据类型有三大类:基本类型、派生类型和指针类型。
基本类型包括字节型(char)、整型(int)和浮点型(float/double)。
定义基本类型变量时,可以使用符号属性signed、unsigned(对于char、int),和长度属性short、long(对于int、double)对变量的取值区间和精度进行说明。

3 派生类型
包括数组、结构体(struct)、共用体(union)和枚举类型(enum)。其中前三种也合称为构造类型。
除数组类型以外,后三种类型在定义变量(实例)之前,均需要向编译器说明该类型的具体信息,struct和union需要说明组成成员,而enum则需要明确取值值域和枚举元素。

3.1 数组类型
是一种简单聚合构造方式,在变量名后加“[ ]”即可。数组中每个元素可以是任何基本类型、派生类型、指针类型,甚至也可以是数组类型,这时定义的也就是多维数组。
例如:unsigned char g_compQuantTable[2][DCT_SIZE*DCT_SIZE];
在g_compQuantTable[DCT_SIZE*DCT_SIZE]的基础上,在数组名g_compQuantTable后再加“[2]”,就定义了一个二维数组,它由两个长度为DCT_SIZE*DCT_SIZE的unsigned char一维数组型元素组成。

3.2 结构体(struct)
结构是一种复杂聚合构造方式,通常用作:

(1) 将一般作为一个整体来使用的数据元素结合到一起,如:
typedef struct {
DWORD buffer;
BYTE ptr;
FILE *fp;
}StrmType;

(2) 从函数中返回多个数据元素,如利用输出参数回传:
int LoadBMPHeader(struct BMP_Header *image, const char *fileName) {
...
}
或者返回结构体实例:
TokenType Operate(TokenType a, TokenType theta, TokenType b) {
TokenType r;
...
return r;
}

(3) 构造链式数据结构,如二叉树节点的定义:
typedef struct HuffNode {
BYTE val;
struct HuffNode *left;
struct HuffNode *right;
}HuffNode, *HuffTree;

(4) 映射数据在硬件设备、网络链接和存储介质上的组织方式,如BMP文件头:
typedef struct tagBITMAPFILEHEADER {
WORD bfType;
DWORD bfSize;
WORD bfReserved1;
WORD bfReserved2;
DWORD bfOffBits;
} BITMAPFILEHEADER
结构内字段的次序依赖于所处的架构和所用的编译器,结构内各元素的表达也依赖于架构和操作系统。即使是基本类型的数据,比如整型,字节的存储方式也会有所不同。因此,这种应用天生不可移植。

(5) 实现抽象数据类型

(6) 以面向对象的方式编程
将数据元素和函数指针聚合成结构,模拟类的字段和方法,创建与类相仿的实体。由于“对象”可以使用不同的“方法”(函数指针)来初始化,并通过相同的界面来使用(通过同一结构成员名调用),这种技术实现了面向对象语言中的虚方法(virtual method)和多态编程(polymorphic programming)。

3.3 共用体(union)
与聚合构造不同,共用体是一种联合构造类型,多个成员共享同一存储区域。通常用于:

(1) 有效地利用存储空间

(2) 实现多态(polymorphism),例如表达式分析中记号(token)的定义:
typedef enum {
OPTR,FUNC,OPND
}TagType;
typedef union {
char optr;
FuncType func;
double opnd;
}DataType;
typedef struct {
TagType tag;
DataType data;
}TokenType;
每个记号只能是操作符、函数或操作数,利用标记成员进行区分和识别。
同一对象(结构)用于代表不同的类型,这些不同类型的数据都存储在独立的共用体成员中,这种方法实现了面向对象语言中的多态数据(polymorphic data)。

(3) 使用不同的内部表达方式对数据进行访问,例如为了方便地对WORD的每位进行访问而定义如下共用体:
typedef union {
WORD val;
struct {
WORD bit0: 1; WORD bit1: 1;
WORD bit2: 1; WORD bit3: 1;
WORD bit4: 1; WORD bit5: 1;
WORD bit6: 1; WORD bit7: 1;
WORD bit8: 1; WORD bit9: 1;
WORD bit10: 1; WORD bit11: 1;
WORD bit12: 1; WORD bit13: 1;
WORD bit14: 1; WORD bit15: 1;
}bits;
}WORD_REG_TYPE;

3.4 枚举类型(enum)
枚举类型不是一种构造类型, 因为它不能再分解为任何类型。因此,C语言教科书上将它归为基本数据类型。
从本质上来看,它可以算作是整型的一种变体。
这里暂且将它看作派生类型,是因为在定义枚举变量前,需要向编译器提供额外的信息,这点与结构体、共用体相似,而且类型声明的结构也极为相象。

4 指针类型
指针变量中存放的是内存地址,若该地址处存放的是变量,则称该指针为指向变量的指针;若该地址对应某函数的入口,则称该指针为指向函数的指针,简称函数指针。

4.1 指向变量的指针
在变量名之前添加“*”号,即可实现指针的定义。注意,这里说“在变量名之前”,而不是“在类型说明之后”,因为后一种做法容易引起误解。后一种做法看似是顺理成章的:类型说明之后加“*”号,构造了一种新的数据类型——指向该类型的指针类型。
但是,如果程序中出现这样的定义:
ArcNode* p, q;
那么,q是指针吗?回想一下数组的定义,如果程序中出现:
int zlen[DCT_SIZE*DCT_SIZE], cnt;
没有人会认为cnt也是数组。“*”和“[ ]”的作用对象都是紧邻它们的变量。那么为什么要将“*”紧靠在类型说明之后,故意造成误解?一个折中的办法是:在涉及指针的定义时,一行定义一个变量,于是上面的定义也就成了:
ArcNode* p;
ArcNode q;

还有两个容易混淆的概念,指针数组和指向数组的指针:
int *ptr;
ptr是指向整型的指针变量,在变量名后加“[ ]”,变为:
int *(ptr[MAX_LENGTH]); 或 int *ptr[MAX_LENGTH];
这时,ptr就成了长度为MAX_LENGTH指针数组,它的元素是指向整型的指针变量。
类似地:
int val[MAX_LENGTH];
val是整型数组,在变量名前后加“*”,变为:
int (*val)[MAX_LENGTH];
由于默认运算符结合优先级的关系,必须加上括号,以强调结合次序。
这时,val成了指向整型数组的指针。
这与前面所述“变量名后加‘[ ]’,原变量类型简单聚合,成数组;变量名前加‘*’,成指针,指向原变量类型。”的规则相符。

4.2 函数指针
函数指针是将函数参数化的工具,许多C库函数使用函数指针作为参数,如bsearch、qsort:
qsort((void *)list, length, sizeof(int), Comp);
函数名Comp标识了函数的入口地址,将它作为实际参数,传递给qsort的函数指针形式参数,该形参被定义为:
int (*fcmp)(const void *, const void *)
对比一下Comp函数的定义:
int Comp(const void *p1, const void *p2) {
...
}
qsort通过形参fcmp调用Comp函数对数组中的元素比较排序。

函数指针还可以参数化代码体内的控制。另外,利用函数指针数组,也可以创建基于表格驱动的应用程序。

4.3 指针的应用

(1) 构造链式数据结构,见结构体的应用(3)

(2) 引用动态分配的数据结构,如线性链表的构造:
void CreateList_L(LinkList *L, int n) {
LinkList p;
int i;
*L = (LinkList)malloc(sizeof(LNode));
(*L)->next = NULL;
for(i=n; i>0; --i) {
p = (LinkList)malloc(sizeof(LNode));
scanf("%d", &p->data);
p->next = (*L)->next;
(*L)->next = p;
}
}

(3) 实现引用调用(call by reference),见结构体的应用(2)

(4) 访问和迭代数据元素,如:
Status GetElem_L(LinkList L, int i, int *e) {
int j = 1;
LinkList p = L->next;
while(p!=NULL && j<i) {
p = p->next; ++j;
}
if(p==NULL || j>i) return ERROR;
*e = p->data;
return OK;
}

(5) 传递数组参数,如:
void MakeHuffTable(DWORD code[], BYTE size[], const BYTE bit[], const BYTE val[]){
...
}
code、size、bit和val虽然均以数组形式定义,用以接收数组实参,但其实是指针变量。

(6) 引用函数,例如二叉树遍历中,访问函数的调用:
Status PreOrderTraverse(BiTree T, Status (* Visit)(char)) {
if(T != NULL) {
if(Visit(T->data))
if(PreOrderTraverse(T->lchild, Visit))
if(PreOrderTraverse(T->rchild, Visit)) return OK;
return ERROR;
}
else return OK;
}

(7) 作为其它值的别名

(8) 代表字符串,如:
LPCTSTR lpszMenuName = "TestBMPMenu";

(9) 直接访问系统内存,以下是DOS环境中,访问显示存储器的代码片段:
#define MK_FP(seg, ofs) ((void far *)(((unsigned long)(seg)<<16)|(unsigned)(ofs)))
...
BYTE *p = MK_FP(0xa000, offset);
...
if (T_bpp == 1)
*p = g;
else
memcpy(p, &T_color[g], T_bpp);
DOS存储空间的布局为:
段地址 长度 用途
0000H 640K 基本内存
A000H 128K 映射显示存储器
C000H 256K BIOS ROM区
宏MK_FP用于按给定的段地址(16位)和段内偏移(16位)构造远端指针(32位,段地址<<16 + 段内偏移)。而实际存储位置的物理地址值为20位(段地址<<4 + 段内偏移)。8086/8088只有20根地址线,因此,最大寻址空间为1M。
此时,远指针p指向的内存区域没有被定义为变量,它直接访问了系统内存。

5 函数的数据类型
函数的数据类型也就是函数返回值的数据类型,原则上可以是除了数组类型以外的任何类型。当然,函数也可以没有返回值,这时函数的数据类型为void。


c语言热门文章排行
网站赞助商
购买此位置

 

关于我们 | 网站地图 | 文档一览 | 友情链接| 联系我们

Copyright © 2003-2022 电脑爱好者 版权所有 备案号:鲁ICP备09059398号