*注:所有加粗仅代表个人角度
文章中可能有的指针部分星号与变量之间有空格,可自行删除,因为我在编辑时发现可能会识别成斜体或加粗,所以我就加了空格
如内容有问题,欢迎给我留言,我会及时更改
部分PPT上的代码没有放上来,可以百度一下相关标题或者自行查看PPT
我整理的教材资源
包括计算机硬件和计算机软件。
硬件指构成计算机系统的元器件、部件和设备,其中包括运算器、控制器、存储器、输入和输出设备,运算器和控制器是计算机的核心部分,人们将它们称为中央处理器(CPU)。
软件是用户操纵计算机的接口界面,通常,按照应用层次可以将软件划分成系统软件、支撑软件和应用软件三个层次。
是指设计、编写和调试程序的方法与过程,大致经历了以下几个阶段。
1)面向计算机的程序设计
2)面向过程的程序设计
3)面向对象的程序设计
程序设计语言是用于编写计算机程序的语言。按照语言级别可以将它分为两个类别:低级语言和高级语言。
低级语言是一种与特定计算机体系结构密切相关的程序设计语言,主要包括机器语言和汇编语言。
高级语言是一类采用接近数学语言,并力求与具体机器无关的程序设计语言形式,它具有描述能力强,便于阅读理解,易于修改维护等特点。C语言支持结构化程序设计,C++语言和Java语言支持面向对象程序设计。
C语言本身比较简单,具有简明的数据定义和流程控制机制。开发者可以通过模块的组合来构造结构化的复杂程序,并且允许软件系统不同程序模块的分别开发。C语言支持底层程序设计。
C程序是由若干个函数组成的,每个函数用于描述一项操作的具体实现过程。
任何一个完整的C程序都必须有且仅有一个名为main的主函数。
当程序运行后,系统将率先自动调用主函数。
整型int:用4字节(32位二进制位)表示,包括1位符号,有效位数为31位
实型:常用的实型是双精度型,用double表示。8字节(64位二进制位)表示,包括1位符号,11位指数和52位尾数
字符型char:一对单引号括起来,并且每个字符对应一个ASCII编码,用1个字节(8位二进制位)表示。
Long4字节
Short2字节
Unsigned4字节
(以上三项为个人查阅,有问题不负责)
常量:常量是指在程序运行过程中始终不发生变化的量。
整型常量
实型常量:指数形式:1.87E+10表示1.87×1010
字符常量:一对单引号(‘’)括起来,其内部存储表示是相应字符的ASCII编码。转义符是指用一个反斜杠(\)后跟一个特定字符或一个八进制或十六进制数值表示的字符。例如:\n, \101
字符串常量:字符串常量用一对双引号(“”)括起来。
变量:变量是指其值可以改变的量,每个变量代表了不同的存储单元。
C语言规定:程序中的每一个变量,必须先定义后使用
定义变量的语法格式为:
<数据类型> <变量名>[,<变量名>[,<变量名>…]];
例如:int count;
C语言规定:变量名用标识符表示。标识符是由字母、数字和下划线(_)组成的字符序列,其中第1个字符必须是字母,字母需要区分大小写。
变量的赋值
变量定义之后并没有一个确切的初始值,变量赋值就是将变量所属数据类型的某个数值(介于取值范围之中)放入系统为这个变量分配的存储空间中的操作。
在定义变量的同时为变量赋予一个初始值。
<数据类型> <变量名> = <常量表达式>;
例如:int data = 100;
通过赋值操作为变量赋值。
<变量名> = <表达式>
例如: x = 64;
字符的非格式化输入 getchar( )
字符的非格式化输出 putchar( )
格式化输入 scanf( )
scanf(<格式控制字符串>,<变量地址>,<变量地址>… );
例如:scanf(“%d%d%f%f”, &x, &y, &f1, &f2);
常用的格式控制说明符由“%”后跟一个特定字符或字符序列组成“%d”表示这个位置应该输入一个十进制整型数值;“%c”表示这个位置应该输入一个字符;“%f”表示这个位置应该输入一个实型数值,“%ld”表示这个位置应该输入一个长整型数值
<变量地址>是准备用来存放输入数据的变量地址。例如,&a、&value分别表示变量a、value的存储地址
格式化输出 printf( )
printf(<格式控制字符串>,<表达式>,<表达式>…);
例如: printf(“This value is %d\n”, x);
printf( )函数的基本功能是将每个表达式的结果按照格式控制说明符的规则显示到标准输出设备——显示器上。格式控制说明符需要与将要输出的表达式一一对应。
人们希望能够更加准确地控制每个数值输出时所占据的列数,为此,C语言提供了相应的功能。其格式为:%m 和 %m.n
m表示数值输出时在屏幕上占据的列数,又称为场宽,n表示输出实型数值时小数点后的位数
加(+)、减(-)、乘(*,不能省略 )、除(/)、取余(%)
% (求余) 运算对象只能是整型
整型/整型 相当于取整,一个为实型即为除法
C语言允许char类型的变量或常量参与各种算术运算,但在运算时,它将被看成一个整型数值,其值为字符对应的ASCII编码。例如:‘A’+32等于用大写字符’A’的ASCII编码65与32相加结果为97
自增、自减运算符 + + 、- -
a = 10; b = ++a; 表达式的值取修改后的值 先加再赋值
a 为 11 ,b为11
a=10; b=a++; 表达式的值取修改前的值 先赋值再加
a 为 11 ,b为10
算术表达式是指仅包含算术运算符或自加(++)、自减(–)运算符的表达式。算术表达式的结果一定是数值类型的,即整型或实型
C语言规定,在书写表达式时,应该遵循下列规则:
(1)所有内容都必须写在一行上,例如:必须写成7/8
(2)在表达式中只允许使用圆括号。为了确保表达式中的每个运算符能够按照所期望的顺序进行计算,应该适当地添加括号。
(3)表达式中的乘号不允许省略,例如:(a+1)(a-1) 必须写成 (a+1)*(a-2)
顺序结构是指按照语句的书写顺序依次执行每条语句的语句结构。
选择结构是指根据某些数据的取值或计算结果选取不同操作的处理方式。
选择结构的描述由两个基本部分组成,一是对选择条件的描述;二是对处理分支的描述。
关系运算符
逻辑运算符
对于数学表示形式 0 < a < 10,不能直接地采用这种方式书写,而需要将它分解成两个关系运算,即分解成0小于a并且a小于10,并按照下列格式书写: 0 < a && a < 10
对于逻辑表达式 x >= 0 && y++ ,如果x小于0,将不计算y++。
if ( <条件表达式> ) <真分支语句> else <假分支语句>
switch ( <表达式> ) {
case <常量> : <语句序列>
case <常量> : <语句序列>
…
case <常量> : <语句序列>
default : <语句序列>
}
执行的基本过程:首先计算充当开关角色的表达式;然后,根据计算结果进行控制的转移,即用开关值与下面每个case语句中的常量进行比较;如果开关值等于某个常量,则执行该case语句中的语句序列;如果不存在等于开关值的case常量,则执行default语句中的语句序列。
所有的case常量不允许重复。
经常将break语句作为每个case分支的语句序列的最后一条语句,以表示该分支的计算结束,并随后跳出switch语句,终止switch语句的继续执行。
语法格式:
while ( <条件表达式> ) <语句>
语法格式:
for ( <初值表达式>; <条件表达式>; <增量表达式> ) <语句>
语法格式:
do <循环体语句> while ( <条件表达式> ) ;
利用计算机求解问题的一般过程
(1)问题分析阶段
(2)数据结构设计阶段
(3)算法设计阶段
(4)编码与调试阶段
算法描述
一种是自然语言的描述方法。鉴于自然语言本身过于灵活且又缺乏严谨性,所以容易产生理解上的歧义。
还有一种算法的图形描述方式——流程图。它采用一些标准的图形符号描述算法的操作过程,从而避免了人们对非形式化语言的理解差异。
核心在于明确问题的所有可能性,并针对每种可能情况逐个进行判断,最终找出正确问题的答案。
假设给定的整数用x表示,则判断过程就是确认x不能整除以2到x-1 之间的任何整数。这就需要一一列举出2到x-1之间的每个整数进行排查。
公鸡、母鸡和小鸡的数量是有限的,都不会超过100。通过对不同数量的公鸡、母鸡和小鸡进行组合,可以计算出购买这些鸡所用的花费,但这个题目要求找出那些花费正好100枚且鸡的总数也为100只的情况。因此,可以采用穷举法,将不同的公鸡、母鸡和小鸡的数量枚举一遍,找出那些符合题目要求的解。
递推常用于序列数据的计算。其基本策略是用已知结果和特定关系(递推公式)计算中间值。
采用递推法进行问题求解的关键在于找出递推公式和边界条件。
迭代也是计算机数值计算的一种基本算法,其基本策略是从初值出发,不断计算问题的近似解。
等比数列的递推公式为:
itemi = itemi-1 * ratio 后项等于前项乘以比例值
sumi = sumi-1 + itemi 前i项之和等于前i-1项之和加当前项
由于在重复上述递推计算之前,需要将第1项的值累加到sum中,所以,需要先将item存入sum中。
圆周率π的计算公式为:
π = 4 – 4/3 + 4/5 – 4/7 +4/9 – 4/11 + …
圆周率是通过将数列4、-4/3、4/5…求和得到的。在这个数列中,每个数据项的取值与前一项及该项的序号存在着一定的关系。
可以通过迭代,逐个计算出每一个数据项,再将它们累加起来。
为了满足要求的精度,可以通过检查数据项的大小来控制循环的终止。由于数据项的绝对值是递减的,且相邻项的符号不同,如果第n个数据项的绝对值已经小于精度值,则前n项之和一定已经满足精度要求了。
同时存在若干个用来描述同一性质且不同个体的数据。
只有将这些数据组织在一起形成批量数据,共同参与处理,很多操作才具有实际意义。
定义格式:
<元素类型> <数组变量名>[<元素数量>];
例如: int vote[10];
C语言规定:数组的下标从0开始,因此,表示这10个数据的下标为0~9
在C程序中,系统将会为每个数组型变量分配一片连续的存储空间,所需要分配的存储空间总数将取决于包含的元素个数和每个元素需要的存储空间。
基本格式为:
<元素类型> <数组变量名>[<元素数量>]={<元素初值1>,<元素初值2>,…,<元素初值n>};
例如:double score[5] = {9.2, 9.1, 8.7, 9.1, 8.5};
float score[ ] = {9.2, 9.1, 8.7, 9.1, 8.5};
int letter[26] = {10, 9, 8, 7};
它的执行结果是:将10、9、8、7分别赋予letter数组中下标为0、1、2、3的元素,后面的所有元素赋予初值0。
int vote[10] = {0};将数组型变量中的每一个元素赋予初值0。
<数组变量名>[<下标表达式>]
数组的赋值
利用赋值语句为数组赋值
for (i=0; i<10; i++){
vote[i] = 0;
}
调用标准输入函数为数组赋值
for (i=0; i<13; i++) {
scanf(“%f”, &score[i]);
}
数组的输出
for (i=0; i<10; i++) {
printf(“%5d”, vote[i]);
}
查找问题
查找是指根据某个给定的条件,在一组数据中搜索是否存在满足该条件的数据的过程。
已知一个按非递减有序排列的整型数列(12,23,30,45,48,50,67,82,91,103)。请编写一个程序,查找其中是否存在与给定key相等的数值。
#include
#define NUM 10
main( )
{
int value[NUM] = {12, 23, 30, 45, 48, 50, 67, 82, 91, 103}; /* 非递减整型数列 */
int low, high, mid, key;
printf("\nEnter a key:"); /* 输入查找的数值 */
scanf("%d", &key);
low = 0; high = NUM-1;
while (low<=high) {
mid = (low+high)/2;
if (value[mid]==key) break;
if (value[mid]<key)
low = mid+1;
else
high = mid-1;
}
if (low<=high)
printf("\n%d is found at %d.", key, mid); /* 确认break出口 */
else
printf("\n%d is not found.", key); /* 确认循环正常出口 */
}
排序问题
选择排序
将一组无序的数列重新排列成非递减或非递增的顺序是一种经常需要的操作。
首先从n个数据中选择一个最小的数据,并将它交换到第1个位置;然后再从后面n-1个数据中选择一个最小的数据,并将它交换到第2个位置;以此类推,直至最后从两个数据中选择一个最小的数据,并将它交换到第n-1个位置为止,整个排序操作结束。
字符串的组织形式
字符串是指一个有限长度的字符序列 ,字符串常量用一对双引号(“”)括起来。
字符串中所包含的字符个数被称为字符串长度。
字符串的初始化
char str[ ] = “C program”;
char str[ ] = {‘C’, ‘ ’, ‘p’, ‘r’, ‘o’, ‘g’, ‘r’, ‘a’, ‘m’, ‘\0’};
对于这种初始化形式,系统将其视为字符操作,而不是字符串操作,因此不会在尾部添加结束符‘\0’。
“C program”的存储状态
1、gets(str);
gets 以换行符作为输入结束标记,但不保存换行符。
2、scanf(“%s”, str);
scanf 以空格、换行符或制表符作为结尾。
字符串的输出
1、puts(str);
puts将字符串的内容显示到标准输出设备——屏幕上,并换行。
2、printf(%s”, str);
字符串标准函数
在C语言的标准函数库中,提供了30余种与字符串处理有关的标准函数,从而大大地提高了字符串处理的能力,降低了字符串处理的复杂程度。
计算字符串长度
strlen(str);这个函数的功能是返回字符串中所包含的字符个数,即字符串长度。字符串结束标志‘\0’不计算在内。
字符串比较
strcmp(str1, str2);两个字符串进行比较时将依据每个字符对应的ASCII编码决定其大小。
字符串拷贝
strcpy(str1, str2);其中str2是将要被拷贝的字符串,str1是用于存放拷贝结果的存储区域。
字符串连接
strcat(str1, str2);其中str1和str2是两个字符串。这个函数的功能是:将str2连接在str1之后,并在结束处添加一个字符串结束符‘\0’
字符串转换成数值类型
atof(str);
atoi(str);
atol(str);
其中str是一个字符串。atof() 的返回类型是double,它可以将字符串str转换成一个双精度数值;atoi() 的返回类型是int,它可以将字符串str转换成普通整型;atol() 的返回类型是long,它可以将字符串str转换成长整型(long)。
大小写转换
strlwr(str);
strupr(str);
str是一个字符串。标准函数strlwr( ) 可以将字符串str中出现的所有大写字母转换成小写字母;标准函数strupr( ) 可以将字符串str中出现的所有小写字母转换成大写字母。
定义格式:
<元素类型> <数组变量名>[<元素数量1>][<元素数量2>];
例如: int value[5][4];
value数组的每个元素类型为int,包含5行4列共20个元素
一旦定义了一个二维数组型变量,系统就会立即为其分配相应的存储空间用于存放数组中的每个元素。存储空间的数量取决于数组元素的类型和所定义的行数、列数,即存储空间数量=每个元素所占用的字节数量×行数×列数,并按照行列顺序依次排列 。
int a[4][3]={{12,11,10},{9,8,7},{6,5,4},{3,2,1}};
int a[4][3]={12,11,10,9,8,7,6,5,4,3,2,1};
int a[][3]= {12,11,10,9,8,7,6,5,4,3,2,1};
如果只对二维数组变量中的部分元素进行初始化,可以使用下面两种形式。
int array1[4][3]={{},{1},{1,2},{1,2,3}};
int array2[4][3]={10,9,8,7,6,5};
数组元素的引用
<数组变量名>[<下标表达式1>][<下标表达式2>]
数组的赋值
for (i=0; i<ROWS; i++)
for (j=0; j<COLS; j++)
value[i][j] = i+j;
数组的输入
for (i=0; i<ROWS; i++)
for (j=0; j<COLS; j++)
scanf(“%d”, &value[i][j]);
数组的输出
for (i=0; i<ROWS; i++){
for (j=0; j<COLS; j++)
printf(“%4d”, value[i][j]);
putchar(‘\n’);
}
对于一个给定的NN矩阵array,如果矩阵中的每个元素都满足array[i][j]=array[j][i],则称这个矩阵为对称矩阵。
在判断一个给定的矩阵是否为对称矩阵时,只需要用下三角部分的每个元素与对应的上三角元素进行比较。如果每一对元素都相等,这个矩阵就是对称矩阵,否则,就是非对称矩阵。
#include
#define NUM 5 矩阵行列数
main( )
{
int m[NUM][NUM]; /定义二维数组变量/
int i, j;
/* 输入矩阵 */
printf("\Enter %d rows %d cols datas for the maxtrix:\n",NUM,NUM);
for (i=0; i<NUM; i++)
for (j=0; j<NUM; j++)
scanf("%d", &m[i][j]);
/* 显示矩阵 */
for (i=0; i<NUM; i++){
for (j=0; j<NUM; j++)
printf("%4d", m[i][j]);
printf("\n");
}
/* 判断矩阵是否对称并输出相应的结果 */
for (i=0; i<NUM; i++)
for (j=0; j<i; j++)
if (m[i][j]!=m[j][i]) {
printf("\nThe matrix isn't symmetrical.");
return 0;
}
printf("\nThe matrix is symmetrical.");
}
结构化程序设计方法的核心是功能分解、逐步求精,具体的实现策略是将复杂的问题逐步分解成相对简单的子问题,这样将有利于降低解决问题的难度,提高程序开发的效率。将一个问题分解成若干个子问题的过程称为模块化。
在C程序中,模块用函数实现。函数是构成C程序的基本单位。它由函数首部和函数体两个部分组成,函数首部包含函数的返回类型、函数名称和参数表的声明,函数体包含实现特定功能所需要执行的语句序列。
函数原型是指不包含函数体的函数声明。
C语言规定,所有的函数必须先定义后调用。
标准函数它们的定义已经在C语言提供的标准函数库中,只需要在程序的前面利用编译预处理命令include将相应的函数原型加入到程序中就可以了。
基本格式
<函数返回类型> <函数名>(<参数表>)
{
<函数体>;
}
例:
double distance(int x, int y)
{
double d;
d = sqrt(x*x+y*y);
return d;
}
一个函数可以有返回值,也可以没有返回值。
**函数名不但应该符合C语言的自定义标识符命名规范,还应该“见名知意”。
参数表是函数之间交换信息的接口。**既可以通过它将外界的数据传递给函数,也可以通过它将函数的操作结果带出函数。如果形式参数属于一维数组类型,无须指出一维数组的元素个数。
函数体是函数的具体实现。
函数调用语句的基本格式为:
<函数名>(<实在参数表>);
实在参数与形式参数的数据类型和个数一一对应。
在声明函数的时候,函数名前使用了保留字void,说明这个函数没有返回值;否则,这个函数执行完毕后,应该返回一个相应类型的数值。
return 表达式;
定义函数时所给的参数被称为形式参数,这是由于当函数没有处于执行状态时,系统并不为这些参数分配存储空间,因此,在调用函数时,参数传递需要经历两个基本步骤:首先,根据形式参数的声明格式,为每一个形式参数分配存储空间;然后再将实在参数的值赋给对应的形式参数。
自定义函数的应用实例
1、给定的任意整数N可能存在两个素数,它们的和等于N。请编写程序,输入整数N,输出满足条件的所有素数。
对于这个问题,枚举法显然是最直接的解决方法,也就是逐个检查小于N/2的每个整数n;如果是素数,则检查N-n是否是素数,从而找出所有结果。
#include
int isprime(int x);
main( )
{
int n, m;
printf("请输出一个正整数:");
scanf("%d", &m);
for(n=2; n<m/2; n++) {
if( isprime(n) && isprime(m-n) )
printf("素数%d+%d等于%d\n", n, m-n, m);
}
}
int isprime(int x)
{
int t;
for(t=2; t<x; t++)
if( x%t==0 )
return 0;
return 1;
}
2、计算,要求精确度达到10-6。
在这个公式中,第i项的分子是xi;分母是i!。这些计算显然是相对独立的,都有各自的算法,因此分别设置函数power和factory来完成。
#include
long power(int x, int y);
long factorial(int n);
double expx(int x);
main( )
{
int x;
printf("\nEnter x:");
scanf("%d", &x);
printf("\ne^%d=%f", x, expx(x));
}
long power(int x, int y) /* 计算xy */
{
int result = 1;
for ( ; y>0; y-- ) /* 循环y次 */
result *= x; /* x个y相乘 */
return result;
}
int factorial(int n) /* 计算n! */
{
int result = 1;
while ( n>1 ) /* n! = n*(n-1)*…*2 */
result = result * n--;
return result;
}
double expx(int x)
{
double result = 1.0, tmp;
int i = 1;
do {
tmp = power(x,i)*1.0/factorial(i);
result += tmp;
i++;
} while( tmp >= 1E-6 );
return result;
}
由于用数组作为形式参数传递的是数组首元素的地址,所以在函数中对数组做出的任何改变都会被保留下来。
基本思路是不断地将所有相邻数据进行比效,如果前面的数据大于后面的数据(a[j]>a[j+1]),就将两个位置的数据进行交换,最终实现将所有的数据按照非递减的顺序重新排列的目的。
#include
#include
#define NUM 10
void input(int value[ ]);
void output(int value[ ]);
void sort(int value[ ]);
main( )
{
int value[NUM]; /* 存储待排序的数据数列 */
input(value);
output(value);
sort(value);
output(value);
}
void input(int value[ ]) /* 输入待排序数据 */
{
int i;
printf("\nEnter %d integers:",NUM);
for (i=0; i<NUM; i++)
scanf("%d", &value[i]);
}
void output(int value[ ]) /* 输出显示数据数列 */
{
int i;
printf("\n");
for (i=0; i<NUM; i++)
printf("%5d", value[i]);
}
void sort(int value[ ]) /* 冒泡排序函数 */
{
int i, j, temp;
for (i=NUM-1; i>=1; i--) /* 控制排序趟数 */
for (j=0; j<i; j++) /* 相邻数据比较大小 */
if (value[j]>value[j+1]){ /* 如果两个相邻数据逆序,交换 */
temp = value[j];
value[j] = value[j+1];
value[j+1] = temp;
}
}
有很多问题经过分解后会发现子问题的基本结构与原问题完全相同,因此,可以采用求解原问题的基本方法来求解各个子问题。这种解决问题的思路就是递归。
n!其含义为1 2 3 4 …… (n-1) n。从这个数学公式中可以发现, n!等于n与(n-1)!的乘积。即将计算n! 的过程分解成n与(n-1)!的乘积。
long fact(int n)
{
if (n==0)
return 1;
else
return n* fact(n-1);
}
解决这个问题似乎有些复杂,但采用递归方式就简单多了。3个数的全排列是每个数轮流充当一次第一个数,再加上后面n-1个数的全排列,而求解n-1个数的全排列方法与求解n个数的全排列方法完全一样,因此,可以设计一个递归函数,实现求n个数全排列的操作。考虑到每次递归过程中,将针对n个数据进行排列,而这些数据来自同一数据序列,故设置数组保存数据序列,以数组名和数据个数作为函数的参数。
#include
#define NUM 3
void anagram(int[ ], int); /* 全排列计算 */
void shift(int[ ], int); /* 循环位移 */
void print(int[ ]); /* 结果输出 */
main( )
{
int data[NUM];
int i;
for (i=0; i<NUM; i++)
data[i] = i+1;
anagram(data, NUM);
}
void anagram(int data[ ], int m)
{
int i;
if (m==1) { /* 前m-1元素已经排列好 */
print(data); /* 直接输出排列好的数据 */
return;
}
for (i=0; i<m; i++) { /* m次循环 */
anagram(data, m-1); /* 求数组中后m-1个元素的全排列 */
shift(data, m); /* 将数组中后m个元素向前循环移动一位 */
}
}
void shift(int data[ ], int n)
{
int i, temp;
temp = data[NUM-n]; /* 保留第一个位置的数 */
for (i=NUM-n; i<NUM-1; i++) /* 移动后n-1个数据 */
data[i] = data[i+1]; /* 将每个数据向前移 */
data[NUM-1] = temp; /* 将第一位置的数复制到末尾 */
}
void print(int d[ ]) /* 输出数组内容 */
{
int i;
for (i=0; i<NUM; i++)
printf("%d" ,d[i]);
printf("\n");
}
每个变量必须先定义后引用。
人们将变量占据存储空间的时间称为变量的生存期,将变量可以引用的区域称为变量的作用域。
从作用域角度划分
全局变量:在函数外部定义的变量被称为全局变量。
局部变量:在函数内部定义的变量,包括参数表中定义的形式参数被称为局部变量。
在复合语句中定义的变量被称为块变量。
生存期
局部变量(包括块变量)也叫自动变量(Auto),其生存期就是函数的执行期。
由于全局变量可以用于整个程序中,其生存期自然是这个程序的执行期。
自动变量和静态变量
局部变量(包括块变量)也叫自动变量(Auto),其生存期就是函数的执行期。函数调用时被分配空间,返回时释放空间,同时保存在局部变量中的数据被全部放弃,且被释放的存储空间会分配给后续的函数调用继续使用
静态变量采用保留字static描述,可以被赋初值,这种静态变量的生存期和全局变量相同,作用域和局部变量相同,因此外部函数无法访问该变量,但变量中的数据在多次调用后始终有效,可以连续使用。
void fun( )
{
static int count = 0;
count++;
printf(“%4d”, count);
}
采用下面的语句段调用上面的函数
for (i=1; i<=10; i++) {
fun( );
}
将会再屏幕上看到1 2 3 4 5 6 7 8 9 10
指针类型变量中存放的不是待操作的数据,而是那些待操作数据的存储地址。
地址是用来表示数据在内存中存放位置的一种标识
C语言提供了一种获取变量地址的途径。“&”被称为取地址运算符,它是一个一元运算符,其使用格式为:&变量名 。
<数据类型> * <指针型变量名>;
例如:int * intptr; intptr = &a;
基于指针的数据访问
指针部分的引用格式:<指针型变量>
指针所指变量部分的引用格式:* <指针型变量>
例如: * intptr = 30;
例如: scanf(“%d”, intptr); printf(“%d”, * intptr);
int a,c,*ptr1 = NULL;
int *ptr2 = &a;
NULL是一个特殊的值,它表示目前指针没有指向任何变量,通常将这种状态称为“空”指针。
指针的赋值
ptr1=&c;
ptr2=ptr1
可以将一个指针赋给另一个基类型相同的指针,其含义是两个指针在同一时刻指向同一个变量。
指针的加减
在利用指针访问数组元素的时候,应用这种操作移动指针十分便捷。
例如:int * ptr, iarray [10];
ptr=iarray ;
这样一来, * (ptr+1)就等价于iarray[1]
数组名是一个含有数组第1个元素地址的常量指针,a[i]等价于* (a+i)
利用指针对数组元素进行操作
假设有定义:int iarray[20], * ptr;
ptr=iarray
将数组iarray的内容显示输出
方法1、for (ptr=iarray, i=0; i<20; i++)
printf("%d",* (ptr+i));
方法2、for (ptr=iarray; ptr
指针的比较
用来判断两个指针在同一时刻是否指向同一个变量,或者判断某个指针是否为“空”。
例如 if (ptr1==NULL) return;
指针型参数
将形式参数定义成指针类型,在进行参数传递时,系统将实参变量的存储地址传递给形式参数,此时它们指向同一块存储空间。当在函数中修改形式参数所指的变量时,实际上就是对实在参数所指变量的修改。
交换两个变量的值
void swap(int *x, int *y)
{
int temp;
temp=*x;
*x=*y;
*y=temp;
}
void main()
{
int m=2,n=3;
swap(&m,&n);
printf("m=%d,n=%d\n" ,m,n);
}
指针与数组型参数
数组型的形式参数实际上就是一个指针型变量。
例:数组输入的函数,包含两个形式参数,一个是用于传递一个整型数组value,另一个是数组中包含的整数个数。
void input(int value[ ],int n)
{
int i;
printf("\nEnter %d integers:",n);
for (i=0; i<n; i++)
scanf("%d", &value[i]);
}
字符串处理
int strcmp(char *s, char *t)
{
for(; *s==*t; s++, t++)
if (*s=='\0')
return 0;
return *s-*t;
}
指针型返回值及应用实例
二分查找的递归函数
int *search(int key, int *plow, int *phigh)
{
int *pmid;
if (plow>phigh)
return NULL;
pmid = plow + (phigh-plow)/2;
if (*pmid == key)
return pmid;
if (key < *pmid )
return search(key, plow, pmid-1);
else
return search(key, pmid+1, phigh);
}
指针与一维数组
将形式参数定义成指针类型,在进行参数传递时,系统将实参变量的存储地址传递给形式参数,此时它们指向同一块存储空间。当在函数中修改形式参数所指的变量时,实际上就是对实在参数所指变量的修改。
选择排序函数
void sort( int value[ ], int n )
{
int *p, *q, *pmin, temp;
for (p=value; p<value+n; p++) {
pmin = p;
for (q=p+1; q<value+n; q++)
if (*q<*pmin )
pmin=q;
temp = *p;
*p = *pmin;
*pmin = temp;
}
}
指针与二维数组的关系
假设有下列定义:
#define ROWNUM 5
#define COLNUM 4
int a[ROWNUM][COLNUM];
int *ptr1;
int (*ptr2)[ROWNUM];
输出二维数组的每个元素内容
方法一:
ptr1 = a[0];
for (i=0; i<ROWNUM; i++) {
for (j=0; j<COLNUM; j++)
printf("%3d", *(ptr1+i*COLNUM+j));
printf("\n");
}
方法二:
ptr2 = a;
for (i=0; i<ROWNUM; i++) {
for (j=0; j<COLNUM; j++)
printf("%3d", *(*(ptr2+i)+j));
printf("\n");
}
C语言中字符串就是一种匿名的字符数组,但是各种字符串的长度不同,因此在需要管理一组字符串时,设置一个字符指针数组来进行管理
定义为 char *pSet[ 4 ];
存储结构为:
动态申请存储空间
所谓动态是指在程序运行之后,再根据实际需求申请相应的存储空间,这样既可以满足用户在确定所需的元素数量之后再申请空间,也可以提高存储空间的利用率。
void * malloc(int size) 这个标准函数的功能是:请求系统分配size 个字节的连续存储空间。如果分配成功,将返回所分配的存储空间的首地址;否则返回NULL。
例如:ptr=(int*)malloc(sizeof(int)*20);
void free(void *p) 这个标准函数的功能是:释放由指针p指向且由malloc()函数分配的存储空间。
例如: free(ptr);
#include
void main( )
{
int num, *p, *q;
int i;
scanf("%d",&num);
p = (int *)malloc(sizeof(int)*num ) ;
for (i=0; i<num; i++)
scanf("%d",p+i);
for (i=0; i<num; i++)
printf("\nNo.%d: %d", i+1, *(p+i));
free( p );
}
命令行参数
main函数带参数的形式:
main(int argc, char * argv[ ])
使用这种形式的main函数可以让用户在发出运行程序命令的同时,通过命令行向程序传递若干个参数。参数表中的第一个参数argc负责带入命令行包含的参数个数,第二个参数argv是一个指向字符的指针型数组,它负责带入命令行中的每个参数。
利用命令行参数向程序传递信息,可以加大程序的灵活性,扩展程序的使用范围。
结构体是一种可以将若干个不同数据类型的变量组合在一起的复合型数据类型。人们常常借助于它将表达同一对象的不同属性封装在一起,使之达到逻辑概念与程序变量一一对应的目的,从而提高程序的清晰度,降低程序的复杂度,改善程序的可维护性。
结构体类型的声明
类型声明的语法格式为:
struct <结构体类型名>{
<数据类型> <成员1>;
<数据类型> <成员2>;
…
<数据类型n> <成员n>;
};
例如:
struct point_type{
int x; /*x坐标*/
int y; /*y坐标*/
} ;
这个结构体类型表示:point_type类型的变量将包含两个成员x、y,它们分别用于存储坐标点的两个坐标值。
可以利用point_type类型声明下面这个结构体类型:
struct rectangle_type{
struct point_type lefttop; /*左上角的坐标*/
struct point_type rightbottom; /*右下角的坐标*/
};
在C语言中,允许用户为已经存在的数据类型起一个别名,其说明格式为:
typedef 原数据类型 新数据类型名;
typedef struct point_type{
int x;
int y;
}POINT;
在这里,POINT与struct point_type完全等价
利用结构体类型名定义变量的格式为:
<结构体类型名> <变量名> [,<变量名>];
例如:
POINT p1, p2;
等价于
struct point_type p1, p2;
与其他数据类型的变量一样,一旦定义了变量之后,系统就会为这个变量分配相应的存储空间。对于结构体型变量而言,系统为之分配的存储单元数量取决于结构体所包含的成员数量以及每个成员所属的数据类型。例如,上面定义的结构体型变量p1包含两个int类型的成员。
struct <结构体类型名> <变量名>={<成员值列表>};
例:
struct point_type p = {10, 20};
struct rectangle_type rect = { {10, 10}, {100, 100} };
结构体变量的引用
<结构体变量名>.<成员名>
struct date_type{
int year; /*年 */
int month; /*月 */
int day /*日 */
};
struct date_type d;
scanf("%d%d", &d.year, &d.month, &d.day);
printf("%d %d %d", d.year, d.month, d.day);
d.year = 2018;
d.month = 5;
d.day = 20;
如果一个结构体型变量已经被赋值,并且希望将它的值赋给另外一个类型完全相同的结构体型变量,则可以采用整体赋值的方式。
struct point_type s,* pd;
pd = &s;
s.x = …
pd->x = …
例:通过键盘输入30名学生的基本信息,并在屏幕上输出。然后,再通过键盘输入一个月份和日期,查找并输出本年度在这个给定日期之后过生日的学生信息。
问题分析
为了表示一名学生的基本信息,应该声明一个包括学号、姓名、出生日期、所属院系、所学专业的结构体类型。
“出生日期”需要用三个数据项才能够表示完整,而“日期”是一个独立的概念,也应该为之声明一个结构体类型。
组织30名学生的信息。30名学生的基本信息属于同一个性质的数据,因此,应该利用一维数组将它们组织在一起。
#include
#define NUM 30
typedef struct { /* 日期结构 */
int year;
int month;
int day;
}DATE;
typedef struct { /* 学生信息结构 */
int num;
char name[24];
DATE birthday;
char department[48];
char major[32];
}STUDENTIFNO;
void inputInfo(STUDENTIFNO[ ]);
void outputInfo(STUDENTIFNO[ ]);
void searchInfo(STUDENTIFNO[ ], DATE);
main( )
{
STUDENTIFNO s[NUM];
DATE date;
inputInfo(s);
outputInfo(s);
printf("\n Enter a date(month,day)");
scanf("%d%d", &date.month, &date.day);
searchInfo(s, date);
}
void inputInfo(STUDENTIFNO s[ ])
{
int i;
printf("\nEnter %d student's infmation(name,birthday,department,major)\n", NUM);
for (i=0; i<NUM; i++) {
s[i].num = i+1;
scanf("%s", s[i].name);
scanf("%d%d%d", &s[i].birthday.year, &s[i].birthday.month, &s[i].birthday.day);
scanf("%s", s[i].department);
scanf("%s", s[i].major);
}
}
/* 输出全部学生的信息 */
void outputInfo(STUDENTIFNO s[ ])
{
int i;
printf("\n Num Name Dirthday Department Major\n");
for (i=0; i<NUM; i++) {
printf("\n%4d%14s %4d/%2d/%2d%16s%16s",
s[i].num, s[i].name,
s[i].birthday.year, s[i].birthday.month, s[i].birthday.day,
s[i].department, s[i].major);
}
}
void searchInfo(STUDENTIFNO s[ ], DATE date)
{
int i;
for (i=0; i<NUM; i++){
if (s[i].birthday.month > date.month) {
printf("\n%4d%16s %2d//%2d", s[i].num,
s[i].name, s[i].birthday.month, s[i].birthday.day);
continue;
}
if (s[i].birthday.month==date.month && s[i].birthday.day>date.day) {
printf("\n%4d%16s %2d//%2d", s[i].num,
s[i].name, s[i].birthday.month, s[i].birthday.day);
}
}
}
将一系列结点通过指针连接起来,好像一个链,人们习惯地将这种存储形式称为链表。其中,每个结点有表示数据值部分的数据域(data);表示后继数据存储地址的指针域(next)。
head是头指针,它指向链表中的第一个结点。这是链表操作的唯一入口点,所有链表元素的访问必须从这里出发。由于最后一个结点没有后继结点,所以,它的指针域存放一个特殊值NULL。NULL在图中常用(^)符号表示,代表了表尾,也可以代表一个空表。
#include
#include
typedef struct node
{
int data;
struct node *next;
} NODE;
链表建好后,保存一个头指针,通过头指针可以访问到每个结点
方法一:尾插法
总是插入到当前链表的表尾。
NODE * create ( )
{ int x=1;
NODE *p,*h=NULL,*r=NULL;
while (x <= 10) {
p=(NODE *) malloc(sizeof( NODE ));
p->data=x; p->next=NULL;
if (h==NULL){
h=p; r=p;
}else {
r->next=p; r=p;
}
x++;
}
return h;
}
NODE * create( )
{
int x=1;
NODE *p,*h=NULL;
while (x<=10) {
p = (NODE*) malloc(sizeof(NODE));
p -> data = x;
x++;
}
return h ;
}
void output(NODE *h)
{
while (h!=NULL) {
printf("%d ",h->data);
h=h->next;
}
}
s所指结点插在表头
s->next=head;
head=s;
将s所指结点插入到p所指结点的后面
s->next=p->next;
p->next=s;
删除头
p=head;
head=head->next;
free§;
删除s所指结点
p->next=s->next;
free(s);
文件是指存储在外部介质上的一组相关数据的集合。按照不同的组织方式,文件被划分为两个类别:文本文件和二进制码文件。
文本文件以字符为基本单位,每个字符占用一个字节存放对应的ASCII码;这种文件形式又被称为ASCII文件。
二进制文件是指直接按照二进制编码形式存储数值的方式。
定义文件指针
定义格式为:FILE* <指针变量名>;
例如 FILE *fp;
文件的打开
<文件指针> = fopen(<文件名>,<操作模式>)
if ((fp = fopen("c:\\file.dat", "r")) == NUUL) {
printf(“\nCannot open the file”);
return 1;
}
<文件名>是以字符串形式描述的文件名;<操作模式>是指文件的类别和操作方式。“r”表示只读, “w”表示只写。如果文件打开成功,函数返回FILE型变量的地址,否则,返回NULL。
文件的关闭
fclose(<文件指针>);
例如:fclose(fp);
文件的读写操作
字符读写函数 :fgetc( )和fputc( )
格式化读写函数:fscanf( )和fprinf( )
字符串读写函数:fgets( )和fputs( )
数据块读写函数:fread( )和fwrite( )
字符读写操作
1、fgetc( )的调用格式:
<字符型变量> = fgetc(<文件指针>);
例如:ch = fgetc(fp);
这条语句的功能是:从fp指向的文件中读取一个字符并将这个字符赋给char型变量ch 。
2、fputc( )的调用格式:
fputc(<字符>,<文件指针>);
例如: fputc(ch, fp);
这条语句的功能是:将字符型变量ch的内容写入文件指针fp所指的文本文件中。
例:文本文件的复制。
问题分析
旧文件名和新文件名均通过命令行参数带入。
旧文件以读方式打开,新文件以写方式打开。
一边从旧文件中读取字符,一边往新文件中写入,直到原文件结束。
#include
main(int argc, char *argv[ ])
{
FILE *fp1, *fp2;
int ch;
if(argc!=3) { /* 判断参数的数量 */
printf("No file name.");
return 1;
}
if((fp1=fopen(argv[1], "r")) == NULL) { /* 打开旧文件 */
printf("Cannot open %s\n", argv[1]);
return 1;
}
if((fp2=fopen(argv[2], "w")) == NULL) { /* 打开新文件 */
printf("Cannot open %s\n", argv[2]);
return 1;
}
while((ch=fgetc(fp1)) != EOF) /* 拷贝文件 */
fputc(ch, fp2);
fclose(fp1); /* 关闭两个文件 */
fclose(fp2);
}
格式化读写操作
1、 fscanf( )的调用格式:
fscanf(<文件指针>,<格式字符串>,<输入列表>);
例如: fscanf(fp, “%d%f”, &i, &j) ;
这条语句的功能是:从fp所指的文件中按照格式控制的描述读取一个整型数值给i,一个单精度数值给j。
2、 fprintf ( )的调用格式:
fprintf(<文件指针>,<格式字符串>,<输出列表>);
例如: fprintf(fp, “%d%f”, i, j);
这条语句的功能是:将i、j的内容按照格式控制的描述写入fp所指的文件中。
例:利用格式化读写文件的方式存储学生基本信息。
#include
#define NUM 3
typedef struct info { /* 表示学生基本信息的结构类型 */
int No;
char name[16];
char department[32];
char major[32];
}INFO;
main( )
{
INFO s;
int i;
FILE *fp;
char filename[32];
printf("\nEnter file'name:"); /* 输入文件名 */
gets(filename);
if ((fp=fopen(filename, "w")) == NULL){ /* 以写方式打开文件 */
printf("\Cannot open %s file.", filename);
return 1;
}
for (i=0; i<NUM; i++) { /* 按照格式控制输入学生信息并写入文件 */
scanf("%d%s%s%s", &s.No, s.name, s.department, s.major);
fprintf(fp, "%d %s %s %s\n", s.No, s.name, s.department, s.major);
}
fclose(fp); /* 关闭文件 */
if ((fp=fopen(filename, "r"))==NULL) { /* 以读方式打开文件 */
printf("\nCannot open %s file.",filename);
return 1;
}
while (!feof(fp)){ /* 按照格式控制从文件中读信息并显示输出 */
fscanf(fp, "%d%s%s%s\n", &s.No, s.name, s.department, s.major);
printf("\n%4d%16s%20s%20s", s.No, s.name, s.department, s.major);
}
fclose(fp); /* 关闭文件 */
}
字符串读写操作
1、fgets( )的调用格式:
fgets(<字符数组>,n,<文件指针>);
例如:fgets(str, n, fp);
这条语句的功能是:从fp所指的文件中读出n-1个字符并存入字符数组str中。
2、fputs ( )的调用格式:
fputs(<字符串>,<文件指针>) ;
例如: fputs(“C program”, fp);
这条语句的功能是:将字符串“C program”写入fp所指的文件中。
数据块读写操作
1、 fread( )的调用格式:
fread(buffer, size, count, fp);
例如:fread(buffer, sizeof(int), 5, fp); ;
这条语句的功能是:从fp所指的文件中读取5块数据并放在buffer中,每块数据的大小为int类型占用的字节数量。
2、 fwrite ( )的调用格式:
fwrite(buffer, size, count, fp);
例如: fwrite(buffer, sizeof(float), 5, fp) ;
这条语句的功能是:将buffer中存放的数据写入fp所指的文件中,共写入5块数据,每块数据的大小为float类型占用的字节数量。
例:从键盘输入学生基本信息,写入二进制文件;再从该文件中读取学生基本信息,并显示输出。
问题分析
假设学生基本信息只包含学号、姓名、所属院系和专业几个数据项,为此,需要定义一个结构类型。输入学生信息的时候,将所有数据项存放在一个结构型变量中,并以二进制的形式写入文件;在读取文件的时候,每次读取一个结构类型变量的字节数目,这样可以很轻松地实现将结构型数据存储到磁盘文件中的操作。
#include
#define NUM 30
typedef struct info { /* 表示学生基本信息的结构类型 */
int No; /* 学号 */
char name[16]; /* 姓名 */
char department[32]; /* 所属院系 */
char major[32]; /* 所学专业 */
} INFO;
main( )
{
INFO s;
int i;
FILE *fp;
char filename[32];
printf("\nEnter file'name:"); /* 输入文件名 */
gets(filename);
if ((fp=fopen(filename, "wb")) == NULL) { /* 以二进制写方式打开文件 */
printf("\Cannot open %s file.", filename);
return 1;
}
for (i=0; i<NUM; i++) { /* 通过键盘输入学生信息并写入文件 */
scanf("%d%s%s%s", &s.No, s.name, s.department, s.major);
fwrite(&s, sizeof(INFO), 1, fp);
}
fclose(fp); /* 关闭文件 */
if ((fp=fopen(filename, "rb")) == NULL) { /* 以二进制读方式打开文件 */
printf("\nCannot open %s file.", filename);
return 1;
}
while (!fread(&s, sizeof(INFO), 1, fp)) /* 从文件中读信息并显示输出 */
printf("\n%4d%16s%20s%20s", s.No, s.name, s.department, s.major);
fclose(fp); /* 关闭文件 */
}
联合体
联合类型的语法格式:
union <联合体类型名>
{
<成员列表>
};
联合体变量的操作
联合体部分引用的格式与结构型变量一样。
枚举类型
枚举类型的定义格式:
enum <枚举类型名> { <枚举值列表> };
例如 enum weekday { Sun, Mon, Tue, Wed, Thu, Fri, Sat };
这个类型定义表明:weekday是一个枚举类型,这个类型包含7个值。在默认情况下,每个值对应一个序号,编号从0开始,依次为:0、1、2、3、…、6。
枚举类型变量的定义
enum weekday day;
枚举型变量的操作
可以将枚举类型的值赋给枚举型变量
例如: day = Wed;
枚举类型的值可以实施关系操作
例如: if (mon
枚举类型的值不能够直接地输入输出,只能间接地实现输入输出的操作。
输入
int tag;
scanf(“%d”, &tag);
switch (tag) {
case 0: day = Sun; break;
case 1: day = Mon; break;
case 2: day = Tue, break;
case 3: day = Wed; break;
case 4: day = Thu; break;
case 5: day = Fri; break;
case 6: day = Sat; break;
}
输出
switch (day) {
case Sun: printf(“Sun”); break;
case Mon: printf(“Mon”); break;
case Tue: printf(“Tue”); break;
case Wed: printf(“Wed”); break;
case Thu: printf(“Thu”); break;
case Fri: printf(“Fri”); break;s
case Sat: printf(“Sat”); break;
}