C语言总结(第7章 用函数实现模块化程序设计)

简介: 为什么要用函数为了更好的实现模块化程序设计 函数(function)也有功能的意思

7.1为什么要用函数


为了更好的实现模块化程序设计    函数(function)也有功能的意思


7.2怎样定义函数


为什么要定义函数?


事先编辑好的功能,编译系统按照定义的功能去执行


定义函数应包括以下内容:


指定函数的名字,以便以后按名调用

指定函数的类型,即函数返回值的类型

指定函数的参数的名字和类型,以便在调用函数时向它们传递数据。对无参函数不需要这项

指定函数应该完成什么操作,也就是函数是做什么工作的,即函数的功能


定义函数的方法:

定义无参函数的方法:

函数名()

函数体

或者

函数名(void)

函数体

函数体内包含声明部分和语句部分

定义有参函数

函数程序设计

编写一个函数,利用参数传入一个3位数number,找出101~number之间所有满足下列两个条件的数:

它是完全平方数,又有两位数字相同,如144、676等,函数返回找出这样的数据的个数。请同时编写主函数。

例:(括号内为说明)

输入


3 (repeat=3)

150

500

999

输出

count=2

count=6

count=8

#include <stdio.h>

#include <string.h>

int search(int n);           //对函数的声明

int main(void)

{

int number,ri,repeat;

scanf("%d",&repeat);

for(ri=1;ri<=repeat;ri++){

do{

scanf("%d",&number);

}while(number<101||number>999);

printf("count=%d\n",search(number));

}

return 0;

}

int search(int n)            //对函数的定义

{

int i,j,a1,a2,a3, flag=0;

for (i = 101; i <= n; i++)

{

for (j = 1; j <= i; j++)

{

if (j * j == i)

{

a1 = i % 10;

a2 = i / 10 % 10;

a3 = i /100 % 10;

if ( (a1==a2)||(a1==a3) || (a2==a3) )

{

flag += 1;

break;

}

}

}

}

return flag;

}

定义有参函数的一般形式:


函数名(形式参数表列)



函数体



7.3调用函数


7.3.1函数调用的形式171


计算函数P(n,x) (函数递归)


输入一个正整数repeat (0<repeat<10),做repeat次下列运算:


