《程序设计初步.docx
《《程序设计初步.docx》由会员分享,可在线阅读,更多相关《《程序设计初步.docx(21页珍藏版)》请在冰豆网上搜索。
《程序设计初步
程序设计初步
§1程序设计基本概念
一、程序
程序就是供计算执行后能完成特定功能的指令序列。
二、组成
两部分组成:
描述问题的每个对象及它们之间的关系(数据结构);描述对这些对象作处理的处理规则(算法)。
所以数据结构和算法是程序的最主要的两个方面。
程序=数据结构+算法
三、计算机程序的性质
●目的性——程序有明确的目的,程序运行时能完成赋予它的功能。
●分步性——程序为完成其复杂的功能,由一系列计算机可执行的步骤组成。
●有序性——程序执行步骤是有序的,不可随意改变程序的执行顺序。
●有限性——程序是有限的指令序列,程序所包含的步骤是有限的。
●操作性——有意义的程序总是对某些对象进行操作,使其改变状态,完成其功能。
四.程序开发过程
1.分析:
建立模型,搞清与问题相关领域具备的知识。
2.设计:
分总体设计和详细设计
总体设计:
任务之一是分解问题为易求解的子问题(模块),另一方面是如何组织程序模块。
详细设计:
设计每个模块的数据结构和算法。
3.编码及调试:
用具体的程序设计语言表示各模块的算法和数据结构。
基本过程如下:
4.程序测试:
目的:
主要目的在于发现程序中的错误。
●测试是程序的执行过程,目的在于发现错误。
●一个好的测试实例在于能发现至今未发现的错误。
●一个成功的测试是发现了至今未发现的错误的测试。
方法和技术:
5.编写程序文档
程序使用说明书或用户文档,包括以下内容:
●程序的功能的足够详细的说明
●需要输入的数据类型、格式和取值范围
●需要使用的文件数量、名称、内容以及存放位置等
●程序运行需要的软、硬件环境要求
●程序的装入,启动方式等。
●程序的界面、交互方式和操作方法
●程序运行中用户须使用的交互命令的名称、功能和格式的详细解释和事例。
程序技术说明书或程序技术文档
●程序结构以及各模块的功能描述
●程序使用硬件的有关信息
●主要算法和数据结构的解释和描述
●各变量的名称、作用、类型等
●程序代码清单
五、算法设计与分类
算法设计的任务就是对各类具体问题设计良好的算法及研究设计算法的规律和方法。
常用的算法设计方法:
●数值算法——以数学的方式表示的问题求数值解的方法。
例如,方程求解、矩阵计算、线性方程组求解、数值积分、微分方程求解等。
具体方法有迭代法、递归法、插值法等
●非数值算法——求非数值解的方法。
如排序查找、排列模拟、表格处理、文字处理等。
具体方法有分治法、贪婪法、回溯法等。
六.算法的性质
●输入性:
具有零个或若干个输入量。
●输出性:
至少产生一个输出。
●有穷性:
每一条指令的执行次数是有限的。
●确定性:
每一条指令的含义明确,无二义性。
●可行性:
每一条指令都应在有限的时间内完成。
七.算法设计的目标
●正确性:
算法应当满足具体问题的需求,这是算法设计的基本目标。
通常一个大型问题的需求以特定的规格说明方式给出。
这种问题需求一般包括对于输入、输出、处理等的明确的无歧义性的描述,设计的算法应当能正确地实现这种需求。
●可读性:
即使算法已转变成机器可执行的程序,也需考虑人能较好地阅读理解。
可读性有利于人对算法的理解,这既有利于算法中隐藏错误的排除,也有利于算法的交流和移植。
●健壮性:
当输入数据非法时,算法应当作出适当的处理,而不应产生不可预料的结果。
●高效性:
算法的效率指算法的执行时间。
执行时间短的算法也称高效率的算法。
算法的效率也称作算法的时间复杂度。
●低存储需求:
算法的存储量需求指算法执行过程中所需要的最大存储空间。
算法的存储量需求也称作算法的空间复杂度。
通常,算法的高效率和低存储量需求是互相矛盾的。
八、算法分析
算法分析的任务是对设计出的每一个具体算法,利用数学工具,讨论各种复杂度。
算法的复杂度可分为时间复杂度和空间复杂度。
目的是探讨某种具体算法适用于哪类问题,或某类问题宜采用哪种算法。
算法与以下因素有关:
书写算法的程序设计语言;
②编译产生的机器语言代码质量;
③机器执行指令的速度;
④问题的规模。
前三个因素与选用的算法语言、编译程序及机器有关,因此算法效率应是问题规模的函数。
§2插值法
1、有关概念
设函数y=f(x)在区间[a,b]上定义,且已知在点a≤x0﹤x1﹤…≤b上的值y0,y1,…,yn,若存在一简单函数P(x),
(1)
成立,就称P(x)为f(x)的插值函数,点x0x1xn称为插值节点,包含插值节点的区间[a,b]称为插值区间,求插值函数P(x)的方法称为插值法。
若P(x)是次数不超过n的代数多项式。
即
(2)
其中ai为实数,就称P(x)为插值多项式,相应的插值法称为多项式插值。
若P(x)为分段的多项式,就是分段插值。
若P(x)为三角多项式,就称三角插值。
本章只讨论多项式插值与分段插值。
从几何上看,插值法就是求曲线y=P(x),使其通过给定的n+1格点(xi,yi),i=0,1,…,n,并用P(x)近似已知函数f(x),见下图。
y=P(x)
y=f(x)
2、插值多项式的存在唯一性
对于形如式
(2)的多项式,根据式
(1)可得
这是一个关于
的n+1元线性方程组。
要证明插值多项式存在唯一,只要证明上述方程组存在唯一的解,也就是证明方程组的系数行列式
不为零,利用行列式性质可得
由于
时
,故所有因子
,于是
故方程组存在唯一得一组解。
3、线性插值和抛物线插值
(1)线性插值
问题:
假定已知区间
]的端点处函数值
,要求线性插值多项式
,使它满足
插值函数:
根据平面解析几何知识可知,
即通过(
)与(
)的直线。
根据几何意义可的
的表达式为:
点斜式:
两点式:
4、拉格郎日(Lagrange)插值多项式
§3迭代法(方程求根)
§4线性方程组的直接解法
§5线性方程组的迭代解法
§6§§
C语言程序设计注意事项
1.大小写问题
C语言区分大小写,即对拼写相同、但大小写不同的两个表达式是作为两个不同的表达式对待的。
C语言的全部保留字均为小写,绝大多数库函数名为小写,一般只有常量、全局变量才采用大写拼写。
2.变量未定义
有些程序编写者开始不习惯变量使用前必须先定义这一规则。
会出现使用未定义变量的情况。
除了直接常量外,程序中出现的任何一个标识符都应先定义,再使用。
但有些再库函数中已有定义的标识符则不应重复定义。
3.变量未初始化
指没有给一个变量赋初值而引用了该变量。
该错误较为隐蔽,编译时会有警告信息,但程序可以运行。
若忽略该错误,常会引起运行时产生不正确结果或出现一些莫名其妙的错误。
若是使用未初始化的指针则可能引起严重后果,甚至操作系统的崩溃。
4.数值范围
这是在使用定点数据时容易被忽略的问题。
int类型数只能显示-32768~32767,而无符号整型数显示范围为0~65535。
若在使用整型数时忽略了这个问题,常会导致结果不正确,而程序逻辑又是对的。
该类错误比较隐蔽。
5.输入输出函数中的数据类型
在使用输入输出函数时,尤其是printf及scanf函数,对其参数一定要使用准确。
否则会得到错误结果。
如:
对于定义成unsignedint类型的数使用%d进行输出就看不到希望的输出结果,而必须用%u进行输出。
在用scanf进行输入时,格式必须严格匹配。
如:
scanf(“x=%d”,&x),则输入时必须输入“x=”,否则会产生错误的调用结果。
scanf函数使用时的地址表也必须准确。
fprintf和fscanf也有相似的问题。
6.输入函数的限制
C语言提供的字符串输入函数都有不同程度的缺陷。
如scanf函数不允许串中有空格,因为空格将其前后作为两个字符串处理。
若用scanf函数输入字符串,应在程序中明确限制其长度,如若有定义charss[20];则输入语句中应写成scanf(“%19s”,ss);剩下的一个位置空间留给字符串结束符’\0’。
scanf函数和getchar函数在工作时还会留下一些多余的字符,主要是’\n’字符,它将对后续的输入操作产生影响。
如若有语句序列:
scanf(“%d”,&d);
ch=getchar();
其目的是先输入一个整型数给x,再输入一个字符给ch。
而结果是只输入了一个整型数,程序自动将scanf遗留下的’\n’字符给了字符变量ch。
7.运算符的优先级
在不能肯定优先级顺序时加括号,以强制区分优先级顺序。
如语句c=getchar()!
=Esc本意可能是想先得到一个键盘输入c,再判断该输入c是否为Esc键。
而实际结果可能是:
c=(getchar()!
=Esc),这样就变成了先得到一个键盘输入,再判断该输入是不是Esc,再将判断结果true或false赋给变量c,变量c中存放了一个逻辑值。
8.易混淆的运算符
如=(赋值)和==(关系符);&(按位与的位运算符)和&&(逻辑与);|(按位或的位运算符)和||(逻辑或)等。
9.分号的问题
C语言中语句以分号“;”结束,否则会引起编译错误。
但是多余的分号不会引起编译错误,却能导致运算结果的错误。
如:
If(x>y);
x=y;
语句本身是如果变量x大于变量y,则将y的值赋给x,但现在变成了判断x是否大于y,然后接着执行赋值语句,而不管比较的结果,因为这两个语句已经是分开的两个独立的语句了。
10.循环体中有多条语句
作为循环体中的多条语句必须用大括号{}括起来,否则的话,C语言理解成该循环体只有一条语句,其余的语句将不作为循环体而顺次执行。
11.数组的边界问题
C语言中数组是以0为第一个元素编号,而其它多数语言是以1开始。
这样,最后一个元素在C语言中是数组长度减1,若忽略了这个问题,会导致在程序运行期间非法使用其它内存区的错误。
12.数组的赋值
数组名不能作为赋值运算符的左值。
下面语句皆为错误语句:
inta[3];
a={1,2,3};
charss[10];
ss=”abcdefg”;
但是下面语句正确:
char*ss;
ss=”abcdefg”;
因为,ss是一个指向字符的指针,相当于使其指向”abcdefg”所在的字符串的首地址,因此正确。
并且数组名也不是变量,如a++、a+=3等语句也是错误的。
13.关于指针的问题
指针的常见问题是未初始化、基类型不匹配等。
这些编译程序都会有警告。
但是有一类错误编译程序是检查不出来的。
如下面代码:
inta[10],*p,k;
p=a;
for(k=0;k<10;k++)
scanf(“%d”,p++);
for(k=0;k<10;k++)
printf(“%d”,*p++);
这段代码可以正确输入10个整数,正确赋值,但是却不能正确输出。
为什么呢?
因为指针p在执行完输入语句后没有使它指回数组的起始地址,接下来的输出语句中输出的自然就不是数组中的元素了。
14.结构体类型名不能当变量使用
虽然结构体变量是由多个数据组合而成的,但它与数组不同,结构体变量名表示该变量的值而不是地址。
结构体类型不能当变量使用,比较典型的错误是用结构体名引用其成员。
如:
structnode{
intx,y;
}na;
若使用node.x引用是错误的。
15.函数返回地址
当一个函数需要返回地址时,其地址可以是具有静态性质的变量的地址,或通过参数传递进来的地址,或在函数中动态分配的地址,但一般不能是函数中定义的动态变量(如数组、结构体等)的地址,因为在函数调用结束时这些地址自动被释放,原来存储的值通常被覆盖。
如:
int*fun(void)
{inta[2]={0,1};
returna;
}
这样的函数调用后不会得到完整的数组元素值。
16.函数参数类型的匹配及函数原型
虽然在C语言中的函数在参数传递时允许使用不匹配的参数并进行隐式转换,但是不匹配的参数极易导致错误却不易被发现。
建议必须对自定义的函数做函数原型说明,这样做虽然在输入程序时看起来麻烦一点,但却可以避免很多不必要的错误,节省程序调试的时间。
17.重新定义库函数
如果重新定义了C语言的库函数可能会带来很复杂的问题。
因为可能其它的标准函数也调用了此函数,应尽量避免出现这样的情况。
18.switch语句中的break
在switch语句中,经常用到break,了解一下switch语句的工作流程大有好处。
Switch(x){
case1:
y=10;
case2:
y=4;break;
case3:
y=6;break;
}
上述语句中,若x=1,则先执行y=10,再执行y=4,然后退出switch语句,很明显,在y=10语句后漏掉了break语句。
19.通过指针引用结构体成员
设有结构体定义:
structexam{
intx;
floaty;
}m,*p;
则在引用时应使用如下格式:
对于结构体变量m,应为m.x和m.y;而对于指针变量p,应为m->x和p->y;当然也可使用*,但要注意,应写成(*p).x和(*p).y。
20.动态分配的内存空间在使用完毕后应释放
对于使用函数malloc等函数(或new)动态分配的内存空间,在使用完毕后应及时释放,否则这部分空间在指针指向其它地方后会丢失,即不再处于可以管理的状态,无法再次使用分配。
如果持续不断地犯这样的错误,可能导致操作系统崩溃。
21.文件操作完毕后应关闭
由于系统资源的有限性,系统对同时打开的文件有限制。
在文件操作完毕后,应及时关闭不再进行操作的文件指针(或文件句柄)。
22.浮点数的比较
计算机中的浮点数总是近似表示的,因此浮点数的比较必须格外注意。
如判断一个浮点数是不是等于0,可能会用到的语句形式:
if(a==0.0),但这样做可能得不到想要得结果,而应该写成fabs(a)<0.000001更保险。
简单程序设计举例
例1、输入三个整数,输出其中的最大数。
分析:
为求三个数中的最大者,最简洁的办法是先将其中某一个预作最大者存于某变量中,然后逐一与其它两个数比较,当发现最大者时,就以它重置该变量的值,最后变量中存储的就是最大数。
参考代码
#include"stdafx.h"
#include"stdio.h"
intmain(intargc,char*argv[])
{
inta,b,c,max;
printf("输入三个整数。
\n");
scanf("%d%d%d",&a,&b,&c);
max=a;
if(max
if(maxprintf("最大整数是:
%d\n",max);
return0;
}
例2、输入三个整数,按值从大到小的顺序输出它们。
分析1:
用变量x、y、z分别表示输入的三个数,调整变量x、y、z的值,使它们满足x≧y≧z,可分三步来实现。
首先调整x和y,使x≧y;再调整x和z,使x≧z,此时x有最大值;最后调整y和z,使y≧z。
这样就完成全部调整的要求。
算法如下:
{
输入x、y、z;
if(xif(xif(y输出x、y、z;
}
参考代码
#include"stdafx.h"
#include"stdio.h"
intmain(intargc,char*argv[])
{
intx,y,z,temp;
printf("Enterx,y,z.\n");
scanf("%d%d%d",&x,&y,&z);
if(x=y
temp=x;x=y;y=temp;
}
if(x=z
temp=x;x=z;z=temp;
}
if(y=z
temp=y;y=z;z=temp;
}
printf("%d\t%d\t%d\n",x,y,z);
return0;
}
分析2(用指针表示):
若用三个指针分别指向三个变量x、y、z,当比较后发现需要交换时,就交换变量的指针,而不交换变量的值。
全部比较结束后,变量的值没有改变,但从指针方向来看,它们的值是从小到大排列的。
参考代码:
#include"stdafx.h"
#include"stdio.h"
intmain(intargc,char*argv[])
{
intx,y,z;
int*big=&x,*mid=&y,*sma=&z,*temp;//三个指针变量分别指向x,y,z
printf("Enterx,y,z.\n");
scanf("%d%d%d",big,mid,sma);//顺序分别为x,y,z的输入值
if(*big<*mid){//使*big>=*mid
temp=big;big=mid;mid=temp;
}
if(*big<*sma){//使*big>=*sma
temp=big;big=sma;sma=temp;
}
if(*mid<*sma){//使*mid>=*sma
temp=mid;mid=sma;sma=temp;
}
printf("%d\t%d\t%d\n",x,y,z);//按输入顺序输出x,y,z
printf("%d\t%d\t%d\n",*big,*mid,*sma);//按从大到小的顺序输出
return0;
}
例3、求一元二次方程的根。
分析:
设一元二次方程为
,方程系数a,b和c从键盘输入。
对任何的系数a、b、c,有以下几种情况需要考虑:
(1)
,方程有两个根。
(2)
、
,方程退化为一次方程
。
方程有根-a/b。
(3)
。
方程或为同义反复(c=0),或矛盾(c≠0),称这种情况为方程退化。
由以上分析得到以下程序结构:
{
1输入方程系数a、b、c;
if(a≠0.0)
2求方程;
elseif(b≠0)
3输出方程根-a/b;
elseif(c==0)
4输出方程同义反复字样;
else
5输出方程矛盾字样;
}
参考代码:
#include"stdafx.h"
#include"stdio.h"
#include"math.h"//使用数学库函数
intmain(intargc,char*argv[])
{
doublea,b,c,delta,re,im,root1,root2;
printf("输入方程系数a,b,c\n");
scanf("&lf&lf&lf",&a,&b,&c);
if(a!
=0.0){//有两个根
delta=b*b-4.0*a*c;//计算判别式
re=-b/(2.0*a);
im=sqrt(fabs(delta))/(2.0*a);
if(delta>=0){//有两个实根,先求绝对值大的根
root1=re+(b<0.0?
im:
-im);
root2=c/(a*root1);
printf("两个实根分别是:
%7.5f,%7.5f\n",root1,root2);
}
else
printf("两个复根分别是%7.5f+%7.5fI,%7.5f-%7.5fI\n",re,fabs(im),re,fabs(im));
}
else//a=0.0
if(b!
=0.0)printf("单根:
%7.5f\n",-c/b);
else
if(!
c)printf("方程同义反复!
\n");
elseprintf("方程矛盾。
\n");
return0;
}
例4、输入班级学生考试成绩,求考试平均成绩。
约定当输入负数时,表示输入结束。
分析:
采用考试成绩逐个输入、累计全班总分和计数学生人数的方法,直到输入成绩是负数时循环结束,然后求出平均成绩,并输出。
参考代码:
#include"stdafx.h"
#include"stdio.h"
intmain(intargc,char*argv[])
{
intsum,count,mark;
sum=0;
count=0;
for(;;){//循环条件永远为真
printf("输入成绩(小于0结束)\n");
scanf("%d",&mark);
if(mark<0)break;//跳出循环
sum+=mark;//累计总分
count++;//学生人数计数
}
if(count){
printf("人数总计:
%d人\n",count);
printf("平均成绩为:
%.2f\n",(double)sum/count);
}
else
printf("没有数据输入!
");
return0;
}
例5、统计输入字符中,空白类字符、数字字符和其它字符的个数。
分析:
对于输入的每个字符,采用switch语句分析其类别,然后累加响应的个数。
注意break语句在switch语句中的功能。
参考代码:
#include"stdafx.h"
#include"stdio.h"
intmain(intargc,char*argv[])
{
intc,nblank,nother,ndigit;
nblank=nother=ndigit=0;
printf("Enterstringline\n");
while((c=getchar())!
='\n'){
switch(c){
case'0':
case'1':
case'2':
case'3':
case'4':
case'5':
case'6':
case'7':
case'8':
case'9':
ndigit++;break;
case'':
case'\n':
case'\t':
nblank++;break;
default:
nother++;break;
}
}
printf("digit=%d\tblank=%d\tother=%d\n",ndigit,nblank,nother);
return0;
}
例6、求方程
的实根。
分析:
用牛顿迭代方法求方程
的根的近似解:
当修正量
的绝对值小于某个很小数ε时,就作为方程的近似解。
下面程序迭代初值为–2,精度控制参数ε=1.0e-6。
参考代码:
#include"stdafx.h"
#include"stdio.h"
#include"math.h"
#defineEpsilon1.0e-6
intmain(intargc,char*argv[])
{
doublex,d;
x=2.0;
do{
d=(((3.0*x+4.0)*x-2.0)*x+5.0)/((9.0*x+8.0)*x-2.0);
x-=d;
}while(fabs(d)>Epsilon);
printf("Therootis%.6f\n",x);