c程序设计(第五版)谭浩强著

[复制链接]

8

主题

135

帖子

3270

积分

超级月卡

Rank: 7Rank: 7Rank: 7

积分
3270
发表于 2020-8-16 21:04:53 | 显示全部楼层 |阅读模式
本帖最后由 why 于 2020-9-3 16:25 编辑

汇编学的什么都不会,我们先来学学c语言,加深一下对汇编的理解,汇编课本多次用c语言来佐证,并且最后的一小部分都是需要c语言才能看懂的。其实吧这本c我已经买了好几年了,但是一直都在吃灰。这次我想肯定是能看完的,就看多久能看完了!已经有了汇编基础我要加快进度看c,只对每章的重点进行记录
半个月时间大概浏览了一遍这本书,概念太生涩,很多问题讲一大堆但是也没有讲清楚,并且书中错误的地方非常多,就连例题里的代码都有书写错误的,尤其最后一章。如果不是看完了汇编回来看这本书,根本看不明白这书里都写的啥,当然我能力也有限。估计还是个人理解能力有问题吧!到时候回过头重新用c primer plus学一遍c,都说c primer plus是本不错的书







本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x

回复

使用道具 举报

8

主题

135

帖子

3270

积分

超级月卡

Rank: 7Rank: 7Rank: 7

积分
3270
 楼主| 发表于 2020-8-16 21:06:43 | 显示全部楼层
本帖最后由 why 于 2020-8-18 20:08 编辑

第一章  程序设计和c语言
1.1什么是计算机程序
所谓程序,就是一组计算机能识别和执行的指令。每一条指令使计算机执行特定的操作计算机的一切都是由程序控制的,离开程序,计算机将一事无成。所以计算机的本质是程序的机器,程序和指令使计算机系统中最基本的概念

1.2什么是机器语言
机器语言,计算机工作基于二进制,从根本上说,计算机只能识别和接受0和1组成的指令,汇编里边介绍过这些基本概念
符号语言,为了克服机器语言的难学,难写,难记,难检查,难修改,难以推广使用的缺点,人们创造出符号语言,它用一些英文字母和数字表示一个指令,例如用add表示“加”,sub表示“减”,ld表示传送等,实际上就是汇编语言
机器语言和汇编语言是完全依赖于具体机器特征的,是面向机器的语言,由于它“贴近”计算机,或者说离计算机“很近”,故称为计算机低级语言
高级语言:为了克服低级语言的缺点,20世纪50年代创造出了第一个计算机高级语言-------Fortran语言,它很接近人们习惯使用的自然语言和数学语言,程序中用到的语句和指令使用英文单词表示的,程序中所用的运算符和运算表达式和人们日常所用的数学式子差不多,很容易理解。程序运行的结果用英文和数字输出,十分方便。
这种语言功能很强,且不依赖于具体机器,用它写出的程序对任何型号的计算机都适用(或只须做很少的修改),它与具体机器距离较远,故称为计算机高级语言。
当然计算机也是不能直接识别高级语言程序的,也要进行翻译,用一种称为编译程序的软件把用高级语言写的程序(称为源程序)转换为机器指令的程序(称为目标程序)然后让计算机执行机器指令程序,最后得到结果。高级语言的一个语句往往对应多条机器指令
高级语言经历了不同的发生阶段:
(1)非结构化的语言:初期的语言属于非结构化的语言,编程风格比较随意,只要符合语法规则即可,没有严格的规范要求,程序中的流程可以随意跳转。人们往往追求程序的效率而采用了许多”小技巧“,使程序变得难以阅读和维护。
(2)结构化语言:为了解决以上的问题,提出了”结构化程序的设计方法“,规定程序必须由具有良好特性的基本结构(顺序结构、选择结构、循环结构)构成,程序中的流程不允许随意跳转,程序总是由上而下顺序执行各个基本结构。这种程序结构清晰,易于编写、阅读和维护
以上两种语言都是基于过程的语言,在编写编写程序时需要具体治疗每一个过程的细节。在编写规模较小的程序时,还能得心应手,但是在处理规模较大的程序时,就显得捉襟见肘、力不从心了。在时间的发展中,人们又提出了面向对象的程序设计方法。程序面对的不是过程的细节,而是一个个对象,对象是由数据以及对数据进行的操作组成的
(3)面向对象的语言:近十多年来,在处理规模较大的问题时,开始使用面向对象的语言。c++,c#,visual basic和Java等语言是支持面向对象程序设计方法的语言。
进行程序设计,必须要用到计算机语言,人们根据任务的需要选择合适的语言,编写处程序,然后运行程序得到结果

1.3   c语言的发展及其特定
C语言是一种用途广泛、功能强大、使用灵活的过程性编程语言,既可以用于编写应用软件,又可用于编写系统软件。因此C语言问世后得到迅速推广
C语言有以下一些主要特点
(1)语言简洁、紧凑,使用方便、灵活。C语言一共只有37个关键字、9种控制语句,程序书写形式自由,主要用小写字母表示,压缩一切不必要的成分。C语言程序比其他许多高级语言简练,源程序短,因此输入程序时工作量少
实际上,C语言是一个很小的内核语言,只包括极少的与硬件有关的成分,C语言不直接提供输入和输出的语句、有关文件操作的语句和动态管理的语句等(这些操作是由编译系统提供的函数来实现的),c的编译系统相当简洁
(2)运算符丰富。C语言的运算符包含的范围很广泛,共有34种运算符。C语言把括号、赋值和强制类转换等都作为运算符处理,从而使C语言的运算类型极其丰富,表达式类型多样化。灵活使用各种运算符可以实现在其他高级语言中难以实现的运算
(3)数据类型丰富。C语言提供的数据类型包括整型、浮点型、字符型、数组类型、指针类型、结构体类型和共用体类型等,c99又扩充了复数浮点类型、超长整型和布尔类型等。尤其是指针类型数据,使用十分灵活和多样化,能用来实现各种复制的数据结构(如链表、树、栈)的运算
(4)具有结构化的控制语句(如if……else语句、while语句、do……while语句、switch语句和for语句)。用函数作为程序的模块单位,便于实现程序的模块化。C语言是完全模块化和结构化的语言
(5)语法限制不太严格,程序设计自由度大。例如,对数组下标越界不进行检查,由编写者字节保证程序的正确。对变量的类型使用比较灵活,例如,整型量与字符型数据以及逻辑型数据可以通用。一般高级语言语法检查比较严,能检查出几乎所有的语法错误,而C语言为了使编写者有较大的自由度就放宽了语法检查。程序员应当仔细检查程序,保证其正确、不要过分依赖C语言编译程序查错。”限制“与”灵活“是一对矛盾。限制严格,就失去灵活性;而强调灵活,就必然放松限制。对于不熟练的人员,编写一个正确的C语言程序可能会比编写一个其他高级语言程序难一些。也就是说,对用C语言的人要求更高一些
(6)C语言允许直接访问物理地址,能进行位(bit)操作,能实现汇编语言的大部分功能,可以直接对硬件进行操作。因此C语言既具有高级语言的功能,又具有低级语言的许多功能,可用来编写系统软件。C语言的这种双重性,使它既是成功的系统描述语言,又是通用的程序设计语言
(7)用C语言编写的程序可移植性号。由于C语言的编译系统相当简洁,因此很容易一直到新的系统。而且c编译系统在新的系统上运行时,可以直接编译”标准链接库“中的大部分功能,不需要修改源代码,因为标准链接库是用可移植的C语言写的。因此,几乎在所有的计算机系统中都可以使用C语言
(8)生成目标代码质量高,程序执行效率高
c原来是专门为编写操作系统而设计的,许多大的应用软件也都用C语言编写,这事因为C语言的可移植性号,硬件控制能力高,表达和运算能力强。许多以前只能用汇编语言处理的问题,后来可以改用C语言处理了。目前c的主要用途之一是编写嵌入式操作程序。由于具有上述优点,使C语言应用面十分广发,许多应用软件也用c语言编写

1.4最简单的C语言程序
1.4.1 最简单的C语言举例
【例1.1】要求在屏幕上输出以下一行信息
this is a c program
解题思路:在主函数中用printf函数原样输出以上文字
编写程序:
# include <stdio.h>                                           //这是编译预处理指令
          int main()                                               //定义主函数
          {                                                           //函数开始标志
          printf("this is s c program. \n");                //输出所指定的一行信息
          return 0;                                               //函数执行完毕时返回函数值0  
          }                                                          //函数结束的标志


以上结果是在visual c++ 6.0环境下运行的,其中第一行是程序运行后输出的结果,第二行是visual c++ 6.0系统输出完成运行结果后自动输出的一行信息,告诉用户”如果想继续进行下一步,请按任意键“。当用户按下任意键后,屏幕上不再显示运行结果,而是返回程序窗口,以便进行下一步工作(如修改程序)
本人vm虚拟机装的xp系统,使用的绿色版visual c++ 6.0下载地址:http://www.pc6.com/softview/SoftView_8020.html
头一次怎么使用visual c++ 6.0,还想要自己动手看下教程,课本上没说咋用visual c++ 6.0,教程:https://jingyan.baidu.com/article/90bc8fc8aff9aff652640c68.html

程序分析:先看程序第2行,其中main是函数的名字,表示”主函数“,main前面的int表示此函数的类型是int类型(整型)。在执行主函数后会得到一个值(即函数值),其值为整型。程序第5行”return 0;“的作用是:当main函数执行结束前将整数0作为函数值,返回到调用韩输出。(这个课本上有个注释,定义的这个0是程序行到0这个位置就算正常结束,返回0;如果程序不能正经结束就返回一个非0的值),每一个C语言程序都必须有一个main函数,函数体由花括号{}括起来
本例中主函数有两个语句,程序的第4行是一个输出语句,printf是c编译系统提供的函数库中的输出函数(详见第4章)printf函数中双撇号内的字符串”his is s c program. “按原样输出。\n是换行符,即在输出”his is s c program. “后,显示屏上的光标位置移到下一行的开头,这个光标位置称为输出的当前位置,即下一个输出字符出现在此位置
每个语句最后都有一个分号,表示语句结束在使用函数库中的输入输出函数时,编译系统要求程序提供有关此函数的信息(例如对输入输出函数的声明和宏的定义、全局量的定义等)程序第1行”#include <stdio.h>“的作用就是用来提供这些信息的。stdio.h是系统提供的一个文件名,stdio是standard input&output的缩写,文件后缀.h的意思是头文件(header file),因为这些文件都是放在程序各文件模块的开头的。输入输出函数的相关信息已事先放在stdio.h文件中。现在,用#include指令把这些信息调入供使用。如果没有此#include指令,就不可能执行printf函数。在程序中乳摇用到标准函数库中的输入输出函数,应该在本文将模块的开头加上下面一行
#include <stdio.h>
在以上程序的右侧,如果有//,则表示从此处到本行结束是”注释“,用来对程序有关部分进行必要的说明
C语言允许用两种形式的注释:
(1)//………………               单行注释,下一行还需要注释必须在开头按照注释格式重新输入
(2)/*………………*/           多行注释,注释符号内一整段都会被注释
注释可以用汉字或英文符表示
【例1.2】求两个整数之和
解题思路:设置3个变量,a和b用来存放两个整数,sum用来存放和数。用赋值运算符”=“把相加的结果传送给sum
编写程序:
#include <stdio.h>                 //这是编译预处理指令
int main()                              //定义主函数
{                                          //函数开始
int a,b,sum;                          //本行是程序的声明部分,定义a,b,sum为整型变量
a=123;                                //对变量a赋值
b=456;                                //对变量b赋值
sum=a+b;                           //进行运算a+b,并把结果存放在变量sum中
printf("sum is %d\n",sum);   //结果以十进制形式输出
return 0;                             //使函数返回值为0
}                                        //函数结束





程序分析:第4行是声明部分,定义a,b和sum为整型(int)变量。第5,6行是两个赋值语句,使a和b的值分别为123和456。第7行是sum的值为a、b之和。第8行输出结果,这个printf函数圆括号内有两个参数。第一个参数是双撇号中的内容sum is %d\n,它是输出格式字符串,作用时输出用户希望输出的字符和输出的格式。其中sum is是用户希望输出的字符,%d是指定的输出格式,d表示用十进制整数形式输出。圆括号内第二个参数sum表示要输出变量sum的值。在执行printf函数时,将sum变量的值(以十进制整数表示)取代双撇号中的%d。现在sum的值是579(a、b之和)所以在输出时,十进制整数579取代了%d,n是换行符,到此处程序正常运行结束,因此main函数的返回值应为0



【例1.3】求两个整数中的较大者
解题思路:用一个函数来实现求两个整数中的较大者。在主函数中调用此函数并输出结果
编写程序:
#include <stdio.h>//主函数
int main ()                                            //定义主函数
{                                                         //主函数体开始
int max(int x,int y);                               //对被调用函数max的声明
int a,b,c;                                              //定义变量a,b,c
scanf("%d,%d",&a,&b);                        //输入变量a、b的值
c=max(a,b);                                        //调用max函数,将得到的结果赋值给c
printf("max=%d\n",c);                         //输出c的值
return 0;                                            //返回函数值为0
}                                                       //主函数体结束
//求两个整数中的较大者的max函数
int max(int x,int y)                              //定义max函数,函数值为整型,形式参数x和y为整型
{
int z;                                                 //max函数中的声明部分,定义本函数中用到的变量x为整型
if(x>y)z=x;                                        //若x>y成立,将x的值赋给变量z
else z=y;                                           //否则(即x>y不成立),将y的值赋给变量z
return(z);                                          //将z的值作为max函数值,返回到调用max函数的位置
}


在运行时,第1行输入8和5,赋值给变量a和b,程序在第2行输出:max=8

程序分析:本程序包括两个函数:1)主函数main,2)被调用的函数max

max函数的作用时将x和y中较大者的值赋给变量z。第18行的return语句将z的值作为max的函数值返回给调用max函数的函数(即主函数main)。返回值是通过函数名max带回到main函数中去的(带回到程序第8行,main函数调用max函数处)
程序第5行是对被调用函数max的声明,为什么要做这个函数声明呢?因为在主函数中调用max函数(程序第8行”c=max(a,b)“),而max函数的定义却在main函数之和,对程序的编译是自上而下进行的,在对程序第8行进行编译时,编译系统无法知道max是什么,因而无法把他作为函数调用处理。为了使编译系统能识别max函数,就要在调用max函数之前用”int max(int x,int y)“,"对max函数进行"声明,所谓声明,通俗地说就是告诉编译系统max是什么,以及它的有关信息。
程序第7行scanf是输入函数的名字(scanf和printf都是c的标标准输入输出函数)。scanf函数的作用时输入变量a和b的值。scanf后面的圆括号中包含两部分内容。一是双撇号中的内容,它指定输入的数据按什么格式输入。”%d“的含义是”以十进制整数形式“。二是输入的数据准备放到哪里,即赋值给那个变量。现在scanf函数中指定的是a和b,在a和b的前面各有一个&,在C语言中”&“是地址符,&a的含义是”变量a的地址“,&b是”变量b的地址“。执行scanf函数,从键盘读入两个整数,放到变量a和b的地址,然后把这两个整数分别赋值给变量a和b
程序第8行用max(a,b)调用max函数。在调用时将a和b作为max函数的参数(称为实际参数)的值分别传送给max函数中的参数x和y(称为形式参数),然后执行max函数的函数体(程序第14~19行),使max函数中的变量z得到一个值(即x和y中大者的值),return(z)的作用时把z的值作为max函数值带回到程序第8行”=“的右侧(主函数调用max函数的位置),取代max(a,b),然后把这个值赋值给变量c
第9行用来输出结果,在执行printf函数时,对双撇号括起来的max=%d\n是这样处理的,将max=原样输出,%d由变量c的值取代,\n的作用时换行符

注意:本例程中两个函数都有return语句,请在注意它们的异同,两个函数都定义为整型,都有函数值,都需要哟个return语句为函数指定返回值。但是main函数中return语句指定的返回值一般为0,而max函数的返回值是max函数中求出的两数中的最大值z,只有通过return语句才能把求出的z值作为函数的值并返回调用它的main函数中(即程序第8行,并把此值赋值给变量c)。不要以为在max函数中求出最大值z后就会自动地作为函数值返回调用处,必须用return语句指定将那个值作为函数值。也不要不加分析地在所有函数的最后都写上”return 0;“
本例程用到了函数调用、实际参数和形式参数等概念,只做了简单的解释,往后自然就明白了

1.4.2  c语言程序的结构
通过以上几个程序例子,可以看到一个C语言程序的结构有以下特点:
(1)一个程序由一个或多个源程序文件组成。一个规模较小的程序,往往只包括一个源程序文件,如例1和例2是一个源程序文件中只有一个函数(main函数),例3中有两个函数,属于同一个源程序文件
字啊一个源程序文件中可以包括3个部分
①预处理指令:如#include<stdio.h>(还有一些其他的预处理指令,如#define)
②全局声明:即在函数之外进行的数据声明,本来2中的”int a,b“是一个局部变量,只在花括号内的代码中使用。
③函数定义:如在例1、例2中的main函数;例3中的main函数和max函数
(2)函数时c程序的主要组成部分。程序的几乎全部工作都是由各个函数分别完成的,函数时c程序的基本单位,在设计良好的程序中,每个函数都用来实现一个或几个特定的功能编写c程序的工作主要就是编写一个个函数
(3)一个函数包括两个部分
①函数首部。即函数的第1行,包括函数名、函数类型、函数属性、函数参数(形式参数)名、参数类型。


一个函数名后面必须跟一对圆括号,括号内些函数的参数名及其类型。如果函数没有参数,可以在括号中写void,也可以是空括号
②函数体。即函数首部下面的花括号内的部分。如果在一个函数中包括有多层花括号,则最外层的一对花括号是函数体的范围
函数体一般包括以下两部分。
       *声明部分。声明部分包括:定义在本函数中所用到的变量
       *执行部分。由若干个语句组成,指定在函数中所进行的操作
在某些情况下也可以没有声明部分(如例1),甚至可以既无声明也无执行部分如:
void dump()
{}
是一个空函数,什么也不做,但这是合法的
(4)程序总是从main函数开始执行的,而不论函数在整个程序中的位置如何(main函数可以放在程序的最前头,也可以放在程序的最后,活在一些函数之前、另一些函数之后)
(5)程序中要求计算机完成的操作是由函数中的c语句完成的。如赋值、输入输出数据的操作都是由相应的c语句实现的
c程序书写格式是比较自由的。一行内可以写几个语句,一个语句可以分成多行写,但为了清晰起见,习惯上每行只写一个语句
(6)在每个数据声明和语句的最必须有一个分号。分号是c语句的必要组成部分
(7)C语言本身不提供输入输出语句。输入输出的操作是由库函数scanf和printf等函数来完成的。c语言对输入输出实现“函数化”
(8)程序应当包含注释。一个好的、有使用价值的源程序应当加上必要的注释,以增加程序的可读性

1.5运行c程序的步骤和方法
上一节中看到C语言编写的程序时源程序。计算机不能直接识别和执行用高级语言写的指令,必须用编译程序(也成编译器)把C语言程序翻译成二进制形式的目标程序,然后在将该程序与系统的函数库以及其他目标程序连接起来,形成可执行的目标程序
上一节中我们大概提供了编译器的使用方法,这一章就没啥说的了,起初会用就行,用多了自然就会了
在编写好一个c源程序后,怎样商家进行编译和运行呢?一般要经过以下几个步骤:
(1)上机输入和编辑源程序
(2)对源程序进行编译
(3)进行连接处理
(4)运行可执行的程序,得到运行结果
如图所示,其中实现表示操作流程,虚线表示文件的输入输出




1.6程序设计的任务
程序设计是指从确定任务到得到结果、写出文档的全过程
从确定问题到最后完成任务,一般经历以下几个工作阶段:
(1)问题分期
(2)设计算法
(3)编写程序
(4)对源程序进行编辑、编译和连接
(5)运行程序、分析结果
(6)编写程序文档。许多程序是提供给别人使用的,如同正式的产品应当提供产品说明书一样,正是提供给用户使用的程序,必须向用户提供程序说明书(也称为用户文档)。内容应包括程序名称、程序功能、运行环境、程序的装入和启动、需要输入的数据,以及使用注意事项等
程序文档是软件的一个重要组成部分,软件是计算机程序和程序文档的总称。现在的商品软件光盘中,既包括程序,也包括程序使用说明,有的则在程序中以帮助或readme形式提供

习题1.什么是程序?什么是程序设计
程序就是一组计算机能识别和执行的指令。每一天指令使计算机执行特定的操作。
程序设计是指从确定任务到得到结果、写出文档的全过程


2.为什么需要计算机语言?高级语言有哪些特点?
为了克服低级语言难学,难写,难记,难检查,难修改,难以推广使用的缺点,20世纪50年代创造出了第一个计算机高级语言
高级语言接近人们习惯使用的自然语言和数学语言,程序中用到的语句和指令使用英文单词表示的,程序中所用的运算符和运算表达式和人们日常所用的数学式子差不多,很容易理解。程序运行的结果用英文和数字输出,十分方便。这种语言功能很强,且不依赖于具体机器,用它写出的程序对任何型号的计算机都适用(或只须做很少的修改)。

3.正确理理解以下名词及其含义
(1)源程序,目标程序,可执行程序
高级语言写的程序是源程序
用编译程序把源程序翻译成二进制形式是目标程序
将目标程序与系统的函数库以及其他目标程序连接起来形成可执行程序
(2)程序编程,程序编译,程序连接
程序编程:通过键盘向计算机输入程序
程序编译:编译程序的作用时对源程序进行检查,判定它有无语法方面的错误,无误后编译程序把源程序转换为二进制形式的目标程序
程序连接:经过编译得到的二进制目标文件还不能供计算机直接执行,必须把所有编译后得到的目标模块连接装配起来,再与函数库相联成一个整体,才能生                  成一个可供计算机执行的目标程序

(3)程序,程序模块,程序文件程序:一组计算机能识别和执行的指令。
程序模块:即可由汇编程序、编译程序、装入程序或翻译程序作为一个整体来处理的一级独立的、可识别的程序指令。
程序文件:描述程序的文件称为程序文件。



(4)函数,主函数,被调用函数,库函数
函数就是完成一定功能的一段代码
主函数就是main函数,main函数是c程序里必不可少的,程序从这里开始运行。
被调用函数就是某一个地方调用到的函数,c程序就是通过函数调用方式来运行的
库函数就是预先编制好的一些函数,完成特定的功能

(5)程序调试,程序测试
程序调试是检查程序的错误
程序测试是检测程序是否能达到预期的目的或发现一些逻辑性错误导致的bug

4.编写一个c程序,运行时输出
hello world!
这个程序时国外c教材中作为第一个程序例子介绍的,一般称为hello程序
# include <stdio.h>                                           //这是编译预处理指令
          int main()                                               //定义主函数
          {                                                           //函数开始标志
          printf("hello world! \n");                          //输出所指定的一行信息
          return 0;                                               //函数执行完毕时返回函数值0  
          }                                                          //函数结束的标志



5.编写一个c程序,运行时输出以下图形
*****
  *****
    *****
      *****

# include <stdio.h>                                           //这是编译预处理指令
          int main()                                               //定义主函数
          {                                                           //函数开始标志
          printf("***** \n");               
                  printf("  ***** \n");
                  printf("    ***** \n");
                  printf("      ***** \n");                               //输出所指定的一行信息
          return 0;                                               //函数执行完毕时返回函数值0  
          }                                                          //函数结束的标志



6.编写一个c程序,运行时输入a,b,c三个值,输出其中值最大者
#include <stdio.h>//主函数
int main ()                                  //定义主函数
{                                               //主函数体开始
int max(int x,int y,int z);             //对被调用函数max的声明
int a,b,c,d;                                //定义变量a,b,c,d
scanf("%d,%d,%d",&a,&b,&c);  //输入变量a、b、c的值
d=max(a,b,c);                         //调用max函数,将得到的结果赋值给d
printf("max=%d\n",d);             //输出d的值
return 0;                                 //返回函数值为0
}                                            //主函数体结束
//求三个整数中的较大者的max函数
int max(int x,int y,int z)            //定义max函数,函数值为整型,形式参数x和y为整型
{
int d;                                     //max函数中的声明部分,定义本函数中用到的变量x为整型
if(x>y)d=x;                           //若x>y成立,将x的值赋给变量d
else d=y;                              //否则(即x>y不成立),将y的值赋给变量d
if(d>z)d=d;                          //若d>y成立,将d的值赋给变量d
else d=z;                             //否则(即x>y不成立),将z的值赋给变量d
return(d);                            //将z的值作为max函数值,返回到调用max函数的位置
}




7.看懂《c程序设计(第5版)学习辅导》第16章中介绍的用visual studio 2010对c程序进行编辑、编译、连接、和运行的方法,并进行以下操作
visual studio 2010使用方法:https://jingyan.baidu.com/article/e9fb46e135d4b97521f76628.html
visual studio 2010编程要在“return 0;” 前加上“system("pause");”要不然程序会一闪就退出


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

8

主题

135

帖子

3270

积分

超级月卡

Rank: 7Rank: 7Rank: 7

积分
3270
 楼主| 发表于 2020-8-17 23:19:36 | 显示全部楼层
第2章 算法---程序的灵魂

2.1  程序=算法+数据结构
一个程序主要包括以下两方面的信息:
(1)对数据的描述。在程序中要指定用到哪些数据,以及这些数据的类型和数据的组成形式。这就是数据结构
(2)对操作的描述。要求计算机进行操作的步骤,也就是算法
数据是操作对象,操作的目的是对数据进行加工处理,以得到期望的结果
          算法+数据结构=程序
算法、数据结构、程序设计方法和语言工具4个方面是一个程序设计人员所应具备的知识,在设计一个程序的时候要综合运用这几个方面的知识。在这4个方面中,算法是灵魂,数据结构是加工对象,语言是工具,编程需要采用合适的方法
算法是解决“做什么”和“怎么做”的问题。程序中的语句实际上就是算法的体现。显然不了解算法就谈不上程序设计


2.2什么是算法
为解决一个问题而采用的方法和步骤就称为“算法”
为了有效地进行解题,不仅需要考虑保证算法正确,还要考虑算法的质量,选择合适的算法
计算机的算法可分为两大类别:数值运算算法和非数值运算算法。
数值运算的目的是求数值解,如求方程的根、求函数的定积分;非数值运算涉及的面十分广泛,如图书检索、人事管理。目前计算机在非数值运算方面的应用远远超过了在数值运算方面的应用


2.3简单的算法举例
【例2.1】求1x2x3x4x5
可以用最原始的方法进行:
连续直接相乘得出结果
这样的算法虽然正确,但太麻烦,如果求1x2x3……………………x1000,则这种解法就太麻烦显然不可取
不妨这样考虑:设置两个变量,一个变量代表被乘数,一个变量代表乘数。不另设变量存放乘积结果,而是直接将每一步步骤的乘积放在被乘数变量中,今设变量t为被乘数,变量i为乘数。用循环算法求结果,可以将算法写成如下:s1:令t=1,或写成1=>t(表示将1存放在变量t中)
s2:令i=2,或写成2=>i(表示将1存放在变量i中)
s3:使t与i相乘,乘积仍放在变量t中,可表示为:t*i=>t
s4:使i的值加1,即i+1=>i
s5:如果i不大于5返回重新执行s3及其后的步骤s4和s5;否则,算法结束。最后得出t的值就是5!的值
上面的s1,s2……代表步骤1、步骤2……s是step(步)的缩写。这是写算法的习惯用法
显然这个算法比前面连续相乘的算法更简练
如果题目改为:求1x3x5x7x9x11.
算法只需要做很少的改动:
s1:1=>t
s2:3=>i
s3:t*i=>t
s4:i+2=>i
s5:若i≤11,返回s3;否则,结束
其中s5也可以表示为
s5:若i>11结束,否则返回s3
上面两种写法,作用时相同的
可以看出用这种方法表示算法具有一般性、通用性和灵活性。s3~s5组成一个循环,在满足某个条件(i≤11)时,反复多次执行s3,s4和s5步骤,直到某一次执行s5步骤时,发现乘数i已超过事先指定的数值(11)而不返回s3为止。此时算法结束,变量t的值就是所求结果
由于计算机是高速运算的自动机器,实现循环是轻而易举的,所有计算机高级语言中都有实现循环的语句,因此,上述算法不仅是正确的,而且是计算机能方便实现的较好的算法。
请仔细分析循环结束的条件,即s5。如果在求1x2x3……x11时,将s5写成
s5:若i<11,返回s3这样会有什么问题?得到什么结果
若i<11,则程序执行第一次就返回了

【例2.2】有50个学生,要求输出乘积在80分以上的学生的学号和成绩
为描述方便,可以统一用n表示学生学号,用下表i代表第几个学生,n1代表第一个学生的学号,ni代表第i个学生学号;统一用g表示学生的成绩,g1代表第一个学生的成绩,gi代表第i个学生的成绩
本来问题是很简单的:先检查第1个学生的成绩g1,如果它的值大于或等于80,就将此成绩输出,否则不输出。然后再检查第2个学生的成绩g2……直到检测完第50个学生的成绩g50为止。但是这样表示步骤太多,太繁琐,最好能找到简明的表示方法。分析此过程的规律,每次检查的内容和处理方法都是相似的,只是检测的对象不同,而检查的对象都是学生的成绩g,只是下标不同(从g1变化到g50)。只要有规律地改变下标i的值(从1~50),就可以把检查的对象统一标识为gi,这样就可以用循环的方法来处理了。算法可以如下表示:
s1:1=>i

s2:如果gi≥80,则输出ni和gi,否则不输出
s3:i+1=>i
s4:如果i≤50,返回到步骤s2继续执行,否则算法结束
变量i代表下标,先使它的值为1,检查g1(g1到g50都是已知的)。然后使i增值1,再检查gi。通过控制i的变化,在循环过程中实现了对50个学生的成绩的处理
可以看到,这样表示的算法比最初的表示方法抽象简明,抓住了解题的过来,易于用计算机实现,通过这个例子学会怎样归纳解题的规律,把具体的问题抽象化,设计出简明的算法


【例2.3】判定2000-2500年中的每一年是否为闰年,并将结果输出
先分析闰年的条件:
(1)能被4整除,但不能被100整除的年份都是闰年,如1996年、2008年、2012年、2048年都是闰年
(2)能被400整除的年审是闰年,如1600年、2000年是闰年
不符合这两个条件的年份不是闰年。例如2009年、2100年不是闰年
设year为被检测的年份,算法可如下表示:
s1:2000=>year
s2:若year不能被整除,则输出year的值和“不是闰年”。然后转到s6,检查下一个年份
s3:若year能被4整除,不能被100整除,则输出year的值和“是闰年”。然后转到s6
s4:若year能被100整除,输出year的值和“是闰年”,然后转到s6
s5:输出year的值和“不是闰年”
s6:year+1=>year
s7:当year≤2500时,转s2继续执行,否则算法停止
在这个算法中,采取了多次判断。先判断year能否被4整除,如不能,则year必然不是闰年,如year能被4整除,并不能马上决定它是否闰年,还要检查它能否被100整除。如不能被100整除,则肯定是闰年(例如2008年)。如能被100整除,还不能判断它是否闰年,还要检查它能否被400整除,如果能被400整除,则是闰年;否则不是闰年
在这个算法中,每做一步,都分别分离出一些范围(已能判定为闰年或非闰年),逐步缩小范围,是被判断的范围愈来愈小,直至执行s5时,只可能是非闰年,见下图


从上图可以看出:“其他”这一部分,包括不能被4整除的年份,以及能被4整除,又能被100整除,但不能被400整除的哪些年份(如1900年),它们都是非闰年
考虑算法时,应当仔细分析所需判断的条件,如何一步一步缩小检查的范围。对有的问题,判断的先后次序是无所谓的;而有的问题,判断条件的先后次序是不能任意颠倒的,需要根据具体问题决定其逻辑

【例2.4】求1-1/2+1/3-1/4……+1/99-1/100
解题思路:表面看,每一项都不一样,但稍加分析,就可以看到:
1)第1项的分子分母都是1,即1/1
2)第2项的分母是2,以后每一项的分母都是前一项的分母加1
3)第2项前的运算符为“-”,后一项前面的算符都与前一项的运算符相反
这就找到了多项式的规律,能把多项式表示为一般形式,即吧问题抽象化了
有此基础就可以写出下面的算法,用sign代表当前处理的项前面的数值符号,term代表当前项的值。sum表示当前各项的累加和,deno是当前项的分母。本例中用有含义的单词做变量名,以使算法更易于理解
s1:sign=1
s2:sum=1
s3:deno=2
s4:sign=(-1)*sign
s5:term=sign*(1/deno)
s6:sum=sum+term
s7:deno=deno+1
s8:若deno≤100返回s4执行,否则算法结束
在s1中先预设sign的值为1(sign代表多项式中当前项的符号,它的值为1或-1)。在s2中使sum等于1,相当于将多项式中的第一项加到了sum中了,后面应该从第2项开始累加。在s3中使分母的值为2,它是第2项的分母。在s4中使sign的值变为-1,此时它代表的第2项的符号。在s5中求出多项式中的第2项的值(-1/2)。在s6中将刚才求出的第2项的值(-1/2)累加到sum中。至此,sum的值是(1-1/2)。在s7中使分母deno的值加1(变成3)。执行s8,由于deno≤100,故返回s4,sign的值改为1,在s5中求出term的值为1/3,在s6中将1/3累加到sum中。然后s7再使分母变为4。按此规律反复执行s4~s8步骤,直到分母大于100为止。一共执行了99次循环,项sum累加入了99次分数。sum最后的值就是多项式的值