输入一个整数n (n>=0)和一个双精度浮点数x,输出函数p(n,x)的值(保留2位小数)。


               [1                                    (n=0)

     p(n, x) =   [x                                    (n=1)

               [((2*n-1)*p(n-1,x)-(n-1)*p(n-2,x))/n        (n>1)

例:括号内是说明


输入


3   (repeat=3)

0 0.9 (n=0,x=0.9)

1 -9.8 (n=1,x=-9.8)

10 1.7 (n=10,x=1.7)


输出


p(0, 0.90)=1.00

p(1, -9.80)=-9.80

p(10, 1.70)=3.05

#include <stdio.h>

double p(int n, double x);       //对函数的声明

int main(void)

{

 int repeat, ri;

  int n;

  double x, result;

  scanf("%d", &repeat);

  for(ri = 1; ri <= repeat; ri++)

  {

      scanf("%d%lf", &n, &x);

      result = p(n, x);

      printf("p(%d, %.2lf)=%.2lf\n", n, x, result);

  }

}

double p(int n, double x)        //对函数的定义

{

double t;

if(n==0)  t=1;

else if(n==1)

t=x;

else

t=((2*n-1)*p(n-1,x)-(n-1)*p(n-2,x))/n;

return t;

}

7.3.2函数调用时的数据传递


形式参数和实际参数

在定义函数时函数名后面的括号中的变量名称为“形式参数”或“虚拟参数”。


在主调函数中调用一个函数时,函数名后面括号中的参数称为“实际参数”。


实际参数可以是常量,变量或表达式。


实参和形参间的数据传递

在调用函数过程中,系统会把实参的值传递给被调用函数的形参。也可以这么理解,形参从实参得到一个值。该值在函数调用期间有效,可以参加该函数的运算。


7.3.3函数调用的过程


一个函数只能有一个返回值,(想返回多个可以参考后面的指针中的内容)


如果函数不需要返回值,则不需要return语句,这是函数的类型应定义为void类型。


7.4对被调用函数的声明和函数原型


在一个函数中调用另一个函数(即被调用函数)需要具备以下条件:


首先被调用的函数必须是已经定义的函数(是库函数或用户自己定义的函数)

如果使用库函数,应该在本文件开头用#include指令将调用有关库函数时所需用到的信息“包含”到本文件中去

#include<stdio.h>在stdio.h文件中包含了输入输出库函数的声明


使用数学库中的函数,应使用#include<math.h>


(3)如果使用用户自己定义的函数,而该函数的位置在调用它的函数(即主调函数)的后面(在同一文件中),应该在主调函数中被调用的函数作声明(declaration)。声明的作用是把函数名,函数参数的个数和参数类型等信息通知编译系统,以便在遇到函数调用时,编译系统能正确识别函数并检查调用是否合法。


思考总结:


可以发现,函数的声明和函数定义中的第1行(函数首部)基本上是相同的。只差一个分号(函数声明比函数定义中的首行多一个分号)


为什么要用函数的首部来作为函数声明呢?


这是为了对函数调用的合法性进行检查。因为在函数的首部包含了检查调用函数是否合法的基本信息(包含了函数名,函数值类型,参数个数,参数类型和参数顺序),在检查函数调用时要求函数名,函数类型,参数个数,参数类型和参数顺序必须和函数声明一致,实参类型必须与函数声明中的形参类型相同,这样就能保证函数的正确调用。


函数的“定义”和‘声明’并不是同一回事


函数的定义是指对函数功能的确立,包括指定函数名,函数值类型,形参及其类型以及函数体等,它是一个完整的,独立的函数单位。


而函数的声明的作用则是把函数名,函数值类型,参数个数,参数类型和参数顺序通知编译系统,以便在调用该函数时系统按此进行对照检查(例如:函数名,函数值类型,参数个数,参数类型和参数顺序),他不包含函数体。


7.5函数的嵌套调用


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


7.6函数的递归调用


在调用一个函数的过程中又出现直接或间接地调用该函数本身,称为函数的递归调用。


C语言函数递归调用_外太空程序员的博客-CSDN博客_c语言函数的递归调用


范例:计算5的阶乘,其实就是计算 1*2*3*4*5,


我们并不知道5的阶乘是多少,但是我们知道一点,4的阶乘 * 5他就等于5的阶乘。


我们并不知道4的阶乘是多少,但是我们知道一点,3的阶乘 * 4他就等于4的阶乘。


我们并不知道3的阶乘是多少,但是我们知道一点,2的阶乘 * 3他就等于3的阶乘。


所以递归调用的出口肯定是在1的阶乘,因为1的阶乘是1,可以作为出口,我们能够求出2的阶乘,也就是1*2;以此类推。


#include<stdio.h>


// 函数声明


int dg_jiecheng(int n);

int main()  //主函数

{

   int result = dg_jiecheng(5);

   printf("result=%d\n",result);  // 结果为120

}


// 函数定义


int dg_jiecheng(int n)

{

   int result;  // 局部变量,保存阶乘结果

   if(n==1) // 1的阶乘就是1

   {

       return 1;  // 这里就是该递归调用的出口

   }

   else

   {

     // 第一次是 result = dg_jiecheng(4)*5,然后进入到了 dg_jiecheng(4),这行代码就被暂存了;


        第二次是 result = dg_jiecheng(3)*4,然后进入到了 dg_jiecheng(3),这行代码就被暂存了;


        第三次是 result = dg_jiecheng(2)*3,然后进入到了 dg_jiecheng(2),这行代码就被暂存了;


        第四次是 result = dg_jiecheng(1)*2,然后进入到了 dg_jiecheng(1),这行代码就被暂存了;


      此时,dg_jiecheng(1)的出口条件成立了,终于,能够执行return 1,这可是 return 语句第一次捞着执行。


      第一次:return 1,返回的是1,返回到dg_jiecheng(2)这里:


      return =1*2 并且也执行return result;,返回1*2=2;


返回到dg_jiecheng(3)这里:


      return =2 并且也执行return result;,返回2*3=6;


返回到dg_jiecheng(4)这里:


      return =6 并且也执行return result;,返回6*4=24;


返回到dg_jiecheng(5)这里:


      return =24 并且也执行return result;,返回24*5=120;

       result = dg_jiecheng(n-1)* n;

   }

   return result;

递归的优缺点


优点:


       代码少,代码看起来简洁,精妙。


缺点:


       虽然代码简洁,精妙,但是不好理解。


        如果调用的层次太深,调用栈(内存),可能会溢出,如果真出现这种情况,那么说明不能用递归解决该问题。


       效率和性能都不高,这深层次的调用,要保存的东西很多,所以效率和性能肯定高不起来。有些问题用不用递归都行,有些问题可能必须用递归解决。汉诺塔


递归函数的直接和间接调用:


递归函数的直接调用:调用递归函数f的过程中,f函数有调用自己,这就是直接调用。


递归函数的间接调用:调用函数f1的过程中要调用f2函数,然后f2函数又调用f1函数,这就是间接调用。                                                            


7.7数组作为函数参数


编程题(指针数组)


输入一个正整数repeat (0<repeat<10),做repeat次下列运算:


编写程序,输入一个月份,输出对应的英文名称,要求用指针数组表示12个月的英文名称。


若输入月份错误,输出提示信息。


输入输出示例:括号内为说明


输入:


3 (repeat=3)

5

9

14


输出:


May

September

Wrong input!

#include<stdio.h>

void main()

{

int ri,repeat;

int month;

char *month_name[]={"","January","February","March","April","May","June","July","August","September","October","November","December"};

scanf("%d",&repeat);

for(ri=1;ri<=repeat;ri++){

scanf("%d",&month);

if((month>=1)&&(month<=12))

puts(month_name[month]);

else

printf("Wrong input!");

}

}

     


编程题 (指针数组,查找相同的字符串)


程序填空,不要改变与输入输出有关的语句。


输入一个正整数repeat (0<repeat<10),做repeat次下列运算:


定义一个指针数组将下表的星期信息组织起来,输入一个字符串,在表中查找,若存在,输出该字符串在表中的序号,否则输出-1。


(表格详见实验教材P99)


输入输出示例:括号内为说明


输入:


3 (repeat=3)

Tuesday

Wednesday

year


输出:


3

4

-1

#include<stdio.h>

#include<string.h>

void main()

{

int i,ri,repeat,dateNum;

char *date[]={"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"};

char str[80];

scanf("%d",&repeat);

getchar();

for(ri=1;ri<=repeat;ri ++){

scanf("%s",str);

/*---------*/

dateNum=sizeof(date)/sizeof(char *);

for (i = 0; i<dateNum; i++){

if (strcmp(date[i],str) == 0) {

printf("%d\n",i+1); break;

}

}

if (i==dateNum) printf("%d\n",-1);

}

}


计算最长的字符串长度


编写一个函数int max_len(char *s[ ],  int n),用于计算有n(n<10)个元素的指针数组s中最长的字符串的长度,并编写主程序验证。


例:(括号内为说明)


输入


4 (n=4)

blue

yellow

red

green


输出


length=6

#include <stdio.h>

#include <string.h>

int max_len(char *s[],int n);

void main()

{

int i,n;

char s[10][80],*p[10];

scanf("%d",&n);

for(i=0;i<n;i++){

 scanf("%s",s[i]);

 p[i]=s[i];

}

printf("length=%d\n",max_len(p,n));

}

int max_len(char *s[],int n)

{

int max,i;

max=strlen(s[0]);

for(i=1;i<n;i++)

if(max<strlen(s[i]))  

max=strlen(s[i]);

 return max;

}

   


静态局部变量的值(static局部变量):


有时希望函数中的局部变量的值在函数调用结束后不消失而继续保留原值,即其占用的存储单元不释放,在下一次调用该函数时,该变量已有值(就是上一次函数调用结束时的值),这时就应该指定该局部变量为“静态局部变量”,用关键字static进行声明。


自动变量(auto变量)


函数中的局部变量,如果不专门声明为static(静态)存储类别,都是动态地分配存储空间的,数据存储在动态存储去中,函数中的形参和在函数中定义的局部变量(包括在复合语句中定义的局部变量),都属于此类。在调用该函数时,系统会给这些变量分配存储空间,在函数调用结束时就自动释放这些存储空间。


实际上,关键字auto可以省略,不写auto则隐含指定为“自动存储类别”


int b,c=3    ==========   auto int b,c=316.png



第七章课后题


C语言程序设计第五版谭浩强 第七章答案_月已满西楼的博客-CSDN博客_c语言第七章课后题答案


第七章目录回顾:


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


7.1为什么要用函数167


7.2怎样定义函数169


7.2.1为什么要定义函数169


7.2.2定义函数的方法170


7.3调用函数171


7.3.1函数调用的形式171


7.3.2函数调用时的数据传递172


7.3.3函数调用的过程174


7.3.4函数的返回值174


7.4对被调用函数的声明和函数原型176


7.5函数的嵌套调用179


7.6函数的递归调用181


7.7数组作为函数参数189


7.7.1数组元素作函数实参189


7.7.2一维数组名作函数参数191


7.7.3多维数组名作函数参数194


7.8局部变量和全局变量196


7.8.1局部变量196


7.8.2全局变量197


*7.9变量的存储方式和生存期201


7.9.1动态存储方式与静态存储方式201


7.9.2局部变量的存储类别202


7.9.3全局变量的存储类别205


7.9.4存储类别小结209


7.10关于变量的声明和定义211


*7.11内部函数和外部函数212


7.11.1内部函数212


7.11.2外部函数213


目录
相关文章
|
6天前
|
C语言
c语言调用的函数的声明
被调用的函数的声明: 一个函数调用另一个函数需具备的条件: 首先被调用的函数必须是已经存在的函数,即头文件中存在或已经定义过; 如果使用库函数,一般应该在本文件开头用#include命令将调用有关库函数时在所需要用到的信息“包含”到本文件中。.h文件是头文件所用的后缀。 如果使用用户自己定义的函数,而且该函数与使用它的函数在同一个文件中,一般还应该在主调函数中对被调用的函数做声明。 如果被调用的函数定义出现在主调函数之前可以不必声明。 如果已在所有函数定义之前,在函数的外部已做了函数声明,则在各个主调函数中不必多所调用的函数在做声明
21 6
|
19天前
|
存储 算法 程序员
C语言:库函数
C语言的库函数是预定义的函数,用于执行常见的编程任务,如输入输出、字符串处理、数学运算等。使用库函数可以简化编程工作,提高开发效率。C标准库提供了丰富的函数,满足各种需求。
|
25天前
|
机器学习/深度学习 C语言
【c语言】一篇文章搞懂函数递归
本文详细介绍了函数递归的概念、思想及其限制条件,并通过求阶乘、打印整数每一位和求斐波那契数等实例,展示了递归的应用。递归的核心在于将大问题分解为小问题,但需注意递归可能导致效率低下和栈溢出的问题。文章最后总结了递归的优缺点,提醒读者在实际编程中合理使用递归。
53 7
|
25天前
|
存储 编译器 程序员
【c语言】函数
本文介绍了C语言中函数的基本概念,包括库函数和自定义函数的定义、使用及示例。库函数如`printf`和`scanf`,通过包含相应的头文件即可使用。自定义函数需指定返回类型、函数名、形式参数等。文中还探讨了函数的调用、形参与实参的区别、return语句的用法、函数嵌套调用、链式访问以及static关键字对变量和函数的影响,强调了static如何改变变量的生命周期和作用域,以及函数的可见性。
29 4
|
22天前
|
存储 C语言
【c语言】字符串函数和内存函数
本文介绍了C语言中常用的字符串函数和内存函数,包括`strlen`、`strcpy`、`strcat`、`strcmp`、`strstr`、`strncpy`、`strncat`、`strncmp`、`strtok`、`memcpy`、`memmove`和`memset`等函数的使用方法及模拟实现。文章详细讲解了每个函数的功能、参数、返回值,并提供了具体的代码示例,帮助读者更好地理解和掌握这些函数的应用。
19 0
|
22天前
|
C语言
【c语言】qsort函数及泛型冒泡排序的模拟实现
本文介绍了C语言中的`qsort`函数及其背后的回调函数概念。`qsort`函数用于对任意类型的数据进行排序,其核心在于通过函数指针调用用户自定义的比较函数。文章还详细讲解了如何实现一个泛型冒泡排序,包括比较函数、交换函数和排序函数的编写,并展示了完整的代码示例。最后,通过实际运行验证了排序的正确性,展示了泛型编程的优势。
18 0
|
25天前
|
算法 C语言
factorial函数c语言
C语言中实现阶乘函数提供了直接循环和递归两种思路,各有优劣。循环实现更适用于大规模数值,避免了栈溢出风险;而递归实现则在代码简洁度上占优,但需警惕深度递归带来的潜在问题。在实际开发中,根据具体需求与环境选择合适的实现方式至关重要。
23 0
|
1月前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
33 3
|
C语言
《C语言程序设计》一 第 1 章 程序设计概述
本节书摘来自华章出版社《C语言程序设计》一 书中的第1章,第1.1节,作者:赵宏 陈旭东 马迪芳,更多章节内容可以访问云栖社区“华章计算机”公众号查看。
1160 0
|
C语言
《C语言程序设计与实践(第2版)》——第1章 C语言与程序设计概述 1.1初见C语言程序
我国古代数学家张邱建在其编写的《算经》里提出了历史上著名的“百钱买百鸡”问题:今有鸡翁一,值钱五;鸡母一,值钱三;鸡雏三,值钱一。凡百钱买鸡百只,问鸡翁、母、雏各几何?对于这个问题,很多读者在小学或初中的竞赛中可能都见到过,而且通常都采用不定方程求解。
1134 0