C语言博客作业--函数嵌套调用
一、实验作业
1.1 6-6 递归求简单交错幂级数的部分和
设计思路
double fn( double x, int n )
{
if(n==1)
返回x;
else{
返回x*(1-fn(x,n-1))
}
代码截图
调试问题
- 这题主要遇到的问题是没有思路,能找到递归出口但是递归部分不知道要写啥
- 把n用具体的值来代,然后把公因式x提取出来后,尝试和分析后找到递归部分
1.2 学生成绩管理系统
1.2.1 画函数模块图,简要介绍函数功能。
1.2.2 截图展示你的工程文件
1.2.3 函数代码部分截图
本系统代码总行数:578
要求截图你的头文件、插入学生信息及学生成绩信息代码、删除学生成绩信息代码、总分排序代码。
1.2.4 调试结果展示
学生信息管理系统的功能展示:
-
1.命令1:输入学生信息
-
2.命令4:查看学生信息,发现已实现按学号递增顺序输出
-
3.命令5:按学生的总分从小到大顺序输出
-
4.命令6:实现通过查询学号查看学生信息,图为查看小君信息
-
5.命令2:修改学生成绩信息,图为成功修改Jane数学成绩66->88
-
6.命令5:查看修改Jane成绩后的排名
-
7.命令3:删除某同学的某成绩,图为通过学号删除康康的计算机成绩,并输出其成绩信息发现已删除
-
命令3:删除某同学的某成绩,图为通过姓名删除May的数学成绩,并输出其成绩信息发现已删除
-
8.命令6:通过学号查询学生信息,图为康康的信息,计算机成绩已删除
-
9.命令1:插入学生信息,图为插入某学号后输出正确排序
学生信息管理系统的特殊测试点:
-
1.命令4 命令5:无学生信息时查询学生信息
-
2.命令1:新增的学生已存在
-
3.命令2:没找到要修改的学生信息或输入出错
-
4.命令3:没找到要删除的学生信息或输入出错
1.2.5 调试碰到问题及解决办法。
-
Q1:编译错误:编译的时候会自己跳出这个文件,然后提示
-
A1: 错误分析:多个main定义或者是加入了相同的c文件。解决方法:关闭重复文件
-
Q2:当我想在结构体中加个变量flag来记录删除情况时对flag赋初值后发现编译器提示了如图所示的错误:
-
A2:当询问了林丽老师后得知我们是不能在头文件里对变量进行初始化的~~
-
Q3:在add函数中插入排序时Count值不对,排序后又在结尾多增加了1
-
A3:分析后发现在输入数据的时候我就已经实现了Count++,没必要在排序后再加1
-
Q4: 一开始写的时候有点没过脑子,把add分装成两个函数来写,因为考虑到得传两个不同结构体类型变量的实参,但是分装成两个函数来写,导致了Count值不好利用,还得再引入k来存放Count的值然后进行和对add1一样的操作来操作add2,而且到后面因为代码太长了非常混乱而且代码简洁程度大大降低
-
A4: 谁说不能传两个不同结构体类型的变量给一个函数阿喂!在迷之如梦初醒以后,删掉了add2,重新搞成同一个函数,总算是清晰多了,而且也没有那么乱了
-
Q5:删除信息后按平均分排序输出错误
-
A5:因为平均分的计算我是在刚输入学生成绩信息的时候就进行计算了,如果用户后续选择了删除某课程成绩的话,我排序的时候却仍然用一开始就计算好的平均分必然是不对的。于是有了我们recount函数的存在:就是如果出现了用户选择删除这个命令,那就调用它再次求被删除成绩信息的学生平均分
-
Q6:误以为平均分排序和总分排序是同样的结果
-
A6:考虑到有些同学的某些课程是没有成绩的,所有平均分就算一样的两个同学,他们的总分也不一定相同阿,于是补上平均分排序
-
Q7:因为自己爱搞事情然后想要给学生加上专业信息,于是想要用字符数组来记录输入的专业信息,但是因为输入的专业的字数不确定导致我对该字符数组大小的定义还有待会输出的格式都会造成一定的影响
-
A7:在跟大佬抱怨的时候得知可以直接显示我们写好的专业,然后让其作出选择,虽然这样子专业的选择会比较不自由,但是我们只需要int major 来记录学生的专业信息,可以在某种程度上大大地节省了我们宝贵的内存。
-
Q8:输出全部信息的时候答案不对,第2个同学会出现了学号和姓名空白还有某些成绩会异常即跟输入数据相差较大的现象,但是在排序输出时又正确了
-
A8:但是用大佬的devc和code block 是可以完全没有错误地运行项目功能,而且我在其建议下重新下载了devc和无数次重建项目,仍然还是会有个别数据丢失的现象,但是在最最最后一次的苦苦挣扎时发现竟然可以了!!!呼~这也是至今的未解之谜...
-
Q9:源文件未编译
-
A9:我目前遇到这个情况的原因是:手抖在建项目的时候按的是C++.....然后怎么办呢,还能怎么办重建项目呗!
-
对了,冒泡排序比选择排序要更稳定,所以最好用冒泡排序来完成排序~~
-
补充还知道了一个很了不得的事儿(自以为了不得~)
-
实参是数组名的时候,形参也可以是数组名,因为形参的数组名自动退化成指针
二、截图本周题目集的PTA最后排名。
三、阅读代码
代码1:
#include<stdio.h>
int main()
{
unsigned long count(int n);
int n;
unsigned long m;
printf("请输入楼梯的阶数:");
scanf("%d",&n);
m=count(n);
printf("有%lu种爬楼梯的方法\n",m);
return 0;
}
unsigned long count (int n)
{
unsigned long f;
if(n==1)
f=1;
else if(n==2)
f=2;
else
f=count(n-1)+count(n-2);
return(f);
}
代码2:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int fib(int a,int b,int n);
int n;
scanf("%d",&n);
printf("%d",fib(0,1,n));
return 0;
}
int fib(int a,int b,int n)
{
if(n==3)
{
return a+b;
}
return fib(b,a+b,n-1);
}
- 这两个代码的功能:求爬楼梯的方法种数,一次可以爬一阶或两阶。
- 代码1通过分析:有n个台阶时,设有count(n)种走法,最后一步走1个台阶,有count(n-1)种走法;最后一步走2个台阶,有count(n-2)种走法。于是count(n)=count(n-1)+count(n-2)。可见,此问题的数学模型竟然是斐波那契数,是递归中典型例题之一。该代码优点是:更容易让人理解,而且善于利用数学模型和分析的过程是我作为一个初学递归者学习的地方~
- 代码2:递归思路很清晰,在完成问题建模之后,采用了一种很巧妙的“非常规”的做法,将运算量减少了一半,这就是具有创新和创造意义的优秀代码,值得我好好摸索和学习这种巧妙的思路~
四、本周学习总结
1.介绍本周学习内容
1.1 #define的概念
define命令是C语言中的一个宏定义命令,它用来将一个标识符定义为一个字符串,该标识符被称为宏名,被定义的字符串称为替换文本。
该命令有两种格式:一种是简单的宏定义,另一种是带参数的宏定义。
- (1) 简单的宏定义:
define <宏名> <字符串>
例: define PI 3.1415926 - (2) 带参数的宏定义
define <宏名> (<参数表>) <宏体>
例: #define A(x) x
一个标识符被宏定义后,该标识符便是一个宏名。这时,在程序中出现的是宏名,在该程序被编译前,先将宏名用被定义的字符串替换,这称为宏替换,替换后才进行编译,宏替换是简单的替换。
1.2 宏替换发生的时机
当我们在一个集成的开发环境如Turbo C中将编写好的源程序进行编译时,实际经过了预处理、编译、汇编和连接几个过程
其中预处理器产生编译器的输出,它实现以下的功能:
(1) 文件包含
可以把源程序中的#include 扩展为文件正文,即把包含的.h文件找到并展开到#include 所在处。
(2) 条件编译
预处理器根据#if和#ifdef等编译命令及其后的条件,将源程序中的某部分包含进来或排除在外,通常把排除在外的语句转换成空行。
(3) 宏展开
预处理器将源程序文件中出现的对宏的引用展开成相应的宏 定义,即本文所说的#define的功能,由预处理器来完成。
经过预处理器处理的源程序与之前的源程序有所有不同,在这个阶段所进行的工作只是纯粹的替换与展开,没有任何计算功能,所以在学习#define命令时只要能真正理解这一点,这样才不会对此命令引起误解并误用。
2.1 简单宏定义使用中出现的问题
在简单宏定义的使用中,当替换文本所表示的字符串为一个表达式时,容易引起误解和误用。如下例:
#define N 2+2
void main()
{
int a=N*N;
printf(“%d”,a);
}
(1) 出现问题:在此程序中存在着宏定义命令,宏N代表的字符串是2+2,在程序中有对宏N的使用,一般同学在读该程序时,容易产生的问题是先求解N为2+2=4,然后在程序中计算a时使用乘法,即N×N=4×4=16,其实该题的结果为8,为什么结果有这么大的偏差?
(2)问题解析:如1节所述,宏展开是在预处理阶段完成的,这个阶段把替换文本只是看作一个字符串,并不会有任何的计算发生,在展开时是在宏N出现的地方只是简单地使用串2+2来代替N,并不会增添任何的符号,所以对该程序展开后的结果是a=2+2×2+2,计算后=8,这就是宏替换的实质,如何写程序才能完成结果为16的运算呢?
(3)解决办法:将宏定义写成如下形式
eg #define N (2+2)
这样就可替换成(2+2)×(2+2)=16
2.2 带参数的宏定义出现的问题
在带参数的宏定义的使用中,极易引起误解。例如我们需要做个宏替换能求任何数的平方,这就需要使用参数,以便在程序中用实际参数来替换宏定义中的参数。
一般学生容易写成: #define area(x) x*x
这在使用中是很容易出现问题的,看如下的程序
void main()
{
int y=area(2+2);
printf(“%d”,y);
}
按理说给的参数是2+2,所得的结果应该为4×4=16,但是错了,因为该程序的实际结果为8,仍然是没能遵循纯粹的简单替换的规则,又是先计算再替换了,在这道程序里,2+2即为area宏中的参数,应该由它来替换宏定义中的x,即替换成2+2×2+2=8了。那如果遵循(1)中的解决办法,把2+2 括起来,即把宏体中的x括起来,是否可以呢?#define area(x) (x)×(x),对于area(2+2),替换为(2+2)×(2+2)=16,可以解决,但是对于area(2+2)/area(2+2)又会怎么样呢,有的学生一看到这道题马上给出结果,因为分子分母一样,又错了,还是忘了遵循先替换再计算的规则了,这道题替换后会变为 (2+2)×(2+2)/(2+2)×(2+2)即4×4/4×4按照乘除运算规则,结果为16/4×4=4×4=16,那应该怎么呢?解决方法是在整个宏体上再加一个括号,即#define area(x) ((x)×(x)),不要觉得这没必要,没有它,是不行的。
- 注意:
1.宏定义可以写在程序中任何位置,它的作用范围从定义书写处到文件尾。
2.可以通过“#undef"强制指定宏的结束范围。
3.阅读带宏定义的程序,先全部替换好,最后再统一计算,不可一边替换一边计算,更不可以人为添加括号
头文件:
系统文件以stdio.h
math.h等形式供编程者调用
m.h文件: 头文件。
h文件作用:
- 1.方便开发:包含一些文件共同的常量,结构,类型定义,函数,全局变量;
- 2.程序实现中,无需函数定义。
- 3.提供接口: 对一个软件包来说可以提供- 一个给外界的接口(例如: stdio.h)。
- h 文件里应该有什么:常量,结构,类型定义,函数,变量申明。
- h 文件不应该有什么:变量定义,函数定义。
- 格式# include <需包含的文件名> 和 # include“需包含的文件名”
- 作用
把指定的文件模块内容插入到#include所在的位置,当程序编译连接时,系统会把所有#include指定的文件拼接生成可执行代码。 - 注意
编译预处理命令,以#开头。
在程序编译时起作用,不是真正的C语句,行尾没有分号。
常用标准头文件
常用指令
文件一函数关系
程序
小程序: 主函数+若干函数 一个文件
大程序: 若千程序文件模块(多个文件) 每个程序文件模块可包含若千个函数步各程序文件模块分别编译,再连接
整个程序只允许有一个main()函数
外部变量
全局变量只能在某个模块中定义一次,如果其他模块要使用该全局变量,需要通过外部变量的声明
外部变量声明格式为:
extern 变量名表;
如果在每一个文件模块中都定义一次全局变量,模块单独编译时不会发生错误,一旦把各模块连接在一起时,就会产生对同一个全局变量名多次定义的错误
反之,不经声明而直接使用全局变量,程序编译时会出现“变量未定义”的错误。
文件模块与函数
外部函数
如果要实现在一个模块中调用另一模块中的函数时,就需要对函数进行外部声明。声明格式为:
extern 函数类型函数名(参数表说明) ;
静态的函数
1.把函数的使用范围限制在文件模块内,不使某程序员编写的自用函数影响其他程序员的程序,即使其他文件模块有同名的函数定义,相互间也没有任何关联,
2.增加模块的独立性。
malloc函数
malloc 向系统申请分配指定size个字节的内存空间,返回类型为void* ,表示未确定类型的指针。
使用举例说明:
比如我想将用户的输入数据放到一个数组,因为我不知道用户会输入多少个数,所以我就不知道这个数组定义多大为好
但是我可以让用户先输入自己的数组里面放多少个数,比如说n,然后再定义动态数组
int n;
scanf("%d",&n);
int *a=(int *)malloc(n*sizeof(int));//定义动态数组 相当于int a[n]
2.本周的课堂派错题
错误分析:宏替换只是简单的替换,照搬上去的那种,并不会给你添加括号等,这也是初学者容易犯的错误~
解析:这里涉及到了指针数组,行指针与二维数组关系等知识点,p可看成是行指针,这里的p[i][j]可换成*(p[i]+j)
本周上机没有完成的题目:
6-4 合并两个有序数组
void merge(int* a, int m, int* b, int n); /* 合并a和b到a */
其中a和b是按升序排列的数组,m为数组a中元素的个数,n为数组b的长度;合并后的升序数组仍然存放在a中。
void merge(int* a, int m, int* b, int n) /* 合并a和b到a */
int *c;
int i=0,j=0,k=0;
c = (int*)malloc(m*sizeof(int));
for(i=;i<m;i++) c[i]=a[i];
i=0;
while(i<m&j<n){
if(c[i]<b[j]){
a[k++]=c[i];i++;
}
else if(b[j]<c[i]){
a[k++]=b[j];j++
}
else{
a[k++]=b[j];a[k++]=c[i];j++;i++;
}
}
while(i<m){
a[k++]=c[i++];
}
while(j<n){
a[k++]=b[j++];
}
free(c);
}
3.本周的学习体会
- 1.本周进行了一次指针的上机考,其中还包含了跟递归相关的代码阅读填空,但是我几乎没有填出来,自我检讨后总结出了有两个原因,一是对递归过程还是很不熟悉,二是时间不够用,说明做编程题的效率太低了,特别是在二分查找那里浪费了非常多的时间,甚至花了很长时间才想起来二分查找的过程,可见我对知识点的遗忘是非常快的,这也是很致命的一个问题。
- 解决方法:
1.在查阅大量关于递归的资料后,又询问了大神,对递归过程的理解有进一步加深~~但是可能还需要后续阅读和尝试写更多的递归代码来加深理解吧
2.对二分查找这种很重要的知识点切记不能忘,然后对自己之前写的博客的总结啥的要经常拿出来复习阿喂! - 2.本周还进行了一个大作业:学生信息管理系统。这是我们学习c语言以来第一次的大作业,也是第一次建项目,但是从一开始我就一直碰壁,一开始的无从下手,到后来慢慢学着老师,终于完成了初步的功能,但是因为多个函数,要保证几个函数都能顺利运行对于我们第一次写项目的小白来说是非常困难和不容易。再到后来,要慢慢更加完善项目的功能,但是每完善一下,就小心翼翼去编译,去试探是否有错,在等待项目编译完成的那几秒,内心是极其复杂的,特别是你刚刚好不容易修改了一个错误,也许是花了半小时一小时甚至几个小时才改正的错误。我觉得写代码写项目的过程中,不仅是在积累我们的代码经验,更像是对我们的一种考验,考验我们对编程的热爱程度和耐心。但是毫无疑问完成这么一个几百行代码的项目对我来说,是一件发自内心感到开心的事儿,虽然现在的它还是不够完善,但是更像是对我们一学期编程以来的一种检验和收获的感觉。虽然编程路上漫漫无期,但是撸起袖子加油干呗~~~