chapter 7 用函数实现模块化设计(上)

简介: chapter 7 用函数实现模块化设计

7.1函数


7.1.1 函数是什么?


什么是程序,一个C程序就是由很多很多个源程序文件构成,而每个源程序文件都是由预处理指令 ,全局 数据声明 , 加上一个又一个函数构成;


函数就是用来实现某一个特定功能的东西;而函数名就是给这一功能去起一个名字;


函数是相互平行的概念;彼此间是等价得;不存在主次之分;但是函数不是不可以写在其他函数里面;可以;;这叫做函数的嵌套调用;不管是什么函数都是可以相互调用;若干次 ;任意位置;当然有一个函数不能被其他函数调用 这就是主函数main ;他是被操作系统直接调用;函数不能被嵌套定义;


程序在编译得过程中;只会从main 函数中开始执行;一直到main结束 ;其中遇到那个就调用哪个;当遇到他得时候 主函数不会停下来;他和被调用函数一起;同时进行处理;平行线


7.1.2 那我们有哪些函数呢 ??


无非就两种 ;一个是库函数;还有一个是用户自己定义的函数;

至于库函数就不说了;就是已经定义好了的;你只要会用就完了;

那自己定义的呢;他就是得自己写;我把它分成两类 ;

要返回一个函数值;这个叫做有参函数;

不需要输出返回值;那么叫做无参函数;

调用时候不一定需要输入值;这不重要;我可以输入值的时候返回一个数字,也可以不输入一个数值,也能返回一个数值;同理,我输不输入数值对无参也是一样受用;

具体咱们举例子;

无参函数;

#include <stdio.h>
int main (){
  void print_star();
  void print_message();
  print_star();
  print_message();
  print_star();
  return 0;
}
void print_star()
{
  printf("******************\n"); 无参函数 无返回值
}
void print_message()
{
  printf("wu mingda dashuaibi!\n");  无参函数 无返回值
}

1670490692342.jpg

有参函数:

#include <stdio.h>
int main(){
  int max(int x,int y);
  int a,b,c;
  scanf("%d,%d",&a,&b);
  c=max(a,b);
  printf("max=%d\n",c);
  return 0;
  }
int max(int x,int y){  有参函数 有返回值 有输入值 
  int z;
  if(x>y)z=x;
  else z=y;
  return(z);
  }

1670490715595.jpg


7.2函数定义


7.2.1 怎么定义一个函数;有参 无参函数 分别怎么定义?


定义一个函数;首先我们直接看看定义函数得形式;

举例子;


类型名 函数名 (形式参数)

{

函数体 (执行部分和声明部分)

}


咱们看看就是一般而言函数定义的话;你是不是需要一个函数的名字就是定义函数名;前面说了,其次对于函数要么属于能返回函数值得;或者不能返回函数值得;如果是前者的话;那么就是无参函数 ;如果能返回一个数值;那么可以称呼为有参函数值;同理而言对于能否返回值且返回值是什么类型得;你是不是需要定义;这就是类型名;对于没有返回函数值的函数你应该定义为void 对于能返回函数值得你应该 对其能返回函数值得类型进行一个定义;比如int long longlong float ;你应该有一个具体得说法;还有就是如果是需要输入数据进去得那种 ;你还需要定义一个形式参数;对于这个参数你还需要就是定义他的数据的类型;比如来说int a;具体说形式参数与实际参数得关系 ;这儿先不慌说太多;那就是形式参数其实只是一个可有可无的东西;但是在定义函数中这个参数名字必须给到,int x;

总的来说 对于函数 而言你需要定义如下

函数返回值类型 ;(如果没有 void )

函数名字;

形式参数类型 (形式参数);

函数体;


对于如此即使还有一个特殊得工程上常常定义一些空函数;为什么;就是比如写代码就是写到这;我知道要在这儿一串比较复杂得代码 但是把 还写不出来;不知道要写什么 ;我可以一些空函数放在这;然后就是随时就能回来用它;加以补充;而且写在这儿;也不影响程序的运行;应为他相当于一个空语句’不会对程序产生任何其他得影响;


7.2.2 那么咱们看看这个空函数怎么去写;