【例2.5】给出一个大于或等于3的正整数,判断它是不是一个素数
解题思路:所谓素数,是指除了1和该数本身之外,不能被其他任何整数整除的数。例如,13是素数,因为它不能被2,3,4……12整除
判断一个数n(n≥3)是否为素数的方法是很简单的:将n作为被除数,将2~(n-1)的各个整数先后作为除数,如果都不能被整除,则n为素数
算法可以如下表示:
s1:输入n的值
s2:i=2(i作为除数)
s3:n被i除,地余数r
s4:如果r=0,表示n能被i整除,则输出n“不是素数”,算法结束;否则执行s5
s5:i+1=>i
s6:如果i≤n-1,返回s3;否则输出n的值以及“是素数”,然后结束
实际上,n不必被2~(n-1)的整数除,只须被2~n/2的整数除即可,甚至只须被2~√n的整数除即可。例如,判断13是否为素数,只须将13被2和3除即可,如都除不尽,n必为素数。s6步骤可改为
s6:如果i≤√n,返回s3;否则算法结束

2.4算法的特性
一个有效的算法应当具备以下特点:
(1)有穷性。一个算法应包含有限的操作步骤,而不能是无限的。有穷性往往指在合理的范围内,如果让一个计算机执行一个历时1000年的算法,这虽然使有穷的,但超过了合理的限度,人们也不能把他视为有效算法。究竟什么算“合理限度”,由人们的常识和需要判定
(2)确定性。算法中的每一个步骤都应当是确定的,而不应当是含糊的、模棱两可的。算法的含义应当是唯一的,而不应当产生“歧义性”。所谓“歧义性”,是指可以被理解为两种(或以上)的可能含义
(3)有零个或多个输入。所谓输入是指在执行算法时需要从外界取得必要的信息,一个算法也可以没有输入!
(4)有一个或多个输出。算法的目的是为了求解,“解”就是输出,但算法的输出并不一定就是计算机的打印输出或屏幕输出,一个算法得到的结果就是算法的输出。没有输出的算法是没有意义的
(5)有效性。算法中的每一个步骤都应当有效的执行,并得到确定的结果


2.5怎样表示一个算法
为了表示一个算法,可以用不同的方法。常用的方法有:自然语言、传统流程图、结构化流程图和伪代码等
1,用自然语言表示算法
自然语言就是人们日常使用的语言,可以是汉语、英语或其他语言。用自然语言表示通俗易懂,但文字冗长,容易出现歧义。自然语言表示的含义往往不大严格,要根据上下文才能判断其正确含义,此外自然语言来描述包含分支和循环的算法不大方便(如例2.5的算法),因此,除了那些很简单的问题以外,一般不用自然语言表示算法
2,用流程图表示算法
流程图是用一些图框来表示各种操作。用图形表示算法,直观形象,易于理解。美国国家标准协会规定了一些常用的流程图符号(见下图),已为世界各国程序工作者普遍采用


上图中菱形框的作用是对一个给定的条件进行判断,根据给定的条件是否成立了决定如何让执行其后的操作。它有一个入口,两个出口,见下图



连接点(小圆圈)是用于将画在不同地方的流程线连接起来。如下图中有两个以①为标志的连接点,它表示这两个点是连接在一起的,实际上他们是同一个点,只是画不下才分开来画。用连接点可以避免流程交叉或过长,使流程图清晰。注释框不是流程图中必要的部分,不反映流程和操作,只是为了对流程图中某些框的操作作必要的补充说明,以帮助阅读流程图的人更好地理解流程图的作用


下面将2.3节中所举的几个算法例子,改用流程图表示
【例2.6】将例2.1的算法用流程图表示。求1x2x3x4x5
按照流程图的规定,把算法用下图所示的流程图表示。菱形框两侧的Y和N代表“是”和“否”


【例2.7】例2.2的算法用流程图表示。有50个学生,要求输出成绩在80分以上的学生的学号和成绩


【例2.8】例2.3判断闰年的算法用流程图表示。判断2000--2500年中的每一年是否为闰年,将结果输出




【例2.9】将例2.4的算法用流程图表示,求1-1/2+1/3-1/4……+1/99-1/100


【例2.10】将例2.5的算法用流程图表示,给出一个大于或等于3的正整数,判断它是不是一个素数


通过以上几个例子可以看出流程图是表示算法的较好的工具。一个流程图包括以下几个部分
(1)表示相应操作的框
(2)带箭头的流程线
(3)框内外必要的文字说明
需要提醒的是:流程线不要忘记画箭头,因为他是反映流程的先后的,如不画处箭头就难以判断各框的执行次序
用流程图表示算法直观形象,比较清楚地显示出各个框之间的逻辑关系。但是,这种流程图占用篇幅较多,尤其当算法比较复杂时,画流程图既费时又不方便。在结构化程序设计方法推广之后,许多书刊易用N-S结构化流程图代替这种传统的流程图

2.5.3  三种基本结构和改进的流程图
1传统流程图的弊端
传统流程图用流程线指出各框的执行顺序,对流程线的使用没有严格限制。因此,使用者可以不受限制地使用流程线随意地转来转去,使流程图变得毫无规律,阅读时要花很大精力去跟踪流程,使人难以理解算法的逻辑,如下图所示,这种如乱麻一样的算法称为BS型算法,意为一碗面条(a bowl of spaghetti)毫无头绪


为了提高算法的质量,使算法的设计和阅读方便,必须限制箭头的滥用,即不允许无规律地使流程随意转向,只能顺序地进行下去。但是,算法上难免会包含一些分支和循环,而不可能全部由一个个顺序框组成。为了解决这个问题,人们规定处几种基本结构,然后由这些基本结构按一定规律组成一个算法结构,如果能做到这一点,算法的质量就能得到保证和提高

2,三种基本结构
(1)顺序结构如下图所示,虚线框内是一个顺序结构。其中A和B两个框是顺序执行的。即:在执行完A框所指定的操作后,必然接着执行B框所指定的操作。顺序结构是最简单的一种基本结构


(2)选择结构。选择结构又称选取结构或分支结构,如下图所示。虚线框内是一个选择结构。此结构中比包含一个判断框。根据给定的条件p是否成立而选择执行A框或B框。

注意:无论p条件是否成立,只能执行A框或B框之一,不可能既执行A又执行B


(3)循环结构。又称重复结构,即反复执行某一部分的操作。有两类循环结构
①当型(while型)循环结构。它的作用时:当给定的条件p1成立时,执行A框操作,执行完A后,再判断条件p1是否成立,如果仍然成立,再执行A框,如此反复执行A框,直到某一次p1条件不成立为止,此时不执行A框,而从b点脱离循环结构,如下图所示


②直到型(until型)循环结构。它的作用是:先执行A框,然后判断给定的p2条件是否成立,如果条件不成立,则再执行A,然后再对p2条件作判断,如果p2条件仍然不成立,又执行A……如此反复执行A,直到给定的p2条件成立为止,此时不再执行A,从b点脱离本循环结构,如下图



下图的作用都是输出5个数:1、2、3、4、5。可以看到:对同一个问题既可以用当型循环来处理,也可以用直到型循环来处理


以上三种基本结构,有以下共同特点:
(1)只有一个入口
(2)只有一个出口。请注意,一个判断框有两个出口,而选择结构只有一个出口。不要将判断框的出口和选择框的出口混淆
(3)结构内的每一部分都有机会被执行到。也就是说,对每一个框来说,都应当有一条入口到出口的路径通过它下图中没有一套入口到出口的路径通过A框


(4)结构内不存在“死循环”(无终止的循环)下图就是一个死循环


由以上三种基本结构顺序组成的算法结构,可以解决任何复杂的问题。由基本结构所构成的算法属于“结构化“算法,它不存在无规律的转向,只在本基本结构内才允许存在分支和向前或向后的跳转
起始,基本结构并不一定只限于上面3种,只要具有上述4个特点的都可以作为基本结构。人们可以自己定义基本结构,并由这些基本结构组成结构化程序。例如,也可以将下图这样的结构定义为基本结构,虚线框内的结构只有一个入口和一个出口,并具有上述全部的4个特点。由它们构成的算法结构也是结构化的算法。但是,可以认为像下图那样的结构是由3中基本结构派生出来的。因此,人们普遍认为最基本的是本节介绍的3种基本结构



2.5.4 用N-S流程图表示算法
既然用基本结构的顺序组合可以表示任何复杂的算法结构,那么,基本结构之间的流程线就是多用的了
1973年,美国学者提出了一种新的流程图形式。在这种流程图中,完全去掉了带箭头的流程线。全部算法写在一个矩形框内,在该框内还可以包含其他从属于它的框,或者说,由一些基本的框组成一个大的框。这种流程图又称N-S结构化流程图(N和S是两位美国学者的英文姓氏的首字母)。这种流程图适于结构化程序设计,因而很受欢迎
N-S流程图用以下的流程图符号
(1)顺序结构。如下图所示,A和B两个框组成一个顺序结构


(2)选择结构。如下图所示,当p条件成立时执行A操作,p不成立则执行B操作。注意它是一个整体,代表一个基本结构


(3)循环结构。当型循环结构用下图表示,当p1条件成立时反复执行A操作直到p1条件不成立为止


       直到型循环结构用下图表示


用以上3种N-S流程图中的基本框可以组成复杂的N-S流程图,以表示算法
在上边3中基本结构中的A框或B框,可以是一个简单的操作(如读入数据或打印输出等),也可以是3种基本结构之一。如下图所示又A和B两个基本结构组成一个顺序结构


【例2.11】将例2.1的求5!算法用N-S图表示


【例2.12】将例2.2的算法用N-S图表示。输出50名学生中成绩高于80分者的学号和成绩


【例2.13】将例2.3判断闰年的算法用N-S图表示


【例2.14】将例2.4的算法用N-S图表示,求1-1/2+1/3-1/4……+1/99-1/100


【例2.15】将例2.5的算法用N-S图表示


通过以上几个例子,可以看出用N-S图表示算法的优点。它比文字描述直观、形象、易于理解;比传统流程图紧凑易画,尤其是他废除流程线,整个算法结构是由各个基本结构按顺序组成的,N-S流程图中的伤心顺序就是执行时的顺序,也就是图中为止在上面的先执行,位置在下的后执行。写算法和看算法只须从上到下进行就可以了,十分方便。用N-S图表示的算法都是结构化的算法(它不可能出现流程无规律的跳转,而只能自上而下地顺序执行)
归纳起来可知:一个结构化的算法是由一些基本结构顺序组成的;在基本结构之间不存在向前或向后的跳转,流程的转移只存在于一个基本结构范围之内(如循环中流程的跳转);一个非结构化的算法可以用一个等价的结构化算法代替,其动能不变,如果一个算法不能分解为若干个基本结构,则它必然不是一个结构化的算法
N-S图如同一个多层的盒子,又称盒图

2.5.5用伪代码表示算法
用传统的流程图和N-S图表示算法直观易懂,但画起来比较费事,在设计一个算法时,可能要反复修改,而修复流程图是比较麻烦的。因此,流程图适用表示一个算法,但在射界算法过程中使用不是很理想(尤其当算法比较复杂、需要反复修改时)。为了设计算法时方便,常用一种称为伪代码的工具
伪代码是用介于族人语言和计算机语言之间的文字和符号来描述算法。它如同一篇文章一样,自上而下地写下来。每一行(或几行)表示一个基础操作。他不用图形符号,因此书写方便,格式紧凑,修改方便,容易看懂,也便于想计算机语言(即程序)过渡
用伪代码写算法并无固定的、严格的语法规则,可以用英文,也可以用中英文混用。只要把意思表达清楚,便于书写和阅读即可,书写的格式要写成清晰易读的形式

【例2.16】求5!,用伪代码表示的算法如下:
begin             (算法开始)
  1=>t
  2=>i
while i≤5
   {t*i=>t
     i+1=>i
   }
   print t
end               (算法结束)
在本算法中采用当型循环(第3~6行是一个当型循环)。while意思为”当“,它表示当i≤5时执行循环体(花括号中两行)的操作

【例2.17】求1-1/2+1/3-1/4……+1/99-1/100
用伪代码表示的算法如下:
begin
   1=>sum
   2=>deno
   1=>sign
while deno≤100
{
(-1)*sign=>sign
sign*1/deno=>term
sum+term=>sum
deno+1=>deno
}  
print sum
end

以上例子可以看到:伪代码书写格式比较自由,容易表达出设计者的思想。同时,用伪代码写的算法很容易修改,例如加一行或删一行,获奖后面某一部分调到前面某一位置,都是很容易做到的。而这却是流程图表示算法时所不便的。用伪代码很容易写出结构化的算法。但是用伪代码写算法不然流程图直观,可能会出现逻辑上的错误(例如循环或选择结构的范围弄错等)


2.5.6用计算机语言表示算法
要完成一项工作,包括设计算法和实现算法两部分。设计算法的目的是为了实现算法。因此不仅要考虑如何设计一个算法,也要考虑如何实现一个算法

在用流程图或伪代码描述一个算法后,还要将它转换成计算机语言程序。用计算机语言表示的算法是计算机能够执行的算法
用计算机语言表示算法必须严格遵循所用语言的语法规则,这是和伪代码不同的,下面将前面介绍过的算法用c语言表示
【例2.18】将例2.16表示的算法(求5!)用c语言表示
#include <stdio.h>
int main()
{
int i,t;
t=1;
i=2;
while(i<=5)
{
t=t*i;
i=i+1;
}
printf("%d\n",t);
system("pause");
return 0;
}



2.6 结构化程序设计方法
一个结构化程序就是用计算机语言表示的结构化算法,用3种基本结构组成的程序必然是结果化的程序。这种程序便于编写、阅读、修改和维护,这就减少了程序出错的机会,提高了程序的可靠性,保证了程序的质量
结构化程序设计强调程序设计风格和程序结构的规范化,提倡清晰的结构。怎样才能得到一个结构化的程序呢?如果面临一个复杂的问题,是难以一下子写出一个层次分明、结构清晰、算法正确的程序的。结构化程序设计方式的基本思路是:把一个复杂的问题的求解过程分阶段进行,每个阶段处理的问题都控制在人们容易理解和处理的范围内
具体说,采取以下方法来爆照得到结构化的程序:
(1)自顶向下
(2)逐步细化
(3)模块化设计
(4)结构化编码
在接受一个任务后应怎样着手进行呢?有两种不同的方法:一种是自顶向下,逐步细化,如下图

                                                                                   一种是自下而上,逐步积累

显然自顶向下逐步分解考虑更为周全,结构清晰,层次分明。如果发现某一部分中有一段内容不妥,需要修改,只需要找出该部分,修改有关段落即可,与其他部分无关,提倡用这种自顶向下的方法设计,这就是用工程的方法设计程序
应当掌握自顶向下、逐步细化的设计方法。这种设计方法的过程是将问题求解由抽象 逐步具体化的过程
用这种方法便于验证算法的正确性,在向下一层展开之前应仔细检查本层设计是否正确,只有上一层是正确的才能向下细化。如果每一层设计都没用问题,则整个算法就是正确的。由于每一层向下细化时都不太复杂,因此容易保证整个算法的正确性。检查时也是由上而下逐层检查,这样做,思路清楚,有条不紊地一步一步地进行,既严谨又方便
在程序设计中常采用模块设计的方法,尤其当程序比较复杂时,更有必要。在那倒一个程序模块(实际上是程序模块的任务书)以后,根据程序模块的功能将它划分为若干个子模块,如果这些子模块的规模还嫌大,可以再划分为更小的模块。这个过程采用自顶向下的方法来实现
程序中的子模块在c语言中通常用函数来实现
程序中的子模块一般不超过50行,即把他打印输出时不超过一页,这样的规模便于组织,也便于阅读。划分子模块时应注意模块的独立性,即使用一个模块完成一项功能,耦合性越少越好。模块化设计的思想实际上是一种”分而治之“的思想,把一个大任务分为若干个子任务,每一个子任务就相对简单了
结构化程序设计方式用来解决人脑思维能力的局限性和被处理问题的复杂性之间的矛盾
在设计好一个结构化的算法之后,还要善于进行结构化编码。所谓编码就是将已设计好的算法用计算机语言来表示,即根据已经细化的算法正确地写出计算机程序。结构化的语言都有与3中基本结构对于的语句,进行结构化编程序是不困难的
学习程序设计的目的不只是为了掌握某一种特定的语言,而应当学习程序设计的一般方法。脱离具体的语言去学习程序设计是困难的,但是,学习语言是为了设计程序,它本身绝不是目的。高级语言有许多种,每种语言也都在不断发展,因此千万不能只拘泥于一种具体的语言,而应当能举一反三,在需要的时候能很快地使用另一种语言编程。关键是掌握算法,有了正确的算法,用任何语言进行编程都不是什么困难的事


习题
1、什么叫算法?试从日常生活中找3个例子,描述它们的算法

2、什么将结构化算法?为什么要提倡结构化算法?

3、试述3中基本结构的特点,请另外设计两种基本结构(要符合基本结构的特点)

4、用传统流程图表示求解以下问题算法
(1)有两个瓶子A和B,分别盛放醋和酱油,要求将它们互换(即A瓶原来盛醋,新增改盛酱油,B瓶则相反)

(2)依次将10个数输入,要求输出其中最大的数

(3)有3个数a,b,c,要求按照大小顺序把它们输出

(4)求1+2+3+4……+100

(5)判断一个数n能否同时被3和5整除

(6)将100~200之间的素数输出

(7)求两个数m和n的最大公约数

(8)求方程式ax2+bx+c=0的根。分别考虑
①有两个不等的实根
②有两个相等的实根
5、用N-S图表示第4题中的各题算法
6、用伪代码表示第4题中各题的算法
7、社么叫结构化程序设计?它的主要内容是什么?
8、用自顶而下、逐步细化的方法进行以下算法的设计:
(1)输入1900-2000年中是闰年的年份,符合下面两个条件之一的年份是闰年:
①能被4整除但不能被100整除
②能被100整除且能被400整除
(2)求ax2+bx+c=0的根。分别考虑d=b2-4ac大于0、等于0和小于0这3种情况
(3)输入10个数,输出其中最大的一个数

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

8

主题

135

帖子

3270

积分

超级月卡

Rank: 7Rank: 7Rank: 7

积分
3270
 楼主| 发表于 2020-8-20 06:43:04 | 显示全部楼层
本帖最后由 why 于 2020-8-20 18:19 编辑

第3章  最简单的c程序设计----顺序程序设计为了能编写出c语言程序,必须具备以下的知识和能力
(1)要有正确的解题思路,即学会设计算法,否则无从下手
(2)掌握C语言的语法,直到怎样使用C语言所提供的功能编写处一个完整的、正确的程序。也就是在设计好算法之后,能用c语言正确的表示算法
(3)在写算法和编程时,要采用结构化程序设计方法,编写处结构化的程序


3.1顺序程序设计举例
【例3.1】有人用温度计测量处华氏法表示的文档(如64°F),今要求把它转换为以摄氏度表示的问题(如17.8℃)
解题思路:这个问题的算法很简单,关键在于找到二者的转换公式。根据物理学知识,知道以下转换公式:c=5/9(f-32)
其中f代表华氏温度,c代表摄氏温度。据此可以用N-S图表示算法,见下图


算法由3个步骤组成,这是一个简单的顺序结构
编写程序:
#include <stdio.h>
int main()
{
    float f,c;                               //定义f和c为单精度浮点型变量
    f=64.0;                               //指定f的值
   c=(5.0/9)*(f-32);                 //计算c的值
   printf("f=%f\nc=%f\n",f,c);  //输出f,c的值
   return 0;
}



【例3.2】计算存款利息。有1000元,想存一年。有3种方法可选:(1)活期,年利率为r1;(2)一年期定存,年利率为r2;(3)存两次半年定期,年利率为r3。请分别计算一年后按3种方法所得到的本息和
解题思路:关键是确定计算本息的公式。从数学知识可知,若存款为p0,则:
活期存款一年后的本息和为p1=p0(1+r1)
一年期定期存款,一年后本息和为p2=p0(1+r2)
两次半年定期存款,一年后本息和为p3=p0(1+r3/2)(1+r3/2)
画出N-S流程图,见下图

编写程序:
#include <stdio.h>
int main ()
{
   float p0=1000,r1=0.0036,r2=0.0225,r3=0.0198,p1,p2,p3;     //定义变量
   p1=p0*(1+r1);                                                                  //计算活期本息和
   p2=p0*(1+r2);                                                                  //计算一年定期本息和
   p3=p0*(1+r3/2)*(1+r3/2);                                                //计算计算存两次半年定期本息和
   printf("p1=%f\np2=%f\np3=%f\n",p1,p2,p3);                    //输出结果
   return 0;
}





3.2数据的表项形式及其运算
3.2.1常量和变量
1.常量:在程序运行过程中,其值不能被改变的量称为常量
    (1)整型常量。如1000,12345,0,-345等都是整型常量
    (2)实型常量。有两种表示形式
            ①十进制小数形式,由数字和小数点组成。如123.456, -56.79等
            ②指数形式,如12.34e3(代表12.34x103)。由于在计算机输入或输出时无法表示上角或下角,故规定以字母e或E代表以10为底数的指数。
                               但应注意:e或E之前必须有数字,且e或E后面必须为整数。如不能写成e4,12e2.5
      (3)字符常量。有两种形式的字符常量
            ①普通字符。用单撇号括起来的字符,一般以ASCII码形式存储在机器中
            ②转义字符。比如前边代码中用到的\n,常用的以\开头的特殊字符见下表
   

(4)字符串常量。如“boy”,“12340”,用双撇号把若干个字符括起来。单撇号内只能包含一个字符,双撇号内可以包含一个字符串
         说明:从其字面形式上即可识别的常量称为“字面常量”或“直接常量”。字面常量是没有名字的不变量(5)符号常量。用#define指令,指定一个符号名称代表一个常量。如:
                   #define PI 3.1416                   //注意行末没有分号
经过以上的指定后,本文件从此行开始所有的PI都代表3.1416。在对程序进行编译前,预处理器先对PI进行处理,把所有PI全部置换为3.1416。这种用一个符号代表一个常量的,称为符号常量。在预编译后,符号常量已全部变成字面常量。使用符号常量有以下好处
      ①含义清楚。看程序时从PI就可以大致知道它代表圆周率。在定义符号常量名时应考虑“见名知义”。在一个规范的程序中不提倡使用很多的常数,应尽量                            使用“见名知义”的变量名和符号常量
      ②在需要改变程序中多处用到的同一个常量时,能做到“一改全改”
注意:要区分符号常量和变量,不要把符号常量误认为变量。符号常量不占内存,只是一个临时符号,代表一个值,在预编译后这个符号就不存在了,故不能对符号常量赋新值。为与变量名相区别,习惯上符号常量用大写表示。
2.变量
变量代表一个有名字的、具有特定属性的一个存储单元。它用来存放数据,也就是存放变量的值。在程序运行期间,变量的值是可以改变的
变量必须先定义,后使用。在定义时指定该变量的名字和类型。一个变量应该有一个名字,以便被引用。请注意区分变量名和变量值这两个不同的概念下图中a是变量名,3是变量a的值,即存放在变量a的内存单元中的数据。变量名实际上是一个名字代表的一个存储地址。在对程序编译连接时由编译系统给每一个变量分配对应的内存地址。从变量中取值,实际上是通过变量名找到相对的内存地址,从该存储单元中读取数据


3.常变量
c99允许使用常变量,方法是在定义变量时,前面加一个关键字const,如:
     const int a=3
定义a为一个整型变量,指定其值为3,而且在变量存在期间其值不能改变
常变量与常量的异同是:常变量具有变量的基本属性:有类型,占存储单元,只是不允许改变其值。可以说,常变量是有名字的不变量,而常量是没有名字的不变量。有名字就便于在程序中被引用
请思考:常变量与符号变量有什么不同?如:
#define Pi 3.1415926                         //定义符号常量
const float pi=3.1415926                    //定义常变量
符号常量Pi和常变量pi都代表3.1415926,在程序中都能使用。但二者性质不同:定义符号常量用#define指令,它是预编译指令,它只是用符号常量代表一个字符串,在预编译时仅进行字符替换,字啊预编译后,符号常量就不存在了(全置换成3.1415926了),对符号常量的名字是不分配存储单元的。而常量要占用存储单元,有变量值,只是该变量不改变而已。从使用的角度看,常变量具有符号常量的有点,而且使用更方便。有了常变量一行,可以不必多用符号常量。
说明:有些编译系统还未实现c99的功能,因此不能使用常变量
4.标识符
在计算机高级语言中,用来对变量、符号常量名、函数、数组、类型等命名的有效字符序列统称为标识符(identifier)。简单地说,标识符就是一个对象的名字。前面用到的变量名p1,p2,c,f,符号常量名PI,函数名printf等都是标识符
c语言规定标识符只能由字母、数字和下划线3种字符组成,且第1个字符必须为字母或下划线。下面列出的是合法的标识符,可作为变量名:
                    sum,average,_total,class
下面是不合法的标识符和变量名:
                   M.D.john,¥123,#33,3D64
注意:编译系统认为大写字母和小写字母是两个不同的字符。因此,sum和SUM是两个不同的变量名,同样class和Class也是两个不同的变量名。一般而言,变量名用小写字母表示,与人们日常习惯一致,以提高可读性
3.2.2数据类型
C语言要求在定义所有的变量时都要指定变量的类型。常量也是区分类型的。
所谓类型,就是对数据分配存储单元的安排,包括存储单元的长度(占多少字节)以及数据的存储形式。不同的类型分配不同的长度和存储形式
C语言运行实验的类型见下图。图中有*的是c99所增加的


其中,基本类型(包括整型和浮点型)和枚举型类型变量的值都是数值,统称为算术类型。算术类型和指针类型统称为纯量类型,因为其变量的值是以数字来表示的。枚举类型是程序中用户定义的整数类型。数组类型和结构体类型统称为组合类型,共用体类型不属于组合类型,因为在同一时间内只有一个成员具有值。函数类型用来定义函数,描述一个函数的接口,包括函数返回值的数据类型和参数的类型
不同类型的数据在内存中占用的存储单元是不同的,存储不同类型的数据的方法也是不同的

3.2.3整型数据
1.整型数据的分类
(1)基本整型(int型)编译系统分配给int整型数据2个字节或4个字节(由具体的c编译系统自行决定)。如turbo c 2.0为每一个整型数据分配2个字节,而visual c++为每一个整型数据分配4个字节。在存储单元中的存储方式是:用整数的补码形式存放(补码这里就不说了,汇编的时候学过)

(2)短整型(short int)
类型名为short int或short。如用visual c++编译系统分配给int数据4个字节,短整型2个字节,存储方式与int型相同。一个短整型变量的范围是-32768~32767

(3)长整型(long int)
类型名为long int或long。visual c++对一个long型数据分配4个字节。


(4)双长整型(long long int)
类型名为long long int 或long long,一般分配8个字节。这是c99新增的类型,但许多c编译系统尚未实现
说明:c标准没有具体规定葛总类型数据所占用存储单元的长度,这是由各编译系统自行决定的。c标准值要求long类型数据长度不短于int型,short型不长于int型,即:
      sizeof(short)≤sizeof(int)≤sizeof(long)≤sizeof(long long)
sizeof是测量类型或变量长度的运算符。

2.整型变量的符号属性
以上介绍的几种类型,变量值在存储单元中都是以补码的形式存储的,存储单元中的第1个二进制位代表符号。整型变量的值的范围包括负数到整数,见下图


在实际应用中,有的数据的范围常常只有正值(如学号、年龄、库存量、存款额等)。为了充分利用变量的值的范围,可以将变量定义为“无符号”类型。可以在类型符号前面加上修饰符unsigned,表示指定该变量时“无符号整数”类型。如果加上修饰符signed,则是“有符号类型”。因此。在以上4种整型数据的基础上可以扩展为以下8种整型数据:
有符号基本整型     [signed] int
无符号基本整型     unsigned int
有符号短整型        [signed] short [int]
无符号短整型        unsigned short [int]
有符号长整型        [signed] long [int]
无符号长整型        unsigned long [int]
有符号双长整型*   [signed] long long [int]
无符号双长整型*   unsigned long long [int]
以上有“*”的是c99增加的,方括号表示其中的内容是可选的。既可以有,也可以没有。如果既未指定为signed也未指定为unsigned的,默认为“有符号类型”。如signed int a和int a等价
有符号整型数据在存储单元中最高位嗲表数值的符号(0为正,1为负)。如果指定unsigned型(无符号型),存储单元中全部二进制都用作存放数值本身,而没有符号。无符号型变量只能存放不带符号的整数。由于左面最高位不再用来表示符号,而用来表示数值,因此无符号整型变量中可以存放的正数的范围比一班正想变量中正数的范围扩大一倍。
说明:
(1)只有整型(包括字符型)数据可以加signed或unsigned修饰符,实型数据不能加
(2)对无符号整型数据用“%u”格式输出,%u表示用无符号十进制的格式输出。如:
unsigned short price=50;                     //定义price为无符号短整型变量
printf(“%u\n”,price);                         //指定用无符号十进制数的格式输出
在将一个变量定义为无符号整型后,不应向它赋予一个负值,否则会得到错误的结构。如:
unsigned short price=-1;                     //不能把一个负值存储在无符号变量中
printf(“%d\n”,price);
得到的结果为65535。显然与愿意不符
请思考:这是为什么?因为这是以补码形式显示的


3.2.4 字符型数据
由于字符是按其代码(整数)形式存储的,因此c99把自发性书籍作为整数类型的一种。但是,字符型数据在使用上有自己的特点,因此把他单独列为一节来介绍
1.字符与字符代码
字符与字符代码并不是任意写一个字符,程序都能识别的。例如代表圆周率的π在程序中是不能识别的,只能使用系统的字符集中的字符,目前大多数系统采用ASCII字符集。各种字符集(包括ASCII码字符集)的基本集都包括了127个字符。其中包括:
   *字母:大写英文字母A~Z,小写英文字母a~z

   *数字:0~9
   *专门符号:29个
   *空格符:空格、水平制表符(Tab)、垂直制表符、换行、换页
   *不能显示的字符:空字符(null)(以‘\0’表示)、警告(以‘\a’表示)、退格(以‘\b’表示)、回车(以‘\r’表示)等
一个ASCII码可以用7个二进制位表示(ASCII代码为127时,二进制形式为1111111,7位全为1)。所有C语言中,指定一个字节(8位)存储一个字符(所有系统都不例外)。此时字节中的第1位置为0

2.字符变量
字符变量是用类型char定义字符变量。char是英文character(字符)的缩写,见名即可知义。如:char c='?';
定义c为字符型变量并使初始值为字符’?‘。?的ASCII码是63,系统把63复制给变量c
c是字符变量,实质上是一个字节的整型变量,由于它常用来存放字符,所以称为字符变量。
在输出字符变量的值时,可以选择以十进制形式输出,或以字符形式输出。如:
printf(”%d  %c\n“,c,c);
输出结果是
       63 ?
说明:用”%d“格式输出十进制整数63,用”%c“格式输出字符’?‘
前面介绍了整型变量可以用signed和unsigned修饰符表示符号属性。字符类型也属于整型,也可以用signed和unsigned修饰符
字符型数据的存储空间和值的范围表,见下图


说明:在使用有符号字符型变量时,运行存储的值为-128~127,但字符的代码不可能为负值,所以在存储字符时实际上只用到0~127这一部分,其第一位都是0

3.2.5浮点型数据
浮点型数据是用来表示具有小数点的实数的。为什么c语言中把实数称为浮点数呢?在c语言中,实数是以指数形式存放在存储单元中的。一个实数表示为知识可以有不止一种形式,如3.14159可以表示为3.14159x10^0,0.314159x10^1,0.0314159x10^2,31.4159x10^-1,314.159x10^-2等,它们代表同一个值。可以看出:小数点的位置是可以在314159几个数字之间、之前或之后(加0)浮动的,只要在小数点位置浮动的同时改变指数的值,就可以保证它的值不会改变。由于小数点位置可以浮动,所以实数的指数形式称为浮点数
浮点数类型包括float(单精度浮点型)、double(双精度浮点型)、long double(长双精度浮点型)
(1)float(单精度浮点型)。编译系统为每一个float型变量分配4个字节,数值以规范化的二进制数指数形式存放在存储单元中。在存储时,系统将实型数据分成小数部分和指数部分两个部分分别存放。小数部分的小数点前面的数为0。下图是用十进制数来示意3.14159在内存中的存放形式,实际上在计算机中是用二进制数来表示小数本分以及用2的幂次来表示只是部分的。在4个字节(32位)中,究竟用多少位来表示小数部分,多少位来表示指数部分,c标志并无规定,由各c语言编译系统自定。有的c语言编译系统以24位表示小数部分(包括符号),以8位表示指数部分(包括指数的符号)。由于用二进制形式表示一个实数以及存储单元的长度是有限的,因此不可能得到完全精确的值,只能存储成有限的精确度。小数部分占的位(bit)数越多,数的有效数字就越多,精度也就越高。指数部分占的位数越多,则能表示的数值范围越大。float型数据能得到6位有效数字(小数点后6位),数值范围为-3.4x10^-38~3.4x10^38


(2)double型(双精度浮点型)。为了扩大能表示的数值范围,用8个字节存储一个double型数据,可以得到15位有效数字,数值范围为-1.7x10^-308~1.7x10^308。为了提高运算精度,在c语言中进行浮点数的算术运算时,将float型数据都自动转换为double型,然后进行运算
(3)long double(长双精度型),不同编译系统对long double型的处理方法不同,turbo c对long double型分配16个字节。而visual c++则对long double型double型一样处理,分配8个字节
下标列出实型数据的有关情况(visual c++环境下)


