VC3

来源:互联网 发布:java工程师要学多久 编辑:程序博客网 时间:2024/04/30 13:31

函数 - [C语言]
函数

C程序是由一组或是变量或是函数的外部对象组成的。 函数是一个自我包含的
完成一定相关功能的执行代码段。我们可以把函数看成一个"黑盒子", 你只要将数
据送进去就能得到结果, 而函数内部究竟是如何工作的的, 外部程序是不知道的。
外部程序所知道的仅限于输入给函数什么以及函数输出什么。函数提供了编制程序
的手段, 使之容易读、写、理解、排除错误、修改和维护。
C程序中函数的数目实际上是不限的, 如果说有什么限制的话, 那就是, 一个C
程序中必须至少有一个函数, 而且其中必须有一个并且仅有一个以main为名, 这个
函数称为主函数, 整个程序从这个主函数开始执行。
C 语言程序鼓励和提倡人们把一个大问题划分成一个个子问题, 对应于解决一
个子问题编制一个函数, 因此, C 语言程序一般是由大量的小函数而不是由少量大
函数构成的, 即所谓"小函数构成大程序"。这样的好处是让各部分相互充分独立,
并且任务单一。因而这些充分独立的小模块也可以作为一种固定规格的小"构件",
用来构成新的大程序。
C语言的一个主要特点是可以建立库函数。Turbo C2.0提供的运行程序库有400
多个函数, 每个函数都完成一定的功能, 可由用户随意调用。这些函数总的分为输
入输出函数、数学函数、字符串和内存函数、与BIOS和DOS有关的函数、 字符屏幕
和图形功能函数、过程控制函数、目录函数等。对这些库函数应熟悉其功能, 只有
这样才可省去很多不必要的工作。
本教程后半部分专门介绍Turbo C2.0的库函数, 并对每个函数都给出例程, 读
者可以将自已需要的部分以块的方式定义, 然后将此块写入文件, 这样就可以在进
入Turbo C2.0集成开发环境后, 直接调用此程序, 连接, 运行, 观察结果, 以加深
对该函数的理解。
用户编制Turbo C语言源程序, 就是利用Turbo C的库函数。可以把所有使用的
库函数放在一个庞大的主函数里, 也可以按不同功能设计成一个个用户函数而被其
它函数调用。Turbo C2.0建议用户使用后者, 当用户编制了一些较常用的函数时,
只要将其存在函数库里, 在以后的编程中可被方便的调用而不需要再去编译它们。
连接时将会自动从相应的库中装配成所需程序。

1. 函数的说明与定义
Turbo C2.0中所有函数与变量一样在使用之前必须说明。所谓说明是指说明函
数是什么类型的函数, 一般库函数的说明都包含在相应的头文件<*.h>中, 例如标
准输入输出函数包含在stdio.h中, 非标准输入输出函数包含在io.h中, 以后在使
用库函数时必须先知道该函数包含在什么样的头文件中, 在程序的开头用#include
<*.h>或#include"*.h"说明。只有这样程序在编译, 连接时Turbo C 才知道它是提
供的库函数, 否则, 将认为是用户自己编写的函数而不能装配。

1.1 函数说明

1. 经典方式
其形式为: 函数类型 函数名();
2. ANSI 规定方式
其形式为: 函数类型 函数名(数据类型 形式参数, 数据类型 形式
参数, ......);
其中: 函数类型是该函数返回值的数据类型, 可以是以前介绍的整型(int),
长整型(long), 字符型(char), 单浮点型(float), 双浮点型(double)以及无值型
(void), 也可以是指针, 包括结构指针。无值型表示函数没有返回值。
函数名为Turbo C2.0的标识符, 小括号中的内容为该函数的形式参数说明。可
以只有数据类型而没有形式参数, 也可以两者都有。对于经典的函数说明没有参数
信息。如:
int putlll(int x,int y,int z,int color,char *p)/*说明一个整型函数*/
char *name(void); /*说明一个字符串指什函数*/
void student(int n, char *str); /*说明一个不返回值的函数*/
float calculate(); /*说明一个浮点型函数*/
注意: 如果一个函数没有说明就被调用, 编译程序并不认为出错, 而将此函数
默认为整型(int)函数。因此当一个函数返回其它类型, 又没有事先说明, 编译时
将会出错。

1.2 函数定义

函数定义就是确定该函数完成什么功能以及怎么运行, 相当于其它语言的一个
子程序。Turbo C2.0对函数的定义采用ANSI规定的方式。即:
函数类型 函数名(数据类型形式参数; 数据类型 形式参数...)
{
函数体;
}
其中函数类型和形式参数的数据类型为Turbo C2.0的基本数据类型。函数体为
Turbo C2.0提供的库函数和语句以及其它用户自定义函数调用语句的组合, 并包括
在一对花括号"{"和"}"中。
需要指出的是一个程序必须有一个主函数, 其它用户定义的子函数可以是任意
多个, 这些函数的位置也没有什么限制, 可以在main()函数前, 也可以在其后。
Turbo C2.0将所有函数都被认为是全局性的。而且是外部的, 即可以被另一个文件
中的任何一个函数调用。

2 函数的调用

2.1 函数的简单调用
Turbo C2.0调用函数时直接使用函数名和实参的方法, 也就是将要赋给
Tag:
编程博客 发表于10:21:26 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-20
枚举 - [C语言]
枚 举(enum)

枚举是一个被命名的整型常数的集合, 枚举在日常生活中很常见。
例如表示星期的SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY,
SATURDAY, 就是一个枚举。
枚举的说明与结构和联合相似, 其形式为:
enum 枚举名{
标识符[=整型常数],
标识符[=整型常数],
...
标识符[=整型常数],
} 枚举变量;
如果枚举没有初始化, 即省掉"=整型常数"时, 则从第一个标识符开始, 顺
次赋给标识符0, 1, 2, ...。但当枚举中的某个成员赋值后, 其后的成员按依次
加1的规则确定其值。
例如下列枚举说明后, x1, x2, x3, x4的值分别为0, 1, 2, 3。
enum string{x1, x2, x3, x4}x;
当定义改变成:
enum string
{
x1,
x2=0,
x3=50,
x4,
}x;
则x1=0, x2=0, x3=50, x4=51
注意:
1. 枚举中每个成员(标识符)结束符是",", 不是";", 最后一个成员可省略
","。
2. 初始化时可以赋负数, 以后的标识符仍依次加1。
3. 枚举变量只能取枚举说明结构中的某个标识符常量。
例如:
enum string
{
x1=5,
x2,
x3,
x4,
};
enum strig x=x3;
此时, 枚举变量x实际上是7。

 

类 型 说 明

类型说明的格式为:
typedef 类型 定义名;
类型说明只定义了一个数据类型的新名字而不是定义一种新的数据类型。这
里类型是Turbo C许可的任何一种数据类型。定义名表示这个类型的新名字。
例如: 用下面语句定义整型数的新名字:
typedef int SIGNED_INT;
使用说明后, SIGNED_INT就成为int的同义词了, 此时可以用SIGNED_INT 定
义整型变量。
例如: SIGNED_INT i, j;(与int i, j等效)。
但 long SIGNED_INT i, j; 是非法的。
typedef同样可用来说明结构、联合以及枚举。
说明一个结构的格式为:
typedef struct{
数据类型 成员名;
数据类型 成员名;
...
} 结构名;
此时可直接用结构名定义结构变量了。例如:
typedef struct{
char name[8];
int class;
char subclass[6];
float math, phys, chem, engl, biol;
} student;
student Liuqi;
则Liuqi被定义为结构数组和结构指针。
在第二节讲过的文件操作中, 用到的FILE就是一个已被说明的结构, 其说明
如下:
typedef struct
{
short level;
unsigned flags;
char fd;
unsigned char hold;
short bsize;
unsigned char *buffer;
unsigned char *curp;
unsigned istemp;
short token;
} FILE
这个结构说明已包含在stdio.h中, 用户只要直接用FILE 定义文件指针变量
就可以。事实上, 引入类型说明的目的并非为了方便, 而是为了便于程序的移植。

 

预 处 理 指 令