写在这儿 直接
int main(){
......
 _gaidengshuxue wumingda();
.......
}
void gaidengshuxue wumingda(){
}
这就是一个空语句;你只带怎么用就行了;怎么写;
这就是函数得定义;


7.2.3 区分一下#include<> 与#include""的区别;


#include<>直接从编译器自带的函数库中寻找文件

#include""是先从自定义的文件中找 ,如果找不到在从函数库中寻找文件


如果是自己写的头文件 建议使用#include“”

< >引用的是编译器的类库路径里面的头文件

" "引用的是你程序目录的相对路径中的头文件


如果使用" ",它是会先在你项目的当前目录查找是否有对应头文件

如果没有,它还是会在对应的引用目录里面查找对应的头文件

意思就是,使用#include "stdio.h"如果你项目目录里面,没有stdio.h这个头文件,它还是会定位到库路径里的头文件

stdio.h 等头文件中包含了函数定义和申明;


7.3 调用函数


7.3.1 函数调用的形式


调用的时候就是函数名字加上函数的实参;像那种无参数就空着;具体形式;

函数名 (实参表列)

这就是调用的基本格式;完了就是调用这种函数具体用在代码中分为三种情况;

首先,就是函数调用语句;就是在基本的函数调用的一般格式的后面加上一个分号即可;

for example : age(); age(1,4);


其次就是,函数表达式 ;因为对于大部分的函数而言返回的都是一个有实际的值;而这个值往往可以放在表达式中;构成函数表达式;

举个例子:c=max(a,b);

最后就是;刚才说过有参函数调用后相当于一个数值;那么这个数值是可以放在函数中再次当作参数使用的;所以第三种就是** 函数参数**;

m=max(a,max(9,0));


7.3.2 形式参数 实际参数;


在调用函数时侯 分为形式参数 和 实际参数;形式参数就是你定义的函数括号里面的参数 ;这个在定义的时候是可以有可以没有的 ;这只是形式;只是一个临时的东西;而实际参数而言;他是主函数里面确实存在的;他放在调用的时候的括号里面;它可以是表达式 ;数值 ;函数参数等可以表示一个参数的任何表现形式;

age(a,b);
int age(int x,int y){
...
}


这里面 a.b 就是实际参数 ;x,y就是 形式参数;

如果实际参数和形式参数定义的类型不同该怎么办;那就是先进行数据转换来;前面学过的;


7.3.3 函数调用的过程


函数调用这怎样处理的 ;首先实参 他是有储存单元的;max(a,b);在这里面 a,b都有自己的储存单元;同时 对于形式参数 ;int max(int x,int y)而言 x, y 也是有储存单元的 不过吧 他是临时的 就是他仅仅就是一个 用完自动消散的一个储存单元; 具体的执行过程 ;首先 就是实参储存单元的 数值直接给形式参数 ;在定义的函数体中 形式参数作为一种数值去进行各种处理 操作 数值的变化 ;但无论怎么变化 都不会影响实际参数的值 ;最后执行完结之后 ;调用函数会返回一个数值出来; 这是调用函数在这一次的使命就结束了;那么同理而言;形式参数作为一个单独的储存单元的使命就结束了;那么这个储存单元就临时解散了;

同时这里说了 调用函数会返回一个值; 要用到 return ;;;如果不返回呢 那么这里面就不能用return ;

==??==这里面有一个问题没有解决

c=isdd(a,a+b);这里面a+b作为实参那么他是单独又成为一个储存单元么;

首先 a+b 不能代表变量 ;只是一个表达式 其意义就是数值;而这个数值是放在参数的单独储存空间的;形参有一个 定义的么 实参的话 就是单独创造出来的;也即是说表列是单独有自己的储存空间的;


7.3.4 函数的返回值


作为一个调用函数函数是需要返回值的 但是返回值他需要return 来做返回 ;具体就是

return (z);

return后面的括号可以不要; 比如return 0;还有就是 z可以是表达式 或者其他形式;这样的化还是带上()比较好;函数返回值的类型是你事先定义好的; 必须最后输出的结果必须是这个类型的返回值;但是把在执行体中 的z ,他是不一定非要是这个类型的返回值 了也可以是其他的;这取决与上面对形参的定义 以及 执行体中的具体过程;那你坑定会问 如果不一样了怎么办;那么系统会强制转换就行;


7.3.5 对调用函数的声明