说明:用有限的存储单元不可能完全精确地存储一个实数,例如float型变量能存储的最小正数为1.2x10^-38,不能存放绝对值小于此值的数,例如10^-40。float型变量能存储的范围见下图,即数值可以在3个范围内:(1)-3.4x10^38~1.2x10^-38;(2)0;(3)1.2x10^-38~3.4x10^38




本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

8

主题

135

帖子

3270

积分

超级月卡

Rank: 7Rank: 7Rank: 7

积分
3270
 楼主| 发表于 2020-8-20 20:06:44 | 显示全部楼层
本帖最后由 why 于 2020-8-22 11:40 编辑

3.2.6怎么确定常量的类型
在C语言中,不仅变量有类型,常量也有类型。为什么要把常量分为不同的类型呢?
在程序中出现的常量是要存放在计算机中的存储单元中的,这就必须确定分配给他多少字节,按什么方式存储。例如,程序中有整数12,在visual c++中会分配给它4个字节,按补码方式存储
怎样确定常量的类型呢?从常量的表示形式即可以判断其类型。对于字符常量很简单,只要看到由单撇号括起来的单个字符或转义字符就可以知道他是字符常量。对于数值按以下规律判断
整型常量。不带小数点的数值是整型常量,但应注意其有效范围。如在turbo c中,系统为整型数据分配2个字节,其表值范围为-32768~32767,如果在程序中出现数值常量23456,系统把它作为int型处理,用2个字节存放。如果出现49875,由于超过32767,2个字节放不下,系统会把他作为长整型(long int)处理,分配4个字节。在visual c++中,在范围-2147483648~2147483674的不带小数点的数都作为int型,分配4个字节,在此范围外,而又在long long型数的范围内的整数,作为long long型处理。
在一个整数的末尾加大写字母L或小写字母l,表示他是长整型(long int)。例如123L,234l等。但在visual c++中由于int和long int型数据都分配4个字节,因此没有必要用long int型
浮点型常量。凡以小数形式或指数形式出现的实数均是浮点型常量,在内存中都以指数形式存储。如10是整型常量,10.0是浮点型常量。那么对浮点型常量是按单精度处理还是按双精度处理呢?c编译系统把浮点型常量都按双精度处理,分配8个字节
注意:c语言中的实型常量都作为双精度型常量
如果有float a=3.14159,在进行编译时,对float变量分配4个字节,但对于浮点型常量3.14159,则按双精度处理,分配8个字节。编译系统会发出”警告“。意为”把一个双精度常量转换为float型“,提醒用户注意这种转换可能损失精度。这样的”警告“一般不会影响程序运行结果的正确性,但是会影响程序运行结果的精确度
可以在常量的末尾加专用字符,强制指定常量的类型。如在3.14159后面加字符F或f,就表示是float型常量,分配4个字节。如果在实型常量后面加大写或小写的L,则指定此常量为long double型。如:
float a=3.14159f;                         //把此3.14159按单精度浮点常量处理,编译时不出现”警告“
long double a=1.23L;                     //把此1.23作为long double型处理
注意:要区分类型与变量
每一个变量都属于一个确定的类型,类型是变量的一个重要的属性。变量是占用存储单元的,是具体存在的实体,在其占用的存储单元中可以存放数据。而类型是变量的共性,是抽象的,不占用存储单元,不能用来存放数据


3.3运算符和表达式
3.3.1  c运算符
C语言提供了以下运算符:
(1)算术运算符                           (+  -  *  /  %  ++  --)
(2)关系运算符                           (>   <   ==   >=   <=  != )
(3)逻辑运算符                             (!&&||)
(4)位运算符                               (<<>>~|^ &)
(5)赋值运算符                              (=及其扩展复制运算符)
(6)条件运算符                            (?:)
(7)逗号运算符                            ( ,)
(8)指针运算符                            (*和&)
(9)求字节数运算符                      (sizeof)
(10)强制类型转换运算符              ((类型))
(11)成员运算符                           (.->)
(12)下标运算符                           ([])
(13)其他                                    (如函数调用运算符())

3.3.2基本的算术运算符

最常用的算术运算符见下表


说明:两个实数相除的结果是双精度小数,两个整数相除的结果为整数,如5/3的结果为1,舍去小数部分。但是如果除数或被除数中有一个为负值,则舍入的方向是不固定的。例如-5/3,有的系统中得到的结果为-1,在有的系统中得到结果为-2。多数c编译系统(如visual c++)采取“向零取整”的方法,即5/3=1,-5/3=-1,取整后向零靠拢
%运算符要求参加运算的运算对象(即操作数)为整数,结果也是整数。如8%3,结果为2
除%以外的运算符的操作数都可以是任何算术类型

3.3.3自增(++)自减(--)
自增(++)、自减(--)运算符的作用时使变量的值加1或减1,例如:
++i,--i              (在使用i之前,先使i的值加(减)1)
i++,i--              (在使用i之后,使i的值加(减)1)
如果i的值为3,那么
j=++i                     (先执行i+1,然后将得出的4赋值给j,i为3,j为4)
j=i++                     (先把i的值3赋值给j,j的值为3,然后执行i+1,i为4,j为3)


3.3.4算术表达式和运算符的优先级与结合性
用算术运算符和括号将运算对象(也称操作数)连接起来的、符合c语法规则的式子称为c算术表达式。运算对象包括常量、变量、函数等。例如下面是一个合法的c算术表达式:
a*b/c-1.5+'a'
c语言规定了运算符的优先级(例如先乘除后加减),还规定了运算符的结合性
在表达式求值时,先按运算符的优先级别顺序执行,先乘除后加减
如果在一个运算对象两侧的运算符的优先级别相同,则按规定的“结合方向”处理,算术运算符的结合方向都是“自左至右”,自左至右的结合方向又称“左结合性”,即运算对象先与左面的运算符结合,还有右结合性,例如,赋值运算符,若有a=b=c,按从右到左的顺序,先把变量c的值赋值给b,然后把变量b的值赋值给a
说明:算术运算符是自左至右(左结合性),赋值运算符是自右至左(右结合性)

3.3.5不同类型数据间的混合运算
在程序中经常会遇到不同类型的数据进行运算,如5*4.5。如果一个运算符两次的数据类型不同,则先自动进行类型转换,使二者称为同一种类型,然后进行运算。整型、实型、字符型数据间可以进行混合运算。规律为:
(1)+、-、*、/运算的两个数中有一个位float或double型,结果是double型,因为系统将所有float型数据都先转换为double型,然后进行运算
(2)如果int型与float或double型数据进行运算,先把int型和float型数据转换为double型,然后进行运算,结果是double型
(3)字符(char)型数据与整型数据进行运算,就是把字符的ASCII码与整型数据进行运算。如果字符型数据与实型数据进行运算,则将字符的ASCII码转换为double型数据,然后进行运算

【例3.3】给定一个大写字母,要求用小写字母输出
解题思路:字符数据以ASCII码存储在内存中,形式与整数的存储形式相同。所以字符型数据和其他算术型数据之间可以互相赋值和运算
                 小写字母的ASCII码减去32就是大写字母的ASCII码,汇编中介绍过
编写程序:
#include <stdio.h>
int main ()
{
char c1,c2;
c1='A';                                    //将字符‘A’的ASCII码放到变量c1中
c2=c1+32;                              //计算得出‘a’的ASCII码,放在变量c2中
printf("%c\n",c2);                    //以字符形式输出c2
printf("%d\n",c2);                   //以十进制形式输出c2
system("pause");
return 0;
}


3.3.6强制类型转换运算符
可以利用强制类型转换运算符将一个表达式转换成所需类型。例如:
(double)a             (将a转换成double型)
(int)(x+y)         (将x+y的值转换成int型)
(float)(5%3)      (将5%3的值转换成float型)
其一般形式为:
          (类型名)(表达式)
注意,表达式应该用括号括起来。如果写成
    (int)x+y
则只将x转换成整型,然后与y相加
如果已定义x为float型变量,a为整型变量,进行强制类型运算(int)x后得到一个int类型的临时值,它的值等于x的整数部分,把它赋值给a,注意x的值和类型都未变化,仍旧为float型,该临时值在赋值后就不存在了
从上可知,有两种类型转换:
        一种是在运算时不必用户干预,系统自动进行的类型转换,如3+6.5
        另一种是强制类型转换。当自动类型转换不能实现目的时,可以用强制类型转换,如%运算要求其两侧均为整型量,若xfloat型,则x%3不合法,必须用        (int)x%3,强制类型转换是优先于%运算的,因此先进行(int)x的运算,得到一个整型的中间变量,然后再对3求余。此外在函数调用时,有时为了使             实参与形参类型一致,可以用强制类型转换运算符得到一个所需类型的参数


3.4.1 c语句的作用和分类
在前面的例子中可以看到:一个函数包含声明部分和执行部分,执行部分是由语句组成的,语句的作用是向计算机系统发出操作指令,要求执行相应的操作。一个c语句警告编译后产生若干条机器指令。声明部分不是语句,它不产生机器指令,只是对有关数据的声明
c程序结构可以用下图表示。即一个c程序可以由若干个源程序文件(编译时以文件模块为单位)组成,一个源文件可以由若干个函数和预处理指令以及全局变量声明部分组成(全局变量见第7章)。一个函数由数据声明部分和执行语句组成


c语句分为以下5类
(1)控制语句。控制语句用于完成一定的控制能力。C语言只有9种控制语句,它们的形式是:
①if()……else……              (条件语句)
②for()……                       (循环语句)
③while()……                (循环语句)
④do……while()            (循环语句)
⑤continue                       (结束本次循环语句)
⑥break                           (终止执行switch或循环语句)
⑦switch                           (多分支选择语句)
⑧return                            (从函数返回语句)
⑨goto                               (转向语句,在结构化程序中基本不用goto语句)

上面9种语句表示形式中的()表示括号中是一个“判别条件”,“……”表示内嵌的语句。例如上面的“if()……else……”的具体语句可以写成:
if(x>y)  z=x;
else   z=y;
其中,x>y是一个“判别条件”,“z=x;”和“z=y;”是c语句,这两个语句是内嵌在if……else语句中的。这个if……else语句的作用时:先判断条件“x>y”是否成立,如果成立,就执行内嵌语句“z=y”,否则就执行内嵌语句“z=y”

(2)函数调用语句。函数调用语句由一个函数调用加一个分号构成,例如:
printf(“this is a c statement.”);
其中printf(“this is a c statement.”);是一个函数调用,加一个分号称为一个语句

(3)表达式语句。表达式语句由一个表达式加一个分号构成,最典型的是由赋值表达式构成一个赋值语句。例如:
a=3;
是一个赋值语句。可以看到,一个表达式的最后加一个分号就成了一个语句。一个语句必须在最后有一个分号,分号是语句中不可缺少的组成部分,而不是两个语句间的分隔符号。例如:
i=i+1                 (是表达式不是语句)
i=i+1;            (有分号就是语句了)
任何表达式都可以加上分号而成为语句,例如:
i++;
是一个语句,作用是使i值加1。又例如:
x+y;
也是一个语句,作用时完成x+y的操作,它是合法的,但是并不是把x+y的和赋值给另一个变量,所以它并无实际意义
表达式能构成语句是c语言的一个重要特色。其实“函数调用语句”也属于表达式语句,因为函数调用(如sin(x))也属于表达式的一种。只是为了便于理解和使用,才把“函数调用语句”和“表达式语句”分开来说明

(4)空语句。下面是一个空语句

此语句只有一个分号,它什么也不做。那么它有什么用呢?可以用来作为流程的转向点(流程从程序其他地方转到此语句处),也可用来作为循环语句中的循环体(循环体是空语句,表示循环体什么也不做)

(5)复合语句。可以用{}把一些语句和声明括起来称为符号语句(又称语句块)。例如:
{
float pi=3.14159,r=2.5,area;                            //定义变量
area=pi*r*r;
printf("area=%f",area);
}
可以在复合语句中包含声明部分(如上面的第2行),c99运行将声明部分放在复合语句中的任何位置,但习惯上把它放在语句块开头位置。复合语句常用在if语句或循环中,此时程序需要连续执行一组语句
注意:复合语句中最后一个语句末尾的分号不能忽略不写

3.4.2最基本的语句---赋值语句
在c程序中,最常用的语句是:赋值语句和输入输出语句。其中最基本的是赋值语句。程序中的计算功能大部分是由赋值语句实现的,几乎每一个有实用价值的程序都包括赋值语句。有的程序中的大部分语句都是赋值语句。
【例3.4】给出三角形的三边长,求三角形面积
解题思路:假设给定的三个边符合构成三角形的条件:任意两边之和大于第三边。解此题的关键是要找到求三角形面积的公式。从数学知识已知三角形面积的公式为:
area=√s(s-a)(s-b)(s-c)
其中,s=(a+b+c)/2
编写程序:根据上面的公式编写程序如下:
#include <stdio.h>
#include <math.h>
int main()
{
double a,b,c,s,area;                               //定义各变量,均为double型
a=3.67;                                               //对边长a赋值
b=5.43;                                               //对边长b赋值
c=6.21;                                               //对边长c赋值
s=(a+b+c)/2;                                      //计算s
area=sqrt(s*(s-a)*(s-b)*(s-c));             //计算area
printf("a=%f\tb=%f\t%f\n",a,b,c);       //输出三边的值
printf("area=%f\n",area);                    //输出面积area的值
system("pause");
return 0;
}


注意:以后凡在程序中要用到数学函数库中的函数,都应当在本文件的开头包含math.h头文件。上边的程序中sqrt函数是求平方根的函数


1.赋值运算符
赋值符号“=”就是赋值运算符,它的作用时将一个数据赋值给一个变量。也可以将一个表达式赋值给一个变量

2.复合的赋值运算符
在赋值符=之前加上其它运算符,可以构成符合运算符。如果在“=”前加一个“+”运算符就成了复合运算符“+=”。例如可以有以下的复合赋值运算:
a+=3        等价于a=a+3
x*=y+8     等价于x=x*(y+8)
x%=3        等价于x=x%3以“a+=3”为例来说明,它相当于使a进行一次自加3的操作。即:先使a加3,再把结果赋值给a,同样“x*=y+8”的作用是使x乘以(y+8),结果赋值给x
凡是二元(二目)运算符,都可以与赋值符一起组合成复合赋值符。有关算术运算的复合赋值运算符有+=,-=,*=,/=,%=
C语言采用这种复合运算符,一是为了简化程序,使程序精炼,二是为了提高编译效率,能产生质量较高的目标代码。专业人员往往喜欢使用符合运算符,程序显得专业一点。

3.赋值表达式
由赋值运算符将一个变量和一个表达式连接起来的式子称为“赋值表达式”,它的一般形式为:
                                  变量   赋值运算符  表达式
赋值表达式的作用是将一个表达式赋值给一个变量,因此赋值表达式具有计算和赋值的双重功能
赋值运算符左侧应该是一个可修改值的“左值”。左值的意思是它可以出现在赋值运算符的左侧,它的值是可以改变的。并不是任何形式的数据都可以作为左值的,左值应当为存储空间并可以被赋值。变量可以作为左值,而算术表达式就不能作为左值,常量也不能作为左值,因为常量不能被赋值。能出现在赋值运算符右侧的表达式称为“右值”,显然左值也可以出现在赋值运算符右侧,因而凡是左值都可以作为右值。如:
b=a;                     //b是左值
c=b;                     //b也是右值
赋值表达式中的“表达式”又可以是一个赋值表达式。如:
a=(b=5)
由于赋值运算符是遵循”自右至左“的顺序。a=(b=5)等价于a=b=5,先给b赋值,然后再给a赋值
把赋值表达式作为表达式的一种,使得赋值操作不仅可以出现在赋值语句中,而且可以以表达式的形式出现在其他语句中(如输出语句、循环语句),如:
printf("%d",a=b);

*4赋值过程中的类型转换
如果赋值两侧的类型一致,则直接进行复制。如:
i=234;                //设已定义i为整型变量
此时直接将整数234存入变量i的存储单元中
如果赋值运算符两次的类型不一致,但都是基本类型时,在赋值时要进行类型转换。类型转换是由系统自动进行的,转换的规则是:
(1)将浮点型数据(包括单、双精度)赋值给整型变量时,先对浮点数取整,即舍弃小数部分,然后赋予整型变量。如果i为整型变量,执行”i=3.56;“的结果是使i的值为3,以整数形式存储在整型变量中
(2)将整型数据赋值给单、双精度变量时,数值不变,但以浮点数形式存储到变量中。如果有float变量f,执行”f=23;“。先将整数23转换成实数23.0,再按指数形式存储在变量f中。如将23赋值给double型变量d,即执行”d=23;“,则将整数23转换成双精度实数23.0,然后以双精度浮点数形式存储到变量d中
(3)将一个double型数据赋值给float变量时,先将双精度数转换为单精度,即只取6~7位有效数字,存储到float型变量的4个字节中。应注意双精度数值的大小不能超过float型变量的数值范围。如:将一个double型变量d中的双精度实数赋值给一个float型变量f
double d=123.456789e100;                  //指数为100,超过了float型数据的最大范围
f=d;
f无法容纳如此大的数,就出现错误,无法输出正确的信息
将一个float型数据赋值给double型变量时,数值不变,在内存中以8个字节存储,有效数位扩展到15位
(4)字符型数据赋值给整型变量时,将字符的ASCII码赋值给变量,如:
i='A';                         //已定义i为整型变量
‘A’的ASCII码为65,因此i的值为65
(5)将一个占字节多的整型数据赋值给一个占字节少的整型变量或字符变量(如把占4个字节的int整型数据赋值给占2个字节的short变量或占1个字节的char变量)时,直将其低字节原封不动地送入到被赋值的变量(即发生”截断“),如:
int i=289;
char c='a';
c=i;
赋值情况如下图所示,c的值为33。

要避免把占字节多的整型数据向占字节少的整型变量赋值,因为赋值后数值可能发生失真。如果一定要进行这种赋值,应当保证赋值后数值不会发生变化,即所赋的值在变量允许数值范围内

5.赋值表达式和赋值语句
在一个表达式中可以包含另一个表达式。赋值表达式既然是表达式,那么它就可以出现在其他表达式中。如:
if((a=b)>0)   max=a;
按一般的理解,if后面的括号内应该是一个“条件”,例如可以是
if(a>0)    max=a;
现在,在a的位置上换上一个赋值表达式a=b,其作用是:先进行赋值运算(将b的值赋给a),然后判断a是否大于0,如大于0,执行max=a。注意,在if语句中的a=b不是赋值语句,而是赋值表达式。如果写成:
if((a=b;)>0)   max=a;                      //"a=b;"是赋值语句
就错了。在if的条件中可以包含赋值表达式,但不能包含赋值语句,
注意:要区分赋值表达式和赋值语句
赋值表达式的末尾没有分号,而赋值语句的末尾必须有分号。在一个表达式中国可以包含一个或多个赋值表达式,但决不能包含赋值语句

6.变量赋初值
可以用赋值语句对变量赋值,也可以在定义变量时对变量赋以初值。这样可以是程序简练。如:
int a=3;                     //指定a为整型变量,初值为3
float f=3.56;              //指定f为浮点型变量,初值为3.56
char c='a';                //指定c为字符变量,初值为‘a’
也可以是定义的变量的一部分赋初值。如:
int a,b,c=5;
指定a,b,c为整型变量,但只对c初始化,c的初值为5
如果对几个变量赋予同一个初值,应写成:
int a=3,b=3,c=3;
表示a,b,c的初值都是3。不能写成:
int a=b=c=3;
一般变量初始化不是在编译阶段完成的(只有在静态存储变量和外部变量的初始化是在编译阶段进行的),而是在程序运行时执行本函数时赋予初值的,相当于执行一个赋值语句

3.5 数据的输入输出
【例3.5】求ax2+bx+c=0的方程根。a,b,c由键盘输入,设b2-4ac>0
解题思路:首先要知道求方程式的根的方法。由数学知识已知:如果b2-4a≥0,则一元二次方程有两个实根
             -b+√b2-4ac                   -b-√b2-4ac
     x1=————————        x2=————————
                  2a                                  2a
可以将上面的分式分为两项:
                      -b                   √b2-4ac
                p=———          q=——————
                      2a                       2a

     x1=p+q,x2=p-q
有了这些式子,只要知道a,b,c的值,就能顺利地求出方程的两个根
剩下的问题就是输入a,b,c的值和输出根的值了。需要用scanf函数输入a,b,c的值,用printf函数输出两个实根的值
编写程序:
#include <stdio.h>
#include <math.h>                           //程序中要调用求平方根函数sqrt
int main()
{double a,b,c,disc,x1,x2,p,q;              //disc用来存放判别式(b*b-4ac)的值
scanf("%lf%lf%lf",&a,&b,&c);               //输入双精度型变量的值要用格式声明“%lf”(小写L)
disc=b*b-4*a*c;
p=-b/(2.0*a);
q=sqrt(disc)/(2.0*a);
x1=p+q;x2=p-q;                            //求出方程的两个根,这是两行程序
printf("x1=%7.2f\nx2=%7.2f\n",x1,x2);     //输出方程的两个根
system("pause");
return 0;
}



程序分析:
(1)用scanf函数输入a,b,c的值,注意在scanf函数中括号内变量a,b,c的前面,要用地址符&,即&a,&b,&c。&a表示变量a在内存中的地址。该scanf函数表示从终端输入的3个数据分别送入到地址为&a,&b,&c的存储单元,也就是赋值给a,b,c。双撇号内用%lf格式声明(小写的LF不是1F),表示输入双精度型实数
(2)在scanf函数中,格式声明为“%lf%lf%lf”,连续3个“%lf”,要求输入3个双精度实数。
(3)在printf函数中,不是简单地用%f格式声明,而是在格式符f的前面加了7.2,表示在输出x1和x2时,指定数据占7列,其中小数占2列。这样做的好处是
              ①可以根据实际需要来输出小数的位数,因为并不是任何时候都需要6位小数的。
              ②如果输出多个数据,各占一行,而用同一个格式声明(如%7.2f),即使输出的数据整数部分值不同,但输出时上下行必然按小数点对齐,使得输                     出数据整齐美观


3.5.2 有关数据输出的概念
(1)所谓输入输出时以计算机主机为主体而言的。从计算机想输出设备(如显示器、打印机等)输出数据为输出,从输入设备(如键盘、光盘、扫描仪等)向计算机输入数据称为输入
(2)c语言本身不提供输入输出语句,输入和输出操作是由c标准函数库中的函数来实现的
(3)要在程序文件的开头用预处理指令#include把有关头文件放在本程序中,如:
     #include <stdio.h>