由ANSI的标准规定, 预处理指令主要包括:
#define
#error
#if
#else
#elif
#endif
#ifdef
#ifndef
#undef
#line
#pragma
由上述指令可以看出, 每个预处理指令均带有符号"#"。下面只介绍一些常
用指令。
1. #define 指令
#define指令是一个宏定义指令, 定义的一般形式是:
#define 宏替换名字符串(或数值)
由#define指令定义后, 在程序中每次遇到该宏替换名时就用所定义的字符
串(或数值)代替它。
例如: 可用下面语句定义TRUE表示数值1, FALSE表示0。
#define TRUE 1
#define FALSE 0
一旦在源程序中使用了TRUE和FALSE, 编译时会自动的用1和0代替。
注意:
1. 在宏定义语名后没有";"
2. 在Turbo C程序中习惯上用大写字符作为宏替换名, 而且常放在程序开头。
3. 宏定义还有一个特点, 就是宏替换名可以带有形式参数, 在程序中用到
时, 实际参数会代替这些形式参数。
例如:
#define MAX(x, y) (x>y)?x:y
main()
{
int i=10, j=15;
printf("The Maxmum is %d", MAX(i, j);
}
上例宏定义语句的含义是用宏替换名MAX(x, y)代替x, y中较大者, 同样也
可定义:
#define MIN(x, y) (x<y)?x:y
表示用宏替换名MIN(x, y)代替x, y中较小者。

2. #error指令
该指令用于程序的调试, 当编译中遇到#error指令就停止编译。其一般形式
为:
#error 出错信息

Tag:
编程博客 发表于10:20:01 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-20
联合 - [C语言]
联 合(union)
1. 联合说明和联合变量定义
联合也是一种新的数据类型, 它是一种特殊形式的变量。
联合说明和联合变量定义与结构十分相似。其形式为:
union 联合名{
数据类型 成员名;
数据类型 成员名;
...
} 联合变量名;
联合表示几个变量公用一个内存位置, 在不同的时间保存不同的数据类型
和不同长度的变量。
下例表示说明一个联合a_bc:
union a_bc{
int i;
char mm;
};
再用已说明的联合可定义联合变量。
例如用上面说明的联合定义一个名为lgc的联合变量, 可写成:
union a_bc lgc;
在联合变量lgc中, 整型量i和字符mm公用同一内存位置。
当一个联合被说明时, 编译程序自动地产生一个变量, 其长度为联合中最大
的变量长度。
联合访问其成员的方法与结构相同。同样联合变量也可以定义成数组或指针,
但定义为指针时, 也要用"->"符号, 此时联合访问成员可表示成:
联合名->成员名
另外, 联合既可以出现在结构内, 它的成员也可以是结构。
例如:
struct{
int age;
char *addr;
union{
int i;
char *ch;
}x;
}y[10];
若要访问结构变量y[1]中联合x的成员i, 可以写成:
y[1].x.i;
若要访问结构变量y[2]中联合x的字符串指针ch的第一个字符可写成:
*y[2].x.ch;
若写成"y[2].x.*ch;"是错误的。

2. 结构和联合的区别
结构和联合有下列区别:
1. 结构和联合都是由多个不同的数据类型成员组成, 但在任何同一时刻,
联合中只存放了一个被选中的成员, 而结构的所有成员都存在。
2. 对于联合的不同成员赋值, 将会对其它成员重写, 原来成员的值就不存
在了, 而对于结构的不同成员赋值是互不影响的。
下面举一个例了来加对深联合的理解。
例4:
main()
{
union{ /*定义一个联合*/
int i;
struct{ /*在联合中定义一个结构*/
char first;
char second;
}half;
}number;
number.i=0x4241; /*联合成员赋值*/
printf("%c%c/n", number.half.first, mumber.half.second);
number.half.first=’a’; /*联合中结构成员赋值*/
number.half.second=’b’;
printf("%x/n", number.i);
getch();
}
输出结果为:
AB
6261
从上例结果可以看出: 当给i赋值后, 其低八位也就是first和second的值;
当给first和second赋字符后, 这两个字符的ASCII码也将作为i 的低八位和高八
位。

Tag:
编程博客 发表于10:19:00 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-20
结 构 - [C语言]
结 构(struct)
结构是由基本数据类型构成的、并用一个标识符来命名的各种变量的组合。
结构中可以使用不同的数据类型。

1. 结构说明和结构变量定义
在Turbo C中, 结构也是一种数据类型, 可以使用结构变量, 因此, 象其它
类型的变量一样, 在使用结构变量时要先对其定义。
定义结构变量的一般格式为:
struct 结构名
{
类型 变量名;
类型 变量名;
...
} 结构变量;
结构名是结构的标识符不是变量名。
类型为第二节中所讲述的五种数据类型(整型、浮点型、字符型、指针型和
无值型)。
构成结构的每一个类型变量称为结构成员, 它象数组的元素一样, 但数组中
元素是以下标来访问的, 而结构是按变量名字来访问成员的。
下面举一个例子来说明怎样定义结构变量。
struct string
{
char name[8];
int age;
char sex[2];
char depart[20];
float wage1, wage2, wage3, wage4, wage5;
} person;
这个例子定义了一个结构名为string的结构变量person, 如果省略变量名
person, 则变成对结构的说明。用已说明的结构名也可定义结构变量。这样定义
时上例变成:
struct string
{
char name[8];
int age;
char sex[2];
char depart[20];
float wage1, wage2, wage3, wage4, wage5;
};
struct string person;
如果需要定义多个具有相同形式的结构变量时用这种方法比较方便, 它先作
结构说明, 再用结构名来定义变量。
例如:
struct string Tianyr, Liuqi, ...;
如果省略结构名, 则称之为无名结构, 这种情况常常出现在函数内部, 用这
种结构时前面的例子变成:
struct
{
char name[8];
int age;
char sex[2];
char depart[20];
float wage1, wage2, wage3, wage4, wage5;
} Tianyr, Liuqi;

2. 结构变量的使用
结构是一个新的数据类型, 因此结构变量也可以象其它类型的变量一样赋值、
运算, 不同的是结构变量以成员作为基本变量。
结构成员的表示方式为:
结构变量.成员名
如果将"结构变量.成员名"看成一个整体, 则这个整体的数据类型与结构中
该成员的数据类型相同, 这样就可象前面所讲的变量那样使用。
下面这个例子定义了一个结构变量, 其中每个成员都从键盘接收数据, 然后
对结构中的浮点数求和, 并显示运算结果, 同时将数据以文本方式存入一个名为
wage.dat的磁盘文件中。请注意这个例子中不同结构成员的访问。
例3:
#include <stdio.h>
main()
{
struct{ /*定义一个结构变量*/
char name[8];
int age;
char sex[2];
char depart[20];
float wage1, wage2, wage3, wage4, wage5;
}a;
FILE *fp;
float wage;
char c=’Y’;
fp=fopen("wage.dat", "w"); /*创建一个文件只写*/
while(c==’Y’||c==’y’) /*判断是否继续循环*/
{
printf("/nName:");
scanf("%s", a.name); /*输入姓名*/
printf("Age:");
scanf("%d", &a.wage); /*输入年龄*/
printf("Sex:");
scanf("%d", a.sex);
printf("Dept:");
scanf("%s", a.depart);
printf("Wage1:");
scanf("%f", &a.wage1); /*输入工资*/
printf("Wage2:");
scanf("%f", &a.wage2);
printf("Wage3:");
scanf("%f", &a.wage3);
printf("Wage4:");
scanf("%f", &a.wage4);
printf("Wage5:");
scanf("%f", &a.wage5);
wage=a.wage1+a.wage2+a.wage3+a.wage4+a.wage5;
printf("The sum of wage is %6.2f/n", wage);/*显示结果*/
fprintf(fp, "%10s%4d%4s%30s%10.2f/n", /*结果写入文件*/
a.name, a.age, a.sex, a.depart, wage);
while(1)
{
printf("Continue?<Y/N>");
c=getche();
if(c==’Y’||c==’y’||c==’N’||c==’n’)
break;
}
}
Tag:
编程博客 发表于10:18:00 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-20
指针、结构、联合和枚举 - [C语言]
指针、结构、联合和枚举
本节专门对第二节曾讲述过的指针作一详述。并介绍Turbo C新的数据类型:
结构、联合和枚举, 其中结构和联合是以前讲过的五种基本数据类型(整型、浮
点型、字符型、指针型和无值型)的组合。 枚举是一个被命名为整型常数的集合。
最后对类型说明(typedef)和预处理指令作一阐述。

指 针(point)

学习Turbo C语言, 如果你不能用指针编写有效、正确和灵活的程序, 可以
认为你没有学好C语言。指针、地址、数组及其相互关系是C语言中最有特色的部
分。规范地使用指针, 可以使程序达到简单明了, 因此, 我们不但要学会如何正
确地使用指针, 而且要学会在各种情况下正确地使用指针变量。

1. 指针和地址
1.1 指针基本概念及其指针变量的定义
1.1.1 指针变量的定义
我们知道变量在计算机内是占有一块存贮区域的, 变量的值就存放在这块区
域之中, 在计算机内部, 通过访问或修改这块区域的内容来访问或修改相应的变
量。Turbo C语言中, 对于变量的访问形式之一, 就是先求出变量的地址, 然后
再通过地址对它进行访问, 这就是这里所要论述的指针及其指针变量。
所谓变量的指针, 实际上指变量的地址。变量的地址虽然在形式上好象类似
于整数, 但在概念上不同于以前介绍过的整数, 它属于一种新的数据类型, 即指
针类型。Turbo C中, 一般用"指针"来指明这样一个表达式&x的类型, 而用 "地
址"作为它的值, 也就是说, 若x为一整型变量, 则表达式&x的类型是指向整数的
指针, 而它的值是变量x的地址。同样, 若
double d;
则&d的类型是指向以精度数d的指针, 而&d的值是双精度变量d的地址。所以, 指
针和地址是用来叙述一个对象的两个方面。虽然&x、&d的值分别是整型变量x 和
双精度变量d的地址, 但&x、&d的类型是不同的, 一个是指向整型变量x的指针,
而另一个则是指向双精度变量d的指针。在习惯上, 很多情况下指针和地址这两
个术语混用了。
我们可以用下述方法来定义一个指针类型的变量。
int *ip;
首先说明了它是一指针类型的变量, 注意在定义中不要漏写符号"*", 否则它为
一般的整型变量了。另外, 在定义中的int 表示该指针变量为指向整型数的指针
类型的变量, 有时也可称ip为指向整数的指针。ip是一个变量, 它专门存放整型
变量的地址。
指针变量的一般定义为:
类型标识符 *标识符;
其中标识符是指针变量的名字, 标识符前加了"*"号, 表示该变量是指针变
量, 而最前面的"类型标识符"表示该指针变量所指向的变量的类型。一个指针变
量只能指向同一种类型的变量, 也就是讲, 我们不能定义一个指针变量, 既能指
向一整型变量又能指向双精度变量。
指针变量在定义中允许带初始化项。如:
int i, *ip=&i;
注意, 这里是用&i对ip初始化, 而不是对*ip初始化。和一般变量一样, 对于外
部或静态指针变量在定义中若不带初始化项, 指针变量被初始化为NULL, 它的值
为0。Turbo C中规定, 当指针值为零时, 指针不指向任何有效数据, 有时也称指
针为空指针。因此, 当调用一个要返回指针的函数(第五节中介绍)时, 常使用返
回值为NULL来指示函数调用中某些错误情况的发生。
1.1.2 指针变量的引用
既然在指针变量中只能存放地址, 因此, 在使用中不要将一个整数赋给一指
针变量。下面的赋值是不合法的:
int *ip;
ip=100;
假设
int i=200, x;
int *ip;
我们定义了两个整型变量i, x, 还定义了一个指向整型数的指针变量ip。i, x中
可存放整数, 而ip中只能存放整型变量的地址。我们可以把i的地址赋给ip:
ip=&i;
此时指针变量ip指向整型变量i, 假设变量i的地址为1800, 这个赋值可形象理解
为下图所示的联系。
ip i
┏━━━┓ ┏━━━┓
┃ 1800 ╂──→ ┃ 200 ┃
┗━━━┛ ┗━━━┛
图1. 给指针变量赋值
以后我们便可以通过指针变量ip间接访问变量i, 例如:
x=*ip;
运算符*访问以ip为地址的存贮区域, 而ip中存放的是变量i的地址, 因此, *ip
访问的是地址为1800的存贮区域(因为是整数, 实际上是从1800开始的两个字节),
它就是i所占用的存贮区域, 所以上面的赋值表达式等价于
x=i;
另外, 指针变量和一般变量一样, 存放在它们之中的值是可以改变的, 也就
是说可以改变它们的指向, 假设
int i, j, *p1, *p2;
i=’a’;
j=’b’;
p1=&i;
p2=&j;
则建立如下图所示的联系:
p1 i
┏━━━┓ ┏━━━┓
┃ ╂──→ ┃ ’a’ ┃
┗━━━┛ ┗━━━┛
p2 i
┏━━━┓ ┏━━━┓
┃ ╂──→ ┃ ’b’ ┃
┗━━━┛ ┗━━━┛

Tag:
编程博客 发表于10:17:00 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-20
控制流程语句 - [C语言]
1.3 控制流程语句
Turbo C2.0提供了丰富、灵活的控制流程语句, 主要有:条件语句、循环语
句和开关语句。下面将对这些语句作详细介绍。


1.3.1 条件语句
象其它语言一样Turbo C2.0也提供条件语句。在Turbo C2.0中条件语句的一
般形式为:
if(表达式)
语句1;
else
语句2;
上述结构表示: 如果表达式的值为非0(TURE)即真, 则执行语句1, 执行完语
句1从语句2后开始继续向下执行; 如果表达式的值为0(FALSE)即假, 则跳过语句
1而执行语句2。所谓表达式是指关系表达式和逻辑表达式的结合式, 关于表达式
前面已作过介绍, 这是不再重复。
注意:
1. 条件执行语句中"else 语句2;"部分是选择项, 可以缺省, 此时条件语句
变成:
if(表达式) 语句1;
表示若表达式的值为非0则执行语句1 , 否则跳过语句1继续执行。
2. 如果语句1或语句2有多于一条语句要执行时, 必须使用"{"和"}" 把这些
语句包括在其中, 此时条件语句形式为:
if(表达式)
{
语句体1;
}
else
{
语句体2;
}
3. 条件语句可以嵌套, 这种情况经常碰到, 但条件嵌套语句容易出错, 其
原因主要是不知道哪个if对应哪个else。
例如:
if(x>20||x<-10)
if(y<=100&&y>x)
printf("Good");
else
printf("Bad");
对于上述情况, Turbo C2.0规定: else语句与最近的一个if语句匹配, 上例
中的else与if(y<=100&&y>x)相匹配。为了使else与if(x>20||x<-10)相匹配, 必
须用花括号。如下所示:
if(x>20||x<-10)
{
if(y<=100&&y>x)
printf("Good");
}
else
printf("Bad");
4. 可用阶梯式if-else-if结构。
阶梯式结构的一般形式为:
if(表达式1)
语句1;
else if(表达式2)
语句2;
else if(表达式3)
语句3;
.
.
.
else
语句n;
这种结构是从上到下逐个对条件进行判断, 一旦发现条件满点足就执行与它
有关的语句, 并跳过其它剩余阶梯; 若没有一个条件满足, 则执行最后一个else
语句n。最后这个else常起着"缺省条件"的作用。
同样, 如果每一个条件中有多于一条语句要执行时, 必须使用"{"和"}"把这
些语句包括在其中。


1.3.2 循环语句
Turbo C2.0提供三种基本的循环语句: for语句、while语句和do-while语句。

1.3.2.1 for循环
for循环是开界的。它的一般形式为:
for(<初始化>; <条件表过式>; <增量>)
语句;
初始化总是一个赋值语句, 它用来给循环控制变量赋初值; 条件表达式是一
个关系表达式, 它决定什么时候退出循环; 增量定义循环控制变量每循环一次后
按什么方式变化。这三个部分之间用";"分开。
例如:
for(i=1; i<=10; i++)
语句;
上例中先给i赋初值1, 判断i是否小于等于10, 若是则执行语句, 之后值增
加1。再重新判断, 直到条件为假, 即i>10时, 结束循环。
注意:
1. for循环中语句可以为语句体, 但要用"{"和"}"将参加循环的语句括起来。
2. for循环中的"初始化"、"条件表达式"和"增量"都是选择项, 即可以缺省,
但";"不能缺省。省略了初始化, 表示不对循环控制变量赋初值。 省略了条件
表达式, 则不做其它处理时便成为死循环。省略了增量, 则不对循环控制变量进
行操作, 这时可在语句体中加入修改循环控制变量的语句。
3. for循环可以有多层嵌套。
例16:
main()
{
int i, j, k;
printf("i j k/n");
for (i=0; i<2; i++)
for(j=0; j<2; j++)
for(k=0; k<2; k++)
printf(%d %d %d/n", i, j, k);
}
输出结果为:
i j k
0 0 0
0 0 1
0 1 0
0 1 1
1 0 0
1 0 1
1 1 0
1 1 1

1.3.2.2 while循环
while循环的一般形式为:
while(条件)
语句;
while循环表示当条件为真时, 便执行语句。直到条件为假才结束循环。并
继续执行循环程序外的后续语句。
例17:
#include<stdio.h>
main()
{
char c;
c=’/0’; /*初始化c*/
while(c!=’/X0D’) /*回车结束循环*/
c=getche(); /*带回显的从键盘接收字符*/
}
上例中, while循环是以检查c是否为回车符开始, 因其事先被初始化为空,
所以条件为真, 进入循环等待键盘输入字符; 一旦输入回车, 则c=’/X0D’, 条件
为假, 循环便告结束。
与for循环
Tag:
编程博客 发表于10:16:00 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-20
文件的输入输出函数 - [C语言]
1.2 文件的输入输出函数
键盘、显示器、打印机、磁盘驱动器等逻辑设备, 其输入输出都可以通过文
件管理的方法来完成。而在编程时使用最多的要算是磁盘文件, 因此本节主要以
磁盘文件为主, 详细介绍Turbo C2.0提供的文件操作函数, 当然这些对文件的操
作函数也适合于非磁盘文件的情况。
另外, Turbo C2.0提供了两类关于文件的函数。一类称做标准文件函数也称
缓冲型文件函数, 这是ANSI标准定义的函数; 另一类叫非标准文件函数, 也称非
缓冲型文件函数。这类函数最早公用于UNIX操作系统, 但现在MS-DOS3.0 以上版
本的操作系统也可以使用。下面分别进行介绍。

1.2.1 标准文件函数
标准文件函数主要包括文件的打开、关闭、读和写等函数。不象BASIC 、
FORTRAN语方有顺序文件和随机文件之分, 在打开时就应按不同的方式确定。
Turbo C2.0并不区分这两种文件, 但提供了两组函数, 即顺序读写函数和随机读
写函数。
一、文件的打开和关闭
任何一个文件在使用之前和使用之后, 必须要进行打开和关闭, 这是因为操
作系统对于同时打开的文件数目是有限制的, DOS操作系统中, 可以在DEVICE
.SYS中定义允许同时打开的文件数n(用files=n定义)。其中n 为可同时打开的文
件数, 一般n<=20。因此在使用文件前应打开文件, 才可对其中的信息进行存取。
用完之后需要关闭, 否则将会出现一些意想不到的错误。Turbo C2.0提供了打开
和关闭文件的函数。
1. fopen()函数
fopen函数用于打开文件, 其调用格式为:
FILE *fopen(char *filename, *type);
在介绍这个函数之;前, 先了解一下下面的知识。
(1) 流(stream)和文件(file)
流和文件 在Turbo C2.0中是有区别的, Turbo C2.0 为编程者和被访问的设
备之间提供了一层抽象的东西, 称之为"流", 而将具体的实际设备叫做文件。
流是一个逻辑设备, 具有相同的行为。因此, 用来进行磁盘文件写的函数也同样
可以用来进行打印机的写入。在Turbo C2.0中有两种性质的流: 文字流( text
stream)和二进制(binary stream)。对磁盘来说就是文本文件和二进制文件。本
软件为了便于让读者易理解Turbo C2.0语言而没有对流和文件作特别区分。
(2) 文件指针FILE
实际上FILE是一个新的数据类型。它是Turbo C2.0的基本数据类型的集合,
称之为结构指针。有关结构的概念将在第四节中详细介绍, 这里只要将FILE理解
为一个包括了文件管理有关信息的数据结构, 即在打开文件时必须先定义一个文
件指针。
(3) 以后介绍的函数调用格式将直接写出形式参数的数据类型和函数返回值
的数据类型。例如: 上面打开文件的函数, 返回一个文件指针, 其中形式参数有
两个, 均为字符型变量(字符串数组或字符串指针)。本软件不再对函数的调用格
式作详细说明。
现在再来看打开文件函数的用法。
fopen()函数中第一个形式参数表示文件名, 可以包含路径和文件名两部分。
如:
"B:TEST.DAT"
"C://TC//TEST.DAT"
如果将路径写成"C:/TC/TEST.DAT"是不正确的, 这一点要特别注意。
第二个形式参数表示打开文件的类型。关于文件类型的规定参见下表。
表 文件操作类型
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
字符 含义
────────────────────────────
"r" 打开文字文件只读
"w" 创建文字文件只写
"a" 增补, 如果文件不存在则创建一个
"r+" 打开一个文字文件读/写
"w+" 创建一个文字文件读/写
"a+" 打开或创建一个文件增补
"b" 二进制文件(可以和上面每一项合用)
"t" 文这文件(默认项)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
如果要打开一个CCDOS子目录中, 文件名为CLIB的二进制文件, 可写成:
fopen("c://ccdos//clib", "rb");
如果成功的打开一个文件, fopen()函数返回文件指针, 否则返回空指针
(NULL)。由此可判断文件打开是否成功。
2. fclose()函数
fclose()函数用来关闭一个由fopen()函数打开的文件 , 其调用格式为:
int fclose(FILE *stream);
该函数返回一个整型数。当文件关闭成功时, 返回0, 否则返回一个非零值。
可以根据函数的返回值判断文件是否关闭成功。
例10:
#iclude<stdio.h>
main()
{
FILE *fp; /*定义一个文件指针*/
int i;
fp=fopen("CLIB", "rb"); /*打开当前目录名为CLIB的文件只读*/
if(fp==NULL) /*判断文件是否打开成功*/
puts("File open error");/*提示打开不成功*/
i=f
Tag:
编程博客 发表于10:15:31 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-20
运算符 - [C语言]
5. 运算符

Turbo C的运算符非常丰富, 主要分为三大类: 算术运算符, 关系运算符与
逻辑运算符, 按位运算符。除此之外, 还有一些用于完成特殊任务的运算符。下
面分别进行介绍。

5.1 算术运算符
Turbo C的算术运算符如下:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
操作符 作用
────────────────────────────
+ 加, 一目取正
- 减, 一目取负
* 乘
/ 除
% 取模
-- 减1
++ 加1
━━━━━━━━━━━━━━━━━━━━━━━━━━━━

一、一目和二目操作
一目操作是指对一个操作数进行操作。例如: -a是对a进行一目负操作。
二目操作(或多目操作)是指两个操作数(或多个操作数)进行操作。
在Turbo C中加、减、乘、除、取模的运算与其它高级语言相同。需要注意
的是除法和取模运算。
例如:
15/2 是15除以2商的整数部分7
15%2 是15除以2的余数部分1
对于取模运算符"%", 不能用于浮点数。
另外, 由于Turbo C中字符型数会自动地转换成整型数, 因此字符型数也可
以参加二目运算。
例如:
main()
{
char m, n; /*定义字符型变量*/
m=’c’; /*给m赋小写字母’c’*/
n=m+’A’-’a’; /*将c中的小写字母变成大写字母’B’后赋给n*/
...
}
上例中m=’c’即m=98, 由于字母A和a的ASCII码值分别为65和97。这样可以将
小写字母变成大写字母, 反之, 如果要将大写字母变成小写字母, 则用c+ ’a’
-’A’进行计算。

二、增量运算
在Turbo C中有两个很有用的运算符, 在其它高级语言中通常没有。这两个
运算符就是增1和减1运算符"++"和"--", 运算符"++"是操作数加1, 而"--" 则是
操作数减1。
例如:
x=x+1 可写成x++, 或++x
x=x-1 可写成x--, 或--x
x++(x--)与++x(--x)在上例中没有什么区别, 但x=m++和x=++m却有很大差别。
x=m++ 表示将m的值赋给x后, m加1。
x=++m 表示m先加1后, 再将新值赋给x。

三、赋值语句中的数据类型转换
类型转换是指不同类型的变量混用时的类型改变。
在赋值语句中, 类型转换规则是:
等号右边的值转换为等号左边变量所属的类型。
例如:
main()
{
int i, j; /*定义整型变量*/
float f, g=2.58; /*定义浮点型变量*/
f=i*j; /*i与j的乘积是整型数, 被转换成为浮点数赋给f*/
i=g; /*g中的浮点型数转换成为整型数赋给i*/
...
}
由于Turbo C按上述数据类型转换规则, 因此在作除法运算时应特别注意。
例如:
main()
{
float f;
int i=15;
f=i/2;
}
上面程序经运行后, f=7并不等于准确值7.5。正确的程序应该是:
main()
{
float f;
int i=15;
f=i/2.0;
}
也可直接将i定义为浮点数。

5.2 关系运算符和逻辑运算符
一、逻辑运算符
逻辑运算符是指用形式逻辑原则来建立数值间关系的符号。
Turbo C的逻辑运算符如下:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
操作符 作用
─────────────────────────────
&& 逻辑与
|| 逻辑或
! 逻辑非
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

二、关系运算符
关系运算符是比较两个操作数大小的符号。
Turbo C的关系运算符如下:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
操作符 作用
─────────────────────────────
> 大于
>= 大于等于
< 小于
<= 小于等于
== 等于
!= 不等于
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
关系运算符和逻辑运算符的关键是真(true)和假(false)
Tag:
编程博客 发表于10:14:13 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-20
变量 - [C语言]
4. 变量

4.1 变量说明
Turbo C2.0规定所有变量在使用前都必须中以说明。一条变量说明语句由数据
类型和其后的一个或多个变量名组成。变量说明的形式如下:
类型 <变量表>;
这里类型是指Turbo C2.0的有效数据类型。变量表是一个或多个标识符名, 每
个标识符之间用","分隔。
例如:
int i, j, k; unsigned char c, str[5], *p;

4.2 变量种类
变量可以在程序中三个地方说明: 函数内部、函数的参数定义中或所有的函数
外部。根据所定义位置的不同, 变量可分为局部变量、形式参数和全程变量。
一、局部变量
局部变量是指在函数内部说明的变量(有时也称为自动变量)。用关键字auto进
行说明, 当auto省略时, 所有的非全程变量都被认为是局部变量, 所以auto实际上
从来不用。
局部变量在函数调用时自动产生, 但不会自动初始化, 随函数调用的结束, 这
个变量也就自动消失了, 下次调用此函数时再自动产生, 还要再赋值, 退出时又自
动消失。

二、形式参数
形式参数是指在函数名后面的小括号里定义的变量, 用于接受来自调用函数的
参数。形式参数在函数内部可以象其它局部变量那样来作用。
例如:
puthz(int x, int y, int color, char *p)
{
int i, j, k; /*定义局部变量*/
<程序体>
}
其中x, y, color, *p为函数的形式参数, 不需要再进行说明就可在该函数内
直使用。

三、全程变量
全程变量是指在所有函数之外说明的变量, 它在整个程序内部者是"可见的",
可以被任何一个函数使用, 并且在整个程序的运行中都保留其值。全程变量只要满
足在使用它以前和函数以外这两个条件, 可在程序的任何位置进行说明, 习惯上通
常在程序的主函数前说明。
例如:
#include<stdio.h>
int test; /*定义全程变量*/
void f1(int x, float y); /*子函数说明*/
void f2(void); /*子函数说明*/
main()
{
test=5; /*给全程变量赋值*/
f1(20, 5.5); /*调用有形式参数的子函数f1()*/
/*test的值变成115*/
f2(); /*调用f2(), test的值变为1150*/
}
void f1(int x, float y)
{
float z; /*z定义为局部变量*/
z=x*y; /*计算*/
test=test+z;
}
void f2(void)
{
int count=10; /*定义局部变量并初始化*/
test=test*count;
}

由于全程变量可被整个程序内的任何一个函数使用, 所以可作为函数之间传递
参数的手段, 但全程变量太多时, 内存开销变大。
4.3 变量存储类型
Turbo C2.0支持四种变量存储类型。说明符如下:
auto static extern register
下面分别来介绍。
一、auto
auto称为自动变量, 已在前面作了介绍, 这里不再重复。
二、static
static称为静态变量。根据变量的类型可以分为静态局部变量和静态全程变量。
1. 静态局部变量
它与局部变量的区别在于: 在函数退出时, 这个变量始终存在, 但不能被其它
函数使用, 当再次进入该函数时, 将保存上次的结果。其它与局部变量一样。
2. 静态全程变量
Turbo C2.0允许将大型程序分成若干独立模块文件分别编译, 然后将所有模块
的目标文件连接在一起, 从而提高编译速度, 同时也便于软件的管理和维护。静态
全程变量就是指只在定义它的源文件中可见而在其它源文件中不可见的变量。它与
全程变量的区别是: 全程变量可以再说明为外部变量(extern), 被其它源文件使用,
而静态全程变量却不能再被说明为外部的, 即只能被所在的源文件使用。
三、extern
extern称为外部变量。为了使变量除了在定义它的源文件中可以使用外, 还要
被其它文件使用。因此, 必须将全程变量通知每一个程序模块文件, 此时可用
extern来说明。
例如:
文件1为file1.c 文件2为file2.c
int i, j;/*定义全程变量*/ extern int i, j;/*说明将i, j从
文件1中复制过来*/
char c; extern char c; /*将c复制过来*/
void func1(int k); func2() /*用户定义函数*/
{
main() static float k;/*定义静态变量*/
{ i=j*5/100;
func1(20);/*调用函数*/ k=i/1.5;
func2(); .
. .

Tag:
编程博客 发表于10:13:00 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-20
关键字和标识符 - [C语言]
3 关键字和标识符

3.1 关键字
所谓关键字就是已被Turbo C2.0本身使用, 不能作其它用途使用的字。例如关
键字不能用作变量名、函数名等。
Turbo C2.0有以下关键字:
Turbo C2.0扩展的共11个
asm _cs _ds _es _ss cdecl
far near huge interrupt pascal
由ANSI标准定义的共32个
auto double int struct break else
long switch case enum register typedef
char extern return union const float
short unsigned continue for signed void
default goto sizeof volatile do if
while static

3.2 标识符
所谓标识符是指常量、变量、语句标号以及用户自定义函数的名称。 Turbo C
2.0标识符的定义十分灵活。作为标识符必须满足以下规则:
1. 所有标识符必须由一个字母(a~z, A~Z)或下划线(_)开头;
2. 标识符的其它部分可以用字母、下划线或数字(0~9)组成;
3. 大小写字母表示不同意义, 即代表不同的标识符;
4. 标识符只有前32个字符有效;
5. 标识符不能使用Turbo C2.0的关键字。
下面举出几个正确和不正确的标识符:
正确 不正确
smart 5smart
_decision bomb?
key_board key.board
FLOAT float

Tag:
编程博客 发表于10:12:00 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-20
数据类型 - [C语言]
2. 数据类型
在Turbo C语言中, 每个变量在使用之前必须定义其数据类型。Turbo C有以
下几种类型: 整型(int)、浮点型(float)、字符型(char)、指针型(*)、无值型
(void)以及结构(struct)和联合(union)。其中前五种是Turbo C的基本数据类型、
后两种数据类型(结构和联合)将在第五章介绍。
2.1 整型(int)

一、整型数说明
加上不同的修饰符, 整型数有以下几种类型;
signed short int 有符号短整型数说明。简写为short或int, 字长为2
字节共16位二进制数, 数的范围是-32768~32767。
signed long int 有符号长整型数说明。简写为long, 字长为4字节共
32位二进制数, 数的范围是-2147483648~2147483647。
unsigned short int 无符号短整型数说明。简写为unsigned int, 字长
为2字节共16位二进制数, 数的范围是0~65535。
unsigned long int 无符号长整型数说明。简写为unsigned long, 字长
为4字节共32位二进制数, 数的范围是0~4294967295。

二、整型变量定义
可以用下列语句定义整型变量
int a, b; /*a、b被定义为有符号短整型变量*/
unsigned long c; /*c被定义为无符号长整型变量*/

三、整型常数表示
按不同的进制区分, 整型常数有三种表示方法:
十进制数: 以非0开始的数
如:220, -560, 45900
八进制数: 以0开始的数
如:06; 0106, 05788
十六进制数:以0X或0x开始的数
如:0X0D, 0XFF, 0x4e
另外, 可在整型常数后添加一个"L"或"l"字母表示该数为长整型数, 如22L,
0773L, 0Xae4l。

2.2 浮点型(float)
一、浮点数说明
Turbo C中有以下两种类型的浮点数:
float 单浮点数。字长为4 个字节共32 位二进制数, 数的范围是
3.4x10-38E~3.4x10+38E。
double 双浮点数。字长为 8个字节共 64 位二进制数, 数的范围是
1.7x10-308E~1.7x10+308E。
说明:
浮点数均为有符号浮点数, 没有无符号浮点数。

二、浮点型变量定义
可以用下列语句定义浮点型变量:
float a, f; /*a, f被定义为单浮点型变量*/
double b; /*b被定义为双浮点型变量*/

三、浮点常数表示
例如: +29.56, -56.33, -6.8e-18, 6.365
说明:
1. 浮点常数只有一种进制(十进制)。
2. 所有浮点常数都被默认为double。
3. 绝对值小于1的浮点数, 其小数点前面的零可以省略。如:0.22可写为.22,
-0.0015E-3可写为-.0015E-3。
4. Turbo C默认格式输出浮点数时, 最多只保留小数点后六位。

2.3 字符型(char)
加上不同的修饰符, 可以定义有符号和无符号两种类型的字符型变量, 例如:
char a: /*a被定义为有符号字符变量*/
unsigned char l; /*l被定义为无符号字符变量*/
字符在计算机中以其ASCII码方式表示, 其长度为1个字节, 有符号字符型数
取值范围为-128~127, 无符号字符型数到值范围是0~255。因此在Turbo C语言中,
字符型数据在操作时将按整型数处理, 如果某个变量定义成char, 则表明该变量
是有符号的, 即它将转换成有符号的整型数。
Turbo C中规定对ASCII码值大于0x80的字符将被认为是负数。例如ASCII 值
为0x8c的字符, 定义成char时, 被转换成十六进制的整数0xff8c 。 这是因当
ASCII码值大于0x80时, 该字节的最高位为1, 计算机会认为该数为负数, 对于
0x8c表示的数实际上是-74(8c的各位取反再加1), 而-74 转换成两字节整型数并
在计算机中表示时就是0xff8c( 对0074 各位取反再加1) 。 因此只有定义为
unsigned char 0x8c转换成整型数时才是8c。这一点在处理大于0x80的ASCII码
字符时(例如汉字码)要特别注意。一般汉字均定义为unsigned char(在以后的程
序中会经常碰到)。
另外, 也可以定义一个字符型数组(关于数组后面再作详细介绍), 此时该数
组表示一个字符串。
例如:
char str[10];
计算机在编译时, 将留出连续10个字符的空间, 即str[0]到str[9]共10个变
量, 但只有前9个供用户使用。第10个str[9]用来存放字符串终止符NULL即"/0",
但终止符是编编译程序自动加上的, 这一点应特别注意。

二、字符常数表示
能用符号表示的字符可直接用单引号括起来表示, 如’a’, ’9’, ’Z’, 也可用
该字符的ASCII码值表示, 例如十进制数85表示大写字母’U’, 十六进制数0x5d表示
’]’, 八进制数0102表示大写字母’B’。
一些不能用符号表示的控制符, 只能用ASCII码值来表示, 如十进制数10 表示
换行, 下六进制数0x0d表示回车, 八进制数033表示Esc。Turbo C2.0中也有另外一
种表示表示方法, 如’/033’表示E
Tag:
编程博客 发表于10:11:20 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-20
数据类型、变量和运算符 - [C语言]
数据类型、变量和运算符
本节首先介绍Turbo C程序的基本组成部分; 然后介绍Turbo C的数据类型、
变量类型、变量的初始化和赋值; 最后介绍Turbo C的有关操作。 通过本节的学
习, 可以对Turbo C语言有一个初步认识。

1. Turbo C程序的一般组成部分

Turbo C 2.0 象其它语言一样按其规定的格式和提供的语句由用户编写应用
程序。请看下面一段Turbo C源程序。
例1:
/*Example program of Turbo C*/
#include <stdio.h> /*包含文件说明*/
void lgc(void); /*子函数说明*/
char answer; /*定义全程变量*/
int main() /*主函数定义*/
{
char a; /*定义局部变量*/
clrscr();
gotoxy(12,3);
puts("Welcome to use Turbo C2.0!");
gotoxy(15, 13);
printf("<Esc>--Exit");
gotoxy(15, 15);
printf("<CR>--Continue");
while(1)
{
a=getch();
if(a==27)
break;
if(a==13)
{
lgc();
if(answer==’y’||answer==’Y’)
{
gotoxy(23,14);
puts("Please Write to the Company");
getch();
break;
}
}
}
return(0);
}
void lgc(void)
{
clrscr();
gotoxy(12,8);
printf("The Excellent Selection!");
gotoxy(21,12);
printf("Do you have any question?(Y/N)");
answer=getche();
}
由例子程序可以看出, Turbo C源程序主要有以下几个特点:
1. 程序一般用小写字母书写;
2. 大多数语句结尾必须要用";"作为终止符, 否则Turbo C 不认为该语句结
束;
3. 每个程序必须有一个而且只能有一个称作主函数的main()函数;
4. 每个程序体 (主函数和每个子函数, 如上例中的main()函数和sub()函数)
必须用一对花括号"{"和"}"括起来;
5. 一个较完整的程序大致包括:包含文件(一组#include<*.h>语句)、用户
函数说明部分、全程变量定义、主函数和若干子函数组成。在主函数和子函数中
又包括局部变量定义、若干个Turbo C库函数、控制流程语句、 用户函数的调用
语句等;
6. 注释部分包含在"/*"和"*/"之间, 在编译时它被Turbo C编译器忽略。

说明:
1. 象其它一些语言一样, Turbo C的变量在使用之前必须先定义其数据类型,
未经定义的变量不能使用。定义变量类型应在可执行语句前面, 如上例main()函
数中的第一条语句就是变量定义语句, 它必须放在第一各执行语句clrscr()前面。
2. 在Turbo C中, 大、小写字母是有区别的, 相同字母的大、小写代表不同
的变量。
3. Turbo C程序的书写格式非常灵活, 没有严格限制。

例1的主函数可写成:
main(){char c; clrscr(); gotoxy(12,3);
puts("Welcome to use Turbo C2.0!"); gotoxy(15,13);
printf("<CR>--Continue"); gotoxy(15,15);...}
这样写语法上没有错误, 但阅读起来不方便, 同时也使得程序层次不明确。
作者建议用Turbo C编程时, 一行一条语句, 遇到嵌套语句向后缩进, 必要时对
程序加上注释行。这样可以便程序结构清楚、易于阅读、维护和修改。
通过以上介绍, 可以得出Turbo C源程序的一般形式为:
包含文件
子函数类型说明
全程变量定义
main()
{
局部变量定义
<程序体>
}
sub1()
{
局部变量定义
<程序体>
}
sub2()
{
局部变量定义
<程序体>
}
.
.
.
subN()
{
局部变量定义
<程序体>
}
其中sub1(), ..., subN()代表用户定义的子函数, 程序体指Turbo C 2.0
提供的任何库函数调用语句、控制流程语句或其它用子函数调用语句等。

Tag:
编程博客 发表于10:10:55 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-20
Turbo C 2.0集成开发环境的使用 - [C语言]
1.5 Turbo C 2.0集成开发环境的使用

进入Turbo C 2.0集成开发环境中后, 屏幕上显示:
──────────────────────────────
File Edit Run Compile Project Options Debug Break/watch
┌────────────Edit──────────────┐
│ Line 1 Col 1 Insert Indent Tab File Unindent c:NONAME.C│
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│─────────Message─────────────── │
│ │
│ │
└────────────────────────────┘
F1-Help F5-Zoom F6-Switch F7-Trace F8-Step F9-Make F10-Menu
───────────────────────────────
其中顶上一行为Turbo C 2.0 主菜单, 中间窗口为编辑区, 接下来是信息窗
口, 最底下一行为参考行。这四个窗口构成了Turbo C 2.0的主屏幕, 以后的编程、
编译、调试以及运行都将在这个主屏幕中进行。下面详细介绍主菜单的内容。

1.5.1 主菜单

主菜单 在Turbo C 2.0主屏幕顶上一行, 显示下列内容:
File Edit Run Compile Project Options Debug Break/watch
除Edit外, 其它各项均有子菜单, 只要用Alt加上某项中第一个字母(即大写字
母), 就可进入该项的子菜单中。
一、File(文件)菜单
按Alt+F可进入File菜单, 该菜单包括以下内容:
.Load(加载)
装入一个文件, 可用类似DOS的通配符(如*.C)来进行列表选择。也可装入其它
扩展名的文件, 只要给出文件名(或只给路径)即可。该项的热键为F3, 即只要在主
菜单中按F3即可进入该项, 而不需要先进入File菜单再选此项。
.Pick(选择)
将最近装入编辑窗口的8个文件列成一个表让用户选择, 选择后将该程序装入
编辑区, 并将光标置在上次修改过的地方。其热健为Alt-F3。
.New(新文件)
说明文件是新的, 缺省文件名为NONAME.C, 存盘时可改名。
.Save(存盘)
将编辑区中的文件存盘, 若文件名是NONAME.C时, 将询问是否更改文件名, 其
热键为F2。
.Write to(存盘)
可由用户给出文件名将编辑区中的文件存盘, 若该文件已存在, 则询问要不要
覆盖。
.Directory(目录)
显示目录及目录中的文件, 并可由用户选择。
.Change dir(改变目录)
显示当前目录, 用户可以改变显示的目录。
.Os shell(暂时退出)
暂时退出Turbo C 2.0到DOS提示符下, 此时可以运行DOS 命令, 若想回到
Turbo C 2.0中, 只要在DOS状态下键入EXIT即可。
.Quit(退出)
退出Turbo C 2.0, 返回到DOS操作系统中, 其热键为Alt+X。

说明:
以上各项可用光标键移动色棒进行选择, 回车则执行。也可用每一项的第一个
大写字母直接选择。若要退到主菜单或从它的下一级菜单列表框退回均可用Esc键,
Turbo C 2.0所有菜单均采用这种方法进行操作, 以下不再说明。
二、Edit(编辑)菜单
按Alt+E可进入编辑菜单, 若再回车, 则光标出现在编辑窗口, 此时用户可以
进行文本编辑。
编辑方法基本与wordstar相同, 可用F1键获得有关编辑方法的帮助信息。
与编辑有关的功能键如下:
F1 获得Turbo C 2.0编辑命令的帮助信息
F5 扩大编辑窗口到整个屏幕
F6 在编辑窗口与信息窗口之间进行切换
F10 从编辑窗口转到主菜单
编辑命令简介:
PageUp 向前翻页
PageDn 向后翻页
Home 将光标移到所在行的开始
End 将光标移到所在行的结尾
Ctrl+Y 删除光标所在的一行
Ctrl+T 删除光标所在处的一个词
Ctrl+KB 设置块开始
Ctrl+KK 设置块结尾
Ctrl+KV 块移动
Ctrl+KC 块拷贝
Ctrl+KY 块删除
Ctrl+KR 读文件
Ctrl+KW 存文件
Ctrl+KP 块文件打印
Ctrl+F1 如果光标所在处为Turbo C 2.0库函数, 则获得有关该函数的帮助
信息
Ctrl+Q[ 查找Turbo C 2.0双界符的后匹配符
Ctrl+Q] 查找Turbo C 2.0双界符的前匹配符

说明:
1. Turbo C 2.0的双界符包括以下几种符号:
花括符 {和}
尖括符 <和>

Tag:
编程博客 发表于10:09:01 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-20
Turbo C 2.0的安装和启动 - [C语言]
Turbo C 2.0的安装非常简单, 只要将1#盘插入A驱动器中, 在DOS的"A>" 下键
入:
A>INSTALL
即可, 此时屏幕上显示三种选择:
1. 在硬盘上创造一个新目录来安装整个Turbo C 2.0系统。
2. 对Turbo C 1.5更新版本。
这样的安装将保留原来对选择项、颜色和编辑功能键的设置。
3. 为只有两个软盘而无硬盘的系统安装Turbo C 2.0。
这里假定按第一种选择进行安装, 只要在安装过程中按对盘号的提示, 顺序插
入各个软盘, 就可以顺利地进行安装, 安装完毕将在C盘根目录下建立一个TC 子目
录, TC下还建立了两个了目录LIB和INCLUDE, LIB子目录中存放库文件, INCLUDE
子目录中存放所有头文件。
运行Turbo C 2.0时, 只要在TC 子目录下键入TC并回车即可进入Turbo C 2. 0
集成开发环境。
Tag:
编程博客 发表于10:08:08 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-20
Turbo C 概述 - [C语言]
1.3 Turbo C 概述

1.3.1 Turbo C 的产生与发展

Turbo C 是美国Borland 公司的产品,Borland公司是一家专门从事软件开发、
研制的大公司。该公司相继推出了一套 Turbo系列软件, 如Turbo BASIC, Turbo
Pascal, Turbo Prolog, 这些软件很受用户欢迎。该公司在1987年首次推出Turbo
C 1.0 产品, 其中使用了全然一新的集成开发环境, 即使用了一系列下拉式菜单,
将文本编辑、程序编译、连接以及程序运行一体化, 大大方便了程序的开发。1988
年, Borland 公司又推出Turbo C1.5版本, 增加了图形库和文本窗口函数库等, 而
Turbo C 2.0 则是该公司1989年出版的。Turbo C2.0在原来集成开发环境的基础上
增加了查错功能, 并可以在Tiny模式下直接生成.COM (数据、代码、堆栈处在同一
64K 内存中) 文件。还可对数学协处理器 (支持8087/80287/80387等)进行仿真。
Borland 公司后来又推出了面向对象的程序软件包Turbo C++, 它继承发展
Turbo C 2.0 的集成开发环境, 并包含了面向对象的基本思想和设计方法。
1991年为了适用Microsoft 公司的Windows 3.0 版本, Borland 公司又将Turbo
C++ 作了更新, 即Turbo C 的新一代产品Borlandc C++也已经问世了。


1.3.2 Turbo C 2.0基本配置要求

Turbo C 2.0可运行于IBM-PC系列微机, 包括XT, AT及IBM 兼容机。 此时要求
DOS 2.0或更高版本支持, 并至少需要448K的RAM, 可在任何彩、单色80列监视器上
运行。支持数学协处理器芯片, 也可进行浮点仿真, 这将加快程序的执行。

1.3.3 Turbo C 2.0内容简介

Turbo C 2.0有六张低密软盘(或两张高密软盘)。下面对Turbo C 2.0的主要文
件作一简单介绍:
INSTALL.EXE 安装程序文件
TC.EXE 集成编译
TCINST.EXE 集成开发环境的配置设置程序
TCHELP.TCH 帮助文件
THELP.COM 读取TCHELP.TCH的驻留程序
README 关于Turbo C的信息文件
TCCONFIG.EXE 配置文件转换程序
MAKE.EXE 项目管理工具
TCC.EXE 命令行编译
TLINK.EXE Turbo C系列连接器
TLIB.EXE Turbo C系列库管理工具
C0?.OBJ 不同模式启动代码
C?.LIB 不同模式运行库
GRAPHICS.LIB 图形库
EMU.LIB 8087仿真库
FP87.LIB 8087库
*.H Turbo C头文件
*.BGI 不同显示器图形驱动程序
*.C Turbo C例行程序(源文件)
其中: 上面的?分别为:
T Tiny(微型模式)
S Small(小模式)
C Compact(紧凑模式)
M Medium(中型模式)
L Large(大模式)
H Huge(巨大模式)

Tag:
编程博客 发表于10:06:30 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-20
C 语言的特点 - [C语言]
C 语言发展如此迅速, 而且成为最受欢迎的语言之一, 主要因为它具有强大的
功能。许多著名的系统软件, 如DBASE Ⅲ PLUS、DBASE Ⅳ 都是由C 语言编写的。
用C 语言加上一些汇编语言子程序, 就更能显示C 语言的优势了, 象PC- DOS 、
WORDSTAR等就是用这种方法编写的。归纳起来C 语言具有下列特点:
1. C是中级语言
它把高级语言的基本结构和语句与低级语言的实用性结合起来。C 语言可以象
汇编语言一样对位、字节和地址进行操作, 而这三者是计算机最基本的工作单元。
2. C是结构式语言
结构式语言的显著特点是代码及数据的分隔化, 即程序的各个部分除了必要的
信息交流外彼此独立。这种结构化方式可使程序层次清晰, 便于使用、维护以及调
试。C 语言是以函数形式提供给用户的, 这些函数可方便的调用, 并具有多种循
环、条件语句控制程序流向, 从而使程序完全结构化。
3. C语言功能齐全
C 语言具有各种各样的数据类型, 并引入了指针概念, 可使程序效率更高。另
外C 语言也具有强大的图形功能, 支持多种显示器和驱动器。而且计算功能、逻辑
判断功能也比较强大, 可以实现决策目的。
4. C语言适用范围大
C 语言还有一个突出的优点就是适合于多种操作系统, 如DOS、UNIX,也适用于
多种机型。
Tag:
编程博客 发表于10:04:25 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-20
Turbo C语言概述 - [C语言]
1.1 C 语言的产生与发展

C 语言是1972年由美国的Dennis Ritchie设计发明的, 并首次在UNIX操作系统
的 DEC PDP-11 计算机上使用。 它由早期的编程语言 BCPL( Basic Combind
Programming Language) 发展演变而来。在1970年, AT&T 贝尔实验室的 Ken
Thompson根据BCPL语言设计出较先进的并取名为 B的语言, 最后导了C 语言的问世。
随着微型计算机的日益普及, 出现了许多C 语言版本。由于没有统一的标准,
使得这些C 语言之间出现了一些不一致的地方。为了改变这种情况, 美国国家标准
研究所(ANSI)为C 语言制定了一套ANSI标准, 成为现行的C语言标准。

Tag:
编程博客 发表于10:03:34 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-15
VC教程 - [VC专栏]
第一章 入门http://upload.yourblog.org/20059/kimnan.20050915150730.rar
Tag:
编程博客 发表于03:09:53 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-15
数据结构算法集锦 - [数据结构]
B.判断longint范围内的数是否为素数(包含求50000以内的素数表):
procedure getprime;
var
i,j:longint;
p:array[1..50000] of boolean;
begin
fillchar(p,sizeof(p),true);
p[1]:=false;
i:=2;
while i<50000 do begin
if p[i] then begin
j:=i*2;
while j<50000 do begin
p[j]:=false;
inc(j,i);
end;
end;
inc(i);
end;
l:=0;
for i:=1 to 50000 do
if p[i] then begin
inc(l);pr[l]:=i;
end;
end;{getprime}

function prime(x:longint):integer;
var i:integer;
begin
prime:=false;
for i:=1 to l do
if pr[i]>=x then break
else if x mod pr[i]=0 then exit;
prime:=true;
end;{prime}

二、图论算法
1.最小生成树
A.Prim算法:
procedure prim(v0:integer);
var
lowcost,closest:array[1..maxn] of integer;
i,j,k,min:integer;
begin
for i:=1 to n do begin
lowcost[i]:=cost[v0,i];
closest[i]:=v0;
end;
for i:=1 to n-1 do begin
{寻找离生成树最近的未加入顶点k}
min:=maxlongint;
for j:=1 to n do
if (lowcost[j]<MIN)&NBSP;AND&NBSP;(LOWCOST[J]< />0) then begin
min:=lowcost[j];
k:=j;
end;
lowcost[k]:=0; {将顶点k加入生成树}
{生成树中增加一条新的边k到closest[k]}
{修正各点的lowcost和closest值}
for j:=1 to n do
if cost[k,j]<LWOCOST[J]&NBSP;THEN&NBSP;BEGIN
lowcost[j]:=cost[k,j];
closest[j]:=k;
end;
end;
end;{prim}
B.Kruskal算法:(贪心)
按权值递增顺序删去图中的边,若不形成回路则将此边加入最小生成树。
function find(v:integer):integer; {返回顶点v所在的集合}
var i:integer;
begin
i:=1;
while (i<=n) and (not v in vset[i]) do inc(i);
if i<=n then find:=i else find:=0;
end;
procedure kruskal;
var
tot,i,j:integer;
begin
for i:=1 to n do vset[i]:=[i];{初始化定义n个集合,第I个集合包含一个元素I}
p:=n-1; q:=1; tot:=0; {p为尚待加入的边数,q为边集指针}
sort;
{对所有边按权值递增排序,存于e[I]中,e[I].v1与e[I].v2为边I所连接的两个顶点的序号,e[I].len为第I条边的长度}
while p>0 do begin
i:=find(e[q].v1);j:=find(e[q].v2);
if i<>j then begin
inc(tot,e[q].len);
vset[i]:=vset[i]+vset[j];vset[j]:=[];
dec(p);
end;
inc(q);
end;
writeln(tot);
end;
2.最短路径
A.标号法求解单源点最短路径:
var
a:array[1..maxn,1..maxn] of integer;
b:array[1..maxn] of integer; {b[i]指顶点i到源点的最短路径}
mark:array[1..maxn] of boolean;
procedure bhf;
var
best,best_j:integer;
begin
fillchar(mark,sizeof(mark),false);
mark[1]:=true; b[1]:=0;{1为源点}
repeat
best:=0;
for i:=1 to n do
If mark[i] then {对每一个已计算出最短路径的点}
for j:=1 to n do
if (not mark[j]) and (a[i,j]>0) then
if (best=0) or (b[i]+a[i,j]<BEST)&NBSP;THEN&NBSP;BEGIN
best:=b[i]+a[i,j]; best_j:=j;
end;
if best>0 then begin
b[best_j]:=best;mark[best_j]:=true;
end;
until best=0;
end;{bhf}
B.Floyed算法求解所有顶点对之间的最短路径:
procedure floyed;
begin
for I:=1 to n do
for j:=1 to n do
if a[I,j]>0 then p[I,j]:=I else p[I,j]:=0; {p[I,j]表示I到j的最短路径上j的前驱结点}
for k:=1 to n do {枚举中间结点}
for i:=1 to n do
for j:=1 to n do
if a[i,k]+a[j,k]<A[I,J]&NBSP;THEN&NBSP;BEGIN
a[i,j]:=a[i,k]+a[k,j];
p[I,j]:=p[k,j];
end;
end;
C. Dijkstra 算法:
var
a:array[1..maxn,1..maxn] of integer;
b,pre:array[1..maxn] of integer; {pre[i]指最短路径上I的前驱结点}
mark:array[1..maxn] of boolean;
procedure dijkstra(v0:integer);
begin
fillchar(mark,sizeof(mark),false);
for i:=1 to n do begin
d[i]:=a[v0,i];
if d[i]<>0 then pre[i]:=v0 else pre[i]:=0;
end;
mark[v0]:=true;
repeat {每循环一次加入一个离1集合最近的结点并调整其他结点的参数}
min:=maxint; u:=0; {u记录离1集合最近的结点}
for i:=1 to n do
if (not mark[i]) and (d[i]<MI
Tag:
编程博客 发表于02:53:57 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-07
消息映射的实现 - [MFC]
1. 一些消息处理类的OnCmdMsg的实现
从以上论述知道,OnCmdMsg虚拟函数在MFC命令消息的发送中扮演了重要的角色,CFrameWnd的OnCmdMsg实现了MFC的标准命令消息发送路径。
那么,就产生一个问题:如果命令消息不送给边框窗口对象,那么就不会有按标准命令发送路径发送消息的过程?答案是肯定的。例如一个菜单被一个对话框窗口所拥有,那么,菜单命令将送给MFC对话框窗口对象处理,而不是MFC边框窗口处理,当然不会和CFrameWnd的处理流程相同。
但是,有一点需要指出,一般标准的SDI和MDI应用程序,只有主边框窗口拥有菜单和工具条等用户接口对象,只有在用户与用户接口对象进行交互时,才产生命令,产生的命令必然是送给SDI或者MDI程序的主边框窗口对象处理。
下面,讨论几个MFC类覆盖OnCmdMsg虚拟函数时的实现。这些类的OnCmdMsg或者可能是标准MFC命令消息路径的一个环节,或者可能是一个独立的处理过程(对于其中的MFC窗口类)。
从分析CView的OnCmdMsg实现开始。
• CView的OnCmdMsg
CView::OnCmdMsg(UINT nID, int nCode, void* pExtra,
AFX_CMDHANDLERINFO* pHandlerInfo)
首先,调用CWnd::OnCmdMsg,结果是搜索当前视的类和基类的消息映射数组,搜索顺序是从下层到上层。若某一层实现了对命令消息nID的处理,则调用它的实现函数;否则,调用m_pDocument->OnCmdMsg,把命令消息送给文档类处理。m_pDocument是和当前视关联的文档对象指针。如果文档对象类实现了OnCmdMsg,则调用它的覆盖函数;否则,调用基类(例如CDocument)的OnCmdMsg。
接着,讨论CDocument的实现。
• CDocument的 OnCmdMsg
BOOL CDocument::OnCmdMsg(UINT nID, int nCode, void* pExtra,
AFX_CMDHANDLERINFO* pHandlerInfo)
首先,调用CCmdTarget::OnCmdMsg,导致当前对象(this)的类和基类的消息映射数组被搜索,看是否有对应的消息处理函数可用。如果有,就调用它;如果没有,则调用文档模板的OnCmdMsg函数(m_pTemplate->OnCmdMsg)把消息送给文档模板处理。
MFC文档模板没有覆盖OnCmdMsg,导致基类CCmdTarget的OnCmdMsg被调用,看是否有文档模板类或基类实现了对消息的处理。是的话,调用对应的消息处理函数,否则,返回FALSE。从前面的分析知道,CCmdTarget类的消息映射数组是空的,所以这里返回FALSE。
• CDialog的OnCmdMsg
BOOL CDialog::OnCmdMsg(UINT nID, int nCode, void* pExtra,
AFX_CMDHANDLERINFO* pHandlerInfo)
1. 调用CWnd::OnCmdMsg,让对话框或其基类处理消息。
2. 如果还没有处理,而且是控制消息或系统命令或非命令按钮,则返回FALSE,不作进一步处理。否则,调用父窗口的OnCmdmsg(GetParent()->OnCmdmsg)把消息送给父窗口处理。
3. 如果仍然没有处理,则调用当前线程的OnCmdMsg(GetThread()->OnCmdMsg)把消息送给线程对象处理。
4. 如果最后没有处理,返回FALSE。
• CMDIFrameWnd的OnCmdMsg
对于MDI应用程序,MDI主边框窗口首先是把命令消息发送给活动的MDI文档边框窗口进行处理。MDI主边框窗口对OnCmdMsg的实现函数的原型如下:
BOOL CMDIFrameWnd::OnCmdMsg(UINT nID, int nCode, void* pExtra,
AFX_CMDHANDLERINFO* pHandlerInfo)
1. 如果有激活的文档边框窗口,则调用它的OnCmdMsg(MDIGetActive()->OnCmdMsg)把消息交给它进行处理。MFC的文档边框窗口类并没有覆盖OnCmdMsg函数,所以基类CFrameWnd的函数被调用,导致文档边框窗口的活动视、文档边框窗口本身、应用程序对象依次来进行消息处理。
2. 如果文档边框窗口没有处理,调用CFrameWnd::OnCmdMsg把消息按标准路径发送,重复第一次的步骤,不过对于MDI边框窗口来说不存在活动视,所以省却了让视处理消息的必要;接着让MDI边框窗口本身来处理消息,如果它还没有处理,则让应用程序对象进行消息处理──虽然这是一个无用的重复。
除了CView、CDocument和CMDIFrameWnd类,还有几个OLE相关的类覆盖了OnCmdMsg函数。OLE的处理本书暂不涉及,CDialog::OnCmdMsg将在对话框章节专项讨论其具体实现。
1. 一些消息处理类的OnCommand的实现
除了虚拟函数OnCmdMsg,还有一个虚拟函数OnCommand在命令消息的发送中占有重要地位。在处理命令或者通知消息时,OnCommand被MFC窗口过程调用,然后它调用OnCmdMsg按一定路径传送消息。除了CWnd类和一些OLE相关类外,MFC里主要还有MDI边框窗口实现了OnCommand。
BOOL CMDIFrameWnd::OnCommand(WPARAM wParam, LPARAM lParam)
第一,如果存在活动的文档边框窗口,则使用AfxCallWndProc调用它的窗口过程,把消息送给文档边框窗口来处理。这将导致文档边框窗口的OnCmdMsg作如下的处理:
活动视处理消息→与视关联的文档处理消息→本文档边框窗口处理消息→应用程序对象处理消息→文档边框窗口缺省处理
任何一个环节如果处理消息,则不再向下发送消息,处理终止。如果消息仍然没有被处理,就只有交给主边框窗口了。
第二,第一步没有处理命令,继续调用CFrameWnd::OnCommand,将导致CMDIFrameWnd的OnCmdMsg被调用。从前面的分析知道,将再次把消息送
Tag:
编程博客 发表于02:32:43 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-07
消息映射的实现(2) - [MFC]
2. 虚拟函数OnCmdMsg
CCmdTarget的虚拟函数OnCmdMsg,用来传递和发送消息、更新用户界面对象的状态,其原型如下:
OnCmdMsg(
UINT nID,
int nCode,
void* pExtra,
AFX_CMDHANDLERINFO* pHandlerInfo)
框架的命令消息传递机制主要是通过该函数来实现的。其参数描述参见4.3.3.2章节DispacthCMdMessage的参数描述。
在本书中,命令目标指希望或者可能处理消息的对象;命令目标类指命令目标的类。
CCmdTarget对OnCmdMsg的默认实现:在当前命令目标(this所指)的类和基类的消息映射数组里搜索指定命令消息的消息处理函数(标准Windows消息不会送到这里处理)。
这里使用虚拟函数GetMessageMap得到命令目标类的消息映射入口数组_messageEntries,然后在数组里匹配指定的消息映射条目。匹配标准:命令消息ID相同,控制通知代码相同。因为GetMessageMap是虚拟函数,所以可以确认当前命令目标的确切类。
如果找到了一个匹配的消息映射条目,则使用DispachCmdMsg调用这个处理函数;
如果没有找到,则使用_GetBaseMessageMap得到基类的消息映射数组,查找,直到找到或搜寻了所有的基类(到CCmdTarget)为止;
如果最后没有找到,则返回FASLE。
每个从CCmdTarget派生的命令目标类都可以覆盖OnCmdMsg,利用它来确定是否可以处理某条命令,如果不能,就通过调用下一命令目标的OnCmdMsg,把该命令送给下一个命令目标处理。通常,派生类覆盖OnCmdMsg时,要调用基类的被覆盖的OnCmdMsg。
在MFC框架中,一些MFC命令目标类覆盖了OnCmdMsg,如框架窗口类覆盖了该函数,实现了MFC的标准命令消息发送路径。具体实现见后续章节。
必要的话,应用程序也可以覆盖OnCmdMsg,改变一个或多个类中的发送规定,实现与标准框架发送规定不同的发送路径。例如,在以下情况可以作这样的处理:在要打断发送顺序的类中把命令传给一个非MFC默认对象;在新的非默认对象中或在可能要传出命令的命令目标中。
本节对CCmdTarget的两个成员函数作一些讨论,是为了对MFC的消息处理有一个大致印象。后面4.4.3.2节和4.4.3.3节将作进一步的讨论。
1. MFC窗口过程
前文曾经提到,所有的消息都送给窗口过程处理,MFC的所有窗口都使用同一窗口过程,消息或者直接由窗口过程调用相应的消息处理函数处理,或者按MFC命令消息派发路径送给指定的命令目标处理。
那么,MFC的窗口过程是什么?怎么处理标准Windows消息?怎么实现命令消息的派发?这些都将是下文要回答的问题。
1. MFC窗口过程的指定
从前面的讨论可知,每一个“窗口类”都有自己的窗口过程。正常情况下使用该“窗口类”创建的窗口都使用它的窗口过程。
MFC的窗口对象在创建HWND窗口时,也使用了已经注册的“窗口类”,这些“窗口类”或者使用应用程序提供的窗口过程,或者使用Windows提供的窗口过程(例如Windows控制窗口、对话框等)。那么,为什么说MFC创建的所有HWND窗口使用同一个窗口过程呢?
在MFC中,的确所有的窗口都使用同一个窗口过程:AfxWndProc或AfxWndProcBase(如果定义了_AFXDLL)。它们的原型如下:
LRESULT CALLBACK
AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
LRESULT CALLBACK
AfxWndProcBase(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
这两个函数的原型都如4.1.1节描述的窗口过程一样。
如果动态链接到MFC DLL(定义了_AFXDLL),则AfxWndProcBase被用作窗口过程,否则AfxWndProc被用作窗口过程。AfxWndProcBase首先使用宏AFX_MANAGE_STATE设置正确的模块状态,然后调用AfxWndProc。
下面,假设不使用MFC DLL,讨论MFC如何使用AfxWndProc取代各个窗口的原窗口过程。
窗口过程的取代发生在窗口创建的过程时,使用了子类化(Subclass)的方法。所以,从窗口的创建过程来考察取代过程。从前面可以知道,窗口创建最终是通过调用CWnd::CreateEx函数完成的,分析该函数的流程,如图4-1所示。

图4-1中的CREATESTRUCT结构类型的变量cs包含了传递给窗口过程的初始化参数。CREATESTRUCT结构描述了创建窗口所需要的信息,定义如下:
typedef struct tagCREATESTRUCT {
LPVOID lpCreateParams; //用来创建窗口的数据
HANDLE hInstance; //创建窗口的实例
HMENU hMenu; //窗口菜单
HWND hwndParent; //父窗口
int cy; //高度
int cx; //宽度
int y; //原点Y坐标
int x;//原点X坐标
LONG style; //窗口风格
LPCSTR lpszName; //窗口名
LPCSTR lpszClass; //窗口类
DWORD dwExStyle; //窗口扩展风格
} CREATESTRUCT;
cs表示的创建参数可以在创建窗口之前被程序员修改,程序员可以覆盖当前窗口类的虚拟成员函数PreCreateWindow,通过该函数来修改cs的style域,改变窗口风格。这里cs的主要作用是保存创建窗口的各种信息,::CreateWindowEx函数使用cs的各个域作为参数来创建窗口,关于该函数见2.2.2节。
在创建窗口之前,创建了一个WH_CBT类型的钩子(Hook)。这样,创建窗口时所有的消息都会被钩子过程函数_AfxCbtFilterHook截获。
AfxCbtFilterHook函数首先检查是不是希望处理的Hook──HCBT_CREATEWND
Tag:
编程博客 发表于02:31:55 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-07
消息映射的实现(1) - [MFC]
1. Windows消息概述
Windows应用程序的输入由Windows系统以消息的形式发送给应用程序的窗口。这些窗口通过窗口过程来接收和处理消息,然后把控制返还给Windows。
1. 消息的分类
1. 队列消息和非队列消息
从消息的发送途径上看,消息分两种:队列消息和非队列消息。队列消息送到系统消息队列,然后到线程消息队列;非队列消息直接送给目的窗口过程。
这里,对消息队列阐述如下:
Windows维护一个系统消息队列(System message queue),每个GUI线程有一个线程消息队列(Thread message queue)。
鼠标、键盘事件由鼠标或键盘驱动程序转换成输入消息并把消息放进系统消息队列,例如WM_MOUSEMOVE、WM_LBUTTONUP、WM_KEYDOWN、WM_CHAR等等。Windows每次从系统消息队列移走一个消息,确定它是送给哪个窗口的和这个窗口是由哪个线程创建的,然后,把它放进窗口创建线程的线程消息队列。线程消息队列接收送给该线程所创建窗口的消息。线程从消息队列取出消息,通过Windows把它送给适当的窗口过程来处理。
除了键盘、鼠标消息以外,队列消息还有WM_PAINT、WM_TIMER和WM_QUIT。
这些队列消息以外的绝大多数消息是非队列消息。
2. 系统消息和应用程序消息
从消息的来源来看,可以分为:系统定义的消息和应用程序定义的消息。
系统消息ID的范围是从0到WM_USER-1,或0X80000到0XBFFFF;应用程序消息从WM_USER(0X0400)到0X7FFF,或0XC000到0XFFFF;WM_USER到0X7FFF范围的消息由应用程序自己使用;0XC000到0XFFFF范围的消息用来和其他应用程序通信,为了ID的唯一性,使用::RegisterWindowMessage来得到该范围的消息ID。
1. 消息结构和消息处理
1. 消息的结构
为了从消息队列获取消息信息,需要使用MSG结构。例如,::GetMessage函数(从消息队列得到消息并从队列中移走)和::PeekMessage函数(从消息队列得到消息但是可以不移走)都使用了该结构来保存获得的消息信息。
MSG结构的定义如下:
typedef struct tagMSG { // msg
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
} MSG;
该结构包括了六个成员,用来描述消息的有关属性:
接收消息的窗口句柄、消息标识(ID)、第一个消息参数、第二个消息参数、消息产生的时间、消息产生时鼠标的位置。
2. 应用程序通过窗口过程来处理消息
如前所述,每个“窗口类”都要登记一个如下形式的窗口过程:
LRESULT CALLBACK MainWndProc (
HWND hwnd,// 窗口句柄
UINT msg,// 消息标识
WPARAM wParam,//消息参数1
LPARAM lParam//消息参数2
)
应用程序通过窗口过程来处理消息:非队列消息由Windows直接送给目的窗口的窗口过程,队列消息由::DispatchMessage等派发给目的窗口的窗口过程。窗口过程被调用时,接受四个参数:
a window handle(窗口句柄);
a message identifier(消息标识);
two 32-bit values called message parameters(两个32位的消息参数);
需要的话,窗口过程用::GetMessageTime获取消息产生的时间,用::GetMessagePos获取消息产生时鼠标光标所在的位置。
在窗口过程里,用switch/case分支处理语句来识别和处理消息。
3. 应用程序通过消息循环来获得对消息的处理
每个GDI应用程序在主窗口创建之后,都会进入消息循环,接受用户输入、解释和处理消息。
消息循环的结构如下:
while (GetMessage(&msg, (HWND) NULL, 0, 0)) {//从消息队列得到消息
if (hwndDlgModeless == (HWND) NULL ||
!IsDialogMessage(hwndDlgModeless, &msg) &&
!TranslateAccelerator(hwndMain, haccel, &msg)) {
TranslateMessage(&msg);
DispatchMessage(&msg); //发送消息
}
}
消息循环从消息队列中得到消息,如果不是快捷键消息或者对话框消息,就进行消息转换和派发,让目的窗口的窗口过程来处理。
当得到消息WM_QUIT,或者::GetMessage出错时,退出消息循环。
4. MFC消息处理
使用MFC框架编程时,消息发送和处理的本质也如上所述。但是,有一点需要强调的是,所有的MFC窗口都使用同一窗口过程,程序员不必去设计和实现自己的窗口过程,而是通过MFC提供的一套消息映射机制来处理消息。因此,MFC简化了程序员编程时处理消息的复杂性。
所谓消息映射,简单地讲,就是让程序员指定要某个MFC类(有消息处理能力的类)处理某个消息。MFC提供了工具ClassWizard来帮助实现消息映射,在处理消息的类中添加一些有关消息映射的内容和处理消息的成员函数。程序员将完成消息处理函数,实现所希望的消息处理能力。
如果派生类要覆盖基类的消息处理函数,就用ClassWizard在派生类中添加一个消息映射条目,用同样的原型定义一个函数,然后实现该函数。这个函数覆盖派生类的任何基类的同名处理函数。
下面几节将分析MFC的消息机制的实现原理和消息处理的过程。为此,首先要分析ClassWizard实现消息映射的内幕,然后讨论MFC的窗口过程,分析MFC窗口过程是如何实现消息处理的。
1. 消息映射的定义和实现
1. MFC处理的三类消息
根据处理函数和处理过程的不同,MFC主要处理三类
Tag:
编程博客 发表于02:30:47 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-07
CObject类 - [MFC]
3. CObject类
CObject是大多数MFC类的根类或基类。CObject类有很多有用的特性:对运行时类信息的支持,对动态创建的支持,对串行化的支持,对象诊断输出,等等。MFC从CObject派生出许多类,具备其中的一个或者多个特性。程序员也可以从CObject类派生出自己的类,利用CObject类的这些特性。
本章将讨论MFC如何设计CObject类的这些特性。首先,考察CObject类的定义,分析其结构和方法(成员变量和成员函数)对CObject特性的支持。然后,讨论CObject特性及其实现机制。
1. CObject的结构
以下是CObject类的定义:
class CObject
{
public:
//与动态创建相关的函数
virtual CRuntimeClass* GetRuntimeClass() const;
析构函数
virtual ~CObject(); // virtual destructors are necessary
//与构造函数相关的内存分配函数,可以用于DEBUG下输出诊断信息
void* PASCAL operator new(size_t nSize);
void* PASCAL operator new(size_t, void* p);
void PASCAL operator delete(void* p);
#if defined(_DEBUG) && !defined(_AFX_NO_DEBUG_CRT)
void* PASCAL operator new(size_t nSize, LPCSTR lpszFileName, int nLine);
#endif
//缺省情况下,复制构造函数和赋值构造函数是不可用的
//如果程序员通过传值或者赋值来传递对象,将得到一个编译错误
protected:
//缺省构造函数
CObject();
private:
//复制构造函数,私有
CObject(const CObject& objectSrc); // no implementation
//赋值构造函数,私有
void operator=(const CObject& objectSrc); // no implementation
// Attributes
public:
//与运行时类信息、串行化相关的函数
BOOL IsSerializable() const;
BOOL IsKindOf(const CRuntimeClass* pClass) const;
// Overridables
virtual void Serialize(CArchive& ar);
// 诊断函数
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
// Implementation
public:
//与动态创建对象相关的函数
static const AFX_DATA CRuntimeClass classCObject;
#ifdef _AFXDLL
static CRuntimeClass* PASCAL _GetBaseClass();
#endif
};
由上可以看出,CObject定义了一个CRuntimeClass类型的静态成员变量:
CRuntimeClass classCObject
还定义了几组函数:
构造函数析构函数类,
诊断函数,
与运行时类信息相关的函数,
与串行化相关的函数。
其中,一个静态函数:_GetBaseClass;五个虚拟函数:析构函数、GetRuntimeClass、Serialize、AssertValid、Dump。这些虚拟函数,在CObject的派生类中应该有更具体的实现。必要的话,派生类实现它们时可能要求先调用基类的实现,例如Serialize和Dump就要求这样。
静态成员变量classCObject和相关函数实现了对CObjet特性的支持。
2. CObject类的特性
下面,对三种特性分别描述,并说明程序员在派生类中支持这些特性的方法。
1. 对运行时类信息的支持
该特性用于在运行时确定一个对象是否属于一特定类(是该类的实例),或者从一个特定类派生来的。CObject提供IsKindOf函数来实现这个功能。
从CObject派生的类要具有这样的特性,需要:
• 定义该类时,在类说明中使用DECLARE_DYNAMIC(CLASSNMAE)宏;
• 在类的实现文件中使用IMPLEMENT_DYNAMIC(CLASSNAME,BASECLASS)宏。
1. 对动态创建的支持
前面提到了动态创建的概念,就是运行时创建指定类的实例。在MFC中大量使用,如前所述框架窗口对象、视对象,还有文档对象都需要由文档模板类(CDocTemplate)对象来动态的创建。
从CObject派生的类要具有动态创建的功能,需要:
• 定义该类时,在类说明中使用DECLARE_DYNCREATE(CLASSNMAE)宏;
• 定义一个不带参数的构造函数(默认构造函数);
• 在类的实现文件中使用IMPLEMENT_DYNCREATE(CLASSNAME,BASECLASS)宏;
• 使用时先通过宏RUNTIME_CLASS得到类的RunTime信息,然后使用CRuntimeClass的成员函数CreateObject创建一个该类的实例。
例如:
CRuntimeClass* pRuntimeClass = RUNTIME_CLASS(CNname)
//CName必须有一个缺省构造函数
CObject* pObject = pRuntimeClass->CreateObject();
//用IsKindOf检测是否是CName类的实例
Assert( pObject->IsKindOf(RUNTIME_CLASS(CName));
1. 对序列化的支持
“序列化”就是把对象内容存入一个文件或从一个文件中读取对象内容的过程。从CObject派生的类要具有序列化的功能,需要:
• 定义该类时,在类说明中使用DECLARE_SERIAL(CLASSNMAE)宏;
• 定义一个不带参数的构造函数(默认构造函数);
• 在类的实现文件中使用IMPLEMENT_SERIAL(CLASSNAME,BASECLASS)宏;
• 覆盖Serialize成员函数。(如果直接调用Serialize函数进行序列化读写,可以省略前面三步。)
对运行时类信息的支持、动态创建的支持、串行化的支持层(不包括直接调用Serailize实现序列化),这三种功能的层次依次升高。如果对后面的功能支持,必定对前面的功能支持。支持动态
Tag:
编程博客 发表于02:29:02 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-07
MFC和Win32 - [MFC]
MFC和Win32
1. MFC Object和Windows Object的关系
MFC中最重要的封装是对Win32 API的封装,因此,理解Windows Object和MFC Object (C++对象,一个C++类的实例)之间的关系是理解MFC的关键之一。所谓Windows Object(Windows对象)是Win32下用句柄表示的Windows操作系统对象;所谓MFC Object (MFC对象)是C++对象,是一个C++类的实例,这里(本书范围内)MFC Object是有特定含义的,指封装Windows Object的C++ Object,并非指任意的C++ Object。
MFC Object 和Windows Object是不一样的,但两者紧密联系。以窗口对象为例:
一个MFC窗口对象是一个C++ CWnd类(或派生类)的实例,是程序直接创建的。在程序执行中它随着窗口类构造函数的调用而生成,随着析构函数的调用而消失。而Windows窗口则是Windows系统的一个内部数据结构的实例,由一个“窗口句柄”标识,Windows系统创建它并给它分配系统资源。Windows窗口在MFC窗口对象创建之后,由CWnd类的Create成员函数创建,“窗口句柄”保存在窗口对象的m_hWnd成员变量中。Windows窗口可以被一个程序销毁,也可以被用户的动作销毁。MFC窗口对象和Windows窗口对象的关系如图2-1所示。其他的Windows Object和对应的MFC Object也有类似的关系。
下面,对MFC Object和Windows Object作一个比较。有些论断对设备描述表(MFC类是CDC,句柄是HDC)可能不适用,但具体涉及到时会指出。
1. 从数据结构上比较
MFC Object是相应C++类的实例,这些类是MFC或者程序员定义的;
Windows Object是Windows系统的内部结构,通过一个句柄来引用;
MFC给这些类定义了一个成员变量来保存MFC Object对应的Windows Object的句柄。对于设备描述表CDC类,将保存两个HDC句柄。
2. 从层次上讲比较
MFC Object是高层的,Windows Object是低层的;
MFC Object封装了Windows Object的大部分或全部功能,MFC Object的使用者不需要直接应用Windows Object的HANDLE(句柄)使用Win32 API,代替它的是引用相应的MFC Object的成员函数。
3. 从创建上比较
MFC Object通过构造函数由程序直接创建;Windows Object由相应的SDK函数创建。
MFC中,使用这些MFC Object,一般分两步:
首先,创建一个MFC Object,或者在STACK中创建,或者在HEAP中创建,这时,MFC Object的句柄实例变量为空,或者说不是一个有效的句柄。
然后,调用MFC Object的成员函数创建相应的Windows Object,MFC的句柄变量存储一个有效句柄。
CDC(设备描述表类)的创建有所不同,在后面的2.3节会具体说明CDC及其派生类的创建和使用。
当然,可以在MFC Object的构造函数中创建相应的Windows对象,MFC的GDI类就是如此实现的,但从实质上讲,MFC Object的创建和Windows Object的创建是两回事。
4. 从转换上比较
可以从一个MFC Object得到对应的Windows Object的句柄;一般使用MFC Object的成员函数GetSafeHandle得到对应的句柄。
可以从一个已存在的Windows Object创建一个对应的MFC Object; 一般使用MFC Object的成员函数Attach或者FromHandle来创建,前者得到一个永久性对象,后者得到的可能是一个临时对象。
5. 从使用范围上比较
MFC Object对系统的其他进程来说是不可见、不可用的;而Windows Object一旦创建,其句柄是整个Windows系统全局的。一些句柄可以被其他进程使用。典型地,一个进程可以获得另一进程的窗口句柄,并给该窗口发送消息。
对同一个进程的线程来说,只可以使用本线程创建的MFC Object,不能使用其他线程的MFC Object。
6. 从销毁上比较
MFC Object随着析构函数的调用而消失;但Windows Object必须由相应的Windows系统函数销毁。
设备描述表CDC类的对象有所不同,它对应的HDC句柄对象可能不是被销毁,而是被释放。
当然,可以在MFC Object的析构函数中完成Windows Object的销毁,MFC Object的GDI类等就是如此实现的,但是,应该看到:两者的销毁是不同的。
每类Windows Object都有对应的MFC Object,下面用表格的形式列出它们之间的对应关系,如表2-1所示:
表2-1 MFC Object和Windows Object的对应关系
描述 Windows句柄 MFC Object
窗口 HWND CWnd and CWnd-derived classes
设备上下文 HDC CDC and CDC-derived classes
菜单 HMENU CMenu
笔 HPEN CGdiObject类,CPen和CPen-derived classes
刷子 HBRUSH CGdiObject类,CBrush和CBrush-derived classes
字体 HFONT CGdiObject类,CFont和CFont-derived classes
位图 HBITMAP CGdiObject类,CBitmap和CBitmap-derived classes
调色板 HPALETTE CGdiObject类,CPalette和CPalette-derived classes
区域 HRGN CGdiObject类,CRgn和CRgn-derived classes
图像列表 HimageLIST CimageList和CimageList-derived classes
套接字 SOCKET CSocket,CAsynSocket及其派生类
 
________________________________________
表2-1中的OBJECT分以下几类:
Windows对象,
设备上下文对象,
GDI对象(BITMAP,BRUSH,FONT,PALETTE,PEN,RGN),
菜单,
图像列表,
网络套接字接口
Tag:
编程博客 发表于02:28:01 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-07
MFC概述 - [MFC]
1. MFC概述
1. MFC是一个编程框架
MFC (Microsoft Foundation Class Library)中的各种类结合起来构成了一个应用程序框架,它的目的就是让程序员在此基础上来建立Windows下的应用程序,这是一种相对SDK来说更为简单的方法。因为总体上,MFC框架定义了应用程序的轮廓,并提供了用户接口的标准实现方法,程序员所要做的就是通过预定义的接口把具体应用程序特有的东西填入这个轮廓。Microsoft Visual C++提供了相应的工具来完成这个工作:AppWizard可以用来生成初步的框架文件(代码和资源等);资源编辑器用于帮助直观地设计用户接口;ClassWizard用来协助添加代码到框架文件;最后,编译,则通过类库实现了应用程序特定的逻辑。
1. 封装
构成MFC框架的是MFC类库。MFC类库是C++类库。这些类或者封装了Win32应用程序编程接口,或者封装了应用程序的概念,或者封装了OLE特性,或者封装了ODBC和DAO数据访问的功能,等等,分述如下。
(1)对Win32应用程序编程接口的封装
用一个C++ Object来包装一个Windows Object。例如:class CWnd是一个C++ window object,它把Windows window(HWND)和Windows window有关的API函数封装在C++ window object的成员函数内,后者的成员变量m_hWnd就是前者的窗口句柄。
(2)对应用程序概念的封装
使用SDK编写Windows应用程序时,总要定义窗口过程,登记Windows Class,创建窗口,等等。MFC把许多类似的处理封装起来,替程序员完成这些工作。另外,MFC提出了以文档-视图为中心的编程模式,MFC类库封装了对它的支持。文档是用户操作的数据对象,视图是数据操作的窗口,用户通过它处理、查看数据。
(3)对COM/OLE特性的封装
OLE建立在COM模型之上,由于支持OLE的应用程序必须实现一系列的接口(Interface),因而相当繁琐。MFC的OLE类封装了OLE API大量的复杂工作,这些类提供了实现OLE的更高级接口。
(4)对ODBC功能的封装
以少量的能提供与ODBC之间更高级接口的C++类,封装了ODBC API的大量的复杂的工作,提供了一种数据库编程模式。
2. 继承
首先,MFC抽象出众多类的共同特性,设计出一些基类作为实现其他类的基础。这些类中,最重要的类是CObject和CCmdTarget。CObject是MFC的根类,绝大多数MFC类是其派生的,包括CCmdTarget。CObject 实现了一些重要的特性,包括动态类信息、动态创建、对象序列化、对程序调试的支持,等等。所有从CObject派生的类都将具备或者可以具备CObject所拥有的特性。CCmdTarget通过封装一些属性和方法,提供了消息处理的架构。MFC中,任何可以处理消息的类都从CCmdTarget派生。
针对每种不同的对象,MFC都设计了一组类对这些对象进行封装,每一组类都有一个基类,从基类派生出众多更具体的类。这些对象包括以下种类:窗口对象,基类是CWnd;应用程序对象,基类是CwinThread;文档对象,基类是Cdocument,等等。
程序员将结合自己的实际,从适当的MFC类中派生出自己的类,实现特定的功能,达到自己的编程目的。
3. 虚拟函数和动态约束
MFC以“C++”为基础,自然支持虚拟函数和动态约束。但是作为一个编程框架,有一个问题必须解决:如果仅仅通过虚拟函数来支持动态约束,必然导致虚拟函数表过于臃肿,消耗内存,效率低下。例如,CWnd封装 Windows窗口对象时,每一条Windows消息对应一个成员函数,这些成员函数为派生类所继承。如果这些函数都设计成虚拟函数,由于数量太多,实现起来不现实。于是,MFC建立了消息映射机制,以一种富有效率、便于使用的手段解决消息处理函数的动态约束问题。
这样,通过虚拟函数和消息映射,MFC类提供了丰富的编程接口。程序员继承基类的同时,把自己实现的虚拟函数和消息处理函数嵌入MFC的编程框架。MFC编程框架将在适当的时候、适当的地方来调用程序的代码。本书将充分的展示MFC调用虚拟函数和消息处理函数的内幕,让读者对MFC的编程接口有清晰的理解。
4. MFC的宏观框架体系
如前所述,MFC实现了对应用程序概念的封装,把类、类的继承、动态约束、类的关系和相互作用等封装起来。这样封装的结果对程序员来说,是一套开发模板(或者说模式)。针对不同的应用和目的,程序员采用不同的模板。例如,SDI应用程序的模板,MDI应用程序的模板,规则DLL应用程序的模板,扩展DLL应用程序的模板,OLE/ACTIVEX应用程序的模板,等等。
这些模板都采用了以文档-视为中心的思想,每一个模板都包含一组特定的类。典型的MDI应用程序的构成将在下一节具体讨论。
为了支持对应用程序概念的封装,MFC内部必须作大量的工作。例如,为了实现消息映射机制,MFC编程框架必须要保证首先得到消息,然后按既定的方法进行处理。又如,为了实现对DLL编程的支持和多线程编程的支持,MFC内部使用了特别的处理方法,使用模块状态、线程状态等来管理一些重要信息。虽然,这些内部处理对程序员来说是透明的,但是,懂得和理解MFC内部机制有助于写出功能灵活而强大的程序。
总之,MFC封装了Win32 API,OLE API,ODBC API等底层函数的功能,并提供更高一层的接口,简化了Windows编程。同时,MFC支持对底层API的直接调用。

Tag:
编程博客 发表于02:27:02 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-07
内部排序 - [数据结构]
第十章 内部排序

排序:将一个数据元素的无序序列调整成为一个有序序列。
递增或递减序列:n个关键字序列。
K1()K2()…… ()Kn

排序分类:(两种分法)
第一种分类:稳定、不稳定排序;
第二种分类:内部、外部排序;

无序:
12 34 56 13 12 28 33
有序:
12 12 13 28 33 34 56 稳定的

12 12 13 28 33 34 56 不稳定的

 

内部排序:五类 (每类均基于同一种思想,同一种原则)

1. 插入排序:基于插入的思想;
2. 快速排序:基于交换的思想;
3. 选择排序:每趟在m个纪录中选一个最小(大)的;
4. 归并排序:将两个或两个以上的有序表组成一新的
有序表;
5. 基数排序:借助多关键字的排序思想对单逻辑关键字
进行排序。


──只考虑文件的顺序存贮结构。


Type rcdtype = record
Key : integer;
Otheritem : anytype
end;
listtype = array[ 0..n ] of rcdtype;

10.1 插入排序

1. 直接插入排序
直接插入排序(straight insertion sort):

一种最简单的排序方法, 基本操作是将一个记录插入到已排好序的有序表中, 从而得到一个新的, 记录数增1的有序表。

──设监视哨r[0],每次存待插入的r[i]
──倒查子表,找位置,后移。

Proc straipass( var r : listtype; i : integer );
{已知 r[1..i1]中的记录按关键字非递减有序排列, 本算法插入r[i], 使r[1..i]中记录按关键字非递减排列}
r[0]:=r[i]; j:=i1; x:=r[i].key;
while x<r[j].key do { 找位置,后移 }
[r[j+1]:=r[j] ; j:=j1]
r[j+1]:=r[0] { 插入r[i] }
Endp; {straipass }

Proc straisort(var r : listtype);
{对r[1..n]进行直接插入排序, 本算法使r[1..n]中的记录按关键字非递减有序排列}
for i:=2 to n do straipass( r , i )
Endp; {straisort}

时间复杂度: 1/4n2 ; O(n2)

主要考虑:比较次数
移动次数

2. 折半插入排序
──从比较次数提高效率

──用折半查找确定插入位置;

Proc binpass(var r: listtype; i: integer);
{同算法straipass中的说明,利用折半查找确定插入位置}
r[0]:=r[i]; x:=r[i].key;
s:=1; j:=i1;
while s<=j do
[ m:=[(s+j)/2];
if x<r[m].key
then j:=m1
else s:=m+1
];
for k:=i1 downto j+1 do
r[k+1]:=r[k];
r[j+1]:=r[0]
Endp; { binpass }

Proc binsort(var r : listtype);
{对r[1..n]进行折半插入排序, 本算法使 r[1..n]中记录按关
键字非递减有序排列}
for i:=2 to n do binpass(r, i)
Endp; { binsort }

比较次数<nLog2n

3. 二路插入排序──减少移动次数

──减少移动次数,但增加与原文件等量的辅助空间。
──用一个元素将文件分成两半,分别在这两部份上
作折半插入排序。

4. 链表插入排序──修改指针代替移动

以静态链表作存储结构。

5.希尔(Shell)排序──缩小增量排序

希尔(Shell)排序(又称缩小增量排序):
也属插入排序类的方法,但在时间效率上较前述几种排序方法有较大的改进。

基本思想: 先将待排表(文件)分割为若干个子表(文件),对每个子表(文件)分别进行直接插入排序。
──给出一增量序列(递减最后至1)。
──按增量进行分组,将相隔某个“增量”的纪录组成一个子表(文件)。
──取增量序列应注意,使此序列中的值没有除1以外的公因子,且最后一个的增量值必须为1。


PROC shellins(i , d, s : integer);
{已知序列r[i-kd], r[i-(k-1)d],...,r[i-2d], r[i-d] (1<=i-kd<i)中的记录按关键字非递减有序排列, 本算法插入记录r[i], 使新
的序列r[i-kd],..., r[i-d], r[i]仍按关键字非递减有序。s(=i-(k+1)d) 为所设监视哨的位置 }

r[s]:=r[i]; j:=i-d; x:=r[i].key;
while x<r[j].key do
[ r[j+d]:=r[j]; j:=j-d ];
r[j+d]:=r[s]
Endp; { shellins }

Proc shellpass( dh : integer);
{本算法进行一趟希尔排序, dh为增量}
s:=1 - dh; { 插入r[dh+1]时的监视哨位置}
for i := 1+dh to n do
[ shellins( i, dh, s); s:=s+1;
if s>0 then s:=1 - dh
]
Endp; { shellpass }

Proc shellsort( var r : array[-d[1]+1 .. n] of rcdtype;
d : array[1..t] of integer);
{ 本算法对r[1..n]中记录进行t 趟希尔排序 d[1]<n,
d[2],...,d[t]=1为各趟排序的增量序列 }
for i:=1 to t do shellpass(d[i]);
Endp;
Tag:
编程博客 发表于02:23:00 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-07
查找(1) - [数据结构]
第九章 查 找

查找、排序:非数值程序设计中两个重要技术问题。

查找:总是在含众多的数据元素(或纪录)的表(或文件,
后面一律称表)中查找一个“特定的”数据元素
(或纪录)。

表:线性表、树表、哈希表(Hash)。

术语:

关键字(Key):数据元素中某个数据项的值用以标识
一个数据元素(或纪录);
主关键字(Primary Key):唯一标识一个纪录;
次关键字(Secondary Key):可用以识别多个纪录;

查找:根据给定的某个值,在表中确定一个关键字等
于给定值的纪录。

查找方法随表中纪录按何种结构组织而定。


两方面:
1. 算法;
2. 性能分析(求平均查找长度)


9.1 静态查找表

9.1.1 线性表的查找

表中元素以一维数组或链表的形式存贮。

类型定义:
Type
rectype=record
key : keytype; {值为关键字的域}
… … {记录的其它域}
end;
sqlisttp = array[ 0 .. n ] of rectype;


顺序查找(sequential search) (倒查表)
查找过程为从表中第n个记录开始,逐个进行记录的关键字和给定值的比较,若某个记录的关键字和给定值相等,则查找成功,找到所查记录; 反之,若直至第一个记录,其关键字和给定值比较都不等,则表明表中没有所查记录,查找不成功。

倒查表:利用r[0]作为监视哨 r[0].key:=K ;

Func seqsrch( r : sqlisttp ; K : keytype) : integer;
{r[1..n]中存贮n个纪录,若存在其关键字等于给定值K的纪录,则返回函数值i是记录在线性表r中的序号,否则返回函数值为0 }
r[0].key := K ; i:=n ;
while r[i].key<>K do i:=i1;
return(i)
End; {seqsrch}

查找成功的平均长度:(简记ASL)
对含n个记录的表, 查找成功时的平均查找长度为:
ASL=PiCi i=1 … n, (9-1)
其中: Pi为查找表中第i个记录的概率,
且 Pi=1; i=1…n,
Ci为找到表中第i个记录其关键字与给定关键字相等的记录时,与给定值进行过比较的关键字的个数。
从顺序查找的过程可见,Ci取决于所查记录在表中
的位置.
一般情况下Ci = ni+1
顺序查找的平均查找长度为:
ASL=nP1+ ... +2Pn1+Pn (9-2)
假设每个记录的查找概率相等,即Pi=1/n
则顺序查找成功的平均查找长度为
ASLss=PiCi i=1…n,
=1/n(ni+1) i=1…n,

ASLss=(n+1)/2 (9-3) O(n)


9.1.2 有序表的查找

折半查找的查找过程:
先确定待查记录所在的范围(区间), 然后逐步缩小范围直到找到或找不到该记录为止。

TYPE
Rectype = record
key : keytype;
... …
end;
ordlisttp=array[1..n]of rectype; {有序表}

Func binsrch( r: ordlisttp; K : keytype) : integer;
{在有序表r[1..n]中折半查找其关键字等于给定值K的纪录,函数返回值i是记录在线性表r中的序号,返回值为0表示没有关键字等于给定值K的纪录}
low:=1; high:=n; {置区间初值}
found := false;
while ( low<=high ) and not found do
[ mid:=(low+high) div 2; {判别查找区间大小}
case
K>r[mid].key : low:=mid+1;
K=r[mid].key : found:=true;
K<r[mid].key : high:=mid1;
end;
]
if found then return(mid) {查找成功}
else return(0); {查找不成功}
Endf;

平均查找长度(ASL)
假定表的长度n=2h1( 反之, h=Log2(n+1)), 则描述折半查找的判定树是深度为h的满二叉树。树中层次为1的结点有1个,树中层次为2的结点有2个,树中层次为h的结点有2h-1个。设表中每个记录的查找概率相等(Pi=1/n)。则折半查找的平均查找长度为:
ASLbs=PiCi i=1…n,
=1/nj*2(j-1) j=1…h,
=(n+1)/n*Log2(n+1)1 (9-5)
对任意的n, 当n较大(n>50)时,可有
下列近似结果:
ASLbs= Log2(n+1)1 (9-6)
O(Log2N)

将n2 变为n*Logn,

将n 变为 Logn

另外, 查找有序表的方法还有斐波那契查找和
插值查找。

key=23

f1=1; f2=1;
fn=fn-1 +fn-2

1 1 2 3 5 8 13 21 34 55 89


1 2 3 4 5 6 7 8 9 10 11 12
20 23 25 29 31 35 38 40 45 49 51 55

 

9.1.3索引顺序表的查找

索引顺序查找又称分块查找。

索引表:将整个表分为m个子表,且子表i+1中所有关键字大于子表 i中最大关键字;
索引表存两项内容:
1 子表i中最大关键字
2 子表i在表中的起始位置

索引表是有序表。

分块查找:
1.调折半查找算法在索引表中确定K所属子表i;
2.顺序查找子表i。

1
Tag:
编程博客 发表于02:22:00 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-07
图(2) - [数据结构]
普里姆算法(Prim)求最小生成树 :
连通网: N = ( V, E )
TE 是N上最小生成树中边的集合
初始:U={ u0 }, ( u0V )
TE=;
重复: 在所有uU, vVU的边(u,v)中选一条最小代价的边(u’,v’), 并入集合TE, v’ 并入U,直至U=V。

增设一辅助数组:closedge
Type
R = record
Lowcost : real;
Vex : integer;
end;
Var
Closedge : array[1..vexnumber] of R;

Closedge[v].lowcost=min{ cost[ u , v ]  uU }
{既是v到U中所有顶点的最小代价,若此值为0,则表示v已并入U中。}
Closedge[v].vex=u {uU,且(v,u)是v到U中所有顶点的最小代价,此值存于closedge[v].lowcost }

2 3 4 5 6 U V-U
Vex
Lowcost 1
6 1
1 1
3 max max 1 2,3,4
5,6
Vex
Lowcost 3
5
0 1
3 3
6 3
4 1,3 2,4
5.6
Vex
Lowcost 3
5
0
0 3
6 4
2 1,3
4 2
5,6
Vex
Lowcost 3
5
0
0 3
6
0 1,3
4,6 2,5
Vex
Lowcost
0
0
0 2
3
0 1,3,4
6,2 5
Vex
Lowcost
0
0
0
0
0 1,3,4
6,2,5


Proc minispantree_prim( gn : adjmatrix; u0 : vtxptr);
{从u0出发构造网gn的最小生成树, 按Prim法输出生成树上各条边}
for v:=1 to vtxnum do
if v<> u0 then
with closedge[v] do
[vex:= u0 ; lowcost:=gn[u0, v] ];
closedge[u0]. lowcost:=0; {辅助数组初始化}
for i:=1 to vtxnum - 1 do
[ k:=minimum(closedge);
write(closedge[k].vex , k); {输出生成树的边}
closedge[k].lowcost:=0; {顶点k并入U集}
for v:=1 to vtxnum do
if gn[k, v]<closedge[v].lowcost
then
[closedge[v].lowcost:=gn[k,v];
closedge[v].vex:=k ]
]{在新顶点并入U之後,重新选择具有最小代价边}
Endp;
____适合于求边稠密的网的最小生成树


克鲁斯卡尔算法求最小生成树:

连通网:N=(V, E )
初始:T=(V, { })
重复:在E中选择代价最小的边,若该边依附的顶点落在T中不同的连通分量上, 则将此边加入到T中, 否则舍去此边而选择下一条代价最小的边。直至T中所有顶点都在同一连通分量上为止。

____适合于求边稀疏的网的最小生成树

 

7.5 有向无环图及其应用

有向无环图(directed acycline graph):
一个无环的有向图称做有向无环图,简称DAG图。

有向无环图:
1.描述含有公共子式的表达式;
2.描述工程或系统的进行过程。

问题:
1.工程能否顺利进行(流程图是否有环)
2.估算整个工程所必需最短时间。

★对有向图进行拓扑排序和求关键路径

环的判别
无向图:若在深度优先搜索遍历的过程中遇到已访问
的顶点,则必定有环。
有向图:若从某顶点v出发的搜索在dfs(v)或bfs(v)未结
束之前,出现一条从顶点u到顶点v的回边,则
此有向图必存在环。

前驱、后继:若从顶点 i到顶点 j有一条路径,则 i
是 j的前驱,j是i的后继;
直接前驱、直接后继:若(i , j )是一条弧,则 i是 j的
直接前驱,j是i的直接后继;

判有向图是否有环:若某顶点后继顶点中有其自身,则有环。

7.5.1 拓扑排序

拓扑排序 : 由某个集合上的一个偏序得到该集合上的一个全序,这个操作称之为拓扑排序.

即有序的按原序,无序的指定一个序。

偏序关系 : 若集合X上的关系R是自反的,反对称的和传递的,则称R是集合X上的偏序关系。

全序关系 : 设R是集合X上的偏序,如果对每个x, yX必有xRy或yRx, 则称R是集合X上的全序关系.

AOV网:顶点表示活动,弧表示活动间优先关系。
AOV网不应有环。

判AOV网是否有环:通过对有向图构造其顶点的拓扑有序序列,若网中顶点都在它的拓扑序列中,则该AOV网必定不存在环。

拓扑排序:
1.在有向图中选一没有前驱的顶点输出;
2.从图中删除该顶点和所有以它为尾的弧;
3.重复上述两步,直至全部顶点均已输出,或当前图中不存在无前驱的顶点为止。后一种情况说明有向图中存在环。


* 将表头结点的入度域为0的顶点链成一个链,
构成链栈。

拓扑排序算法:
1.初始建入度为0的顶点链栈;
2.出栈顶,删弧,度为0的进栈;
3.若栈空则结束,否则转步2。

Type
arclink=arcnode;
arcnode=record
adjvex:vexptr;
nextarc:arclink
end;
vexnode=record
vexdata : {顶点信息};
indegree : integer;
firstarc : arclink
end;
adjlisttp = array[vexptr] of vexnode;

Func topsort(var dig:adjlisttp; n, e : integer) : boolean;
{设D为有n个顶点,e条弧的有向图,dig是D的邻接表。若有向图中无环, 则拓扑排序完成, 返回函数值为true; 否则返回函数值为false}
crt_adjlist(dig); {
Tag:
编程博客 发表于02:21:01 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-07
图(1) - [数据结构]
7.1. 图的定义、术语

图的形式定义:
G=(V, E)
V:顶点集;
E:边(弧)集;
V, E均为有限集。

图:有限个顶点和有限条边的集合。

顶点: 图中的数据元素;
有向图:其边是点的有序对,称为弧;
弧:(x , y)有序对, x为弧尾, y为弧头;
无向图:其边是点的无序对;
边:(x , y)无序对;

以下用n表示顶点数,e表示边数,
不考虑顶点到自身的弧或边:

无向图:0 <= e <= n(n1)/2;
有向图:0 <= e <= n(n1);

无向完全图: n顶点, 有n(n1)/2条边
有向完全图: n顶点, 有n(n1)条弧

稀疏图: e << nLogn;
稠密图: e >> nLogn;


权: 与图的边或弧相关联的数
网: 带权图

子图:VV, EE;
G=(V, E), G=(V, E);
则G是G的子图。

邻接点:对无向图G=( V, E), 如果边(v, v)E, 则称顶点v和v互为邻接点;

依附:边(v, v)依附于顶点v和v;
相关联 :(v, v)和顶点v和v相关联。

度:和顶点相关联的边数目, 记TD(v)
入度:以顶点v为头的弧数目, 记ID(v)
出度:以顶点v为尾的弧数目, 记OD(v)
TD(v)=ID(v)+OD(v)


☆ 如果顶点vi的度记为TD(vi), 一个有n个顶点,e条边或弧的图,满足如下关系:

 

路径:无向图G=(V, E)中从顶点v到顶点v’的路径是一个顶点序列。
路径长度:路径上的边或弧的数目。
回路或环:第一个顶点和最后一个顶点相同的路径。
简单路径:序列中顶点不重复出现的路径。
连通:无向图中, 如果从顶点v到顶点 v’有路径, 则称v和v’是连通的。
连通图:图中任意两个顶点都连通。
连通分量:无向图中的极大连通子图。

强连通图:有向图G中,如果对任一对(vi, vj)V, vivj, 从
vi到vj和从vj到vi都存在路径。则称G强连通图。

强连通分量:有向图中的极大连通子图。

生成树:是连通图的一个极小连通子图。含图中所有n个顶点,但只n1条边,如在一棵生成树上添加一条边,必定构成环;一棵有 n个顶点的生成树有且仅有n1条边。(无向图)

判定:如一个图有n个顶点而小于n1条边,则非连通图;如多于n1条边,则一定有环;
但有n个顶点n1条边的图,不一定是生成树。

生成森林:由若干棵有向树组成 (有向图)


★ 图的顶点间不存在全序关系,而为操作方便,
人为为各顶点编号。


★图的基本操作:
(1) LOC_VERTEX(G , v) 顶点定位函数。值为顶点v在图G中的位置。若图中无此顶点, 则函数值为"零"。
(2) GET_VERTEX(G , i ) 取顶点函数。求图G中第i个顶点。若i>图G中顶点数, 则函数值为"空"。
(3) FIRST_ADJ(G, v) 求第一个邻接点函数。求图G中顶点v的第一个邻接点。若v没有邻接点或图G中无顶点v, 则函数值为"零"。
(4) NEXT_ADJ(G , v, w) 求下一个邻接点函数。 已知w为图G中顶点v的某一个邻接点。求顶点v相对于顶点w的下一个邻接点。若w是v的最後一个邻接点,则函数值为"零"。
(5) INS_VERTEX(G, u) 插入顶点操作。在图G中增添一个顶点u为图G的第n+1个顶点, 其中n为插入之前图G中含顶点的个数。
(6) INS_AEC(G, v, w) 插入边或弧的操作。在图G中增添一条顶点v到顶点w的边或弧。
(7) DEL_VERTEX(G, v) 删除顶点操作。在图G中删除顶点v以及所有与顶点v相关联的边或弧。
(8) DEL_ARC(G, v, w) 删除边或弧操作。在图G中删去一条从顶点v到w的边或弧。


7.2 图的存储结构

7.2.1 数组表示法--------------邻接矩阵

☆数组表示法: 用二维数组存贮图。

存储结构:
Const vtxnum = 图的顶点数;
Type
vtxptr = 1 .. vtxnum ;
bit = 0..1 ;
vertex = record {顶点纪录类型}
... ... {和顶点相关的信息}
end;
arc = record {弧(或边)纪录类型}
adj : bit; {表示两顶点间关系}
... ... {和弧(或边)相关信息}
end;
graph = record
vexs : array[vtxptr] of vertex;
arcs : array[vtxptr , vtxptr] of arc
end;

☆ 若图中顶点只有1至vtxnum的编号,边或弧上
无其它信息,则只要以一个二维数组表示图的邻
接矩阵即可。

☆ 无向图的邻接矩阵是对称矩阵。

Proc crt_graph( var ga : graph );
{建立用邻接矩阵存储的无向网ga}
for i:=1 to vtxnum do
read( ga.vexs[i] ); {输入vtxnum个顶点的信息}
for i:=1 to vtxnum do
for j:=1 to vtxnum do ga.arcs[ i, j ].adj := max;
read(e);
for k:=1 to e do
[ read(v1, v2, w); {输入边的信息, w为该边的权}
i :=locvertex(ga, v1); {i和j分别为该边依附的两
j:=locvertex(ga, v2); 个顶点在图中的位置编号}
ga.arcs[ i, j ] .
Tag:
编程博客 发表于02:20:05 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-07
树和二叉树 - [数据结构]
树形结构:一类重要的非线性数据结构。
(以分支关系定义的层次结构)
应用广泛:族谱,社会组织机构, 算法的模块
流程图等等。
讨 论:1存贮结构
2 各种操作
3 树和森林与二叉树的转换

6.1 树定义和操作

★树(tree) : 是n个结点的有限集。
递归定义如下:
n=0时 称为空树;
n0 时 即非空树时:
1有且仅有一个称为根(root)的结点;
2当n>1时,其余结点可分为m(m0)个互不相交的
有限集T1, T2, ..., Tm, 其中每个集合本身又是一棵
树,称为根的子树(subtree)。

★树的图示表示方法主要有以下几种:

1层次表示法(倒挂树);
2嵌套集合表示法;
3广义表表示法;
4凹入表示法;

★基本术语:
1树的结点:包含一个数据元素及若干指向其子树的分枝。
2结点的度:结点拥有的子树数。
3叶子(终端结点):度为0的结点。
4分支结点(非终端结点):度不为0的结点。
5内部结点:除根结点外的分支结点。
6树的度:树内各结点的度的最大值。
7结点的孩子、双亲、兄弟:结点的子树的根称为该
结点 的孩子; 相应的,该结点称为孩子的双亲;
同一个双亲的孩子之间互称兄弟;
8结点的祖先:从根到该结点所经分支上的所有结点。
9结点的子孙: 以该结点为根的子树中的任一结点都称
为该结点的子孙。
10结点的层次:从根开始定义起,根为第一层, 根的
孩子为第二层,若某结点在第L层,则其子树的根
就在第 L+1层。
11结点堂兄弟 : 其双亲在同一层的结点互为堂兄弟。
12树的深度(高度):树中结点的最大层次。
13有序树:各子树从左至右排列不能互换。
14无序树:各子树从左至右排列可以互换。
15森林:m(m0)棵互不相交的树的集合。
对树中每个结点而言,其子树的集合即为森林。


★树的基本操作
(1) Initiate(T) 初始化操作,置T为空树。
(2) Root(T) 或Root(x) 求根函数。求树T的根或求结
点x 所在的树的根结点。若T是空树或结点x不
在树中,则函数值为null。
(3) Parent( T, x ) 求双亲函数。求T中结点x的双亲
结点。 若结点x是树T的根结点或结点x不在树
T中则函数值为 null。
(4) Child( T , x , i ) 求孩子结点函数。求树T 中结点x
的 第i 个孩子结点。若结点x是树T的叶子或无
第i个 孩子结点或不在树T中,则函数值为null。
(5) Right_sibling( T , x )求右兄弟函数。求树T中结点
x 右边的兄弟。若结点x是其双亲的最右边的孩子
结点或结点 x不在树T中,则函数值为null。
(6) Crt_Tree( x , F ) 建树函数。生成一棵以x结点为根
以 森林 F为子树森林的树。
(7) Ins_Child(T, y , i, x ) 插入子树操作。将以结点x为
根的 树作为 树T中结点y的第i 棵子树。若T中
无结点y或 结点y 的子 树个数<i-1,则空操作。
(8) Del_Child(T, x, i) 删除子树操作。删除树T中结点
x的 第 i棵子树。若无结点 x或结点x的子树个
数<i,则空 操作。
(9) Traverse( T ) 遍历操作。按某个次序依次访问树中
各个 结 点,并使每个结点只被访问一次。
(10) Clear(T) 清除树结构操作。将树T置为空树。


6.2 二叉树_________从易到难
二叉树____一般树____森林

6.2.1 二叉树的定义和操作

★ 二叉树(binary tree) : 是n个结点的有限集。此集合
或是空的,或只有一个根的结点,或由根结点加上
两棵分别称为左子树和右子树的互不相交的,次序
不可交换的子二叉树组成。

6.2.2 二叉树的性质:
性质1 在二叉树的第i层上至多有2i-1个结点(i1)。
性质2 深度为k的二叉树至多有2k-1个结点(k1)。
性质3 若有n个结点的二叉树T有n0片叶子,度为
2的结点为n2个,则n0=n2+1。
证:设 度为0的结点为n0个;
度为1的结点为n1个;
度为2的结点为n2个;
分支数为B;
则 n=n0+n1+n2;
而 B+1=n
又有B=2*n2+n1
故:n0+n1+n2=2*n2+n1+1
所以:n0=n2+1 .

★ 满二叉树:深度为k且有2k-1个结点的二叉树。
若 从根开始,自上而下,自左至右为满二叉树编
号,从 而可引出完全二叉树。

★ 完全二叉树:深度为k的,有n个结点的二叉树,
当 且仅当每个结点都与深度为k的满二叉树中的
编号从 1至n 的结点一一对应时。

★完全二叉树的特点:
1叶子结点只能在层次最大两层上出现
2对任一结点,若其右分支下的子孙的最大层次为
L,则其 左分支下的子孙的最大层次不小于L
(左层次右层次)

★完全二叉树的性质:

性质4 具有n个结点的完全二叉树的深度为
Log2n +1。
性质5 若对有n个结点的完全二叉树按其对应的满
二叉树编号,对任意的i,若1in 则有:
1 若i=1则i是二叉树的根,无双亲,若i>1则其
双亲是结点 i/2;
2若2i>n则结点i无左孩子(即结点i为叶子);否

原创粉丝点击