对调用函数的声明是必不可少的;用为编译的时候你遇到调用某个函数的时候;你会发现系统根本不知道那个函数是什么;这就会出现error 所以要事先声明;那么既然声明那么就是让电脑知道你;所以声明要包括 类型名 函数名 参数类型 参数名 参数的数量 ;都要清楚 ;就相当于一个没有执行体的定义过程;当然这里面函数名字可以省略;

函数类型 函数名(参数类型1 参数名1,函数类型2 参数名2…);

函数类型 函数名(参数类型1 ,函数类型2 …);

就这两种;;


7.3.6 函数的嵌套调用;


之前说了 ,函数是可以嵌套调用的; 但是不能够相互间嵌套定义;

怎么嵌套调用 看一个实际例子就可以了;

#include <stdio.h>
int main (){
  int max4(int a,int b,int c,int d);
  int a,b,c,d,max;
  printf("please enter 4 number:");
  scanf("%d %d %d %d",&a,&b,&c,&d);
  max=max4(a,b,c,d);
  printf("max=%d\n",max);
  return 0;
}
int max4(int a,int b,int c,int d)
{
  int max2(int a,int b);
  return (max2(max2(max2(a,b),c),d));
}
int max2(int a,int b){
  return (a>=b?a:b);
}
/*
int max4(int a,int b,int c,int d)
{
  int max2(int a,int b);
  int m;
  m=max2(a,b);
  m=max2(m,c);
  m=max2(m,d);
  return (m);
}
int max2(int a,int b){
  return (a>=b?a:b);
}
*/


从这段代码可以看出;函数的嵌套;就是像上面这样使用的;多用就完了


7.3.7递归调用以及例题论证


函数的递归调用;咱们首先来看看咱们书本上面的题目;**直接调用或者间接调用函数本身叫做递归调用;**咱们看看 ;具体的例子;

/*这是一道求n!的题目*/
#include <stdio.>
int main(){
  int fac(int n);
  int n;
  int y;
  printf("input an inputer mnumber:");
  scanf("%d",&n);
  y=fac(n) ;
  printf("%d!=%d\n",n,y);
  return 0;
} 
int fac(int n){
  int f;
  if(n<0) printf("数据错误;");
  else if(n==o||n==1) f=1;
  else f=fac(n-1)*n;
  return(f);
}


#include <stdio.h>
int main(){
    int age(int n);
    printf("no.5.age:%d\n",age(5));
    reyurn(0);
}
int age(int n){
    int c;
    if(n==1) c=10;
    else c=age(n-1)+2;
    return (c);    
}


就是这两道题目;然后咱们看看就是;什么是函数的·递归调用就是 说 咱们 就是首先函数当调用自己的 那么就是会出现这个情况 ;执行这个函数 ;遇到调用;原来是自己;调用自己;又遇到自己;在调用自己;如此反复;重复就是;出现了一个又一个的死循环;那么需要有一个能结束死循环的东西;所以就出现了 ;**if else 语句 来终止循环 ;**还有就是函数递归调用其实本质就是函数的回溯 递推 的过程;所谓回溯就是反推 一直推到终止条件;即使解决死循环的key ;有了这个Key 你就可返回来一步步求出具体值;那这个过程就是递推;;以上你可以看出;

求n!
先是age(n-1)*n
然后要求age(n-1)=age(n-2)*(n-1) 
.........
回溯到了 age(1)=1;
然后一步步倒回去;
得出age(2)
......
age(n-1)
age(n);
就是结束;


自己调用自己 就是函数递归调用;解决办法就是函数回溯 递推;


再看一道例题

??例三

#include <stdio.h>
int main(){
  void hanoi(int n,char one,char two,char three);
  int m;
  printf("input the number of diskes:");
  scanf("%d",&m);
  printf("the step to move %d diskes :\n",m);
  hanoi(m,'A','B','C');
}
void hanoi (int n,char one,char two,char three){
  void move(char x,char y);
  if(n==1) move(one,three);
  else{
  hanoi(n-1,one,three,two);
  move(one,three);
  hanoi(n-1,two,one,three);
  }
}
void move(char x,char y){
  printf("%c--->%c\n",x,y);
}


7.4 数组类做函数参数


7.4.1 数组元素作为函数实参