说明:#include指令还有一种形式,头文件不是用尖括号括起来,而是用双撇号,如:
              #include “stdio.h”
         这两种调用方式的区别:用尖括号时,编译系统从存放c编译系统的子目录中去找所要包含的文件,这称为标准方式
                                            用双撇号时,在编译时,编译系统先在用户的当前目录(一般是用户存放源程序文件的子目录)中寻找要包含的文件,若找不                                              到,再按标准方式查找。如果该头文件不在当前目录中,可以在双撇号中写出文件的路径(如:#include “c:\temp\filel.h”)
注意:应养成习惯,只要在本程序中使用标志输入输出函数库时,一律用尖括号

3.5.3 用printf函数输出数据
在c程序中用来实现输出和输入的主要是printf和scanf函数,这两个函数时格式输入和格式输出。用这两个函数时,程序设计人员必须指令输入输出数据的格式,即根据数据的不同指定不同的格式
printf函数(格式输出函数)用来向终端(或系统隐含指定的输出设备)输出若干个任意类型的数据
1,printf函数的一般格式
     printf函数的一般格式为
     printf(格式控制,输出列表)例如:printf(“%d,%c\n”,i,c)括号内包括两部分:
      (1)“格式控制”是用双撇号括起来的一个字符串,称为格式控制字符串,简称格式字符串。它包含两个信息:
                      ①格式声明:格式声明由“%”和格式字符组成,如%d、%f。它的作用是将输出的数据转换为指定的格式后输出。格式声明总是由%字符开始
                      ②普通字符:普通字符即需要在输出时原样输出的字符。例如上面printf函数中双撇号内的逗号、空格和换行符,也可以包括一些其他字符
      (2)输出列表是程序要输出的一些数据,可以是常量、变量或者表达式
         下面是printf函数的具体例子:


格式控制字符串中的普通字符按原样输出,其数字位数由其中的变量的值而定
由于printf是函数,因此,格式控制字符串和输出列表实际上都是函数的参数
printf函数的一般形式可以表示为
printf(参数1,参数2,参数3……参数n)
参数1是格式控制字符串,参数2~参数n是需要输出的数据。执行printf函数时,将参数2~参数n按参数1的格式进行输出。参数1是必须有的,参数2~n是可选的

2.格式字符
在输出时,应对不同类型的数据指定不同的格式声明,而格式声明中最重要的内容是格式字符。常用的有以下几种格式字符
(1)d格式符。用来输出一个有符号的十进制整数;可以在格式声明中指定输出数据的域宽(所占的列数)如用“%5d”,指定输出的数据占5列,输出的数据显           示在此5列区域的右侧。如:
         printf(”%5d\n%5d\n“,12,-345);
         执行后,输出结果为:
              12          (12前面有3个空格)
           -345          (-345前面有1个空格)
         若输出long(长整型)数据,在格式符d前加字母l(代表long),即”%ld“。若输出long long(双长整型)数据,在格式符d前加两个字母ll(代表long                   long),即”%lld“
(2)c格式符。用来输出一个字符。例如:
        char ch='a';
        printff("%c",ch);
        执行后输出
        a
        也可以指定域宽,如
        printff("%5c",ch);
             a                  (a前面有4个空格)
        一个整数,如果在0~127范围中,也可以用”%c“使之按字符行事输出,在输出前,系统会将该整数作为ASCII码转换成形影的字符
        如果这个整数比较大,则把它的最后一个字节的信息以字符的形式输出,如”
        int a=377;                   (377的二进制码是0000 0001 0111 1001)
        printf(“%c”,a);         (a的值为0111 1001,只输出一个字节的值)      
(3)s格式符。用来输出一个字符串。如:
        printf(“%s”,"china");
(4)f格式符。用来输出实数(包括单精度、双精度、长双精度),以小数形式输出,有几种用法:
         ①基本型,用%f:不指定输出数据的长度,由系统根据数据的实际情况决定所占的列数。系统处理的方法一般是:实数中的整数部分全部输出,小数部               分输出6位
【例3.6】用%f输出实数,只能得到6位小数
              #include <stdio.h>
                int main()
                {double a=1.0;
                  printf("%f\n",a/3);
                  return 0;
                   }
         

          ②指定数据宽度和小数位数,用%m.nf
        如果在例3.6的printf函数中指定“%7.0f”格式声明,由于其整数部分为0,因此输出的结果为0。所以不要轻易指定小数的位数为0
        如果想在例3.6中输出双精度变量a的15位小数,可以用“%20.15f”格式声明(数值长度20.小数部分15位,小数点前有4位,所以输出后0前边有3个空格)
         

这时输出了15位小数,但是应该注意:一个双精度数只能保证15位有效数字的精确度,即使指定小数位数为50位,也不能保证输出的50位都是有效数字


注意:在用%f输出时要注意数据本身能提供的有效数字,如float型数据的存储单元只能保证6位有效数字。double型数据能保证15位有效数字

【例3.7】float型数据的有效位数
#include <stdio.h>
int main()
{float a;
a=10000/3.0;
printf("%f\n",a);
system("pause");
return 0;
}


注意看结果,小数点后3位后的值是错误的(正确的值应该是3333.33333循环)。由于float型数据只能保证6~7位有效数字,因此虽然程序输出了6位小数,但从左开始的第7位数字(即第3位小数)以后的数字并不能保证是绝对正确的
如果将a改为double型,其他不变,
#include <stdio.h>
int main()
{double a;
a=10000/3.0;
printf("%f\n",a);
system("pause");
return 0;
}


%f默认输出6位小数,而double型数据有效数字可达15位,所以输出的结果是正确的
            ③输出的数据向左对齐,用%-m.nf
               在m.n 的前面加一个负号,其作用与%m.nf的形式作用基本相同,但当数据长度不超过m时,数据向左靠,右端补空。如:
                 printf("%-25.15f,%25.15f",a,a);
               
第1次输出a时结果向左靠,右端空5列。第2次输出a时结果向右靠,左端空5列

(5)e格式符。用格式声明%e指定以指数形式输出实数,如果不指定输出数据所占的宽度和数字部分的小数位数,许多c编写系统会自动给数组部分的小数6位,指数部分占5列(如e+002,其中“e”占1列,指数符号占1列,指数占3列)。数值按标准化指数形式输出(即小数点前必须有而且只有1位非零数字)。如:
printf(“%e”,123.456);
输出如下:
1.234560 e+002
        6列                5列
所输出的实数占13列宽度(注意:不同系统的规定略有不同)
printf(“%13.2e”,123.456);
输出为:
    1.23e+002             (数的前面有4个空格,注意%f输出的是有效数字不算小数点。%e是输出的是实数要给小数点也算一列)
格式符e也可以写成大写E的形式,此时输出的数据中的指数不是以小写e表示而是以大写E表示

*(6)其他格式符
C语言还提供以下几种输出格式符
  ①i格式符。作用和d格式相同,按十进制整型数据的实际长度输出,一般习惯用%d而很少用%i
  ②o格式符。以八进制整数形式输出。将内存单元中的各位的值(0或1)按八进制形式输出。因此输出的数值不带符号,即将符号位也一起作为八进制的一部分输出。如:
   int a=-1;
   printf("%d\t%o\n",a,a);
-1在内存单元中的存形式(以补码形式存放在4个字节)如下:
      11111111 11111111 11111111 11111111
运行时输出:


用%d(十进制整数形式)输出a时,得到-1,按%o输出时,按内存单元中实际的二进制数按3位一组构成八进制数形式,如上面的32个二进制位可以从右至左每3位为一组:
      11  111  111  111  111  111  111  111  111  111  111
二进制111就是八进制数7,因此上面的数用八进制表示就是37777777777,八进制数是不会带负号的。用%o格式声明可以得到存储单元中实际的存储情况

  ③x格式符。以十六进制数型输出整数
   printf("%d\t%o\t%x\n",a,a,a);

同样可以用“%lx”输出长整型数,也可以指定输出字段的宽度,如“%12x”
④u格式符。用来输出无符号型数据,以十进制整数形式输出
⑤g格式符。用来输出浮点数,系统自动选f格式或e格式输出,选择其中长度较短的格式,不输出无意义的0.如:
double a=12345678954321;
   printf("%f\t%e\t%g\n",a,a,a);

可以看到用%f格式输出占21列,用%e输出占13列,故%g采用%e格式输出
综合上面的介绍,格式声明的一般形式可以表示为
       %  附加字符   格式字符
以上介绍的加在格式字符前面的字符(如1,m,n,-等等)就是附加字符,又称为修饰字符,起补充声明的作用
下表列出了printf函数中用到的格式字符和附加字符


在格式声明中,在%和上述格式字符间可以插入下表中列出的几种附加符号(又称修饰符)


说明:
(1)printf函数输出时,务必注意输出对象的类型应与上述格式说明匹配,否则将会出现错误
(2)除了X,E,G外,其他格式字符必须用小写字母,如%d不能写成%D
(3)可以在printf函数中的格式控制字符串内包含转义字符,如\n,\t,\b,\r,\f和\377等
(4)表3.6中所列出的字母,如用在格式声明中就作为格式字符。一个格式声明以“%”开头,以上12个格式字符之一作为结束符,中间可以插入附加格式字符
      (也称为修饰符)。例如:
   
       第一个格式声明为“%c”而不包括其后的字母f;第二个格式声明为“%f”,不包括其后的字符s;第三个格式声明为“%s”。其他字符都是在输出时按原样输出
       的普通字符
(5)如果想输出字符“%”,应该在“格式控制字符串”中用连续两个"%"表示,如:
printf(“%f%%\n”,1.0/3);
实现输出%号

3.5.4用scanf函数输入数据
1.scanf函数的一般形式
scanf(格式控制,地址表列)
“格式控制”的含义同printf函数。“地址表列”是由过干个地址组成的表列,可以是变量的地址,或字符串的首地址
2.scanf函数中的格式声明
与printf函数中的格式声明相似,以%开始,以一个格式字符结束,中间可以插入附加的字符
在格式字符串中除了有格式声明%f以外,还有一些普通字符(有“a=”,"b=","c="和“,”)
下表列出了scanf函数所用的格式字符和附加字符。它们的用法和printf函数中的用法差不多



3.使用scanf函数时应注意的问题
(1)scanf函数中的格式控制后面应当是变量地址,而不是变量名。如:若a和b为整型变量,如果写成:
scanf("%f%f%f",a,b,c);
是不对的,应将”a,b,c“改为”&a,&b,&c“
(2)如果格式控制字符串中除了格式声明以外还有其他字符,则在输入数据时在对应的位置上应输入与这些字符相同的字符。如果有:scanf(”a=%f,b=%f,c=%f“,&a,&b,&c);
在输入数据时,应在对应的位置上输入同样的字符。即输入
a=1,b=2,c=3            (注意输入的内容)
其他输入格式都是错误的,因为系统会把输入内容格式和scanf函数中的格式字符串逐个字符对照检查
(3)在用”%c“格式声明时,空格字符和转义字符中的字符都作为有效字符输入,例如:
scanf(”%c%c%c“,&c1,&c2,&c3);
在输入此函数时应该连续输入3个字符,中间不要有空格。如:
abc                    (中间没有空格或其他字符)
提示:一般在输入数值时,在两个数值之间需要插入空格(或其他分隔符),以使系统能区分两个数值。在连续输入字符时。在两个字符之间不要插入空格或其他分隔符(除非在scanf函数中的格式字符串中有普通字符,这是输入数据时要在原位置插入这些字符),系统能区分两个字符
(4)在输入数值数据时,如输入空格、回车、Tab键或遇非法字符(不属于数值的字符),认为该数据结束

3.5.5字符输入输出函数
除了可以用printf函数和scanf函数输出和输入字符外,c函数库还提供了一些专门用于输入输出字符的函数。它们是很容易理解和使用的。
1.用putchar函数输出一个字符
putchar函数一般形式为
      putchar(c)
putchar(c)的作用时输出字符变量c的值,显然输出的是一个字符
【例3.8】先后输出BOY三个字符
解题思路:定义3个字符变量,分别赋以初值’B‘,'O','Y',然后用putchar函数输出这3个字符变量的值
编写程序:
#include <stdio.h>
int main()
{
char a='B',b='O',c='Y';                       //定义3个字符变量并初始化
putchar(a);
putchar(b);
putchar(c);
putchar('\n');
system("pause");
return 0;
}

用putchar函数既可以输出能在显示器屏幕上显示的字符,也可以输出屏幕控制字符。如putchar(‘\n’)的作用时输出一个换行符,使输出的当前位置移到下一行的开头
如果把上面的程序改为以下这样:
#include <stdio.h>
int main()
{
char a=66,b=79,c=89;                       //定义3个字符变量并初始化
putchar(a);
putchar(b);
putchar(c);
putchar('\n');
system("pause");
return 0;
}

字符类型也属于整数型,因此将一个字符赋值给字符变量和将字符的ASCII码赋值给字符变量作用时完全相同的(但应注意,整型数据的范围为0~127)。putchar函数是输出字符的函数,它输出的是字符而不能输出整数
说明:putchar(c)中的c可以是字符常量、整型常量、字符变量或整型变量(其值在字符ASCII码范围内)
可以用putchar函数输出转义字符。如:
putchar(‘\101’)                  (输出字符A)
putchar(‘\”)                       (单撇号中是\',输出单撇号字符)
putchar(‘\015’)                   (八进制数15等于十进制13,就是回车键的ASCII码,因此输出回车,但不换行!我们一般在文本输入回车并换行是因为输入回                                               车的时候自动输入了换行符’\n‘)
前边转义字符表中说过,转义字符’\o,\oo,\ooo‘后边的数字只能是八进制数据,’\xh‘才是十六进制数据

2.用getchar函数输入一个字符
可以调用系统函数库中的getchar函数(字符输入函数)
getchar函数的一般形式为
         getchar()
getchar函数没有参数,它的作用是从计算机终端(一般是键盘)输入一个字符,即计算机获得一个字符。getchar函数的值就是从输入设备得到的字符。getchar函数只能接收一个字符。如果想输入多个字符就要用多个getchar函数
【例3.9】用三个getchar函数先后从键盘向计算机输入BOY3个字符,然后用putchar函数输出
编写程序:
#include <stdio.h>
int main()
{
char a,b,c;           //定义字符变量a,b,c
a=getchar();          //从键盘输入一个字符,送给字符变量a
b=getchar();          //从键盘输入一个字符,送给字符变量b
c=getchar();          //从键盘输入一个字符,送给字符变量c
putchar(a);           //输出变量a的值
putchar(b);           //输出变量b的值
putchar(c);           //输出变量c的值
putchar('\n');        //换行
system("pause");
return 0;
}




注意:连续输入3个字符后按回车键,未按回车键前字符是不会显示的,
在用键盘输入信息时,并不是在键盘上敲一个字符,该字符就立即送到计算机中去,这些字符先暂存在键盘的缓冲区中,只有按下了enter键才把这些字符一起送到计算机中,然后按先后顺序分别赋值给相应的变量
执行getchar函数不仅可以从输入设备获得一个可显示的字符,而且可以获得在屏幕上无法显示的字符,如控制字符(回车、换行这类字符都是可以获得的)
用getchar函数得到的字符可以赋值给一个字符变量或整型变量,也可以不赋值给任何变量,而作为表达式的一部分,在表达式中利用它的值。如:
#include <stdio.h>
int main()
{
putchar(getchar());           //将接收的字符输出
putchar(getchar());           //将接收的字符输出
putchar(getchar());           //将接收的字符输出
putchar('\n');                   //换行
system("pause");
return 0;
}



也可以在printf函数中输出刚接收到的字符:
printf("%c",getchar());                 //以%c格式输出接收到的字符
在执行语句前,先从键盘输入一个字符,然后用输出格式符%c输出该字符
【例3.10】改写例3.3程序,使之可以适用于大写字母。从键盘输入一个大写字母,在显示屏上显示对应的小写字母
解题思路:用getchar函数从键盘读入一个大写字母,把他转换为小写字母,然后用putchar函数输出该小写字母
编写程序:
#include <stdio.h>
int main()
{
char c1,c2;                    
c1=getchar();             //从键盘读入一个大写字母,赋值给变量c1
c2=c1+32;                 //用大写字母的ASCII码计算出小写字母的ASCII码
putchar(c2);               //输出c2的值
putchar('\n');
system("pause");
return 0;
}


也可以用printf函数输出,
#include <stdio.h>
int main()
{
char c1,c2;                    
c1=getchar();             //从键盘读入一个大写字母,赋值给变量c1
c2=c1+32;                 //用大写字母的ASCII码计算出小写字母的ASCII码
putchar(c2);               //输出c2的值
printf("大写字母:%c\n小写字母:%c\n",c1,c2);
system("pause");
return 0;
}

习题
练习题我们先留着,反正看一遍也看不出个所以然,我们先系统的浏览一遍,再回过头来重新看一遍


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

8

主题

135

帖子

3270

积分

超级月卡

Rank: 7Rank: 7Rank: 7

积分
3270
 楼主| 发表于 2020-8-22 15:00:27 | 显示全部楼层
本帖最后由 why 于 2020-8-23 12:50 编辑

第4章 选择结构程序设计
4.1选择结构和条件判断
C语言有两种选择语句:
      (1)if语句,用来实现两个分支的选择结构
      (2)switche语句,用来实现多分支的选择结构
【例4.1】在例3.5的基础上对程序进行改进。求ax2+bx+c=0方程的根,由键盘输入a,b,c。假设a,b,c的值任意,并不保证b2-4ac≥0,就计算并输出方程的两个实根,如果b2-4ac<0,就输出“此方程无实根”的信息
解题思路:见下图的流程图


编写程序:
#include <stdio.h>
#include <math.h>                      //程序中要用调用求平方根函数sqrt
int main()
{
double a,b,c,disc,x1,x2,p,q;         //disc是判断式sqrt(b*b-4ac)
scanf("%lf%lf%lf",&a,&b,&c);     //输入双精度浮点型变量的值要用格式声明“%lf”
disc=b*b-4*a*c;
if(disc<0)
printf("此方程无实根\n");           //如果disc<0成立输出“此方程无实根”,否则向下执行
else
{p=-b/(2.0*a);
  q=sqrt(disc)/(2.0*a);
  x1=p+q;   
  x2=p-q;                                                     //求出方程的两个根
  print("real roots:\nxl=%7.2f\nx2=%7.2f\n",x1,x2);   //输出结果
}
system("pause");
return 0;
}

运行结果:

输入(6 3 1),程序输出“此方程无实根”


输入(2 4 1),程序输出两个实根

程序分析:
在用scanf函数输入双精度实型数据时,不能使用%f格式声明,而应当用“%lf”格式声明,前边说过的是LF的小写,表示“长浮点型”,即双精度型。在输出双精度实型数据时,可以用%f、%lf、%m.nf,以指定输出的长度
输入实根时用“%7.2f”格式声明,保留两位小数,对小数点后3位自动四舍五入,并且保证上下行小数点对齐
这类型判断型语句汇编中用到过,很好理解就不多解释了

4.2 用if语句实现选择结构
4.2.1用if语句处理选择结构举例
【例4.2】输入两个实数,按由小到大的顺序输出这两个数
解题思路:这个问题的算法很简单,只要做一次比较,然后进行一次交换即可。用if语句实现条件判断。
关键是怎么实现两个变量的值的互换。不能把两个变量直接互相赋值,如为了将a和b对换,不能用下面的办法:
a=b;                   //把变量b的值赋给a,a的值等于b的值
b=a;                   //再把变量a的值赋给b,变量b的值没有变化
为了实现互换,必须借助于第3个变量。
编写程序:
#include <stdio.h>
int main()
{
float a,b,t;
scanf("%f,%f",&a,&b);
if(a>b)                              //如果a>b,成立则往下执行,否则执行输出
{t=a;                                //把a的值赋给t,执行后t=a
a=b;                                 //把b的值赋给a,执行后a=b
b=t;                                 //把t的值赋给b,执行后b=t=a
}
printf("%5.2f,%5.2f\n",a,b);
system("pause");
return 0;
}

这个题的结果在visual6.0中结果是正确的


在visual studio2010版本中结果如下:


经过if语句的处理后,变量a是较小的数,b是较大的数。依次输出a和b,就实现了由小到大的顺序输出

【例4.3】输入3个数a,b,c,要求按由小到大的顺序输出
解题思路:解此题的算法比上一题稍微复杂一些。可以先用伪代码写出算法:
s1:if a>b,将a和b调换          (交换后,a是a、b中的较小者)
s2:if a>c,将a和c调换          (交换后,a是a、c中的较小者,因此a是三者中的最小者)
s3:if b>c,将b和c调换          (交换后,b是b、c中的较小者,也是三者中的次小者)
s4:顺序输出a,b,c
编写程序:
#include <stdio.h>
int main()
{
float a,b,c,t;
scanf("%f,%f,%f",&a,&b,&c);
if(a>b)                              //如果a>b,成立则往下执行,否则不执行花括号内代码
{t=a;                                //把a的值赋给t,执行后t=a
a=b;                                 //把b的值赋给a,执行后a=b
b=t;                                 //把t的值赋给b,执行后b=t=a
}
if(a>c)                             
{t=a;                                
a=c;                                 
c=t;                                 
}
if(b>c)                             
{t=b;                                
b=c;                                 
c=t;                                 
}
printf("%5.2f,%5.2f,%5.2f\n",a,b,c);
system("pause");
return 0;
}



4.2.2if语句的一般形式
if语句的一般形式如下:
      if(表达式)语句1
          [else 语句2]
if语句中的“表达式”可以是关系表达式、逻辑表达式,设置是数值表达式。其中最直观、最容易理解的是关系表达式,所谓的关系表达式就是两个数值进行比较的式子
在if语句中方括号内的部分【else 语句2】为可选的,既可以有,也可以没有
语句1和语句2可以是一个简单的语句,也可以是一个复合语句,还可以是另一个if语句(即在一个if语句中又包含另一个或多个内嵌的if语句)
根据if语句的一般形式,if语句可以写成不同的形式,最常用的有以下3种形式:
  (1)if(表达式)   语句1                           (没有else部分)
  (2)if(表达式)                                      (有else部分)
             语句1
          else
             语句2
  (3)if(表达式)           语句1                   (在else部分又嵌套了多层的if语句)
          else if(表达式2)   语句2
          else if(表达式3)   语句3   
                   :                   :
          else if(表达式m)  语句m
          else                       语句m=1

注意else子句不能作为语句单独使用,它必须是if语句的一部分,与if配对使用


4.3 关系运算符和关系表达式
在C语言中比较符(或称为比较运算符)称为关系运算符。所谓关系运算符就是比较运算符,将两个数值进行比较,判断其比较的结果是否符合给定的条件。

4.31关系运算符及其有限次序
C语言提供的6种关系运算符:
      (1) <     小于
      (2) ≤     小于等于
      (3) >     大于
      (4) ≥     大于等于
以上4种优先级相同(高)
      (5) ==  等于
      (6) !=   不等于
以上2种优先级相同(低)
关于有限次序:
(1)前面4种关系运算符的优先级是相同的,后2种也相同,前4中高于后2种
(2)关于运算符的优先级低于算术运算符
(3)关系运算符的优先级高于赋值运算符


4.3.2关系表达式
用关系运算符将两个数值或数值表达式连接起来的式子,称为关系表达式
关系表达式的值是一个逻辑值,即真或假,逻辑运算中1代表真,0代表假
关系表达式“a>b”的值为真,表达式的值为1
关系表达式“(a>b)==c”的值为真(因为a>b的值为1,等于c的值),表达式的值为1
关系表达式“b+c<a”的值为假,表达式的值为0
如果有以下赋值表达式:
d=a>b,由于a>b为真,因此关系表达式a>b的值为1,所以赋值后d的值为1
f=a>b>c,则f的值为0.因为“>”运算符是自左至右的结合方向,先执行“a>b”得值为1,再执行关系运算符“1>c”,得值0,赋值给f,所以f的值为0

4.4逻辑运算符和逻辑表达式有时候要求判断的条件不是一个简单的条件,而是由几个给定简单条件组成的复合条件。
用逻辑运算符将关系表达式或其他逻辑量连接起来的式子就是逻辑表达式

4.4.1逻辑运算符及其优先次序
有3种逻辑运算符:与(and)、或(or)、非(not),在c语言中不能在程序中直接用and、or、not作为逻辑运算符,而是用其他符号代替


“&&”和“||”是双目(元)运算符,它要求有两个运算对象(操作数),“!”单目运算符,只要求有一个运算对象


在一个逻辑表达式中如果包含多个逻辑运算符,如!a&&b||x>y&&c。则按以下优先次序:
(1)!(非)→&&(与)→||(或),即“!”为三者中最高级别
(2)逻辑运算符中&&和||低于关系运算符,!高于算术运算符。如下图


4.4.2逻辑表达式
C语言编译系统咋表示逻辑运算结果时,用数值1代表真,用0代表假,但在判断一个量是否为真时,以0代表假,以非0代表真,即将一个非0的值默认为真
在逻辑表达式的求解中,并不是所有的逻辑运算符都被执行,指数在必须执行下一个逻辑元算分才能求出表达式的解时,才执行该运算符
判别用year表示某一年是否为闰年,可以用一个逻辑表达式来表示。闰年的条件是符合下面二者之一(1)能被4整除,但不能被100整除(2)能被400整除,可以写出逻辑表达式:
(year%4==0&&year%100!=0)||year%400==0
year%4求余,得到的值和0比较,如果能整除余数为0,如果成立,值为真,输出1
year%100!求余,得到的余数不等于0,如果成立,值为真,输出1
year%4==0&&year%100!=0   等式成立,值为真,输出1
year%400求余,得到的值和0比较,如果能整除余数为0,0==1得到假值,输出0
(year%4==0&&year%100!=0)||year%400==0,1||0进行或运算,结果非0,非0为真,输出1

Not:反闸,输出与输入永远相反。
And:及闸,(输入) 有 0,(输出) 就有 0。
Or:或闸,(输入) 有 1,(输出) 就有 1。


4.5条件运算符和条件表达式
条件表达式的一般形式为:
表达式1?表达式2:表达式3
先计算出表达式1的值,表达式1后面的问号表示”该往哪里走啊?“有两条路,如果表达式1的值为真(非0),自然直接到表达式2,如果为假(0),就饶过表达式2,到表达式3
说明:
(1)条件运算符的执行顺序:先求解表达式1,若为非0则求表达式2,此时表达式2的值就作为整个条件表达式的值。若表达式1的值为0,则求解表达式3表达式3的值就是整个条件表达式的值
(2)条件运算符优先于赋值运算符,因此表达式的求解过程是先求解条件表达式,再将它进行赋值
”表达式2“和”表达式3“不仅可以是数值表达式,还可以是赋值表达式或函数表达式
【例4.4】输入一个字符,判别它是否为大写字母,如果是,将它转换成小写字母,如果不是,不转换,然后输出最后得到的字符
解题思路:用条件表达式来处理,当字母是大写时,转成成小写字母,否则不转换
编写程序:
#include <stdio.h>
int main()
{
char ch;
scanf("%c",&ch);
ch=(ch>='A'&&ch<='Z')?(ch+32):ch;   //ch大于等于A且小于等于Z成立,则把ch+32赋值给ch,否则ch=ch
printf("%c\n",ch);
system("pause");
return 0;
}



条件表达式相当于一个不带关键字if的if语句,用它处理简单的选择结构可使程序简洁

4.6选择结构嵌套
在if语句中又包含了一个或多个if语句称为if语句的嵌套,其一般形式如下:
if()
      if()       语句1
      else      语句2
else
      if()       语句3
      else      语句4
应当只有if与else的配对关系,else总是与它上面的最近的未配对的if配对
如果if与else的数目不一样,为实现程序设计者的思想,可以加花括号来确定配对关系,如:
if()
      {
        if()   语句1
      }
else         语句2
这时”{}“限定了内嵌if语句的范围,此时else和花括号外的if配对


4.7用switch语句实现多分支选择结构
if语句只有两个分支可选,而实际问题中常常需要用到多分支的选择,C语言提供switch语句直接处理多分支选择,switcher语句使多分支选择语句
【例4.6】要求按照考试成绩的等级输出百分制分数段,A等为85分以上,B等为70~84分,C等为60~69分,D等为60分以下。成绩的等价由键盘输入
解题思路:这是一个多分支选择的问题,根据百分制分数将学生成绩分成4个等级,如果用if语句来处理至少要用3层嵌套if,进行3次检查判断。用switche语句,进行一次检查即可得到结果
编写程序:
#include <stdio.h>
int main()
{
char grade;
scanf("%c",&grade);
printf("Your score:");
switch(grade)
     {
      case'A':printf("85~100\n");break;
      case'B':printf("70~84\n");break;
      case'C':printf("60~69\n");break;
      case'D':printf("60\n");break;
      default:printf("enter data error!\n");
     }
system("pause");
return 0;
}


注意在每个case的末尾都有一个break,它的作用是使流程跳转到switch语句的末尾(实际上就是无论执行那一句以后跳转处switch代码段)
switch语句的作用是根据表达式的值,使流程跳转到不同的语句。switch语句的一般形式如下:
switch(表达式)
{
    case  常量1  :语句1
    case  常量2  :语句2
                 :
    case  常量n  :语句n
    default         :    语句n+1
}
说明:
switch一般形式中括号内的”表达式“,其值的类型应为整数类型(包括字符型)
花括号内是一个复合语句,这个复合语句包括若干语句,他是switch语句的语句体。语句体内包含多个以关键字case开头的语句行和最后一个以default开头的行。case后面跟一个常量(或常量表达式)如:case ‘A’,它们和default都是起标号的作用,用来标记一个位置。执行switch语句时,先计算switch后面的”表达式“值,然后将它与各case标号比较,如果与某一个case标号中的常量相同,流程就转到此case标号后面的语句。如果没有与switch表达式相匹配的case常量,流程转去执行default标号后面的语句
可以没有default标号,此时如果没有与switch表达式相匹配的case常量,则不执行任何语句,流程转到switch语句的下一个语句
各个case标号的次序不影响执行结果
每一个case常量必须互不相同
case标号只起到标记的作用。在执行switch语句时,根据switch表达式的值找到匹配的入口标号,并不在此进行条件检查,在执行完一个case标号后面的语句后,就从此标号开始执行下去,不再进行判断
注意:一般情况下,在执行一个case子句后,应当用break语句使流程跳出switch结构,终止switch语句的执行。最后一个case语句可以不必家break语句
在case子句中虽然包含了一个以上执行语句,但可以不必用花括号括起来,会自动执行case标号后面所有的语句
多个case可以共用一组执行语句,如:
case ‘A’:
case ‘B’:
case ‘C’:printf(">60\n");break;
当值为A,B,C时执行同一语句,输出”>60“然后换行


【例4.7】用switch语句处理菜单命令。在许多应用程序中,用菜单对流程进行控制,例如从键盘输入一个‘A’或‘a’字符,就会执行A操作,输入一个‘B’或‘b’,就会执行B操作。可以按以下思路编写程序
#include <stdio.h>
int main()
{
void actionl(int,int),action2(int,int);                   //函数声明
char ch;
int a=15,b=23;
ch=getchar();
switch(ch);
     {
      case'a':
      case'A':action1(a,b);break;                     //调用action1函数,执行A操作
      case'b':
      case'B':action2(a,b);break;                     //调用action2函数,执行A操作

           :
      default:putchar('\a');                             //输入其他字符发出警告
      }
return 0;
}

4.8 选择结构程序综合举例
【例4.8】写一程序,判断某一年是否为闰年
解题思路:前面已经介绍过判断闰年的方法,现在用不同的方法编写程序
程序1:先画出判别闰年算法的流程图,见下图,用变量leap代表是否为闰年的信息,若为闰年,令leap=1,非闰年,leap=0最后判断leap是否为真,若是输出”闰年“信息


编写程序:
#include <stdio.h>
int main()
{
int year,leap;                             //定义变量
printf("enter year:");                  //输出enter year:
scanf("%d",&year);                   //输入一个十进制数值,存储在变量year中
if(year%4==0)                         //如果year除4的余数等于0,往下执行,否则leap为0
    {
     if(year%100==0)                //如果year初100的余数等于0,往下执行,否则leap为1
        {
         if(year%400==0)            //如果year同时满足除4和除100余数都为0,同时满足除400的余数也为0
             leap=1;                      //那么leap=1
         else
             leap=0;                      //否则leap=0
        }
     else
        leap=1;
    }
else
   leap=0;
if(leap)                                     //如果leap为真
     printf("%d is ",year);            //输出”是闰年“
else
     printf("%d is not ",year);      //否则输出”不是闰年“
printf("a leap year.\n");
system("pause");
return 0;
}


有汇编的基础这种跳转还是很好理解的

程序2:也可以将程序第7~20行改写成以下if的语句
iif(year%4!=0)
      leap=0;
else if(year%100!=0)
      leap=1;
else if(year%400!=0)
      leap=0;
else
     leap=1;


程序3:可以用一个逻辑表达式包含所有的闰年条件,将上述if语句用下面的if语句代替:
if(year%4==0&&year%100!=0)||(year%400==0)
    leap=1;
else
    leap=0;

这个条件语句之前解释过,这里就不解释了

【例4.9】求ax2+bx+c=0方程的解
解题思路:在例4.1中曾编写过程序,但实际上应该有以下几种可能
   (1)a=0,不是二次方程
   (2)b2-4ac=0,有两个相等的实根
   (3)b2-4ac>0,有两个不等的实根
   (4)b2-4ac<0,有两个共轭复根。应当以p+qi和p-qi的形式输出复根。其中,p=-b/2a,q=(√b2-4ac)/2a
画出N-S流程图表示算法


编写程序:
#include <stdio.h>
#include <math.h>
int main()
{
  double a,b,c,disc,x1,x2,realpart,inagpart;                         //定义双精度变量
  scanf("%lf,%lf,%lf",&a,&b,&c);                                      //输入双精度值,并存储在a,b,c中
  printf("this equation");                                         
  if(fabs(a)<=1e-6)                                                        //如果a的值小于等于0.000001,则认定a为0 ,浮点数不能直接用于判等,只能看它是否小于一个
                                                                                   //非常小的值,这里用10的负6次方来进行对比  
     printf("is not a quadratic\n");                                    //如果a为0,则输出“不是二次方程”
  else
  {
     disc=b*b-4*a*c;
     if(fabs(disc)<=1e-6)                                               //否则判断disc是否小于等于0.000001,如果成立
        printf("has two equal roots:%8.4f\n",-b/(2*a));      //输出“有两个相等的实根”
     else                                                                     
        if(disc>1e-6)                                                      //否则判断disc是否大于0.000001,如果成立
           {
               x1=(-b+sqrt(disc))/(2*a);
               x2=(-b-sqrt(disc))/(2*a);
               printf("has distinct real roots:%8.4f and %8.4f\n",x1,x2);  //输出“有两个共轭复根”  
           }
         else
           {
              realpart=-b/(2*a);                                       //否则执行计算实根
              imagpart=sqrt(-disc)/(2*a);                         //计算虚根
              printf("has complex roots:\n");                    //输出有“复根”
              printf("%8.4f+%8.4fi\n",realpart,imafpart);  //用4位小数输出实根
              printf("%8.4f-1%8.4fi\n",realpart,imafpart); //用4位小数输出虚根
            }
  }
system("pause");
return 0;
}


【例4.10】运输公司对用户计算运输费用。路程越远,运费就越低。标志如下:
                 s<250               没有折扣
         250≤s<500                 2%折扣
         500≤s<1000               5%折扣
       1000≤s<2000               8%折扣
       2000≤s<3000              10%折扣
       3000≤s                       15%折扣
解题思路:
设每吨每千米货物的基本运输费为p,货物中为w,距离为s,折扣为d,则总运费的计算公式为
                         f=p*w*s*(1-d)
编写程序:
#include <stdio.h>
int main()
{
int c,s;                                        //定义整型变量
float p,w,d,f;                               //定义运费、重量、折扣、总运费为浮点型变量
printf("请输入运费,重量,距离:");                  //提示输入数据
scanf("%f,%f,%d",&p,&w,&s);     //运费、重量以浮点型数据输入,距离有符号整数输入
if(s>=3000)                               //如果距离大于等于3000
      c=12;                                 //折扣为12
else
      c=s/250;                            //否则,折扣为距离除以250,把值赋给c
switch(c)                                  //多分支选择c的值
     {
      case 0:d=0;break;
      case 1:d=2;break;
      case 2:
      case 3:d=5;break;
      case 4:
      case 5:
      case 6:
      case 7:d=8;break;
      case 8:
      case 9:
      case 10:
      case 11:d=10;break;
      case 12:d=15;break;
     }
f=p*w*s*(1-d/100);                    //计算总运费f
printf("总运费=%10.2f\n",f);
system("pause");
return 0;
}



习题
先放下

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

8

主题

135

帖子

3270

积分

超级月卡

Rank: 7Rank: 7Rank: 7

积分
3270
 楼主| 发表于 2020-8-23 22:42:36 | 显示全部楼层
本帖最后由 why 于 2020-8-26 09:29 编辑

第5章  循环结构程序设计

5.1为什么需要循环控制
程序中常用到的顺序结构和选择结构,但是只有这两种结构是不够的,还需要用到循环结构(或称重复结构)

5.2用while语句实现循环
while语句的执行过程是:开始时变量i的值为1,while语句首先检查变量i的值是否小于等于设定的值,如果是则执行while后面的语句(称为循环体),每执行一次给i的变量自增1,直到符合设定的表达式结束循环。
while语句的一般形式如下:
  while(表达式)语句
其中语句就是循环体。循环体只能是一个语句,可以是一个简单的语句,还可以是一个复合语句(用花括号括起来的若干语句)。执行循环体的此时是由循环条件控制的,这个循环条件就上面一般形式中的表达式,它也称为循环条件表达式
while语句可简单地记为:只要当循环条件表达式为真(即给定的条件成立),就执行循环体语句
注意:while循环的特点是先判断条件表达式,后执行循环体语句
【例5.1】求1+2+3+……+100
编写程序:
  1. #include <stdio.h>
  2. int main()
  3.     {
  4.      int i=1,sum=0;                              //定义初始变量i为1,sum为0
  5.      while(i<=100)                               //当i小于等于100执行循环
  6.           {
  7.             sum=sum+i;                          //第一次累加后sum=0+1
  8.             i++;                                      //i的值加1
  9.            }
  10.        printf("sum=%d\n",sum);             //十进制输出结果
  11.        system("pause");
  12.        return 0;
  13.       }
复制代码





5.3用do……while语句实现循环
除了while语句以外,c语言还提供了do……while语句来实现循环结构,如:
int i=1;
do
   {
    printf("%d",i++);                   //执行输出i的值,然后给i加1
    }
while(i<=100);                          //判断i的值,i<=100循环执行输出i

do……while语句的执行过程是:先执行循环体,然后再检查条件是否成立,若成立,在执行循环体
do……while语句的特点是:先无条件执行循环体一次,然后才判断循环条件是否成立
do……while语句的一般形式为:

do
    语句
while(表达式);

【例5.2】用do……while语句求1+2+3+4……+100
编写程序:
  1. #include <stdio.h>
  2. int main()
  3. {
  4.     int i=1,sum=0;
  5.     do
  6.        {
  7.           sum=sum+i;
  8.           i++;
  9.         }
  10.     while(i<=100);
  11.     printf("sum=%d\n",sum);
  12.     system("pause");
  13.     return 0;
  14. }
复制代码




当while后边的表达式的值为“真”时,两种循环得到的结果相同;否则,二者结果不相同(指二者具有相同的循环体的情况)

5.4用for语句实现循环
for语句的一般形式为
for(表达式1;表达式2;表达式3)
   语句

括号中的3个表达式的主要作用是:
表达式1:设置初始条件,只执行一次,可以为零个、一个或多个变量设置初值
表达式2:是循环条件表达式,用来判定是否继续循环。在每次执行循环体前先执行此表达式,决定是否继续执行循环
表达式3:作为循环的调整,例如使循环变量增值,它是在执行完循环体后才进行的

最常用的for语句形式是:
for(循环变量赋初值;循环条件;循环变量增值)
  语句

例如:
for(i=1;i<=100;i++)
    sum=sum+i;
其中的”i=1“是给循环变量i设置初值
”i<=100“是指定循环条件:当循环变量i的值小于等于100时,循环继续执行
”i++“是先用i参与运算,然后再自加1,用来控制循环次数

5.5循环的嵌套
一个循环体内又包含另一个完整的循环结构,称为循环嵌套。内嵌的循环中还可以嵌套循环,这就是多层循环。

5.6几种循环的比较
(1)3中循环都可以用来处理同一问题,一般情况下它们可以互相代替
(2)在while循环和do……while循环中,只在while后边的括号内指定循环条件,因此为了使循环能正常结束,应在循环体中包含循环趋于结束的语句
         for循环可以在表达式3中包含使循环趋于结束的操作,甚至可以将循环体中的操作全部放到表达式3中,因此for语句的功能更强,凡是用while循环能完
         成的,用for循环都能完成
(3)用while和do……while循环时,循环变量初始化的操作应在while和do……while语句之前完成。而for循环语句可以在表达式1中实现循环变量的初始化
(4)while循环、do……while循环和for循环都可以用break语句跳出循环,用continue语句结束本次循环

5.7改变循环执行的状态
5.7.1用break语句提前终止循环
【例5.4】在全系1000名学生中举行慈善募捐,当总数达到10万元时就结束,统计此时捐款的人数及平均每人捐款的数目
编写程序:
  1. #include <stdio.h>
  2. #define SUM 100000                      //指定符号常量SUM代表100000
  3. int main()
  4.   {
  5.     float amount,aver,total;       //定义浮点型变量捐款数、人均捐款、总捐款数
  6.     int i;                                        //定义整型变量i
  7.     for(i=1,total=0;i<=1000;i++)    //给i赋初值为1,总捐款数初值为0;由于不知道多少人次可以捐款10万,故循环次数设置为最大1000次
  8.         {
  9.            printf("请输入捐款数:");
  10.            scanf("%f",&amount);        //从键盘读取一个单精度数值,存储在捐款数中
  11.            total=total+amount;        //总捐款数=总捐款数+捐款数
  12.            if(total>=SUM)break;        //如果总捐款数大于等于10完,跳出循环
  13.          }
  14.      aver=total/i;                          //人均捐款=总捐款数/捐款人数
  15.      printf("num=%d\naner=%10.2f\n",i,aver);  //以十进制输出捐款人数,以2位小数输出人均捐款
  16.      system("pause");
  17.      return 0;
  18.    }
复制代码



break语句的一般形式为
    break
其主要作用是使流程跳到循环体之外,接着执行循环体下面的语句
注意:break语句只能用于循环语句和switch语句之中,而不能单独使用

5.7.2用continue语句提前结束本次循环
有时候并不希望终止整个循环的操作,而只是希望提前结束本次循环,而接着执行下次循环。这时候可以用continue语句
【例5.5】要求输出100~200的不能被3整除的数
显然需要对100~200的每一个整数进行检查,如果不能被3整除,就将此数输出,若能被3整除就不输出此数,无论是否输出次数,都需要接着检查下一个数(直到200为止)
编写程序:
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.      int n;                                    //定义整型变量n
  5.      for(n=100;n<=200;n++)       //设置n的初始值为100;当n小于等于200时执行循环;循环次数加1
  6.        {
  7.           if(n%3==0)                    //如果n/3的余数等于0
  8.                continue;                   //结束本次循环
  9.           printf("%d",n);                //否则以十进制输出n
  10.         }
  11.       printf("\n");                        //执行完循环体后,输出换行符
  12.       system("pause");
  13.       return 0;
  14.   }
复制代码



continue语句的一般形式为:
   continue;
其作用为结束本次循环,即跳过循环体中下面尚未执行的语句,转到循环体结束点之前,接着执行for语句中的”表达式3“,然后进行下一次循环的判断

5.7.3break语句和continue语句的区别
continue语句只结束本次循环,而不是终止整个循环的执行。而break语句则是结束整个循环过程,不再判断执行循环的条件是否成立

【例5.6】输出以下4x5的矩阵
                      1   2   3   4    5  
                      2   4   6   8  10
                      3   6   9 12  15
                      4   8 12 16  20
解题思路:可以用循环的嵌套来处理此问题,用外循环来输出一行数据,用内循环来输出一列数据。要注意设法输出以上矩阵的格式(每行5个数据),即每输完5个数据后换行
编写程序:
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.    int i,j,n=0;                         //定义整型变量i,j,n。并设置n的初始值为0
  5.    for(i=1;i<=4;i++)              //设置i=1,如果i小于等于4执行循环体,每执行一个给i加1
  6.        for(j=1;j<=5;j++,n++)  //设置j=1,如果j小于等于5执行循环体,每执行一个给j加1、n加1
  7.            {if(n%5==0)             //如果n/5的余数为0
  8.                 printf("\n");          //输出换行符
  9.              printf("%d\t",i*j);    //否则以十进制输出i*j后将光标移动到下一制表位置,(\t是水平制表符,大约输出4个空格键的位置)        
  10.             }
  11.    printf("\n");                      //循环完以后输出换行符
  12.    system("pause");
  13.    return 0;
  14.    }
复制代码



5.8 循环程序举例
【例5.70】用公式π/4≈1-1/3+1/5-1/7……求π的近似值,知道发现某一项绝对值小于10^-6为止(该项不累加)
解题思路:这是求π值的近似方法中的一种。求π值可以用不同的近似方法。应当设法用计算机的特点,用一个循环来处理就能全部解决问题。经过仔细分析,发现多项式的各项是有规律的:
(1)每项的分子都是1
(2)后一项的分母是前一项的分母加2
(3)第1项的符号为正,从第二项起,每一项的符号与前一项的符号相反
找到这些规律后,就可以用循环来处理了。例如前一项的值是1/n,则可以推出下一项为-1/n+2,其中分母中n+2的值是上一项分母n再加上2。后一项的符号则与上一项符号相反
在每求出一项后,检查它的绝对值是否大于或等于10^-6,如果是,则还需要继续求下一项,知道某一项的值小于10^-6,则不必再求下一项了。认为足够近似了
编写程序:
  1. #include <stdio.h>
  2. #include <math.h>                        //程序中要用到数学函数fabs,应包含头文件math.h
  3. int main()
  4.   {
  5.     int sign=1;                                //sign用来表示数值的符号
  6.     double pi=0.0,n=1.0,term=1.0;  //pi开始代表多项式的值,最后代表π的值,n代表分母,term代表当前项的值
  7.     while(fabs(term)>=1e-6)       //求term的绝对值后判断是否大于等于10^-6,如果大于等于则执行循环体
  8.        {
  9.            pi=pi+term;                      //把当前项term累加到pi中
  10.            n=n+2;                            //n+2是下一项的分母
  11.            sign=-sign;                       //sign代表符号,下一项的符号与上一项符合相反
  12.            term=sign/n;                    //求出下一项的值term
  13.          }   
  14.      pi=pi*4;                                //while不成立转到此处执行,多项式的值乘4得出π的值
  15.      printf("pi=%10.8f\n",pi);
  16.      system("pause");
  17.      return 0;  
  18.     }
复制代码



程序分析:
        fabs是求绝对值。fabs(x),求x绝对值,得到的结果是一个双精度的值
         abs是求绝对值。abs(x),求x绝对值,得到的结果是一个整型的值
        得出的值是8位小数,但只有5位是准确的。因为while的条件设定小于6位小后不在执行循环,对第7位小数四舍五入到第6位。导致第6位开始结果不准确

【例5.8】求fibonacci(斐波那契)数列的前40个数。这个数列有如下特点:第1,2两个数为1,1。从第3个数开始,该数是其前面两个数之和。即该数列为1,1,2,3,5,8,13…,这是一个有趣的古古典数学问题:有一对兔子,从出生后第3个月起每个月生一对兔子。小兔子长到3个月后每个月又生一对兔子。假设所有兔子都不死,问每个月的兔子总数为多少?
解题思路:最简单易懂的方法是,根据题意,从前两个月的兔子数可以推出第3个月的兔子数。设第1个月的兔子数f1=1,第2个月的兔子数f2=1,则第3个月的兔子数f3=f1+f2。当然可以在程序中继续写:f4=f2+f3,f5=f4+f3……,但这样的程序冗长。应当善于利用循环来处理这样就要重复利用变量名,一个变量在不同时间代表不同月的兔子数
编写程序:
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.      int f1=1,f2=1,f3;
  5.      int i;
  6.      printf("%12d\n%12d\n",f1,f2);
  7.      for(i=1;i<=38;i++)
  8.        {
  9.           f3=f1+f2;
  10.           printf("%12d\n",f3);              //输出12位整型数,不足12位以右边对齐
  11.           f1=f2;
  12.           f2=f3;
  13.          }
  14.     system("pause");
  15.     return 0;
  16.    }
复制代码



程序分析:
共输出了40个月的兔子数。这个程序虽然正确,运行结果也是正确的,但算法不是最好的,并且每个月的输出占一行,篇幅太长,不可取
程序改进:
可以修改程序,在循环中一次求出下两个月的兔子数,而且只用两个变量f1和f2就够了,不必用f3.
  这里有一个技巧,把f1+f2的结果不放在f3中,而放在f1中取代了f1的值,此时f1不再代表前两个月的兔子数,而代表新求出来的第3个月的兔子数,再执行f2+f1,由于此时的f1已是第3个月的兔子数,因此f2+f1就是第4个月的兔子数了,把他存放在f2中。可以看到此时的f1和f2已是新求出的最近两个月的兔子数。再由此推出下两个月的兔子数
修改后的程序如下:
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.       int f1=1,f2=1;
  5.       int i;
  6.       for(i=1;i<=20;i++)
  7.       {
  8.          printf("%12d %12d",f1,f2);          //先输出两个月的兔子总数
  9.          if(i%2==0)
  10.                 printf("\n");                         //当循环输出两次后执行一次换行
  11.          f1=f1+f2;                                  //计算出下一个月的兔子数存放在f1中
  12.          f2=f2+f1;                                  //计算出下两个月的兔子数存放在f2中
  13.        }   
  14.       system("pause");
  15.       return 0;
  16.     }
复制代码



【例5.9】输入一个大于3的整数n,判断它是否为素数
解题思路:采用的算法是,让n被i除(i的值送2变到n-1),如果n能被2~(n-1)的任何一个整数整除,则表示b肯定不是素数,不必再机选被后面的整数除,因此可以提前结束循序。此时i的值必然小于n
编写程序:
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.      int n,i;                                                           //定义整型变量n,i
  5.      printf("please enter a integer number,n=?");
  6.      scanf("%d",&n);                                            //键盘输入一个整数存入变量n中
  7.      for(i=2;i<n;i++)                                           //设定i的初始值为2,如果n大于2,每执行循环一次给i加1
  8.        if(n%i==0)break;                                       //如果n/i的余数为0,退出循环
  9.        if(i<n)                                                       //如果n大于i
  10.            printf("%d is not a prime number.\n",n);  //输出不是一个素数
  11.        else
  12.            printf("%d is a prime number.\n",n);       //否则输出是一个素数
  13.      system("pause");
  14.      return 0;        
  15.     }
复制代码

一个素数只能被1和它本身整除,满足这两个条件的都是素数



【例5.10】求100~200的全部素数
解题思路:有了例5.9的基础,解本题就不困难了,只要增加一个外循环,先后对100~200的全部整数一一进行判断即可。也就是用一个嵌套的for循环即可处理
编写程序:
  1. #include <stdio.h>
  2. #include <math.h>
  3. int main()
  4.   {
  5.     int n,k,i,m=0;
  6.     for(n=101;n<=200;n=n+2)
  7.       {
  8.        k=sqrt(n*1.0);                                 //求n的根
  9.           for(i=2;i<=k;i++)                         //设置i=2,i小于等于k则执行循环,每次循环给i+1
  10.             if(n%i==0)break;                       //如果n/i的余数为0,退出循环
  11.           if(i>=K+1)                                   //如果i大于等于√n+1
  12.             {
  13.               printf("%d ",n);                         //十进制输出n
  14.               m=m+1;                                 //给m加1
  15.               }
  16.           if(m%10==0)                             //如果m/10的余数为0
  17.               printf("\n");                            //输出换行符
  18.         }
  19.      printf("\n");                                     //如果i大于k输出换行符
  20.      system("pause");
  21.      return 0;  
  22.     }
复制代码




sqrt(n),求平方根,要求参数为float或者double型数据,在执行时会自动将int类型n转换为双精度型数据,求出的值也是双精度型数据,再把它赋值给变量k,k是int型变量,系统会自动舍弃小数部分,只把整数部分赋值给k,编译时系统会给出警告,但不影响结果

【例5.11】译密码。为使电文保密,往往按一定规律将其转换成密码,收报人再按约定的规律将其译回原文。例如,可以按以下规律将电文变成密码:
将字母A变成E,a变成e,即变成其后的第4个字母,W变成A,X变成B,Y变成C,Z变成D,字母按上述规律转换,非字母字符保持原状不变,从键盘输入一行字符,要求输出其相应的密码
解题思路:
先输入一个字符给变量c,判断它是否为字母(大小写)。若不是字母,不改变c的值原样输出;如是字母,后四个字母ASCII码变回到前四个字母,其他起码顺序加4
编写程序:
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     char c;
  5.     c=getchar();
  6.     while(c!='\n')
  7.        {
  8.          if((c>='a'&&c<='z')||(c>='A'&&c<='Z'))
  9.            {
  10.              if(c>='W'&&c<='Z'||c>='w'&&c<='z')
  11.                 c=c-22;
  12.              else
  13.                 c=c+4;
  14.              }
  15.           printf("%c",c);
  16.           c=getchar();   
  17.          }
  18.     printf("\n");
  19.     system("pause");
  20.     return 0;
  21.     }
复制代码



习题
先放下

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

8

主题

135

帖子

3270

积分

超级月卡

Rank: 7Rank: 7Rank: 7

积分
3270
 楼主| 发表于 2020-8-25 12:32:28 | 显示全部楼层
本帖最后由 why 于 2020-8-26 10:33 编辑

这些基本的我们在汇编里边都学过,只是语法不同,所以进度非常快,仅仅只是了解一下就可以了。

第6章  利用数组处理批量数据
(1)数组是一组有序数据的集合。数组中各个数据的排列是有一定规律的,下标代表数据在数组中的序号
(2)用一个数组名和下标来唯一地确定数组中的元素
(3)数组中的每一个元素都属于同一个数据类型
C语言规定用方括号中的数字来表示下标

6.1怎样定义和引用一维数组
一维数组是数组中最简单的,它的元素只需要用数组名加一个下标,就唯一确定。
6.1.1 怎样定义一维数组
要使用数组,必须在程序中先定义数组,即通知计算机:由哪些数据组成数组,数组中有多少元素,属于那个数据类型。如:
    int a[10];
它表示定义了一个整型数组,数组名为a,此数组包含10个整型元素
定义一维数组的形式为:
类型说明符  数组名[常量表达式]
说明:
(1)数组名的命名规则和变量名相同,遵循标识符命名规则
(2)在定义数组时,需要指定数组中元素的个数,方括号中的常量表达式用来表示元素的个数,即数组长度
(3)常量表达式中可以包括常量和符号常量,如:“int a[3+5];”是合法的。不能包含变量,如:“int a[n];”是不合法的。也就是说,C语言不允许对数组的大小作动态定义,即数组的大小不依赖于程序运行过程中变量的值

6.1.2 怎样引用一维数组元素
只能引用数组元素而不能一次整体调用整个数组全部元素的值
引用数组元素的表示形式为
    数组名[下标]
注意:定义数组时用到的“数组名[常量表达式]”和引用数组元素时用的“数组名[下标]”形式相同,但含义不同。如:
   int a[10];                      //前面有int,代表定义数组,指定数组包含10个元素
   t=a[6];                         //这里得a[6]表示引用a数组中序号为6的元素

【例6.1】对10个数组元素一次赋值为0,1,2,3,4,5,6,7,8,9,要求逆序输出
解题思路:显然首先要定义一个长度为10的数组,由于赋给的值是整数,因此,数组可以定义为整型,要赋的值是0~9,有一定规律,可以用循环来赋值。同样,用循环来输出这10个值,在输出时,先输出最后的元素,按下标从大到小输出这10个元素。
编写程序:
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     int i,a[10];
  5.     for(i=0;i<=9;i++)                            //对数组元素a[0]~a[9]赋值
  6.       a=i;
  7.     for(i=9;i>=0;i--)                              //输出a[9]~a[0]共10个数组元素
  8.       printf("%d",a);
  9.     printf("\n");
  10.     system("pause");
  11.     return 0;
  12.     }
复制代码



6.1.3一维数组的初始化
为了使程序简洁,常在定义数组的同时给各个数组元素赋值,这称为数组的初始化。可以用“初始化列表”方法实现数组的初始化
(1)在定义数组时对全部数组元素赋予初值。例如:
            int a[10]={0,1,2,3,4,5,6,7,8,9};
将数组中各元素的初值顺序放在一对花括号内,数据间用逗号分隔。花括号内的数据就成为“初始化列表”
(2)可以只给数组中的一部分元素赋值,如:
            int a[10]={0,1,2,3,4,5};
定义a数组有10个元素,但花括号内只提供5个初值,这表示只给前面5个元素赋初值,系统自动给后5个元素赋初值为0
(3)如果想使一个数组中全部元素值为0可以写成
            int a[10]={0,0,0,0,0,0,0,0,0,0};

           int a[10]={0};                     //未赋值的部分元素自动设定为0
(4)在对全部数组元素赋初值时,由于数据的个数已经确定,因此可以不指定数组长度。如:
           int a[5]={1,2,3,4,5};
可以写成
           int a[ ]={1,2,3,4,5};
第2中写法中,花括号中有5个数,虽然没有在方括号中指定数组的长度,但是系统会根据花括号中数据的个数确定a数组有5个元素。但是,如果数值长度与提供初值的格式不相同,则方括号中的数组长度不能省略。如:想定义数组长度为10,就不能省略数组长度的定义,而必须写成
            int a[10]={0,1,2,3,4,5};     
只初始化前5个元素,后5个元素为0

说明:如果在定义数值型数组时,指定了数组的长度并对之初始化,凡未被“初始化列表”指定初始化的数组元素,系统会自动把它们初始化为0(如果是字符型数组,则初始化为‘\0’,如果是指针型数组,则初始化为null,即空指针)

6.1.4一维数组程序举例
【例6.2】用数组来处理求fibonacci数列问题
解题思路:在第5章例5.8中是用简单变量处理的,只定义了两个或三个变量,程序可以顺序计算并输出各个数,假如想直接输出数列中第25个数,是很困难的。如果用数组来处理,在概念上反而简单了:每一个数组元素代表数列中的一个数,依次求出各个数并存放在相应的数组元素中即可:
编写程序:
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     int i;
  5.     int f[20]={1,1};             //对元素f[0]和f[1]赋初值,其他元素自动置0
  6.     for(i=2;i<20;i++)
  7.       f=f[i-2]+f[i-1];          //求出每个月元素的对应关系,第一次执行循环:第三个元素的值=第一元素的值+第二元素的值,以此类推
  8.     for(i=0;i<20;i++)
  9.       {
  10.         if(i%5==0)
  11.            printf("\n");            //如果i/5余数为0,输出换行
  12. printf("%12d",f[i]);    //以12位十进制数输出f,如果数据长度不足12位,则以右对齐
  13.         }
  14.      printf("\n");
  15.      system("pause");
  16.      return 0;
  17.     }  
复制代码



【例6.3】有10个地区的面积,要求对它们按由小到大的顺序排列
解题思路:这种问题称为数的排序(sort)。排序的规律有两种,一种是“升序”,从小到大;另一种是“降序”,从大到小。本题使用起泡法排序,每次对相邻的两个数进行大小比较,将较小值调到前面,没经过一轮排序,最大的数都将排至末尾。
编写程序:
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     int a[10];                                          //定义整数型数组
  5.     int i,j,t;                                             //定义整数型变量
  6.     printf("input 10 numbers:\n");            //输出“请输入10个数字”
  7.     for(i=0;i<10;i++)                             //设置i的初值为0,循环次数10次,每执行一次循环给i加1
  8.       scanf("%d",&a[i]);                          //以十进制输入数组元素;定义数组各元素的值
  9.     printf("\n");                                      //当i<10不成立,输出换行符
  10.     for(j=0;j<9;j++)                              //设置j的初值为0,循环次数9次,每执行一次循环给j加1
  11.        for(i[i]=0;i<9-j;i++)                         //设置i的初值为0,循环次数10次,每执行一次循环给i加1
  12.          if(a>a[i+1])                            //如果当前元素大于后一元素
  13.             {t=a[i];a[i]=a[i+1];a[i+1]=t;}  //将较大数调换至后一位
  14.     printf("the sorted numbers:\n");        //否则输出“排序后的数字”
  15.     for(i=0;i<10;i++)                            //设置i的初值为0,循环次数10次,没执行一次给i加1
  16.        printf("%d",a[i]);                          //以十进制循环输出数组各元素的值
  17.     printf("\n");                                     //执行完输出换行符
  18.     system("pause");
  19.     return 0;   
  20.     }
复制代码



6.2怎样定义和引用二维数组
二维数组常称为矩阵。把二维数组写成行和列的排列形式,可以有助于形象化地理解二维数组的逻辑结构

6.2.1怎样定义二维数组
其基本概念和一维数组相似。如:
   float pay[3][6];
以上定义一个float型二维数组,第1维有3个元素,第2维有6个元素(相当于是3行6列这个概念)。每一维的长度分别用一对方括号括起来
二维数组定义的一般形式为:
     类型说明符  数组名[常量表达式][常量表达式];
例如:
    float a[3][4],b[5][10];
定义a为3x4(3行4列)的数组,b为5x10(5行10列)的数组
注意:用矩阵形式(如3行4列形式)表示二维数组,是逻辑上的概念,能形象地表示出行列关系,而在内存中,个元素时连续存放的,不是二维的,是线性的,如下图:


C语言还允许使用多维数组。有了二维数组的基础,再掌握多维数组时不困难的。例如,定义三维数组的方法如下所示:
            float a[2][3][4];         //定义三维数组a,它有2页,3行,4列
多维数组在内存中的排列顺序为:第1维的下标变化最慢,最右边的下标变化最快


6.2.2怎样引用二维数组的元素
二维数组元素的表示形式为:
   数组名[下标][下标]
下标应是整型表达式
数组元素可以出现在表达式中,也可以被赋值,如:
b[1][2]=a[2][3]/2
注意:在引用数组元素时,下标值应在已定义的数组大小的范围内,如:
          int a[3][4]           //定义二维数组3(0,1,2)列*4(0,1,2,3)行
            :
          a[3][4]=3;          //不存在a[3][4]下标,a的下标最多只能是a[2][3]
         数组在定义的时候从1起始,但引用的时候是从0起始的,a[0][0]代表第一行第一列的值

6.2.3二维数组的初始化
可以用”初始化列表“对二维数组初始化
(1)分行给二位数组赋初值。如:
        int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
这种赋值比较直观,把第1个花括号内的值赋给a[0][0]~a[0][3],把第2个花括号内的值赋给a[1][0]~a[1][3],把第3个花括号内的值赋给a[2][0]~a[2][3]。
(2)可以将所有数据写在一个花括号内,按数组元素在内存中的排列顺序对各元素赋初值。如:
        int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};
这样赋值其实效果和(1)一样,但以(1)方法好,一行对一行,界限清楚
(3)可以对部分元素赋初值。如:
        int a[3][4]={{1},{5},{9}};      
它的作用是给每列的第1个元素赋值,结果是a[0][0]=1,a[1][0]=5,a[2][0]=9,其余元素系统自动置0
(4)如果对全部元素都赋初值(即提供全部初始数据),则定义数组时对第1维的长度可以不指定,但第2维的长度不能省略。如:
        int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};
与下面的定义等价:
        int a[ ][4]={1,2,3,4,5,6,7,8,9,10,11,12};
系统会根据数据总个数和第2维的长度计算出第1维的长度
在定义时也可以只对部分元素赋初值而省略第1维的长度,但应分行赋初值。如:
        int a[ ][4]={{0,0,3},{ },{0,10}};
这样的写法,能通知编译系统,数组共有3行,各元素的值为
           0  0   3  0
           0  0   0  0
           0  10 0  0
C语言在定义数组和表示数组元素时采用a[][]这种两个放括方的方式,对数组初始化时十分有用,它使概念清楚,使用方便,不易出错
6.2.4二维数据程序举例
【例6.4】将一个二维数组行和列的元素互换,存到另一个二维数组中。例如:
                                 
解题思路:可以定义两个数组:数组a为2*3,存放6个数。数组b为3*2,开始时不赋值。只要将a数组中的元素a存放到b数组中的b[j][j]元素中即可。用嵌套的for循环即可完成此任务
编写程序:
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.      int a[2][3]={{1,2,3},{4,5,6}};              //定义数组a的值
  5.      int b[3][2],i,j;                                     //数组b定义下标变量i、j
  6.      printf("array a:\n");                            //输出数组a的值
  7.      for(i=0;i<=1;i++)                             //循环本段2次,每次给i加1
  8.         {
  9.            for(j=0;j<=2;j++)                      //循环本段3次,每次给i加1        
  10.              {
  11.                 printf("%5d",a[i][j]);             //以5位整型数据循环输出a[0]数组下3个元素
  12.                 b[j][i]=a[i][j];                      //a数组每行的值储存在b数组的每列;到这里得时候b的值在内存中
  13.                }
  14.            printf("\n");                              //循环两次后输出换行符
  15.           }
  16.      printf("array b:\n");                        //输出b数组的值;开始从内存向屏幕缓存送入数据
  17.      for(i=0;i<=2;i++)                         //循环执行3次,每次给i加1
  18.        {
  19.           for(j=0;j<=1;j++)                   //循环执行2次,每次给i加1
  20.              printf("%5d",b[i][j]);             //以5位整型数据循环输出2列元素
  21.           printf("\n");                            //换行
  22.          }
  23.      printf("\n");                                //换行
  24.      system("pause");
  25.      return 0;
  26.     }
复制代码

【例6.5】有一个3x4的矩阵,要求编程序求出其中最大的那个元素的值,以及其所在的行号和列号
解题思路:此题用打擂台算法。先假设矩阵中第一个值最大,把他赋值给max,让他与矩阵中的所有数依次比较,每次比较都把值较大的数值赋值给max,直到所有数都比较一遍,那么max中的值就是最大值
编写程序:
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     int i,j,row=0,colum=0,max;
  5.     int a[3][4]={{1,2,3,4},{9,8,7,6},{-10,10,-5,2}};    //定义数组并赋值
  6.     max=a[0][0];                                                     //先假设数组中第一个值最大
  7.     for(i=0;i<=2;i++)                                              //设置i为0,循环次数3次,每次循环给i加1
  8.        for(j=0;j<=3;j++)                                          //设置j为0,循环次数4次,每次循环给i加1
  9.           if(a[i][j]>max)                                             //如果数组中当前元素的值大于下一个元素的值
  10.             {
  11.               max=a[i][j];                                            //那就把下一个元素的值赋值给max
  12.               row=i;                                                    //同时存储max的行号位置
  13.               colum=j;                                                 //存储max的列号位置
  14.                }
  15.     printf("max=%d\nrow=%d\ncolum=%d\n",max,row,colum);   //十进制输出max的值、同时输出所在行号、列号
  16.     system("pause");
  17.     return 0;
  18.     }
复制代码



6.3字符数组
字符型数据是以字符的ASCII码存储在存储单元中的,一般占一个字节。由于ASCII码也属于整数形式,因此在c99标准中,把字符类型归纳为整型类型中的一种
6.3.1怎样定义字符数组
用来存放字符数据的数组时字符数组。在字符数组中的一个元素内存放一个字符
定义字符数组的方法与定义数值型数组的方法类似。如:
char c[10];
c[0]='I';c[1]=' ';c[2]='a';c[3]='m';c[4]=' ';c[5]='h';c[6]='a';c[7]='p';c[8]='p';c[9]='y';
y以上定义了c为数组,包含10个元素,值分别是对应的字符的ASCII码
由于字符型数据是以整数形式(ASCII码)存放的,因此也可以用整型数组来存放字符数据
6.3.2字符数组的初始化
对字符数组初始化,最容易理解的方式是用”初始化列表“,把各个字符依次赋值给数组总的各元素。如:
       char c[10]={'I',' ','a','m',' ','h','a','p','p','y'} ;
把10个字符依次赋值给c[10]
如果在定义字符数组时不进行初始化,则数组中各元素的值是不可预料的。如果花括号中提供的初始个数(即字符个数)大于数组长度,则出现语法错误。如如果初值个数小于数组长度,则只将这些字符赋值给前面的元素中,其余的元素自动定位空字符(即‘\0’)如:
       char c[10]={'c',' ','p','r','o','g','r','a','m'} ;
如果提供的初值个数与预定的数组长度相同,在定义时可以省略数组长度下标,系统会自动根据初值个数确定数组长度。如:
       char c[ ]={'I',' ','a','m',' ','h','a','p','p','y'} ;
       数组c的长度自动定为10。用这种方式可以不必人工去数字符的个数,尤其在赋初值的字符个数比较多时,比较方便
也可以定义和初始化一个二维字符数组,如:
char diamond[ ][5]={{' ',' ','*'},{' ','*',' ','*'},{'*',' ',' ',' ','*'},{' ','*',' ','*'},{' ',' ','*'}};
用它代表一个菱形的平面图

6.3.3怎样引用字符数组中的元素
【例6.6】输出一个已知的字符串
解题思路:先定义一个字符数组,并用”初始化列表“对其赋以初值。然后用循环逐个输出此字符数组中的字符
编写程序:
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     char c[15]={'I',' ','a','m',' ','a',' ','s','t','u','d','e','n','t','.'};
  5.     int i;
  6.     for(i=0;i<15;i++)
  7.        printf("%c",c);
  8.     printf("\n");
  9.     system("pause");
  10.     return 0;
  11.     }
复制代码




【例6.7】输出一个菱形图
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     char diamond[ ][5]={{' ',' ','*'},{' ','*',' ','*'},{'*',' ',' ',' ','*'},{' ','*',' ','*'},{' ',' ','*'}};
  5.     int i,j;
  6.     for(i=0;i<5;i++)
  7.       {
  8.         for(j=0;j<5;j++)
  9.            printf("%c",diamond[i][j]);
  10.         printf("\n");
  11.         }
  12.     system("pause");
  13.     return 0;
  14.     }
复制代码



6
.3.4字符串和字符串结束标志
在C语言中是将字符串作为字符数组来处理的
C语言规定了一个”“字符串结束标志,以字符‘\0'作为结束标志。如果字符数组中存有若干字符,前面9个字符都不是空字符(’\0‘),而第10个字符是空字符,则认为数组中有一个字符串,其有效字符为9个。也就算说,在遇到字符’\0‘时,表示字符串结束,把它前面的字符组成一个字符串
注意:c系统在用字符数组存储字符串常量时会自动加一个'\0'作为结束符
有了结束标志’\0‘后,字符串的长度就显得不那么重要了。在程序中往往依靠检测'\0'的位置来判定字符串是否结束,而不是根据数组的长度来决定字符串长度
对数组初始化的方法补充一种,即用字符串常量使字符数组初始化
char c[ ]={"I am happy"};
也可以直接写成:
char c[ ]="I am happy";
这样定义数组,字符长度编译系统会自动在末尾增加一个‘\0',所有相应的字符长度也会比实际多处一个字节
说明:字符数组并不要求它的最后一个字符为’\0‘,甚至可以不包含’\0‘。像以下这样写完全是合法的:
char c[5]={'c','h','i','n','a'};
是否需要加’\0‘完全根据需要决定,由于系统在处理字符串常量存储时会自动增加一个’\0‘,因此,为了使处理方法一致,便于测定字符串的实际长度,以及在程序中作相应的处理,在字符数组中也常常人为地加上一个'\0'

6.3.5字符数组的输入输出
字符数组的输入输出可以有两种方法
(1)逐个字符输入输出。用个师傅”%c“输入或输出一个字符
(2)将整个字符串一次输入或输出。用”%s“,意思是对字符串(string)的输入输出
说明:
(1)输出的字符中不包括结束符'\0'
(2)用%s输出字符串时,printf函数中的输出项是字符数组名,而不能带下标。如:
      printf("%s",c);                        //以字符串个数输出c数组,当遇到’\0‘结束符就输出完毕了
(3)如果数值的长度大于字符串的实际长度,也只输出到遇’\0‘就结束
(4)如果一个字符串数组中包含一个以上’\0',则遇到第一个’\0‘自动结束输出
(5)可以用scanf函数输入一个字符串,如:
         scanf("%s",c);
         如果利用一个scanf函数输入多个字符串,则应在输入时以空格分隔。如:
        char str[5],str2[5],str3[5]
        scanf("%s%s%s",str1,str2,str3);
        输入数据:how are you?
        由于有空格字符分隔,作为3个字符串输入,在输入完毕后,str1,str2,str3数组的状态如下:
               
how\0\0
are\0\0
you\0\0
数组中未被赋值的元素自动置’\0'注意:scanf函数中的输入向如果是字符数组名,不用加地址符&,因为C语言中数组名代表该数组第一个元素的地址(或说数字的起始地址),下面写法不正确:
       scanf("%s",&str);                      //str前面不应加&
若数组占6个字节,数组名c代表地址2000,可以用下面的输出语句得到数组第一个元素的地址
       printf("%o",c);                  //以八进制形式输出数组c的起始地址,实际就是汇编中数据的ip
(6)前面介绍的输出字符串的方法:
  printf(“%s”,c);
实际上是这样执行的:按字符数组名c找到其数组第一个元素的地址,然后逐个输出其中的字符,直到遇到’\0‘为止

6.3.6使用字符串处理函数
1.puts函数----输出字符的函数
   其一般形式为
   puts(字符数组)
  其作用时将一个字符串(以'\0'结束的字符序列)输出到终端,加入已定义一个字符数组名str,且该数组已被初始化为“china”则执行:
        puts(str);
   界面就会显示出“china”,和printf效果一样
   用puts函数输出的字符串中可以包含转义字符(如\n)
2.gets函数----输入字符串的函数
   其一般形式为
    gets(字符数组)
   其作用是从终端输入一个字符串到数组,并且得到一个函数值。该函数值是字符数组的起始地址
    gets(str);     //str是已定义的字符数组
   输入的字符串会自动加上'\0'结束符,返回的函数值是字符数组str的第一个元素的地址,一般利用gets函数的目的是向字符数组输入一个字符串,而不关心       其函数返回值
注意:用puts和gets函数只能输入输出一个字符串,不能写成
     puts(str1,str2);
或  
     gets(str1,str2);
3.strcat函数----字符串连接函数
   其一般形式为
   strcat(字符数组1,字符数组2)
   其作用是把两个数组中的字符串连接起来,把字符串2连接到字符串1的后面,结果放在字符串1中,函数调用后得到一个数值----字符串1的地址
说明:字符数组1必须足够大,以便能容纳连接后的新字符串
          连接前两个字符串的后面都有’\0‘,连接时将字符串1后面的’\0‘取消,只在新字符串最后保留'\0'
4.strcpy和strncpy函数-----字符串复制函数
   其一般形式为
   strcpy(字符数组1,字符串2)
   作用是将字符串2复制到字符数组1中去,如
                 char str1[10],str2[]="china";
                 strcpy(str1,str2);
说明:字符数组1必须定义得足够大,以便能容纳被复制的字符串2,字符数组1的长度不应小于字符串2的长度
          字符数组1必须写成数组形式(如str),字符串2可以是字符数组名,也可以是一个字符串常量,如:
                       strcpy(str1,"china");        //作用与strcpy(str1,str2);一样
          如果复制前未对str1数组初始化或赋值,则str1各字节中的内容是无法预知的,复制时将str2中的字符串和气候的’\0‘一起复制到字符数组1中,取代字符            数组1中的前面6个字符,最后4个字符并不一定是’\0‘,而是str1中原有的最后4个字节的内容
          第二个字符串连’\0‘一起被复制到字符数组1中
          不能用赋值语句将一个字符串常量或字符数组直接给一个字符数组,字符数组名是一个地址常量,它不能改变值,正如数值型数组名不能被赋值一样。             下面两行是不合法的:
                        str1=“china”;            //用赋值语句将一个字符串常量直接赋值给另一个字符数组
                        str1=str2;              //用赋值语句将一个字符数组直接赋值给另一个字符数组
            只能用strcpy函数将一个字符串复制到另一个字符数组中去,用赋值语句只能将一个字符赋值给一个字符型变量或字符数组元素
           可以用strncpy函数将字符串2中前面的n个字符赋值到字符数组1中去,如:
               strncpy(str1,str2,2);
             作用是将str2中最前面2个字符复制到str1中,取代str1中原有的最前面2个字符,但复制的字符个数n不应多余str1中原有的字符(不包括‘\0’)
5.strcmp函数------字符串比较函数
   其一般形式为
    strcmp(字符串1,字符串2)
   作用时比较字符串1和字符串2
说明:字符串比较的规则是:将两个字符串自左至右逐个字符相比(按ASCII码值大小比较),直到出现不同字符或遇到‘\0’为止
            (1)如全部字符相同,则认为两个字符串相等
            (2)若出现不相同的字符,则以第1对不相同的字符的比较结果为准
                    说明:如果参加比较的两个字符串都由英文组成,则有一个简单的规律:在英文字典中位置在后面的为大,但小写字母比大写字母“大”
    比较的结果由函数值带回
         (1)如果相等,则函数值为0
         (2)如果字符串1>字符串2,则函数值为一个正数
         (3)如果字符串1<字符串2,则函数值为一个负数
注意:对两个字符串比较,不能用以下形式:
           if(str1>str2)
              printf("yes");
因为str1和str2代表地址而不代表数组中的全部元素,而只能用
          if(strcmp(str1,str2)>0)
              printf("yes")