咱们看形参只是一个临时变量;他是在函数调用完后自行解除的;所以只能是实参来提供,而不能直接赋值;函数是可能被调用多次的;所以说只能形式参数可以是任意变量提供的数值;所以就是所以不能直接赋值实参,那么他就没有绝对的自由;只能是某一个储存空间数值的改变;如果是这样他是不合规矩的;他应该是任意储存空间都能提供数值;那么就是说;只能通过实参转化形参;

那那些可以作为实参呢;常量;变量;??表达式;数组;数组元素;…都可以做函数的实参;

那么我是不是可以理解为实参本身提供一个数据空间;那么常量;表达式;作为熟知的结果储存;实参的储存空间是一直不变的;而形式参数是用完就取消的;他是通过实参传递的;

这里我们讲一讲 数组元素 作为实参函数


/*输入10个数,要求输出其中最大的数以及这是第几个数字*/
#include <stdio.h>
int main(){
  int max(int x,int y);
  int a[10],m,n,i;
  printf("enter 10 integer numbers:");
  for(i=0;i<10;i++)
  scanf("%d",&a[i]);
  printf("\n");
  for(i=1,m=a[0],n=0;i<10;i++){
  if (max(m,a[i])>m){
    m=max(m,a[i]);
    n=i; 
  }
  }
  printf("the largest number is %d\nit is the %dth number.\n",m,n+1);
} 
int max(int x,int y){
  return (x>y?x:y);
}


从这串代码中我们可以看出数组元素就是某个数值;所以不用担心;它就是数值的赋值;


7.4.2 数组名 作函数参数(实参 形参)


先观察这道题目,仔细看看实参和形参位置的区别;


/*一维数组score 内放置十个成绩 求平均成绩*/
#include <stdio.h>
int main()
{
  float average(float array[10]);
  float score[10],aver;
  int i;
  printf("input 10 scores:\n");
  for (i=0;i<10;i++) scanf("%f",&score[i]);
  printf("\n");
  aver=average(score);
  printf("average score is %5.2f\n",aver);
  return 0;
  } 
  float average(float array[10])
  {int j;
  float ever,sum=array[0];
  for(j=1;j<10;j++) sum=sum+array[j];
  ever=sum/10;
  return(ever);
  }


你会发现实参和形参都是数组;而实参那儿是数组名字;仔细研究是你会发现主函数定义的数组名字不一样;但是类型一样的;我来解释一下,这是为什么;

就是首先;;之前说了实参和形参的关系 ;就是函数实参是有一个单独的储存空间的,是为了给形参赋值而存在的;所以实参里面是一个空间;;因为我只要满足让形参知道这个数组的开头就行;所以实参是数组名字;就是给形参一个地址,储存的开头的地址;所以不需要是score[10],这样首先变成了第十一个元素的地址;不行;;反正不能出现[];;只要函数名字就行;只要地址就ok;而形参,他是需要定义一个全新的数组 ;应为他和原数组就是两个;但是他们的储存空间是同一个;也就是说他和变量还是不一样的;变量的形参是给予一个临时的储存空间;他是实参把地址给过来,他们是一个地址,自然是同一个储存空间;同理,形参数值改变,原本数组的数值也改变;

所以就是说类型就要给到;而且类型最好相同;但是来说数组数量可以有 可以没有;因为编译系统不检查形参的数组大小的;可以想象成要多大有多大;任意的;不限定大小;应为会有不同数组进来嘛;所以就是说只是实参吧第一位给了形参第一位 这样形参和原数组第一位读取后是等价的;第n位置也是相互等价的;

总而言之怎么用呢;

实参数组名就行;

形参类型名要定义;目前类型要一样(也会出现不一样的也许还没遇到);

形参的数组名不能和原数组一样;否则出现错误;

后面[]要有;可以不给数值;最好不给;、

咱们再看看一个例子