6.strlen函数-----测字符串长度的函数
    其一般形式为
    strlen(字符数组)
    它是测字符串长度的函数,函数的值为字符串中的实际长度(不包括‘\0’在内),如:
           char str[10]="china";
           printf("%d",strlen(str));
    输出的结果是5,而不是10。也可以直接测试字符串常量的长度,如:
           strlen(”china“);
7.strlwr函数-----转换为小写的函数
   其一般形式为
    strlwr(字符串)
   作用是将字符串中大写字母转换成小写字母
8.strupr函数-----转换为大写的函数
   其一般形式为
    strupr(字符串)
   作用是将字符串中小写字母转换成大写字母
注意:在使用字符串处理函数时,应当在程序文件的开头用
     #include  <string.h>
把string.h文件包含到本文件中
6.3.7字符数组应用举例
【例6.8】输入一行字符,统计其中有多少个单词,单词之间空空格符分开
解题思路:问题的关键点是怎么确定”出现一个新单词了“。可以采用判断空格键的方式来确定是否输入了新的单词。
编写程序:
  1.   #include <stdio.h>
  2. int main()
  3. {
  4.     char string[81];                                          //定义字符数组
  5.     int i,num=0,word=0;                                  //定义int型变量
  6.    char c;                                                       //定义字符变量c
  7. gets(string);                                                  //向字符数组输入字符
  8.    for(i=0;(c=string[i])!='\0';i++)                    //置i为0,字符数组元素不为\0,执行循环,否则结束循环
  9.      if(c==' ')                                                 //如果字符为空格键
  10.          word=0;                                             //word值为0
  11.     else if(word==0)                                      //如果字符不为空格,word为0
  12.        {
  13.            word=1;  
  14.            num++;                                          //那么置Word为1,给num增加1
  15.          }
  16.      printf("There are %d words in this line.\n",num);   //输出num的值,得到多少字符串的结果
  17.      system("pause");
  18.      return 0;
  19.     }
复制代码



【例6.9】有3个字符串,要求找出其中”最大“者
解题思路:可以设一个二维的字符数组str,大小为3*20。每一行存放一个字符串
编写程序:
  1. #include <stdio.h>
  2. #include <string.h>
  3. int main()
  4.   {
  5.      char str[3][20];                         //定义一个二维字符数组
  6.      char string[20];                         //定义一个字符数组用来存放比较后的较大字符串
  7.      int i;
  8.      for(i=0;i<3;i++)                        //设置i为0,循序次数3次
  9.         gets(str[i]);                        //读入3个字符串至二维字符数组
  10.      if(strcmp(str[0],str[1])>0)             //二维字符数组第1个字符串和第2个字符串比较,如果返回正数值
  11.         strcpy(string,str[0]);               //把第1个字符串复制到字符数组string
  12.      else
  13.          strcpy(string,str[1]);              //否则把第2个字符串复制到字符数组
  14.      if(strcmp(string,str[2])<0)             //字符数组和第2个字符串比较,如果返回负数值
  15.          strcpy(string,str[2]);              //把第2个字符串复制到字符数组string
  16.      printf("\nthe largest string is:\n%s\n",string);  //输出string,得到字符串中最大者
  17.      system("pause");
  18.      return 0;  
  19.     }
复制代码



习题

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

8

主题

135

帖子

3270

积分

超级月卡

Rank: 7Rank: 7Rank: 7

积分
3270
 楼主| 发表于 2020-8-26 10:50:32 | 显示全部楼层
本帖最后由 why 于 2020-8-28 22:49 编辑

第7章  用函数实现模块化程序设计

函数就是功能,每一个函数用来实现一个特定的功能。函数的名字应反映其代表的功能
【例7.1】想输出以下的结果,用函数调用实现
         *****************
            How do you do!
         *****************
解题思路:在输出的文字上下分别有一行”*“号,显然不必重复写这段代码,用一个函数print_star来实现输出一行”*“的功能。在写一个print_message函数来实现输出中间一行文字信息,用主函数分别调用这两个函数即可
编写程序:
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.      void print_star();                     //声明print_star函数
  5.      void print_message();                  //声明print_message函数
  6.      print_star();                          //调用print_star函数
  7.      print_message();                       //调用print_message函数
  8.      print_star();                          //调用print_star函数
  9.      system("pause");
  10.      return 0;
  11.     }
  12. void print_star()                          //定义print_star函数
  13.    {
  14.       printf("******************\n");
  15.      }
  16. void print_message()                   //定义print_message函数
  17.    {
  18.      printf("  How do you do!\n");
  19.      }
复制代码




程序分析:
在定义print_star和print_message函数时指定函数类型为void,意为函数无类型,即无函数值,也就是说,执行这两个函数后不会把任何值带回main函数
函数声明的作用时把有关函数的信息(函数名、函数类型、函数参数的个数与类型)通知编译系统,一遍在编译系统对程序进行编译时,在进行到mian函数调用函数时知道它们是函数而不是变量或其他对象

函数分两类:
无参函数:在调用无参函数时,主函数不向被调用函数传递数据,无参函数可以带回或不带回数据,但一般以不带回函数值的居多
有参函数:在调用函数时,主函数在调用被调用函数时,通过参数向被调用函数传递数据,一般情况下,执行被调用函数时会得到一个函数值,供主函数使用

7.2怎样定义函数
7.2.1为什么要定义函数
C语言要求,在程序中用到的所有函数,必须”先定义,后调用“
定义函数应包括以下几个内容:
(1)指定函数的名字,以便以后按名调用
(2)指定函数的类型,即函数返回值的类型
(3)指定函数的参数的名字和类型,以便在调用函数时向它们传递数据。对无参函数不需要这项
(4)指定函数应当完成什么操作,也就是函数是做什么的,即函数的功能。这是最重要的,是在函数体重解决的

7.2.2定义函数的方法
1.定义无参函数
定义无参函数的一般形式为
   类型名  函数名()
     {
          函数体
     }

  类型名  函数名(void)
    {
        函数体
     }
函数名后边括号内的void表示”空“,即函数没有参数

函数体包括声明部分和语句部分
在定义函数时要用”类型标识符“指定函数的类型,即指定函数带回来的值的类型

2.定义有参函数
以下定义的max函数是有参数的:
  int max(int x,int y)
    {
      int z;                            //声明部分
      z=x>y?x:y;                 //执行语句部分
     return (z);                     //带回主函数的返回参数
      }
第一行起始的int指定函数值是整型的。max为函数名。括号中有两个形式参数x、y,它们都是整型的。在调用此函数时,主函数把实际参数的值传递给被调用函数中的形式参数。花括号内是函数体,它可以包括声明部分和语句部分。声明部分包括对函数中用到的变量进行定义以及对要调用的函数进行声明,利用z=x>y?x:y;语句求出z的值, return (z);  的作用是指定将z的值作为函数值(称函数返回值)带回到主函数中,在函数定义时已指定max函数为整型,即指定函数的值是整型,今在函数体中定义z为整型,并将z的值作为函数值返回,这是一致的。此时,函数max的值等于z
定义有参函数的一般形式:
  类型名 函数名(形式参数表列)
   {
      函数体  
     }
函数体包括声明部分和语句部分

3定义空函数
在程序设计中有时会用空函数,它的形式为
  类型名  函数名()
    {}
如:
void  dummy()
  {}
函数体是空的。调用此函数时,什么工作也不做,没有任何实际作用。


7.3调用函数
定义函数的目的是为了调用此函数,以得到预期的结果

7.3.1函数调用形式
函数调用的一般形式为:
  函数名(实参表列)
如果调用无参函数,则”实参列表“可以没有,但括号不能省略。如果实参表列包含多个实参,则各参数间用逗号隔开
按函数调用在程序中出现的形式和位置来分,可以有以下3中函数调用方式:
1.函数调用语句
把函数调用单独作为一个语句,这时不要求函数带回值,只要求函数完成一定的操作
2.函数表达式
函数调用出现在另一个表达式中,这时要求函数带回一个确定的值以参加表达式的运算。如:
      c=2*max(a,b);            //max为一个有参函数
3.函数参数
函数调用作为另一个函数调用时的实参,如:
m=max(a,max(b,c));
其中,max(b,c)是一次函数调用,它的值是b和c二者中的”较大者“,把它作为max另一次调用的实参,经过赋值后,m的值是a,b,c中的最大者。有如:
  printf(”%d“,max(a,b));
也是把max(a,b)作为printf函数的一个参数
说明:调用函数并不一定要去包括分号(如print_star();)。只有作为函数调用语句才需要有分号。如果作为函数表达式或函数参数,函数调用本身是不必有分号的,不能写成
  printf(”%d“,max(a,b););       //max(a,b)后边多了一个分号

7.3.2函数调用时的数据传递
1.形式参数和实际参数
在调用有参函数时,主函数和被调用函数之间有数据传递关系:在定义函数时函数名后面括号中的变量名称为”形式参数“(简称形参)或”虚拟参数“。在主调用函数中调用一个函数时,函数名后面括号中的参数称为”实际参数“(简称实参)。实际参数可以是常量、变量或表达式
2.实参和形参的数据传递
在调用函数过程中,系统会把实参的值传递给被调用函数的形参。
在调用函数过程中发生的实参与形参间的数据传递称为”虚实结合“

【例7.2】输入两个整数,要求输出其中值较大者。要求用函数来找到大数
解题思路:从两个数中找处最大者,算法是很简单的,现在的关键是要用一个函数来实现它,在定义函数时,要确定几个问题:
(1)函数名,
(2)函数的类型,由于给定的两个数是整数,显然其中最大者也是整数,max的函数值定义为整型
(3)max函数的参数个数和类型
编写程序:
(1)先编写max函数:
int max(int x,int y)                 //定义max函数,有两个参数
  {
   int z;                                   //定义临时变量z
   z=x>y?x:y;                          //条件语句。把x和y中较大者赋值给z
   return (z);                           //把z作为max函数的值带回main函数
    }
(2)编写主函数:
#include <stdio.h>
int main()
  {
    int max(int x,int y);
    int a,b,c;
    printf("请输入两个整数:");
    scanf("%d,%d",&a,&b);
    c=max(a,b);
    printf("较大数是:%d\n",c);
    system("pause");
    return 0;
    }
最终程序如下:
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     int max(int x,int y);
  5.     int a,b,c;
  6.     printf("请输入两个整数:");
  7.     scanf("%d,%d",&a,&b);
  8.     c=max(a,b);                               //a,b的值传递给int x,int y。返回值z赋值给c
  9.     printf("较大数是:%d\n",c);
  10.     system("pause");
  11.     return 0;
  12.     }
  13. int max(int x,int y)                 //定义max函数,有两个参数
  14.   {
  15.    int z;                                   //定义临时变量z
  16.    z=x>y?x:y;                          //条件语句。把x和y中较大者赋值给z
  17.    return (z);                           //把z作为max函数的值带回main函数
  18.     }
复制代码



7.3.3函数调用的过程
(1)在定义函数中指定的形参,在未出现函数调用时,它们并不占内存中的存储单元,在发生函数调用时,函数max的形参才被临时分配给内存
(2)将实参的值传递给对应的形参
(3)在执行max函数期间,由于形参已经有值了,就可以利用形参进行有关的运算
(4)通过return语句将函数值带回到主调函数。如果不需要返回值,则不需要return语句,这时函数的类型定义为void类型
(5)调用结束,形参单元被注释。注意:实参单元仍保留并维持原值
注意:实参向形参的数据传递是”值传递“,单向传递,只能由实参传给形参,而不能由形参传给实参,实参和形参在内存中占有不同的存储单元,实参无法得到形参的值。

7.3.4函数的返回值
通常,希望通过函数调用使主调用函数能得到一个确定的值,这就是函数值(函数的返回值)
下面对函数值做一些说明
(1)函数的返回值是通过函数中的return语句获得的。return语句将被调用函数中的一个确定值带回到主调用函数中去。如果需要从被调用函数带回一个函数值(供主调函数使用)被调用函数中必须包含return语句。如果不需要从被调用函数带回函数值可以不要return语句
       一个函数中可以有一个以上的return语句,执行到哪一个return语句,那一个return语句就起作用。return语句后面的括号可以不要,如return(z)与return z等价。return后面的值可以是一个表达式,如例7.2中的函数max可以改写如下:
          max(int x,int y)               
              {                           
                 return(x>y?x:y);                          
                }
这样的函数体更为简短,只用一个return语句就把求值和返回都解决了
(2)函数值的类型,既然函数有返回值,这个值当然应属于某一个确定的类型,应当在定义函数时指定函数值的类型。
注意:在定义函数时要指定函数的类型(过去的编译系统不要求是否定义函数类型,如无定义则默认为int类型)
(3)在定义函数时指定的函数类型一般应该和return语句中的表达式类型一致
如果函数值的类型和return语句中表达式的值不一致,则以函数类型为准。对数值型数据,可以自动进行类型转换,即函数类型决定返回值的类型

【例7.3】将例7.2稍作改动,将在max函数中定义的变量z改为float型。函数返回值的类型与指定的函数类型不同,分析其处理方法
解题思路:如果函数返回值类型与指定的函数类型不同,按照赋值规则处理
编写程序:
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     int max(float x,float y);
  5.     float a,b;
  6.         int c;
  7.     printf("请输入两个整数:");
  8.     scanf("%f,%f",&a,&b);
  9.     c=max(a,b);                              
  10.     printf("较大数是:%d\n",c);
  11.     system("pause");
  12.     return 0;
  13.     }
  14. int max(float x,float y)                 
  15.   {
  16.    float z;                                   
  17.    z=x>y?x:y;                          
  18.    return (z);                           
  19.     }
复制代码


(4)对于不带返回值的函数,应当用定义函数为”void类型“(或称”空类型“)。这样,系统就保证不使函数带回任何值,即禁止在调用函数中使用被调用函数的返回值,此时在函数体中不得出现return语句

7.4对被调用函数的声明和函数原型
在一个函数中调用另一个函数(即被调用函数)需要具备如下条件:
(1)首先被调用的函数必须是已经定义的函数(是库函数或用户自己定义的函数)。但仅有这一条还是不够的
(2)如果使用库函数,应该在本文件开头用#include指令调用有关库函数时所需用到的信息”包含“到本文件中来
(3)如果使用用户自己定义的函数,而该函数的位置在调用它的函数(即主函数)的后面(在同一个文件中),应该在主调函数中对被调函数作声明。声明的作用时把函数名、函数参数的个数和参数类型等信息通知编译系统,以便在遇到函数调用时,编译系统能正确识别函数并检查调用是否合法
【例7.4】输入两个实数,用一个函数求出它们之和
编写程序:
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     float add(float x,float y);                     //对add函数做声明
  5.     float a,b,c;                           
  6.     printf("请输入两个数:");                   //提示输入
  7.     scanf("%f,%f",&a,&b);                      //输入两个实数,存放在a,b中
  8.     c=add(a,b);                                    //调用add函数
  9.     printf("和是:%f\n",c);                    //输出两数之和
  10.     system("pause");
  11.     return 0;
  12.     }
  13. float add(float x,float y)                       //定义add函数
  14.    {
  15.       float z;
  16.       z=x+y;
  17.       return z;
  18.      }
复制代码



函数的声明和函数定义中的第1行(函数首部)基本上是相同的,只差一个分号(函数声明比函数定义中的首行多一个分号)。因此写函数声明时,可以简单地照写以定义的函数的首行,再加一个分号,就成了函数的”声明“。函数的首行(即函数首部)称为函数原型
说明:使用函数原型作声明是c的一个重要特点。用函数原型来声明函数,能减少编写程序时可能出现的错误
实际上,在函数声明中的形参可以省略不写,而值写形参的类型,如上面的声明可以写为
           float add(float ,float );                  //不写参数名,只写参数类型
编译系统只关心和检查参数个数和参数类型,而不检查参数名,因为在调用函数时只要求保证实参类型与形参类型一致,而不必考虑形参名是什么,因此在函数声明中,形参名可写可不写,形参名是什么都无所谓,如:
           float add(float a ,float b );                  //参数名不用x,y而用a,b也是合法的
根据以上的介绍,函数的一般声明有两种形式,分别为:
(1)函数类型 函数名(参数类型1  参数名1,参数类型2 参数类型2,参数类型n 参数类型n……)
(2)函数类型 函数名(参数类型1 ,参数类型2,参数类型n……)
注意:对函数的”定义“和”声明“不是一回事。函数的定义是指对函数功能的确立,包括指定函数名、函数值类型、形参及其类型以及函数体等,它是一个完整的、独立的函数单位。
而函数的声明的作用则是把函数的名字、函数类型以及形参的类型、个数和顺序通知编译器,以便在调用该函数时系统按此进行对照检查
如果已在文件的开头(在所有函数之前),已对本文件中所调用的函数进行了声明,则在各函数中不必对其所调用的函数再作声明
由于在文件的开头(在函数的外部)已对要调用的函数进行了声明(这些称为“外部的声明”),因此在程序编写时,编译系统已从外部声明中知道了函数的有关信息,所有不必在主调函数中重复进行声明。卸载所有函数前面的外部声明在整个文件范围中有效

7.5函数的嵌套调用
C语言的函数定义时互相平行、独立的,也就是说,在定义函数时,一个函数内不能再定义另一个函数,即不能嵌套定义,但可以嵌套调用函数,即在调用一个函数的过程中,又调用另一个函数如下图所示:


【例7.5】输入4个数,找出其中最大的数,用函数嵌套调用来处理
解题思路:定义两个函数max4和max2,max4用来处理4用来找出4个数中的最大者,max2函数用来找两个数中的最大者,max4调用3次max2得到结果
编写程序:
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     int max4(int a,int b,int c,int d);               //对max4函数声明
  5.     int a,b,c,d,max;
  6.     printf("请输入4个数:");                        //提示输入4个数
  7.         scanf("%d %d %d %d",&a,&b,&c,&d);              //4个数存入到变量中
  8.     max=max4(a,b,c,d);                            //调用max4,得到4个数中的最大数
  9.     printf("%d\n",max);                            //输出4个数中个最大值
  10.     system("pause");
  11.     return 0;
  12.     }
  13. int max4(int a,int b,int c,int d)                  //定义max4函数
  14.   {
  15.     int max(int a,int b);                             //对max2声明
  16.     int m;
  17.     m=max2(a,b);                                    //调用max2函数,得到a,b中较大数,存放在m中
  18.     m=max2(m,c);                                   //调用max2函数,得到a,b,c中较大数,存放在m中
  19.     m=max2(m,d);                                   //调用max2函数,得到a,b,c,d中较大数,存放在m中
  20.     return m;
  21.     }
  22. int max2(int a,int b)                                  //定义max2函数
  23.     {
  24.       if(a>=b)
  25.          return a;                                      //若a大于等于b把a作为返回值
  26.       else
  27.         return b;                                      //否则把b作为返回值      
  28.       }
复制代码



这是一种递推方法,先求出2个数的较大数;再以此为基础求出3个数的较大者,再以此文基础求出4个数的最大值。m的值一次一次地变化,直到实现最终要求
程序我们可以改进的更为简练
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     int max4(int a,int b,int c,int d);               //对max4函数声明
  5.     int a,b,c,d,max;
  6.     printf("请输入4个数:");                        //提示输入4个数
  7.         scanf("%d %d %d %d",&a,&b,&c,&d);              //4个数存入到变量中
  8.     max=max4(a,b,c,d);                            //调用max4,得到4个数中的最大数
  9.     printf("%d\n",max);                            //输出4个数中个最大值
  10.     system("pause");
  11.     return 0;
  12.     }
  13. int max4(int a,int b,int c,int d)                  //定义max4函数
  14.   {
  15.     int max(int a,int b);                             //对max2声明
  16.     int m;
  17.     return max2(max2(max2(a,b),c),d) ;    //调用max2函数,得到a,b中较大数m,连续调用max用m和c、d比较得出最大数m,返回给主调函数
  18.     }
  19. int max2(int a,int b)                                 //定义max2函数
  20.     {return(a>=b?a:b);                            //若a大于等于b把a作为返回值,否则把b作为返回值  
  21.       }
复制代码



7.6函数的递归调用
在调用一个函数的过程中又出现直接或间接地调用该函数本身,称为函数的递归调用。C语言的特点之一就在于允许函数的递归调用。如:
int f(int x)
  {
   int y,z;
   z=f(y);                              //在执行函数的过程中又要调用f的函数
   return (2*z);
    }
在调用函数f的过程中,又要调用f函数(本身),这是直接调用本函数,如下图:

如果在调用f1函数的过程中要调用f2函数,而在调用f2函数的过程中又要调用f1函数,这是间接调用本函数,如上图:

可以看到这两种递归调用都会发生死循环存在,程序会无休止的直接调用本函数或间接调用本函数。显然程序中是不能出现这种无休止的递归调用的,而只应出现有限的次数、有终止的调用,这可以用if语句来控制,只有在某一条件成立的时才继续执行递归调用;否则就不再继续
【例7.6】有5个学生坐在一起,问第5个学生多少岁,他说比第4个学生大2岁,问第4个学生,它说比第3个学生大2岁,问第3个学生,又说比第2个学生大2岁,问第2个学生又说比第1个学生大2岁。最后问第1个学生,他说是10岁。请问第5个学生多大
这是一个递归的问题,求解阶段可以分成两个阶段:第1阶段是“回溯”,即第5个学生的年龄表示为第4个学生的年龄加2、第4个学生的年龄表示为第3个学生的年龄加2,依次回溯直到第1个学生的年龄。此时第1个学生的年龄是已知的,不必再回溯了。然后开始第2阶段,才用递推方法,由第一个学生的已知年龄退出第2个学生的年龄,依次递推到第5个学生的年龄。也就是说一个递归的问题可以分为“回溯”和“递推”两个阶段。要经历若干步骤才能求出最后的值。显而易见,如果要求递归的过程不是无限进行下去的,必须具有一个递归结束的条件,例如,第1个学生的年龄已知为10,就是递归结束的条件
编写程序:
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     int age(int n);                            //对age函数声明
  5.     printf("NO.5,age:%d\n",age(5)); //输出第5个学生的年龄
  6.     system("pause");
  7.     return 0;
  8.     }
  9. int age(int n)                                //定义递归含
  10.   {
  11.     int c;
  12.     if(n==1)
  13.       c=10;                                  //如果n等于1,年龄为10
  14.    else
  15.       c=age(n-1)+2;                    //否则年龄是前一个学生的年龄加2
  16.    return c;
  17.     }
复制代码



【例7.7】用递归方法求n!
解题思路:求n!可以用递归方法,即从1开始,乘2,乘3……一直乘到n
编写程序:
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     int fac(int n);                           //fac函数声明
  5.     int n;
  6.     int y;
  7.     printf("input an integer number:");
  8.     scanf("%d",&n);
  9.     y=fac(n);
  10.     printf("%d!=%d\n",n,y);
  11.     system("pause");
  12.     return 0;
  13.     }
  14. int fac(int n)                               //定义fac函数
  15.   {
  16.     int f;
  17.     if(n<0)                                 
  18.        printf("n<0,data error!");            //如果n<0,输出数据错误
  19.     else
  20.       if(n==0||n==1)                 
  21.          f=1;                               //如果n=0或1,n!=1
  22.       else
  23.          f=fac(n-1)*n;                       /*n>1时,这一段的执行顺序是:程序走到fac(n-1)处会一直调用fac函数来计算n-1的值,直到n满足=1,计算出来的值计算机会分别存储在内存中,然后才计算f的值。这一步值的存储我以为是会覆盖掉先计算的一个,查了一下递归不同于循环,递归每次计算出的值都会单独开辟空间存储,这样就很好理解了*/
  24.     return f;
  25.     }
复制代码



【例7.8】hanoi(汉诺)塔问题,这是一个古典的数学问题,是一个用递归方法解答的典型例子。问题是这样的:古代有一个梵塔,塔内有3个座a,b,c。开始时a座上有64个盘子,盘子大小不等,大的在下,小的在上。有一个老和尚想把这64个盘子从a座移动到c座,但规定每次只允许移动一个盘子,且在移动过程中在3个座上都始终保持大盘子在下,小盘子在上。在移动过程中可以利用b座,
解题思路:这个问题类似于汇编的出入栈问题,但始终要保持先入栈的任何时候都在栈顶,概念类似。
编写程序模拟移动过程:
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     void hanoi(int n,char one,char two,char three);           //声明hanoi函数
  5.     int m;
  6.     printf("input the number of diskes:");                        //输入盘子数量
  7.     scanf("%d",&m);
  8.     printf("the step to move %d diskes:\n",m);
  9.     hanoi(m,'A','B','C');
  10.     system("pause");
  11.     return 0;
  12.     }
  13. void hanoi(int n,char one,char two,char three)              //定义hanoi函数
  14.       //hanoi(n,A,B,C); 设有n个盘子(n为最大的盘子) ,从A柱子移动到C柱子,借助B柱
  15.     {
  16.       void move(char x,char y);                                    //声明move函数
  17.       if(n==1)                                                            
  18.         move(one,three);                                             //如果只有一个盘子,那就是最大的那个盘子n,直接从a柱移动到c柱(A->C),任务完成
  19.       else
  20.         {
  21.           hanoi(n-1,one,three,two);                              //这里把n-1看做一个盘子(无论多少盘子),把n-1从a柱移动到b柱(A->B)
  22.           move(one,three);                                          //调用move,输出n-1移动的步骤(A->B)
  23.           hanoi(n-1,two,one,three);                              //把n-1从b柱移动到c柱,(B->C)任务完成
  24.            }
  25.        }
  26. void move(char x,char y)                                          //定义move函数
  27.      {
  28.         printf("%c->%c\n",x,y);                                  //输出接收到的变量
  29.          }
复制代码



这道题太抽象了,昨天下午我把这几个程序都编译成8086汇编,基本也看不懂,但是到递归的时候处理流程是这样的,用循环,循环次数就是实参。循环的结束条件就是跳出递归的命令。每次循环得到的值入栈。流程大概就是这么个样子。
7.7数组最为函数参数
调用函数时,需要提供实参。实参可以是常量、变量或表达式。数组元素的作用与变量相当,一般来说,凡是变量可以出现的地方,都可以用数组元素代替。因此数组元素也可以作为函数实参,其雍华府与变量相同,向形参传递数组元素的值。此外数组名也可以作为实参和形参,传递的是数组第一个元素的地址

7.7.1数组元素做函数实参
数组元素可以用作函数实参,但是不能用作形参。隐性形参是在函数被调用时临时分配存储单元的,不可能为一个数组元素单独分配存储单元(数组是一个整体,在内存中占连续的一段存储单元)。在用数组元素作函数实参时,把实参的值传给形参,是”值传递“方式。数据传递的方向是从实参传递到形参,单向传递

【例7.9】输入10个数,要求输出其中值最最大的元素和该数是第几个数
解题思路:可以定义一个数组a,长度为10,用来存放10个数,设计一个函数max,用来求两个数中的大者。在主函数中定义一个变量m,m的初值为a[0],每次调用max后的返回值存放在m中。用"打擂台"算法,依次将数组元素a[1]~a[9]与m比较,最后得到的m值就是10个数中的大者
编写程序:
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     int max(int x,int y);
  5.     int a[10],m,n,i;
  6.     printf("enter 10 integer numbers:");
  7.     for(i=0;i<10;i++)                                   //设置i为0,循环次数10次,每次循环后执行i++
  8.        scanf("%d",&a[i]);                               //循环从显存送入10个字符到数组a
  9.     printf("\n");
  10.     for(i=1,m=a[0],n=0;i<10;i++)                //设置i为0,循环次数9次,每次循环后执行i++
  11.       {
  12.         if(max(m,a[i])>m)                             //如果max返回的值大于m
  13.           {
  14.             m=max(m,a[i]);                            //m等于max的返回值
  15.             n=i;                                             //n等于max返回值的下标
  16.             }
  17.         }
  18.     printf("The largest number is %d\nit is the %dth number. \n",m,n+1);    //输出最大的值,同时输出下标+1(计算机从0计数,下标要+1)
  19.     system("pause");
  20.     return 0;
  21.     }
  22. int max(int x,int y)
  23.   {
  24.     return(x>y? x:y);
  25.     }

复制代码




7.2.2一维数组名作函数参数
除了可以用数组元素作为函数参数外,还可以用数组名作为函数参数(包括实参和形参)
注意:用数组元素作实参时,向形参变量传递的是数组元素的值,而用数组名作函数实参时,向形参(数组名或指针变量)传递的是数组首元素的地址
【例7.10】有一个一维数组score,内放10个学生成绩,求平均成绩
解题思路:用一个函数average来求平均成绩,不用数组元素作为函数实参,而是用数组名作为函数实参,形参也用数组名,在average函数中引用各数组元素,求平均成绩并返回main函数
编写程序:
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     float average(float array[10]);
  5.     float score[10],aver;
  6.     int i;
  7.     printf("input 10 scores:\n");
  8.     for(i=0;i<10;i++)
  9.       scanf("%f",&score[i]);
  10.     printf("\n");
  11.     aver=average(score);
  12.     printf("average score is %5.2f\n",aver);
  13.     system("pause");
  14.     return 0;
  15.    }
  16. float average(float array[10])
  17.   {
  18.     int i;
  19.     float aver,sum=array[0];
  20.     for(i=1;i<10;i++)
  21.       sum=sum+array[i];
  22.     aver=sum/10;
  23.     return(aver);
  24.     }
复制代码



【例7.11】有两个班级,分别有35名学生和30名学生,调用一个average函数,分别求这两个班的学生的平均成绩
为简化,设两个班的学生分别为5和10
编写程序:
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     float average(float array[],int n);
  5.     float score1[5]={98.5,97,91.5,60,55};
  6.     float score2[10]={67.5,89.5,99,69.5,77,89.5,76.5,54,60,99.5};
  7.     printf("The average of class A is %6.2f\n",average(score1,5));  
  8.     printf("The average of class B is %6.2f\n",average(score2,10));  
  9.     system("pause");
  10.     return 0;
  11.     }
  12. float average(float array[],int n)
  13.   {
  14.     int i;
  15.     float aver,sum=array[0];
  16.     for(i=1;i<n;i++)
  17.       sum=sum+array[i];
  18.     aver=sum/n;
  19.     return(aver);
  20.     }
复制代码



注意:用数组名作函数实参时,不是把数组元素的值传递给形参,而是把实参数组的首元素的地址传递给形参数组,这样两个数组就共占一段内存。意思就是形参指向的内存地址就是实参的内存地址,如果形参的内容改变,同样的实参内容就改变了,看半天才搞懂这段提醒什么意思。
【例7.12】用选择法对数组中的10个整数按由小到大的排序
解题思路:所谓选择法就是先将10个数中最小的数与a[0]对换;再将a[1]~a[9]中最小的数与a[1]对换,每比较一轮,找出一个未经排序的数中最小的一个。共比较9轮
编写程序:
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     void sort(int array[],int n);
  5.     int a[10],i;
  6.     printf("enter array:\n");
  7.     for(i=0;i<10;i++)
  8.         scanf("%d",&a[i]);
  9.     sort(a,10);                             //传递a数组和10个元素到sort函数
  10.     printf("The sorted array:\n");
  11.     for(i=0;i<10;i++)
  12.        printf("%d ",a[i]);
  13.     printf("\n");
  14.     system("pause");
  15.     return 0;
  16.     }           
  17. void sort(int array[],int n)
  18.   {
  19.     int i,j,k,t;
  20.     for(i=0;i<n-1;i++)              //设置i为0,循环次序i<n-1,每次循环i+1
  21.       {
  22.         k=i;                              
  23.         for(j=i+1;j<n;j++)         //设置j为i+1,循环次序j<n,每次循环j+1
  24.            if(array[j]<array[k])     //如果数组的后一元素的值小于当前元素的值
  25.              k=j;                         //当前元素的值不变
  26.         t=array[k];                    //否则变量t为当前元素
  27.         array[k]=array[i];          //后一元素的传递到当前元素的位置
  28.         array[i]=t;                    //变量t的值送入到后一元素,完成前后值调换
  29.         }
  30.     }  
复制代码
   
                          


7.7.3多维数组名作函数参数
多维数组元素可以作函数参数,这点与前述的情况类似
可以用多维数组名作为函数的实参和形参,在被调用函数中对形参数组定义时可以指定每一维的大小,也可以省略第一维的大小说明。如:
    int array[3][10];

     int array[ ][10];  
二者是等价的,都是合法的
【例7.13】有一个3x4的矩阵,求所有元素中的最大值
解题思路:先使变量max的初值等于矩阵中第1个元素的值,然后将矩阵中各个元素的值与max相比,每次比较后都把较大者存放在max中,全部元素比较完成后,max的值就是所有元素中的最大值
编写程序:
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     int max_value(int array[][4]);
  5.     int a[3][4]={{1,3,5,7},{2,4,6,8},{15,17,34,12}};
  6.     printf("Max value is %d\n",max_value(a));
  7.     system("pause");
  8.     return 0;
  9.     }
  10. int max_value(int array[][4])
  11.    {
  12.      int i,j,max;
  13.      max=array[0][0];
  14.      for(i=0;i<3;i++)
  15.        for(j=0;j<4;j++)
  16.          if(array[i][j]>max)
  17.             max=array[i][j];
  18.      return(max);
  19.      }   
复制代码

   

7.8局部变量和全局变量
在本函数开头定义的变量,在本函数中可以被引用
每一个变量都有一个作用域,即它们在什么范围内有效

7.8.1局部变量
定义变量可能有3种情况
(1)在函数的开头定义
(2)在函数内的复合语句内定义
(3)在函数的外部定义
在一个函数内部定义的变量只在本函数范围内有效,也就是说只有在本函数内才能引用它们,在此函数以外是不能使用这些变量的。在复合语句内定义的变量只在本复合语句范围内有效,只有在本复合语句内才能引用它们。在该复合语句以外是不能使用这些变量的,以上这些称为”局部变量“

在主函数中定义的变量也只在主函数中有效,并不是因为在主函数中定义而在整个文件或程序中有效。主函数也不能使用其他函数中定义的变量
不同函数中可以使用同名的变量,它们代表不同的对象,互不干扰
形式参数也是局部变量
在一个函数内部,可以在复合语句中定义变量,这些变量只在本复合语句中有效,这种复合语句也称为”分程序“或”程序块“

7.8.2全局变量
在函数内定义的变量是局部变量,而在函数外定义的变量称为外部变量,外部变量时全局变量(也称为全程变量)。全局变量可以为本文件中其他函数所共用。它的有效范围从定义变量的位置开始直到本源文件结束
注意:在函数内定义的变量时局部变量,在函数外定义的变量时全局变量
设置全局变量的作用时增加了函数间数据联系的渠道。由于同一文件中的所有函数都能引用到全局变量的值,因此如果在一个函数中改变了全局变量的值,就能影响到其他函数中全局变量的值。相当于各个函数间有直接的传递通道。由于函数的调用只能带回一个函数的返回值,因此有时可以利用全局变量来增加函数间的联系渠道,通过函数调用能得到一个以上的值
为了便于区别全局变量和局部变量,在c程序设计人员中有一个习惯(但非规定),将全局变量名的第一个字母用大写表示
【例7.14】有一个一维数组,内放10个学生成绩,写一个函数,当主函数调用到此函数后,能求出平均分、最高分和最低分。
解题思路:调用一个函数可以得到一个函数的返回值,现在希望通过函数调用能得到3个结果。可以利用全局变量来达到此目的
编写程序:
  1. #include <stdio.h>
  2. float Max=0,Min=0;                            //定义全局变量
  3. int main()
  4.   {
  5.     float average(float array[ ],int n);
  6.     float ave, score[10];
  7.     int i;
  8.     printf("Please enter 10 scores:");
  9.     for(i=0;i<10;i++)
  10.       scanf("%f",&score[i]);
  11.     ave=average(score,10);
  12.     printf("max=%6.2f\nmin=%6.2f\naverage=%6.2f\n",Max,Min,ave);
  13.     system("pause");
  14.     return 0;
  15.     }
  16. float average(float array[ ],int n)
  17.   {
  18.     int i;
  19.     float aver,sum=array[0];
  20.     Max=Min=array[0];
  21.     for(i=1;i<n;i++)
  22.       {
  23.         if(array[i]>Max)
  24.            Max=array[i];
  25.         else
  26.            if(array[i]<Min)
  27.               Min=array[i];
  28.         sum=sum+array[i];
  29.          }
  30.     aver=sum/n;
  31.     return(aver);
  32. }
复制代码

               

建议不在必要时不要使用全局变量
(1)全局变量在程序的全部执行过程中都占用存储单元,而不是仅在需要的时候才开辟单元
(2)它使函数的通用性降低了,因为如果在函数中引用了全局变量,那么执行情况会受到有关的外部变量的影响,如果将一个函数移到另一个文件中,还要考虑把有关的外部变量及其值一起移过去。但是若该外部变量与其他文件的变量名同名时,就会出现问题。这就降低了程序的可靠性和通用性。在程序设计中,在划分模块时要求模块的“内聚性”强、与其他模块的“耦合性”弱。即模块的功能要单一(不要把许多互补想干的功能放到一个模块中),与其他模块的相互影响要尽量少,而用全局变量时不符合这个原则的。一般要求C程序中的函数做成一个相对封闭体,除了可以通过“实参---->形参”的渠道与外界发生联系外,没有其他渠道。这样的程序可移植性好,可读性强
(3)使用全局变量过多,会降低程序的清晰性,人们往往难以清楚地判断出每个瞬时各个外部变量的值。由于在各个函数执行时都可能改变外部变量的值,程序容易出错。因此要限制使用全局变量
注意:如果在同一个源文件中,全局变量与局部变量同名,这时回显以下3种情况:
            1)出错
            2)局部变量无效,全局变量有效
            3)在局部变量的作用范围内,局部变量有效,全局变量被“屏蔽”,即不起作用


【例7.15】若外部变量与局部变量同名,分析结果
编写程序:
  1. #include <stdio.h>
  2. int a=3,b=5;                     //定义全局变量
  3. int main()
  4.   {
  5.     int max(int a,int b);       //声明max函数
  6.     int a=8;                       //局部变量a赋值
  7.     printf("max=%d\n",max(a,b));
  8.     system("pause");
  9.     return 0;
  10. }
  11. int max(int a,int b)
  12.   {
  13.     int c;
  14.     c=a>b? a:b;
  15.     return(c);
  16. }
复制代码



在主函数中的局部变量a屏蔽掉了全局变量中的b,根据程序顺序执行的原则,在定义局部变量a=8以前,a的变量为3。a=8这条执行修改了a的值,同时把a的值传递给了max函数,导致结果出错
汇编中的寄存器冲突没搞死我,这问题对我来说太敏感了

7.9变量的存储方式和生存期
7.9.1动态存储方式与静态存储方式
从变量的作用域(即空间)的角度来观察,变量可以分为全局变量和局部变量
还可以从另一个角度,即从变量值存在的时间(即生存期)来观察。有的变量在程序运行的整个过程都是存在的,而有得变量则是在调用其所在的函数时才临时分配存储单元,而在函数调用结束后该存储就马上释放了,变量不存在了。也就是说,变量的存储有两种不同的方式:静态存储方式和动态存放方式。静态存储方式是指程序在运行期间由系统分配固定的存储空间的方式,而动态存储方式则是在程序运行期间根据需要进行动态的分配存储空间的方式。
先看一下内存中供用户使用的存储空间的情况
(1)程序区
(2)静态存储区
(3)动态存储区


动态分配区应该是从ds:100开始分配的
数据分别存放在静态存储区和动态存储区中。全局变量全部存放在静态存储区中,在程序开始执行时给全局变量分配存储区,程序执行完毕就释放。在程序执行过程中它们占据固定的存储单元,而不是动态地进行分配和释放
在动态存储区中存放以下数据:
1)函数形式参数,在调用函数时给形参分配存储空间
2)函数中定义的没有用的关键字static声明的变量,即自动变量(见后面介绍)
3)函数调用时的现场保护和返回地址等
对以上这些数据,在函数调用开始时分配动态存储空间,函数结束释放这些空间。在程序执行过程中,这种分配和释放是动态的,如果在一个程序中两次调用同一函数,而在此函数中定义了局部变量,在两次调用时分配给这些局部变量的存储空间的地址可能是不相同的。
这实际上是个栈,

如果一个程序中包含若干函数,每个函数中的局部变量的生存期并不等于整个程序的执行周期,它只是程序执行周期的一部分。在程序执行过程中,先后调用各个函数,此时会动态的分配和释放存放空间
在C语言中,每一个变量和函数都有两个属性:数据类型和数据的存储类别。存储类型指的是数据在内存中存储的方式(静态或动态)
在定义和声明变量和函数时,一般应同时指定其数据类型和存储类别,也可以采用默认方式指定(即用户不指定,系统会隐含地指定某一种存储类别)
c的存储类别包括4中:自动的(auto)、静态的(statis)、寄存器的(register)、外部的(extern)。根据变量的存储类型,可以知道变量的作用域和生存期

7.9.2局部变量的存储类别
1、自动变量(auto变量)
函数中的局部变量,如果不专门声明为static(静态)存储类别,都是动态地分配存储空间的,数据存储在动态存储区中。函数中的形参和在函数中定义的局部变量(包括在复合语句中定义的局部变量),都属于此。在调用该函数时,系统会给这些变量分配存储空间,在函数调用结束时就自动释放这些存储空间。因此这类局部变量称为自动变量。自动变量用关键字auto作存储类别的声明。如:
       int f(int a)
         {
           auto int b,c=3;                       //定义b,c为自动变量
           }
其中a是形参,b和c是自定变量,对c赋值3。执行完f后,自动释放a,b,c所占的存储单元
关键字auto可以省略,不写auto则隐含指定为“自动存储类别”,它属于动态存储方式。

2、静态局部变量(static局部变量)
有时候希望函数中的局部变量的值在函数调用结束后不消失而继续保留原值,即其占用的存储单元不释放,在下一次在调用该函数时,该变量已有值(就是上一次函数调用结束时的值)。这时就应该指定该局部变量为“静态局部变量”,用关键字static进行声明
【例7.16】考察静态局部变量的值
编写程序:
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     int f(int);
  5.     int a=2,i;
  6.     for(i=0;i<3;i++)
  7.      printf("%d\n",f(a));
  8.    system("pause");
  9.    return 0;
  10.     }
  11. int f(int a)
  12.    {
  13.      auto int b=0;
  14.      static int c=3;
  15.      b=b+1;
  16.      c=c+1;
  17.      return(a+b+c);
  18. }
复制代码



什么情况下需要用局部静态变量呢?需要保留函数上一次调用结束时的值时
【例7.17】输出1到5的阶乘值
解题思路:可以编写一个函数用来进行连乘,如第一次调用时进行1x1,第二次调用时再乘,依次类推
编写程序:
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     int fac(int n);
  5.     int i;
  6.     for(i=1;i<=5;i++)
  7.       printf("%d!=%d\n",i,fac(i));
  8.     system("pause");
  9.     return 0;
  10. }
  11. int fac(int n)
  12.   {
  13.     static int f=1;
  14.     f=f*n;
  15.     return (f);
  16. }
复制代码



3.寄存器变量(register变量)
一般情况是,变量(包括静态存储方式和动态存储方式)的值是存放在内存中的。当程序用到哪一个变量的值时,由控制器发出指令将内存中该变量的值动到cpu运算器中,经过运算器计算,如果需要存放结果,再从运算器将数据送到内存存放
如果有一些变量使用频繁(如,在一个函数中执行10000次循环,每次循环中都要引用某局部变量),则为存钱变量的值要花费不少时间。为了提高执行效率,允许将局部变量的值放在cpu中的寄存器中,需要用时直接从寄存器取出参与运算,不必再到内存中区存取。由于对寄存器的存取速度远高于对内存的存取速度,因此这样做可以提高执行效率。这种变量叫做寄存器变量,用关键字register作声明。如:
    register int f;                      //定义f为寄存器变量
由于现在的计算机速度越来越快,性能越来越高,优化的编译系统能够识别使用频繁的变量,从而自动地将这些变量放在寄存器中,而不需要程序设计者指定。因此,现在实际用register声明的变量必要性不大
注意:3种局部变量的存储位置是不同的:自动变量存储在动态存储区;静态局部变量存储在静态存储区;寄存器变量存储在cpu中个寄存器中
实际上自动变量就是汇编中我们定义的stack段,静态就是我们定义的data段,由于汇编中我们对程序的处理都是通过寄存器来实现的,从内存送入数据到寄存器,执行完毕后得到结果再存入内存中,c编译器优化了这种执行方案,直接省掉了内存和寄存器之间的数据传递。运算器从内存读入数据,处理完毕再送回内存中,省掉了汇编中间的从内存送入寄存器和结果再送入内存这两步。
7.9.3全局变量的存储类别
全局变量都是存放在静态存储区中的。因此它们的生存期是固定的,存在于程序的整个运行过程。但是,对全局变量来说,还有一个问题尚待解决,就是它的作用域究竟是从什么位置起,到什么位置止
一般来说,外部变量时在函数的外部定义的全局变量,它的作用域是从变量的定义处开始,到本程序的末尾。在此作用域内,全局变量可以为程序中各个函数所引用。但有时程序设计人员希望能扩展外部变量的作用域。有以下几种情况:
1.在一个文件内扩展外部变量的作用域
如图外部变量不在文件的开头定义,其有效的作用范围只限于定义处起到文件结束。在定义点之前的函数不能引用该外部变量。如果由于某种考虑,在定义点之前的函数需要引用该外部变量,则应该在引用之前用关键字extern对该变量做“外部变量声明”,表示把该外部变量的作用域扩展到此位置,有了此声明,就可以从“声明”处,合法地使用该外部变量。如:
【例7.18】调用函数,求3个正数中的大者
解题思路:用extern声明外部变量,扩展外部变量在程序文件中的作用域
编写程序:
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     int max();
  5.     extern int A,B,C;                               //扩展全局变量的作用域到此处
  6.     printf("Please enter three integer numbers:");
  7.     scanf("%d %d %d",&A,&B,&C);
  8.     printf("max is %d\n",max());
  9.     system("pause");
  10.     return 0;
  11. }
  12. int A,B,C;                                                 //定义全局变量                                             
  13. int max()
  14.   {
  15.     int m;
  16.     m=A>B?A:B;
  17.     if(C>m)
  18.       m=C;
  19.     return(m);
  20. }
复制代码



注意尽量将外部变量定义放在引用它的所有函数之前,这样可以避免在函数中多加一个extern

2,将外部变量的作用域扩展到其他文件
一个c程序可以由一个或多个源程序文件组成,如果程序由多个源程序文件组成,那么如何在一个文件中引用另一个文件中已定义的外部变量呢?
如果一个程序包含两个文件,在两个文件中都要用到同一个外部变量num,不能分别在两个文件中各自定义一个外部变量num,否则在进行程序的连接时会出现”重复定义“的错误。正确的做法是:在任一个文件中定义外部变量num,而在另一个文件中用extern对num做”外部变量声明“,即”extern num“。在编译和连接时,系统会由此知道num有”外部链接“,可以从别处找到一定义的外部变量num,并将在另一文件中定义的外部变量num的作用域扩展到本文件,在本文件中可以合法地引用外部变量num
【例7.19】给定b的值,输入a和m,求a*b和a^m的值解题思路:分别编写两个文件模块,其中文件file1包含主函数,另一个文件file2包含求a^m的函数。在file1文件中定义外部变量A,在file2中用extern声明外部变量A,把A的作用域扩展到file2文件
编写程序:
  1. //文件file1.c:
  2. #include <stdio.h>
  3. int A;
  4. int main()
  5.   {
  6.     int power(int);
  7.     int b=3,c,d,m;
  8.     printf("enter the number a and ist power m:\n");
  9.     scanf("%d,%d",&A,&m);
  10.     c=A*b;
  11.     printf("%d*%d=%d\n",A,m,c);
  12.         d=power(m);
  13.         printf("%d**%d=%d\n",A,m,d);
  14.     system("pause");
  15.     return 0;
  16. }
  17. //文件file2.c:
  18. extern A;
  19. int power(int n)
  20.   {
  21.     int i,y=1;
  22.     for(i=1;i<=n;i++)
  23.       y*=A;
  24.     return (y);
  25. }
复制代码



计算机无法输入上角,所有用”**“代表幂次,13**3表示13^3

3.将外部变量得作用域限制在本文件中
有时在程序设计中希望某些外部变量只限于被本文件引用,而不能被其他文件引用。这时可以在定义外部变量时加一个static声明。如:
file1.c                              file2.c
static int A;                      extern int A;               //虽然file2声明了扩展域,但是file1中有static声明,所有扩展域失败
int main()                        int main()
{                                     {
    :                                     A=A*m;                //扩展域失败,所以这里A是未定义的数据,会出错
     }                                     }
这种加上static声明、只能用于本文件的外部变量称为静态外部变量
用static声明一个变量的作用是:
(1)对局部变量用static声明,把它分配在静态存储区,该变量在整个程序执行期间不释放,其所分配的空间始终存在
(2)对全局变量用static声明,则该变量的作用域只限于本文件模块(即被声明的文件中)
注意:用auto、register、static声明变量时,是在定义变量的基础上加上这些关键字,而不能单独使用

7.9.4存储类别小结
对你一个数据的定义,需要指定两种属性:数据类型和存储类别,分别用两个关键字。如:
    static int a;                               //静态局部整型变量或静态外部整型变量
    auto char a;                              //自动变量,在函数内定义
    register int d;                            //寄存器变量,在函数内定义
此外,可以用extern声明已定义的外部变量,如:
    extern b;                                  //将已定义的外部变量b的作用域扩展至此

7.10关于变量的声明和定义
一个函数一般由两部分组成:声明部分和执行语句。声明部分的作用时对有关的标识符(如变量、函数、结构体、共用体)的属性进行声明
对变量而已,一般把建立存储空间的声明称定义,把不需要建立存储空间的声明称为声明

7.11内部函数和外部函数
根据函数能否被其他源文件调用,将函数区分为内部函数和外部函数
7.11.1内部函数
如果一个函数只能被本文件中其他函数所调用,它称为内部函数。在定义内部函数时,在函数名和函数类型的前面加static,即:
        static 类型名 函数名(形参表);
如:
        static int fun(int a,int b)
表示fun是一个内部函数,不能被其他文件调用
内部函数又称为静态含,因为它是用static声明的。使用内部函数,可以使函数的作用域只局限于所在文件
7.11.2外部函数
如果在定义函数时,在函数首部的最左端加关键字extern,则此函数是外部函数,可供其他文件调用。如:
    extern int fun(int a,int b)
这样函数fun就可以为其他文件调用,c语言规定,如果在定义函数时省略extern,则默认为是外部函数
【例7.20】有一个字符串,内有若干个字符,现输入一个字符,要求程序将字符串中该字符扇区。用外部函数实现
编写程序:
  1. //file1.c
  2. #include <stdio.h>
  3. int main()
  4.   {
  5.     extern void enter_string(char str[ ]);                  //对函数的声明
  6.     extern void delete_string(char str[ ],char ch);        //对函数的声明   
  7.     extern void print_string(char str[ ]);                 //对函数的声明
  8. //以上3行声明了在本函数中将要嗲用的已在其他文件中定义的3个函数
  9.     char c,str[80];
  10.     enter_string(str);                                  //调用字符输入功能
  11.     scanf("%c",&c);                                      //输入要删除的字符
  12.     delete_string(str,c);                               //调用删除功能
  13.     print_string(str);                                 //调用输出功能
  14.     system("pause");
  15.     return 0;   
  16. }
  17. //file2.c
  18. void enter_string(char str[ ])
  19. {
  20.    gets(str);                                        //向字符数组输入字符串
  21. }
  22. //file3.c
  23. void delete_string(char str[ ],char ch)
  24. {
  25.    int i,j;
  26.    for(i=j=0;str[i]!='\0';i++)
  27.      if(str[i]!=ch)
  28.        str[j++]=str[i];
  29.    str[j]='\0';
  30. }
  31. //file4.c
  32. void print_string(char str[ ])
  33. {
  34.    printf("%s\n",str);
  35. }
复制代码



从7.9章开始将的东西非常模糊,如果不是对汇编了解一些。这几节的东西概念都分不清,说的基本都是些概念,并且还没说清楚
习题

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

8

主题

135

帖子

3270

积分

超级月卡

Rank: 7Rank: 7Rank: 7

积分
3270
 楼主| 发表于 2020-8-28 23:47:50 | 显示全部楼层
本帖最后由 why 于 2020-8-31 23:05 编辑

这一章网上说是整本书最难的地方,但指针就相当于汇编中的寻址,所以对于有点汇编基础来说这一章应该问题不是很大
前天把一段c程序汇编成8086程序,发现大部分都是计算都是内存里直接操作进行的,上一节中最后也提到了这点

指针在内存中的情况:
  有一个变量a的地址在内存2000:100处,内容为12h
  有一个变量b的地址在内存2000:102处,内容为10h
  有一个指针变量p1的地址在内存2000:0处,内容为a的地址(即2000:100)
  有一个指针变量p2的地址在内存2000:2处,内容为b的地址(即2000:102)

指针变量始终只能存地址!
   *p               //指针变量p指向a地址中的内容。*p的值为a地址的内容12h,参与运算的是12h
   p1=&a;     //p1地址为2000:0,内容为a的地址2000:100,参与运算的是2000:100
   p1=a;      //p1地址为2000:0,内容为a的地址2000:100,参与运算的是2000:100  ;如果a为数组,那么p1内容为a[0],参与运算的是数组下标0
这是指针和数据在内存中的关系,理解了这个关系指针就相对简单多了
指针实际上就是内存中新定义了一个数据段,这个数据段在内存中有实际地址。指针数据段存储的是内存中其他数据的地址


第8章 善于利用指针
8.1指针是什么
将地址形象化地称为“指针”。以上就是通过它能找到以它为地址的内存单元
除了位置信息外,还需要数据的类型。确定去几个字节的数据
如果有一个变量专门用来存放另一变量的地址(即指针),则它称为“指针变量”
8.2指针变量
8.2.1
【例8.1】通过指针变量访问整型变量
解题思路:先定义2个整型变量,再定义两个指针变量,分别指向这两个整型变量,通过访问指针变量,可以找到它们所指向的变量,从而得到这些变量的值
编写程序:
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     int a=100,b=10;
  5.     int *pointer_1,*pointer_2;
  6.     pointer_1=&a;
  7.     pointer_2=&b;
  8.     printf("a=%d,b=%d\n",a,b);
  9.     printf("*pointer_1=%d,*pointer_2=%d\n",*pointer_1,*pointer_2);
  10.     system("pause");
  11.     return 0;
  12. }
复制代码



8.2.怎样定义指针变量
定义指针变量的一般形式为:
     类型名  *指针变量
如:
    int *pointer_1,*pointer_2;
左端的int是在定义指针变量时必须指定的“基类型”。指针变量的类型用来指定此指针变量可以指向的变量的类型。如上面定义的基类型为int的指针变量,可以用来指向整型变量,但不能指向浮点型变量
指针变量时基本数据类型派生出来的类型,它不能离开基本类型而独立存在。下面都是合法的定义“
float *pointer_3               //pointer_3是指向float型变量的指针变量,简称float指针
char *pointer_4               //pointer_4是指向char型变量的指针变量,简称char指针
可以在定义指针变量的同时对它进行初始化,如:
    int *pointer_1=&a,*pointer_2=&b;     //定义指针变量pointer_1.pointer_2,并分别指向a,b
指针变量前面的”*“表示该变量为指针类型变量
在定义指针变量时必须指定基类型
一个变量的指针的含义包括两个方面,一是以存储单元编号表示的纯地址,一是它指向的存储单元的数据类型
8.2.3怎样引用指针变量
在引用指针变量时,可能有3种情况:
(1)给指针变量赋值。如:
        p=&a;               //把a的地址赋值给变量p
(2)引用指针变量指向的变量
如果已执行p=&a; 即指针变量p指向了整型变量a,则
   printf(”%d“,*p);
其作用是以整数形式输出指针变量p所指向的变量的值,即变量a的值
如果有以下赋值语句:
*p=1;
表示将整数1赋值给p当前所指向的变量,如果p指向变量a,则相当于把1赋值给a
(3)引用指针变量的值。如:
printf("%o",p);
作用时以八进制数形式输出指针变量p的值,如果p指向了a,就是输出了a的地址,即&a
【例8.2】输入a和b两个整数,按先后大小的顺序输出
解题思路:用指针的方法来处理这个问题,不交换整型变量的值,而是交换两个指针变量的值
编写程序:
  1. #include <stdio.h>
  2. int main()
  3. {
  4.    int *p1,*p2,*p,a,b;
  5.    printf("pleass enter two integer numbers:");
  6.    scanf("%d,%d",&a,&b);
  7.    p1=&a;                                         //p1的内容是&a
  8.    p2=&b;                                         //p2的内容是&b
  9.    if(a<b)                                         //如果&a的内容小于&b的内容
  10.      {
  11.        p=p1;                                      
  12.        p1=p2;
  13.        p2=p;                                     //把指针p1、p2中存储的a、b的地址进行交换
  14.       }
  15.    printf("a=%d,b=%d\n",a,b);         //输出a和b的值
  16.    printf("max=%d,min=%d\n",*p1,*p2); //输出较大、较小值
  17.    system("pause");
  18.    return 0;
  19. }
复制代码



8.2.4指针变量作为函数参数
函数的参数不仅可以是整型、浮点型、字符型等数据,还可以是指针类型。它的作用时将一个变量的地址传送到另一个函数中
【例8.3】对输入的两个整数按大小顺序输出。现用函数处理,而且用指针类型的数据做函数参数
编写程序:
  1. #include <stdio.h>
  2. int main()
  3. {
  4.    void swap(int *p1,int *p2);                   //声明swap函数
  5.    int a,b;                                               //整型变量a,b
  6.    int *pointer_1,*pointer_2;                    //整型指针变量*pointer_1,*pointer_2
  7.    printf("please enter a and b:");             //输入两个值
  8.    scanf("%d,%d",&a,&b);
  9.    pointer_1=&a;                                    //&a的地址赋值给*pointer_1
  10.    pointer_2=&b;                                    //&b的地址赋值给*pointer_2
  11.    if(a<b)
  12.      swap(pointer_1,pointer_2);               //如果a的值小于b,调用函数swap
  13.    printf("max=%d,min=%d\n",a,b);       //输出最大最小值
  14.    system("pause");
  15.    return 0;
  16.   }
  17. void swap(int *p1,int *p2)
  18. {
  19.    int temp;                                          //定义整型数据暂存变量temp
  20.      temp=*p1;                                     //整型指针p1地址赋值给temp
  21.       *p1=*p2;                                     //整型指针p2地址赋值给p1
  22.       *p2=temp;                                   //整型指针temp地址赋值给p2
  23. }
复制代码



这个例子和上个例子有点区别,上个例子是对调指针中数据的地址,最终完成的数据对调
                                             这个例子是对调了指针地址,完成的数据对调
指针实际上就是内存中新定义了一个数据段,这个数据段在内存中有实际地址。指针数据段存储的是内存中其他数据的地址
指针在内存中的情况:
  有一个变量a的地址在内存2000:100处,内容为12h
  有一个变量b的地址在内存2000:102处,内容为10h
  有一个指针变量p1的地址在内存2000:0处,内容为a的地址(即2000:100)
  有一个指针变量p2的地址在内存2000:2处,内容为b的地址(即2000:102)

   p1=&a;     //a的地址送入p1中,p1地址为2000:0,内容为a的地址2000:100
  *p1=*p2;    //p2的地址等于p1,p1地址为2000:2,内容为b的地址2000:102

这是指针和数据在内存中的关系,理解了这个关系指针就相对简单多了



【例8.4】对输入的两个整数按大小顺序输出
解题思路:常识调用swap函数来实现题目要求,在函数中改变形参(指针变量)的值,希望由此改变实参(指针变量)的值
编写程序:
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     void swap(int *p1,int *p2);
  5.     int a,b;
  6.     int *pointer_1,*pointer_2;
  7.     printf("please enter two integer numbers:");
  8.     scanf("%d,%d",&a,&b);
  9.     pointer_1=&a;
  10.     pointer_2=&b;
  11.     if(a<b)
  12.       swap(pointer_1,pointer_2);
  13.     printf("max=%d,min=%d\n",*pointer_1,*pointer_2);
  14.     system("pause");
  15.     return 0;
  16. }
  17. void swap(int *p1,int *p2)
  18.   {
  19.     int *p;
  20.     p=p1;
  21.     p1=p2;
  22.     p2=p;                                 //指针值的交换
  23. }
复制代码



上题中的错误在于,想用形参的值返回到main函数中去。在swap函数中确实完成了指针值交换,但c语言中实参和形参之间的数据时单向的”值传递“。只能从主函数传递实际参数给swap,swap不能再把函数返回给main函数

【例8.5】输入3个正数a,b,c,要求按由大到小的顺序将它们输出。用函数实现
解题思路:用swap函数交换两个变量的值,用exchange函数改变3个变量的值
编写程序:
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     void exchange(int *q1,int *q2,int *q3);
  5.     int a,b,c,*p1,*p2,*p3;
  6.     printf("please enter therr numbers:");
  7.     scanf("%d,%d,%d",&a,&b,&c);
  8.     p1=&a;
  9.     p2=&b;
  10.     p3=&c;
  11.     exchange(p1,p2,p3);
  12.     printf("The order is:%d,%d,%d\n",a,b,c);
  13.     system("pause");
  14.     return 0;
  15. }
  16. void exchange(int *q1,int *q2,int *q3)
  17.   {
  18.     void swap(int *pt1,int *pt2);
  19.     if(*q1<*q2)
  20.      swap(q1,q2);
  21.     if(*q1<*q3)
  22.      swap(q1,q3);
  23.     if(*q2<*q3)
  24.      swap(q2,q3);
  25. }
  26. void swap(int *pt1,int *pt2)
  27. {
  28.    int temp;                                          //定义整型数据暂存变量temp
  29.    temp=*pt1;                                     //整型指针p1地址赋值给temp
  30.    *pt1=*pt2;                                     //整型指针p2地址赋值给p1
  31.    *pt2=temp;                                   //整型指针temp地址赋值给p2
  32. }
复制代码




8.3通过指针引用数组
8.3.1数组元素的指针
所谓数组元素的指针就是数组元素的地址
可以用一个指针变量指向一个数组元素。如:
int a[10]={1,3,5,7,9,11,13,15,17,19};
int *p;
p=&a[0];                 //数组a[0]的地址送入指针变量p中
引用数组元素可以用下标法(如a[3]),也可以用指针法,即通过指向数组元素的指针找到所需的元素。使用指针法能是目标程序质量高(占内存少,运行效率高)

下面两个语句是等价的
p=&a[0];                 //数组a[0]的地址送入指针变量p中
p=a;                       //数组a的首元素地址送入指针变量p中。实际上就是a[0],并不是送入整个数组的元素

8.3.2在引用数组元素时指针的运算
当数组指向数组元素时可以指向加减运算

8.3.3通过指针引用数组元素
引用一个数组元素,可以用下面两种方法:
(1)下标法:如a[ i ]
(2)指针法:如*(a+i)或*(p+1)其中a是数组名,p的内容是a。【例8.6】有一个整型数组a,有10个元素,要求输出数组中的全部元素
解题思路:引用数组中各元素的值有3种方法:
(1)下标法
(2)通过数组名计算数组元素地址
(3)用指针变量指向数组元素
分别写出程序
编写程序:
//下标法
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     int a[10];
  5.     int i;
  6.     printf("please enter 10 integer numbers:");
  7.     for(i=0;i<10;i++)
  8.       scanf("%d",&a[i]);
  9.     for(i=0;i<10;i++)
  10.       printf("%d",a[i]);                             //数组元素用数组名加下标循环输出
  11.     printf("%\n");
  12.     system("pause");
  13.     return 0;
  14. }
复制代码


//通过数组名计算数组元素地址,找出元素的值
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     int a[10];
  5.     int i;
  6.     printf("please enter 10 integer numbers:");
  7.     for(i=0;i<10;i++)
  8.       scanf("%d",&a[i]);
  9.     for(i=0;i<10;i++)
  10.       printf("%d",*(a+i));
  11.     printf("\n");
  12.     system("pause");
  13.     return 0;
  14. }  
复制代码
//用指针变量指向数组元素
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     int a[10];
  5.     int *p,i;
  6.     printf("please enter 10 integer numbers:");
  7.     for(i=0;i<10;i++)
  8.       scanf("%d",&a[i]);
  9.     for(p=a;p<(a+10);p++)
  10.       printf("%d",*P);                             //循环输出*p的内容,*p的内容是个数据的地址,每循环一次指向下一个数据地址
  11.     printf("\n");
  12.     system("pause");
  13.     return 0;
  14.   }
复制代码

以上3种方式输出的结果是一致的

【例8.7】通过指针变量输出整型数组a的10个元素
解题思路:用指针变量p指向数组元素,通过改变指针变量的值,使p先后指向a[0]~a[9]各元素
编写程序:
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     int *p,i,a[10];
  5.     p=a;                                                          //*p的内容等于a[0]
  6.     printf("please enter 10 integer numbers:");
  7.     for(i=0;i<10;i++)
  8.        scanf("%d",p++);                                   //循环把十进制字符送入到p对应的数组,每次循环p指向下一数组元素
  9.     for(i=0;i<10;i++,p++)                               //注意这是变量p已经经过10次循环了,p=(a+10)
  10.        printf("%d ",*p);                                    //循环输出*p的内容,*p的内容是变量a的地址,实际是输出a的内容,每次输出*p加1指向下一数值元素
  11.     printf("\n");
  12.     system("pause");
  13.     return 0;
  14. }  
复制代码



课本上用这个例子来执行错误,就是为了让我们察觉到错误存在在哪里,修改程序让指针重新指向a
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     int *p,i,a[10];
  5.     p=a;                                                          //*p的内容等于a[0]
  6.     printf("please enter 10 integer numbers:");
  7.     for(i=0;i<10;i++)
  8.        scanf("%d",p++);                                   //循环把十进制字符送入到p对应的数组,每次循环p指向下一数组元素
  9.     for(i=0;i<10;i++,p++)                               //这时*p指向(&a+10)位置
  10.     p=a;                                                         //重新让*p的内容变为a
  11.        printf("%d ",*p);                                    //循环输出*p的内容,*p的内容是变量a的地址,实际是输出a的内容,每次输出*p加1指向下一数值元素
  12.     printf("\n");
  13.     system("pause");
  14.     return 0;
  15. }  
复制代码


程序正确执行