/*两个班级 35人 30人 调用average 分别求平均成绩*/
#include <stdio.h>
int main()
{
   float average(float array[],int q);
   float score1[3]={1};
   float score2[5]={1};
   float aver;
   int i;
   printf("input 10 scores:\n");
   for (i=0;i<3;i++) scanf("%f",&score1[i]);
   printf("\n");
   aver=average(score1,3);
   printf("average score is %5.2f\n",aver);
    for (i=0;i<5;i++) scanf("%f",&score2[i]);
   printf("\n");
   aver=average(score2,5);
   printf("average score is %5.2f\n",aver);
   return 0;
    } 
    float average(float array[],int q)
    {int j;
    float ever,sum=array[0];
    for(j=1;j<q;j++) sum=sum+array[j];
    ever=sum/q;
    return(ever);
    }


再看一个例子:

/*用选择法对数组中十个数由小到大排列*/ 
#include <stdio.h>
int main()
{void sort(int array[],int n);
int a[10],i;
printf("enter array:\n");
for(i=0;i<10;i++) scanf("%d",&sa[i]);
sort(a,10);
printf("the sorted array:\n");
for(i=0;i<10;i++) 
{printf("%d",a[1]);
printf("\n");
}
return 0; 
}
void sort(int array[],int n)
{int i,j,k,t;
for(i=0;i<n-1;i++) 
{k=i;
for(j=i+1;j<n;j++)
if (array[j]<array[k]) 
k=j;
t=array[k];array[k]=array[i];array[i]=t;
}
}

看这个例子是选择法来对数组中是个数进行排列;咱们看一看 这里面定义的函数有依旧符和上面的逻辑;这里面是形参的数组在改变数值;但是由于他们是属于一个地址 ,所以说形参的数值改变;依旧会导致原数组储存数值的改变;然后输出原数组时候是改变后的值;

相关文章
探索Python中的函数和类:构建模块化和面向对象的程序
探索Python中的函数和类:构建模块化和面向对象的程序
探索Python中的函数和类:构建模块化和面向对象的程序
第7章 用函数实现模块化程序设计
第7章 用函数实现模块化程序设计
69 0
|
3月前
|
设计模式 Java
重构你的代码:探索Java中的混合、装饰器与组合设计模式
【8月更文挑战第30天】在软件开发中,设计模式为特定问题提供了结构化的解决方案,使代码更易理解、维护及扩展。本文将介绍三种常用的 Java 设计模式:混合模式、装饰器模式与组合模式,并附有示例代码展示实际应用。混合模式允许通过继承多个接口或抽象类实现多重继承;装饰器模式可在不改变对象结构的情况下动态添加新功能;组合模式则通过树形结构表示部分-整体层次,确保客户端处理单个对象与组合对象时具有一致性。
36 1
|
3月前
|
存储 算法 C++
【CPP】栈简介及简化模拟实现
【CPP】栈简介及简化模拟实现
|
5月前
|
数据安全/隐私保护 Python
Python装饰器是高阶函数,用于在不修改代码的情况下扩展或修改函数行为。它们提供可重用性、模块化和无侵入性的功能增强。
【6月更文挑战第20天】Python装饰器是高阶函数,用于在不修改代码的情况下扩展或修改函数行为。它们提供可重用性、模块化和无侵入性的功能增强。例如,`@simple_decorator` 包装`my_function`,在调用前后添加额外操作。装饰器还能接受参数,如`@logged(&quot;INFO&quot;, &quot;msg&quot;)`,允许动态定制功能。
45 6
|
6月前
|
JavaScript 前端开发 测试技术
编写JavaScript模块化代码主要涉及将代码分割成不同的文件或模块,每个模块负责处理特定的功能或任务
【5月更文挑战第10天】编写JavaScript模块化代码最佳实践:使用ES6模块或CommonJS(Node.js),组织逻辑相关模块,避免全局变量,封装细节。利用命名空间和目录结构,借助Webpack处理浏览器环境的模块。编写文档和注释,编写单元测试以确保代码质量。通过这些方法提升代码的可读性和可维护性。
50 3
|
6月前
第七章 用函数实现模块化程序设计
第七章 用函数实现模块化程序设计
22 0
|
Go
Go编程模式 - 3.继承与嵌入
业务逻辑依赖控制逻辑,才能保证在复杂业务逻辑变化场景下,代码更健壮!
53 0
|
Go
Go编程模式 - 5.函数式选项
编程的一大重点,就是要 `分离变化点和不变点`。这里,我们可以将必填项认为是不变点,而非必填则是变化点。
37 0
|
Python
Python Class 04-函数和代码的复用
Python Class 04-函数和代码的复用