8.3.4用数组名作函数参数
【例8.8】将数组a中n个整数按相反顺序存放
解题思路:将a[0]与a[n-1]对换,再将a[1]与a[n-2]对换……直到将a[int(n-1)/2]与a[n-int(n-1)/2-1]对换,今用循环处理此问题,设两个”位置指示变量“i和j,i的初值为0,j的初值为n-1.将a[ i ]与a[j]交换,然后使i的值加1,j的值减1,再将a[ i ]与a[j]对换,直到i=(n-1)/2为止。
用一个函数inv来实现交换,实参用数组名a,形参可用数组名,也可用指针变量名
编写程序:
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     void inv(int x[ ],int n);                                 //声明inv函数
  5.     int i,a[10]={3,7,9,11,0,6,7,5,4,2};                //定义一位数组
  6.     printf("The priginal array:\n");                     //输出”The priginal array:“后换行
  7.     for(i=0;i<10;i++)                                   
  8.       printf("%d ",a[i]);                                   //循环输出数组a中的字符
  9.     printf("\n");                                             //换行
  10.     inv(a,10);                                                //传送数据到inv函数
  11.     printf("The array has been inverted:\n");    //输出”The array has been inverted::“后换行
  12.     for(i=0;i<10;i++)
  13.       printf("%d ",a[i]);                                 //十进制循环输出a数组中的元素
  14.     printf("\n");
  15.     system("pause");
  16.     return 0;
  17. }
  18. void inv(int x[ ],int n)
  19.   {
  20.     int temp,i,j,m=(n-1)/2;                 //定义整型变量
  21.     for(i=0;i<=m;i++)                       //定义循环次数为i≤(10-1)/2=5(四舍五入)
  22.       {
  23.         j=n-1-i;                                  //j为字符串最后一个字符
  24.        temp=x[i];
  25.        x[i]=x[j];
  26.        x[j]=temp;                              //执行首尾字符调换,每次执行i+1,
  27.      }
  28.    return;
  29. }
复制代码




【例8.9】改写上一例子,用指针变量做参数
编写程序:
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     void inv(int *x,int n);
  5.     int i,arr[10],*p=arr;                                //定义一维数组,指针变量p指向arr首元素
  6.     printf("The orginal array:\n");                  //输出“"The orginal array:”
  7.     for(i=0;i<10;i++,p++)
  8.       scanf("%d",p);                                     //循环输入10个十进制整型数值到*p指向的变量arr中
  9.     printf("\n");
  10.     p=arr;                                                  //*p的内容为arr[0]的地址
  11.     inv(p,10);                                             //给inv函数传递参数
  12.     printf("The array has been inverted:\n");
  13.     for(p=arr;p<arr+10;p++)
  14.       printf("%d ",*p);                                //循环输出*p指向地址中的内容
  15.     printf("\n");
  16.     system("pause");
  17.     return 0;
  18. }
  19. void inv(int *x,int n)
  20.   {
  21.     int *p,m,temp,*i,*j;                             //定义变量
  22.     m=(n-1)/2;                                         //m为5
  23.     i=x;                                                   //i指向arr[0]
  24.     j=x+n-1;
  25.     p=x+m;                                             //p指向arr[0+5]
  26.     for(;i<=p;i++,j--)                               //设置循环次数(地址相减得出5),每次循环a[i]指向下一数组元素,a[j]指向上一数组元素
  27.       {
  28.         temp=*i;
  29.         *i=*j;
  30.         *j=temp;                                      //实现数组首尾元素对调
  31.      }
  32.   return;
  33. }
复制代码




如果用指针变量作实参,必须先使指针变量有确定值,直线给一个已定义的对象【例8.10】用指针方法对10个整数按由大到小顺序排序
解题思路:在主函数中定义数组a存放10个整数,定义int *型指针变量p并指向a[0]。定义函数sort使数组a中的元素按由大到小的顺序排列。在主函数中调用sort函数,用指针变量p作实参。sort函数的形参用数组名。
编写程序:
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     void sort(int x[ ],int n);
  5.     int i,*p,a[10];
  6.     p=a;
  7.     printf("please enter 10 integer numbers:");
  8.     for(i=0;i<10;i++)
  9.       scanf("%d",p++);
  10.     p=a;
  11.     sort(p,10);
  12.     for(p=a,i=0;i<10;i++)
  13.       {
  14.         printf("%d ",*p);
  15.         p++;
  16.      }
  17.    printf("\n");
  18.    system("pause");
  19.    return 0;
  20. }
  21. void sort(int x[ ],int n)
  22.   {
  23.     int i,j,k,t;
  24.     for(i=0;i<n-1;i++)
  25.       {
  26.         k=i;
  27.         for(j=i+1;j<n;j++)
  28.            if(x[j]>x[k])
  29.              k=j;                                  //较大值的下标往后兑换
  30.                if(k!=i)
  31.                  {
  32.                    t=x[i];
  33.                    x[i]=x[k];
  34.                    x[k]=t;
  35.                 }
  36.      }
  37.   }
复制代码



有10个数需要按从小到大的顺序排序
冒泡排序法:两两比较,发现较大的值立刻调换往后边,执行一轮最后一个数为最大者,循环执行直到第一个数为最小值,每次循环不用再对比已经排好序的
选择排序法:两两比较,发现较小值把下标往后兑换,同时把较小值调换到最前边的位置,让这个数再次和后边的数比较,不停在最前边位置更新出最小值,一轮比较得出最前边的值为最小值,每次循环不用再对比已经排好序的

8.3.5通过指针引用多维数组
这一节本来没有什么难度,但是书上说的看一遍就出现各种难度了。实际就是偏移地址,但课本上从头到尾都没有说到问题的根本,越看越难

【例8.12】有一个3x4的二维元素,要求用指向元素的指针变量输出二维数组各元素的值
编写程序:
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     int a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23};      //定义二维数组
  5.     int *p;                                                              //定义指针变量
  6.     for(p=a[0];p<a[0]+12;p++)                               //p的内容为a[0]元素的地址,循序次数12(地址相减),每次循环指向下一元素地址
  7.       {
  8.         if((p-a[0])%4==0)                                         
  9.           printf("\n");                                                 //如果p的内容减去a[0]元素的地址除4余0,执行输出换行。实际就是每输出4次换行一次
  10.         printf("%4d",*p);                                           //否则输出指针p指向的地址中的内容                  
  11.      }
  12.         printf("\n");
  13.     system("pause");
  14.     return 0;
  15. }
复制代码



【例8.13】输出二维数组任意行任意列元素的值
编写程序:
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     int a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23};        //定义二维数组
  5.     int (*p)[4],i,j;                                                     //指针变量指向包含4个一维数组的一维元素
  6.     p=a;                                                                  //p指向a[0]
  7.     printf("please enter row and colum:");                   //输出“输入两个数:”
  8.     scanf("%d,%d",&i,&j);                                         //把输入的数送入&i,&j
  9.     printf("a[%d,%d]=%d\n",i,j,*(*(p+i)+j));             //输出a[i][j]的内容,p的值加1即指向下一个一维数组
  10.     system("pause");
  11.     return 0;
  12. }
复制代码




【例8.14】有一个班,3个学生,各学4门课,计算总平均分数以及第n个学生的成绩
编写程序:
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     void average(float *p,int n);                           //声明函数
  5.     void search(float (*p)[4],int n);                     //声明函数
  6.     float score[3][4]={{65,67,70,60},{80,87,90,81},{90,99,100,98}};//定义三维数组
  7.     average(*score,12);                                     //调用求平均分函数
  8.     search(score,2);                                          //调用查找第n个学生的成绩
  9.     system("pause");
  10.     return 0;
  11. }
  12. void average(float *p,int n)
  13.   {
  14.     float *p_end;
  15.     float sum=0,aver;
  16.     p_end=p+n-1;                                     //p指向a[0]元素的地址,p_end指向a元素最后一个地址
  17.     for(;p<=p_end;p++)                            //循环12次,每次循环p指向下一个元素地址
  18.       sum=sum+(*p);                                 //sum+*p指向的元素的值,赋值给sum
  19.     aver=sum/n;                                        //求平均分
  20.     printf("average=%5.2f\n",aver);            //以2为小数输出平均分
  21. }
  22. void search(float (*p)[4],int n)                  //p是指向具有4个元素的一维数组的指针
  23.   {
  24.     int i;
  25.     printf("The score of No.%d are:\n",n);   //输出main函数传递过来的n
  26.     for(i=0;i<4;i++)                                  //循序次数为4,每次循环i+1
  27.       printf("%5.2f ",*(*(p+n)+i));              //以2位小数输出a[n][i]
  28.     printf("\n");
  29. }
复制代码




【例8.15】在上例题的基础上,查找有一门以上课程不及格的学生,输出他们的全部课程的成绩
编写程序:
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     void search(float (*p)[4],int n);
  5.     float score[3][4]={{65,57,70,60},{58,87,90,81},{90,99,100,98}};
  6.     search(score,3);
  7.     system("pause");
  8.     return 0;
  9. }
  10. void search(float (*p)[4],int n)
  11.   {
  12.     int i,j,flag;
  13.     for(j=0;j<n;j++)
  14.       {
  15.         flag=0;
  16.         for(i=0;i<4;i++)
  17.           if(*(*(p+j)+i)<60)
  18.             flag=1;
  19.         if(flag==1)
  20.           {
  21.             printf("No.%d fails,his scores are:\n",j+1);
  22.                         for(i=0;i<4;i++)
  23.                                 printf("%5.2f ",*(*(p+j)+i));
  24.         printf("\n");
  25.          }
  26.      }
  27. }
复制代码



8.4通过指针引用字符串
用字符数组存放一个字符串,可以通过数组名和下标引用字符串中一个字符,也可以通过数组名和格式声明”%s“输出该字符串
【例8.16】定义一个字符数组,在其中存放字符串”I love china!“,输出该字符串和第8个字符
编写程序:
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     char string[ ]="I love China!";
  5.     printf("%s\n",string);
  6.     printf("%c\n",string[7]);
  7.     system("pause");
  8.     return 0;
  9. }
复制代码



【例8.17】通过字符指针变量输出一个字符串
编写程序:
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     char *string="I love China!";          //*string指向"I love China!"的首字节
  5.     printf("%s\n",string);
  6.     system("pause");
  7.     return 0;
  8. }
复制代码



【例8.18】将字符串a复制为字符串b然后输出字符串b
编写程序:
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     char a[ ]="I am a student.",b[20];  //定义a字符串并赋值,定义b为一维字符数组
  5.     int i;
  6.     for(i=0;*(a+i)!='\0';i++)               //循序执行到a数组中出现ASCII码为0的字符为止
  7.       *(b+i)=*(a+i);                           //a数组的内容顺序移动到b数组中
  8.     *(b+i)='\0';                                 //b数组末尾加上\0
  9.     printf("string a is:%s\n",a);           //输出a数组中的字符串         
  10.     printf("string b is:");
  11.     for(i=0;b[i]!='\0';i++)                  //循环执行到b[i]指向为\0的字符
  12.       printf("%c",b[i]);                       //字符形式输出数组b
  13.     printf("\n");                     
  14.     system("pause");
  15.     return 0;
  16. }
复制代码




【例8.19】用指针变量来处理8.18的问题
编写程序:
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     char a[ ]="I am a boy.",b[20],*p1,*p2;
  5.         int i;
  6.     p1=a,p2=b;
  7.     for(;*p1!='\0';p1++,p2++)
  8.       *p2=*p1;
  9.     *p2='\0';
  10.     printf("string a is:%s\n",a);
  11.     printf("string b is:%s\n",b);
  12.     system("pause");
  13.     return 0;
  14. }
复制代码




8.4.2字符指针做函数参数
【例8.20】用函数调用实现字符串的复制
编写程序:
//用字符数组名作为函数参数
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     void copy_string(char from[],char to[]);             //声明函数
  5.     char a[]="I am a tcacher.";                              //字符数组a赋值
  6.     char b[]="You are a student.";                         //字符数组a赋值
  7.     printf("string a=%s\nstring b=%s\n",a,b);        //输出字符数组a.b
  8.     printf("copy string a to string b:\n");
  9.     copy_string(a,b);                                           //调用函数
  10.     printf("\nstring a=%s\nstring b=%s\n",a,b);      
  11.     system("pause");
  12.     return 0;
  13. }
  14. void copy_string(char from[],char to[])
  15.   {
  16.     int i=0;
  17.     while(from[i]!='\0')                  //循环到a数组出现\0为止
  18.       {
  19.         to[i]=from[i];i++;               //将a数组的值赋值给b数组,每次循环a,b数组同时指向下以元素
  20.       }
  21.     to[i]='\0';                              //复制完毕给b数组元素末尾加上\0
  22. }
复制代码



//用字符型指针变量作实参
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     void copy_string(char from[],char to[]);             //声明函数
  5.     char a[]="I am a tcacher.";                              //字符数组a赋值
  6.     char b[]="You are a student.";                         //字符数组a赋值
  7.     char *from=a,*from=b;                                   //定义指针变量分别指向a,b
  8.     printf("string a=%s\nstring b=%s\n",a,b);        //输出字符数组a.b
  9.     printf("copy string a to string b:\n");
  10.     copy_string(from,to);                                      //调用函数
  11.     printf("\nstring a=%s\nstring b=%s\n",a,b);      
  12.     system("pause");
  13.     return 0;
  14. }
  15. void copy_string(char from[],char to[])
  16.   {
  17.     int i=0;
  18.     while(from[i]!='\0')                  //循环到a数组出现\0为止
  19.       {
  20.         to[i]=from[i];i++;               //将a数组的值赋值给b数组,每次循环a,b数组同时指向下以元素
  21.       }
  22.     to[i]='\0';                              //复制完毕给b数组元素末尾加上\0
  23. }
复制代码


//用字符指针变量作形参和实参
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     void copy_string(char *from,char *to);            
  5.     char *a="I am a tcacher.";                              //字符指针a指向"I am a tcacher."
  6.     char b[]="You are a student.";                        //字符数组b赋值   
  7.     char *p=b;                                                    //字符指针p指向b
  8.     printf("string a=%s\nstring b=%s\n",a,b);         
  9.     printf("copy string a to string b:\n");
  10.     copy_string(a,p);                                          
  11.     printf("\nstring a=%s\nstring b=%s\n",a,b);      
  12.     system("pause");
  13.     return 0;
  14. }
  15. void copy_string(char from[],char to[])
  16.   {
  17.     for(;*from!='\0';from++,to++)                     //循序执行到a数组元素为\0为止,每次循环a、b数组同时指向下一元素
  18.         {
  19.           *to=*from;                                           //a数组元素的值赋值给b数组元素
  20.          }
  21.         *to='\0';                                                //\0赋值给b数组有效元素末尾
  22. }
复制代码

以上几种编写方法输出的结果都一样

8.4.3使用字符指针变量和字符数组的比较
用字符数组和字符指针变量都能实现字符串的存储和运算,但它们二者之间是由区别的
字符数组由若干个元素组成,每个元素中放一个字符,而字符指针变量中存放的是地址
赋值方式,可以对字符指针变量赋值,但不能对数组变量名赋值
存储单元的内容,编译时为字符数组分配若干存储单元,以存放各元素的值,而字符指针变量,只分配一个存储单元
指针变量的值是可以改变的,而字符数组代表一个固定的值(数组元素的地址),不能改变
【例8.21】改变指针变量的值
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     char *a="I love China!";    //字符指针变量指向字符串首字节,
  5.     a=a+7;                           //
  6.     printf("%s\n",a);
  7.     system("pause");
  8.     return 0;
  9. }
复制代码



字符数组中各元素的值是可以改变的(可以对它们再赋值),但字符指针变量指向的字符串常量中的内容是不可以被取代的(不能对它们再赋值)
引用数组元素。对字符数组可以用下标法(用数组名和下标)引用一个数组元素(如a[5]),也可以使用地址法(如*[a+5])
用指针变量指向一个格式字符串,可以用它代替printf函数中的格式字符串。如:
     char *format;
     format=”a=%d,b=%f\n“;              //指向格式字符
     printf(format,a,b);                   //相当于printf("a=%d,b=%f\n",a,b);
这种printf函数称为可变格式输出函数
也可以用字符数组实现。如:
             char format[]=”a=%d,b=%f\n“;
             printf(format,a,b)
但使用字符数组时,只能采用在定义数组时初始化或逐个对元素赋值的方法,而不能用复制语句对数组整体赋值。如:
             char format[];
             format=”a=%d,b=%f\n“;          //非法

8.5指向函数的指针

8.5.1什么是函数指针
函数名就是函数的指针,它代表函数的起始地址
可以定义一个指向函数的指针变量,用来存放某一函数的起始地址,这就意味着此指针变量指向该函数。如:
  int (*p)(int,int);
定义p是一个指向函数的指针变量,它可以指向函数类型为整型且有两个整型参数的函数。此时,指针变量p的类型用int(*)(int,int)表示

8.5.2用函数指针变量调用函数
【例8.22】用函数求整数a和b中的大者
编写程序:
//通过函数名调用
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     int max(int,int);
  5.     int a,b,c;
  6.     printf("please enter a and b:");
  7.     scanf("%d,%d",&a,&b);
  8.     c=max(a,b);
  9.     printf("a=%d\nb=%d\nmax=%d\n",a,b,c);
  10.     system("pause");
  11.     return 0;
  12. }
  13. int max(int x,int y)
  14.   {
  15.     int z;
  16.       if(x>y)
  17.         z=x;
  18.       else
  19.         z=y;
  20.    return (z);
  21. }
复制代码



//通过指针变量调用它所指向的函数
#include <stdio.h>
int main()
  {
    int max(int,int);              //声明函数
    int (*p)(int,int);              //定义函数指针p
    int a,b,c;
    p=max;                        //函数指针p指向max函数,p的值为max的地址
    printf("please enter a and b:");
    scanf("%d,%d",&a,&b);
    c=(*p)(a,b);                //调用函数指针变量p。p的值为max,从max函数处执行程序
    printf("a=%d\nb=%d\nmax=%d\n",a,b,c);
    system("pause");
    return 0;
}
int max(int x,int y)
  {
    int z;
    if(x>y)
      z=x;
    else
      z=y;
   return (z);  
}
运行结果与上边一致

8.5.3怎样定义和使用指向函数的指针变量
定义指向函数的指针变量的一般形式为:
   类型名 (*指针变量名)(函数参数列表)
如:int (*p)(int,int); 这里得“类型名”是指函数返回值的类型
由于优先级的关系,“*指针变量名”用圆括号括起来就是函数变量。
说明:
定义指向函数的指针变量,它只能指向定义时指定的类型的函数,并且要和形参的函数列表对应
如果要用指针调用函数,必须先使指针变量指向该函数
在给函数指针变量赋值时,只须给出函数名而不必给出参数
用函数指针变量调用函数时,值需要将(*p)代替函数名即可,在(*p)之后的括号中根据需要写上实参
对执行函数的指针变量不能进行算术运算
【例8.23】输入两个数,然后让用户选择1或2,选1时调用max函数,输出二者中的大者,选2时调用min函数,输出二者中的小数
编写程序:
  1. #include <stdio.h>
  2. int main()
  3.   {
  4.     int max(int,int);
  5.         int min(int x,int y);
  6.     int (*p)(int,int);
  7.     int a,b,c,n;
  8.     p=max;
  9.     printf("please enter a and b:");
  10.     scanf("%d,%d",&a,&b);
  11.         printf("please choose 1 and 2:");
  12.     scanf("%d",&n);
  13.     if(n==1)
  14.                 p=max;
  15.         else if(n!=1)
  16.                 p=min;
  17.         c=(*p)(a,b);        
  18.     printf("a=%d\nb=%d\n",a,b);
  19.         if(n==1)
  20.                 printf("max=%d\n",c);
  21.         else
  22.                 printf("min=%d\n",c);
  23.     system("pause");
  24.     return 0;
  25. }
  26. int max(int x,int y)
  27.   {
  28.     int z;
  29.     if(x>y)
  30.       z=x;
  31.     else
  32.       z=y;
  33.    return (z);  
  34. }
  35. int min(int x,int y)
  36.   {
  37.     int z;
  38.     if(x<y)
  39.       z=x;
  40.     else
  41.       z=y;
  42.    return (z);  
  43. }
复制代码




8.5.4用指向函数的指针作函数参数
指向函数的指针变量的一个重要用途是把函数的入口地址作为参数传递到其他函数
【例8.2.4】有两个整数a和b,由用户输入1,2,3。如果输入1,程序就给出a和b中的大者,输入2就给出a和b中的小者,输入3就给出a和b的和
编写程序:
  1. #include <stdio.h>
  2. int main()
  3. {
  4. int fun(int x,int y,int(*p)(int,int));        //声明函数fun
  5. int max(int,int);                                 //声明函数max
  6. int min(int,int);                                 //声明函数min
  7. int add(int,int);                                 //声明函数add
  8. int a=34,b=-21,n;
  9. printf("please choose 1,2 or 3:");
  10. scanf("%d",&n);
  11. if(n==1)
  12.          fun(a,b,max);
  13. else
  14.          if(n==2)
  15.                  fun(a,b,min);
  16.          else if(n==3)
  17.                  fun(a,b,add);
  18. system("pause");
  19. return 0;
  20. }
  21. int fun(int x,int y,int (*p)(int,int))
  22. {int result;
  23. result=(*p)(x,y);            //根据输入选项设置*p指向的函数,返回的结果赋值给result
  24. printf("%d\n",result);     //输出结果
  25. }
  26. int max(int x,int y)
  27.   {
  28.     int z;
  29.     if(x>y)
  30.       z=x;
  31.     else
  32.       z=y;
  33.         printf("max=");
  34.    return (z);  
  35. }
  36. int min(int x,int y)
  37.   {
  38.     int z;
  39.     if(x<y)
  40.       z=x;
  41.     else
  42.       z=y;
  43.         printf("min=");
  44.    return (z);  
  45. }
  46. int add(int x,int y)
  47.   {
  48.     int z;
  49.     z=x+y;
  50.         printf("add=");
  51.    return (z);  
  52. }
复制代码



8.6返回指针值的函数
一个函数可以返回一个整型值、字符值、实型值等,也可以返回指针型的数据,即地址,其概念与以前类似,只是返回的值的类型是指针类型而已
定义返回指针值的函数的原型一般形式为:
类型名 *函数名(参数列表)
【例8.25】有a个学生,每个学生有b门课程的成绩。要求在用户输入学生序号以后,能输出该学生的全部成绩。用指针函数来实现
编写程序:
  1. #include <stdio.h>
  2. int main()
  3. {
  4.         float score[][4]={{60,70,80,90},{56,89,67,88},{34,78,90,66}};  //定义3个学生4门功课的数组
  5.         float *search(float(*pointer)[4],int n);                                      //声明函数指针search
  6.         float *p;                                                                               //定义浮点型指针
  7.         int i,k;
  8.         printf("enter the number of student:");
  9.         scanf("%d",&k);
  10.         printf("The scores of No.%d are:\n",k);
  11.         p=search(score,k);                                                               //调用search函数,返回值存放在p,指针p值为地址
  12.         for(i=0;i<4;i++)
  13.                 printf("%5.2f\t",*(p+i));                  //输出p+i指向的值,p为地址,每次加1指向下一个数组元素
  14.         printf("\n");
  15.         system("pause");
  16.         return 0;
  17. }
  18. float *search(float(*pointer)[4],int n)
  19. {
  20.   float *pt;
  21.   pt=*(pointer+n);                                  //数组下标加上k的值赋值给pt,得到一个地址
  22.   return (pt);
  23. }
复制代码



【例8.26】对例8.25中的学生,找出其中有不及格的课程的学生及其学生号
编写程序:
  1. #include <stdio.h>
  2. int main()
  3. {
  4.         float score[][4]={{60,70,80,90},{56,89,67,88},{34,78,90,66}};
  5.         float *search(float(*pointer)[4]);                  //声明函数
  6.         float *p;
  7.         int i,j;
  8.                 for(i=0;i<3;i++)                                //循环执行3次,每次给i加1,每次循序执行二维数组的下一个一维数组
  9.                 {
  10.                  p=search(score+i);                           //调用search函数,二位数组每加1执行的是下一个一位数组,得到float型数值
  11.                  if(p==*(score+i))                             //p的值与数组数组中的元素比较如果相等
  12.                    {
  13.                      printf("No.%d score:",i+1);               //输出i的值,i代表学号
  14.                     for(j=0;j<4;j++)                          
  15.                            printf("%5.2f ",*(p+j));          //循环4次输出*p指向的值,每次输出*p指向下一个数据
  16.                    printf("\n");
  17.                    }
  18.                  }
  19.         system("pause");
  20.         return 0;
  21. }
  22. float *search(float(*pointer)[4])
  23. {
  24.   int i=0;
  25.   float *pt;
  26.   pt=NULL;                              //空字符
  27.   for(;i<4;i++)                         //循环4次,每次循环指向下一个数组元素
  28.           if(*(*pointer+i)<60)       //如果数组中的值小于60,(*pointer+i)得到的是元素地址;*(*pointer+i)得到的是元素地址中的值
  29.                   pt=*pointer;          //元素中的值赋值给pt
  30.   return (pt);                           
  31. }
复制代码



8.7指针数组和多重指针
8.7.1什么是指针数组
一个数组,若其元素均为指针类型数据,称为指针数组,也就是数组中的每一个元素都存放一个地址,相当于一个指针变量。下面定义一个指针数组:
    int *p[4];
由于[]比*优先级高,因为p先与[4]结合,形成一个p[4]的数组,然后在于p前面的*结合,形成一个指针,相当于p数组有4个元素,每个元素都是指针
注意: int (*p)[4];  这是指向一维数组的指针变量
定义一维指针数组的一般形式:
  类型名 *数组名[数组长度]
【例8.27】将若干字符串按字母顺序(由小到大)输出
编写程序:
  1. #include <stdio.h>
  2. #include <string.h>
  3. int main()
  4. {
  5. void sort(char *name[],int n);                       //声明sort函数
  6. void print(char *name[],int n);                      //声明print函数
  7. char *name[]={"Follow me","BASIC","Great Wall","FORTRAN","Computer design"};    //字符型指针数组name指向5个字符串
  8. int n=5;
  9. sort(name,n);                                          
  10. print(name,n);
  11. system("pause");
  12. return 0;
  13. }
  14. void sort(char *name[],int n)
  15. {
  16. char *temp;                             
  17. int i,j,k;
  18. for(i=0;i<n-1;i++)                                //循环5次
  19. {
  20.   k=i;
  21.   for(j=i+1;j<n;j++)                             //循环4次
  22.           if(strcmp(name[k],name[j])>0)    //字符串比较。数组k大于数组j
  23.                   k=j;                                  //把j的值赋值给k
  24.   if(k!=i)                                              //如果k被重新赋值
  25.   {
  26.    temp=name[i];        
  27.    name[i]=name[k];
  28.    name[k]=temp;                                //调换指针数组元素中的地址(字符型指针数组中每个元素存放的是一个字符串的地址)
  29.   }
  30. }
  31. }
  32. void print(char *name[],int n)
  33. {
  34. int i;
  35. for(i=0;i<n;i++)                               //循环5次
  36.          printf("%s\n",name[i]);             //以字符串形式从数组元素对应的首地址开始输出直到遇到\0为止
  37. }
复制代码



8.7.2指向指针数据的指针变量
在了解了指针数组的基础上,需要了解指向执行数据的指针变量,简称为指向指针的指针
下面定义一个指向指针数据的指针变量
  char **p;
表示p指向一个字符指针变量(这个字符指针变量指向一个字符型数据)
【例 8.28】使用指向指针数据的指针变量
编写程序:
  1. #include <stdio.h>
  2. int main()
  3. {
  4.         char *name[]={"Follow me","BASIC","Great Wall","FORTRAN","Computer design"};
  5.         char **p;                          //定义p为字符型多重指针,**p指向*name,p的值是*name的地址
  6.         int i;
  7.         for(i=0;i<5;i++)               //循环执行5次
  8.         {
  9.          p=name+i;                     //每循环依次指向下一数组元素,p得到首元素地址
  10.          printf("%s\n",*p);           //以字符串形式输出,p得到的首元素地址中的字符串
  11.         }
  12.         system("pause");
  13.         return 0;
  14. }
复制代码



【例8.29】有一个指针数组,其元素分别指向一个整型数组的元素,用指向指针数据的指针变量,输出整型数组各元素的值。
编写程序:
  1. #include <stdio.h>
  2. int main()
  3. {
  4.         int a[5]={1,3,5,7,9};                                             //定义a为一维整型数组
  5.         int *num[5]={&a[0],&a[1],&a[2],&a[3],&a[4]};       //定义num为一维整型数组指针,每个指针指向a元素
  6.         int **p,i;                                          
  7.         p=num;                                                               //p为指向num的指针
  8.         for(i=0;i<5;i++)
  9.         {
  10.          printf("%d ",**p);                                               /*循环输出**p,每次循环给p+1,p的值是num的地址,num的值是a[]的地址;**p则是指向num的的值(num的值是指向a[]的值,所以**p的值是a[]的值),p没加1,num指向下一个a数组元素*/
  11.          p++;
  12.         }
  13.         printf("\n");
  14.         system("pause");
  15.         return 0;
  16. }
复制代码



8.7.3指针数组做main函数的形参
某些情况下,main函数可以有参数,即:
  int main(int argc, char *argv[])
注意:如果用带参数的main函数,其第一个形参必定是int型,用来接收形参个数,第二个形参必须是字符指针数组,用来接收从操作系统命令行传来的字符串中首字符的地址
命令行的一般形式为
   命令名  参数1 参数2……参数n

如果有一个名为file1的文件,它包含以下的main函数
int main(int argc,*argv[])             //main函数包含3个形参,一个字符指针。
  {
    while(argc>1)                             //argc值为3。
      {
        ++argv;                                //数组参与运算的是下标。下标从0开始,++argv后为argv[1],数组的第2个元素
        printf("%s\n",*argv);              //输出*argv的值,*argv指向数组的第2个元素
        --argc;                                   //每循环一次先给argc-1,得到的值返回循环参与运算;argc--的话就是argc先给条件赋值,运算一次回来再减1
      }
     return
}

main函数指针指向情况:

    argv→argv[0]→file1
             argv[1]→china
             argv[2]→beijing

以上程序得到的输出结果是:
china
beijing

8.8动态内存分配与指向它的指针变量
8.8.1什么是动态内存分配
汇编语言的时候我们不用建立stack,系统就能自动入栈出栈。这个栈等同于这里所说的栈,由编译系统自动分配
堆区:是需要程序员向系统申请才能使用的

8.8.2怎样建立内存的动态分配
对内存的动态分配是通过系统提供的函数库来实现的,主要有malloc,calloc,ferr,realloc这4个函数
1,用malloc函数开辟动态存储区
其函数原型为
  void *mailloc(unsigned int size);
其作用时在内存的动态存储区中分配一个长度为size的连续空间。形参size的类型定位无符号整数(不允许为负数)。此函数的值(即”返回值“)是所分配区域的第一个字节的地址。如:
  malloc(100);  //开辟100个字节的临时分配域,函数值为其第一个字节的地址
注意指针的基类型为void,即不执行任何类型的数据,只提供一个内存地址。如果此函数未能成功执行(如内存空间不足),则返回空指针(NULL)

2,用calloc函数开辟动态存储区
其函数原型为
  void *calloc(unsigned n,unsigend size);
其作用时在内存的动态存储区中分配n个长度为size的连续空间,这个空间一般比较大,足以保存一个数组
用calloc函数可以为一维数组开辟动态存储空间,n为数组元素个数,每个元素长度为size。这就是动态数组。函数返回指向所分配域的第一个字节的指针;如果分配不成功,返回NULL。如:
  p=calloc(50,4);     //开辟50*4个字节的临时分配域,把首地址赋值给指针变量p

3,用realloc函数重新分配动态存储区
其函数原型为
  void *realloc(void *p,unsigned int size);
如果已经通过malloc函数或calloc函数获得了动态空间,想改变其大小,可以用recalloc函数重新分配
用realloc函数将p所指向的动态空间的大小改变为size。p的值不变。如果重新分配失败,返回NULL。如:
  realloc(p,50);                            //将p指向的已分配的动态空间改为50字节

4,用free函数释放动态存储区
其函数原型为
  void free(void *p);
其作用时释放指针变量p所指向的动态空间,使这部分空间能重新被其他变量使用。p应是最近一次调用calloc或malloc函数时得到的函数返回值。如:
  free(p);             //释放指针变量p所指向的已分配的动态空间
free函数无返回值
以上这4个函数的声明在stdlib.h头文件中,在用到这些函数时应当用#include <stdlib.h>指令把stdlib.h头文件包含到程序文件中

8.8.3void指针类型
void成为空指针,它不指向任何一种具体的类型数据,值提供一个地址,不能存储数据。可以自动把指向的地址赋值给有基类型的指针,系统自动转换基类型
【例8.30】建立动态数组,输入5个学生的成绩,另外用一个函数放检查其中有无低于60分的。输出不合格的成绩
编写程序:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. int main()
  4. {
  5. void check(int *);                         //声明check函数
  6. int *p1,i;
  7. p1=(int *)malloc(5*sizeof(int));    //开辟5个int型空间,转换成int指针赋值给p1,*p1指向开辟的5个int型临时空间
  8. for(i=0;i<5;i++)
  9.          scanf("%d",p1+i);              //循环输入5个十进制数据,存放在p1指向的空间中,每次循环p1指向下一个临时空间地址
  10. check(p1);                                 //调用函数check,把p1传递给函数,p1的值为地址指向临时空间的第一个整型数据
  11. system("pause");
  12. return 0;
  13. }
  14. void check(int *p)
  15. {
  16. int i;
  17. printf("They are fail:");
  18. for(i=0;i<5;i++)                         //循环5次
  19.          if(p[i]<60)                         //如果p指向的临时空间中的数值小于60
  20.                  printf("%d ",p[i]);      //输出p指向的这个值
  21. printf("\n");
  22. }
复制代码



8.9有关指针的小结
小结也就不写了我也不想看了,全是概念。既然指针如此重要,并且直接操作内存的,整章很少提及内存地址的相关知识。用的是纯地址来讲解的指针,如果没有先看汇编。这一章不可能这么快看完,也不可能理解。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

快速回复 返回顶部 返回列表