[编程神域 C语言浮游塔 第④期] 函数的基本知识


 前言

通过上一期的学习,我们知晓了数组及其不同的使用方法。

本期学习的是C语言中的函数,可类比为java中的方法。

函数是构成C程序的基本单元,可以说C程序通常由若干函数组成,它包括函数头和函数体。

它是一个个能够独立完成某种功能的程序块,每个都有各自的独立性,其中封装了程序代码和数据,实现了更高级的抽象和数据隐藏,这样看起来它的内涵似乎比较复杂。

但在实际运用过程中,我们并不需要去了解这些具体细节,只要知道它的功能和用法即可。

本期将重点讨论包括函数的定义、调用、参数与返回值等。

下面,就让我们开始学习函数吧。

Link Start!

目录

浮游塔第31层:C程序的函数构成及定义

浮游塔第32层:函数的分类

浮游塔第33层:函数的调用

浮游塔第34层:函数的嵌套调用和递归调用

浮游塔第35层:函数的声明

浮游塔第36层:数组元素作为函数参数

浮游塔第37层:数组名作为函数参数

浮游塔第38层:变量的作用域

浮游塔第39层:函数的作用域

浮游塔第40层:结构化程序设计


浮游塔第31层:C程序的函数构成及定义

一个 C 程序由一个主函数(main 函数)与多个子函数构成。

其中,主函数 main( )  可以调用任何函数,各函数之间也可以相互调用,但是一般函数不能调用主函数

所有函数都是平行独立的,不能嵌套定义。

若已定义的函数由有返回值,则函数调用可以作为独立的语句存在,可以作为一个函数的实行参数,也可以出现在表达式中,但不可以作为一个函数的形式参数。

函数可以接受用户传递的数据,

在接收用户数据的函数在定义时要指明参数,

不接收用户数据的不需要指明,根据这一点可以将函数分为有参函数无参函数

在学习函数时,要弄清楚两个概念,实际参数(实参)与形式参数(形参)。

形式参数必须是变量,但实际参数可以时常量、变量或表达式,

且实际参数的个数和类型要与对应的形式参数一致,各参数之间用逗号隔开。

根据函数是否接受数据的不同,其定义形式自然也不同。

无参函数的定义

类型说明符 函数名()
{
    函数体;
}
如:
void speak()
{
    printf("hello world!");
}

有参函数的定义

类型说明符 函数名(参数列表)
{
    函数体;
}
如:
int  sum(int a,int b)
{
    int sum;
    sum=a+b;
    return sum;
}

在“形式参数表列”中给出的参数称为形式参数,它们可以是各种类型的变量,同时要对这些变量给予类型说明,各参数之间用逗号间隔。

在进行函数调用时,主调函数将赋予这些形式参数实际的值。


浮游塔第32层:函数的分类

按照定义分

①库函数:

       库函数又称为标准函数,由c系统提供,无需程序员定义,可直接使用,但需要在程序开头包含原型声明的头文件。

(1)I/O 函数:

          例如: getchar,putchar,printf,scanf,fopen,fclose等。

(2)字符串和字符函数:

          例如:isalnum,isalph,strcat,strchr,strcmp,strcpy,strlen,strstr等。

(3)数学函数:

          例如:sin,cos,exp(e的x次方),log,sqrt(开平方),pow(x的y次方)等。

(4)时间、日期和与系统有关的函数:

          例如:time返回系统的时间;asctime返回以字符串形式表示的日期和时间。

(5)动态分配分配:包括"申请分配"和"释放"内存空间的函数。

          例如:calloc,free,malloc,realloc等。

(6)目录管理:包括磁盘目录建立、查询、改变等操作的函数。

(7)过程控制:包括最基本的过程控制函数。

(8)字符屏幕和图形功能:包括各种绘制点、线、圆、方和填色等的函数。

(9)其它函数。

②自定义函数
       由程序员根据自己的需求编写,自定义函数不仅要在程序中定义函数本身,必须还要在主函数中调用该函数。

按照有无返回值函数

①有返回值函数:
       该类函数被调用执行完毕,将向调用者返回一个执行结果,称为函数的返回值

②无返回值函数:

       无返回值函数不需要向主调函数提供返回值

按照函数形式分类

①无参函数:

       在函数的声明、定义和调用中均不带参数,特点:在调用无参函数主调函数并不将数据传输给被调用函数,此类函数通常被用来完成指定的功能,可以返回或不返回函数值。

②有参函数:

       在函数定义、声明都都有参数。特点:主调函数调用被调函数时,主调函数必须把值传输给形参,以供被调函数使用。


浮游塔第33层:函数的调用

(拥有工具而不去使用它,那么这个工具就没有任何意义)

在C程序中,

被调用的函数被叫做"被调函数",而调用它的函数则称为“主调函数”。

函数调用的形式:函数名(实参列表)

double areafCircle (float r)//函数的定义
{ 
   float PI = 3.14159;
   return PI*r*r;
}

int main(void)
{
   float r=0,area=0;
   area=areaofCircle(r);  //函数的调用
   return 0;
}

函数调用语句的常见表现形式

  • 函数语句 areaofCircle(10);
  • 函数表达式 c=10*areaofCircle(10);
  • 函数参数 printf(“%f”,areaofCircle(10));

函数调用过程

  1. 主调程序保护现场
  2. 给形式参数(如果有)分配内存,将实参传给形参,将程序的控制权交给被调函数
  3. 执行被调函数语句
  4. 保存返回值(如果有)到某处,释放被调函数在栈区分配的空间
  5. 将程序控制权交给主调函数,主调函数取得返回值(返回值的类型由定义函数时所指定的函数类型决定)

被调函数

double areaofCircle(double r)
{
   const double PI=3.14159;
   return PI*r*r;
}

主调函数

int main( )
{
   double r,area;
   area=areaofCircle(r);
   return 0;
}

若函数调用时的实参为变量时函数的形式参数和实行参数分别占用不同的存储单元。


浮游塔第34层:函数的嵌套调用和递归调用

(函数不能够进行嵌套定义,但却可以进行嵌套调用。)

什么叫做嵌套调用呢?

就是在定义fA函数的过程中,调用fB函数,又在定义fB函数的过程中,调用了fC函数,如此反复。

下面给出一个实例:

#include<stdio.h>
int max4(int a,int b,int c,int d);  //对max4函数进行声明
int max2(int a,int b);  //对max2函数进行声明

int main()
{
	int a,b,c,d,max;
	printf("请输入四个值:");
	scanf("%d,%d,%d,%d",&a,&b,&c,&d);

	max=max4(a,b,c,d);   //调用max4函数,得到4个数中的最大值
	printf("max=%d\n",max);
	return 0;
}

int max4(int a,int b,int c,int d)  //定义max4函数
{
	int m;
	m=max2(a,b);  //调用max2函数,得到a,b中的更大者,并放在m中。
	m=max2(m,c);  //调用max2函数,得到a,b,c中的更大者,并放在m中。
	m=max2(m,d);  //调用max2函数,得到a,b,c,d中的更大者,并放在m中。
	return m;     //把4个数中的最大值最为函数返回值带回main函数中
}

int max2(int a,int b)   //定义max2函数
{
	if(a>=b)
		return a;   //若a>=b,将a为函数返回值
	else
		return b;  //若a<b,将b为函数返回值
}

注意;

用户定义的函数中可以没有return语句。

一个自定义函数中可以根据不同情况设置多条return语句。

用户定义的函数中可以若没有return语句,则应当定义函数为void类型。

函数的return语句中可以没有表达式。

介绍完嵌套调用,下面介绍一个递归调用。

递归调用的本质,其实就是通过直接或者间接的方法去调用本身。

任何一个递归调用程序必须包括两部分

  • 递归部分(将大化小的过程
  • 递归结束条件(与非递归函数的区别)

下面以计算阶乘的C程序为例:

#include<stdio.h>
int fac(int n);  //函数的声明

int main()
{
	int n;
	printf("请输入一个数:");
	scanf("%d",&n);
	printf("%d的阶乘为:%d",n,fac(n));  //函数的调用
	return 0;
}

int fac(int n)  //函数的定义
{
	if(n==0||n==1)
		return 1;
	else
		return fac(n-1)*n;  //在此调用fac函数,并将结果返回
}

递归总结:

①:问题的求解可通过降低问题规模实现,而小规模的问题求解方式与原问题的一样,

小规模问题的解决导致问题的最终解决。

②:递归调用应该能够在有限次数内终止递归。

③:递归调用如果不加以限制,将无数次的调用。

④:必须在函数内部加控制语句,只有当满足一定条件时,递归终止。


浮游塔第35层:函数的声明

之前提到过,main函数可以出现在程序的任意位置,我个人喜欢将它放在开头,有些人也喜欢将它放在结尾,那么,二者有什么区别嘛?

答案是:函数是否经过声明

 上图中的两段代码,在结果上是一致的,均为五。

 最大不同点就在于:图1中的sum函数是经过声明的。

 那么为什么要进行声明呢?

(告诉编译器,该被调函数已经定义过了,可以正常使用了。)

因为在正常情况下,当被调函数(此处为sum函数)的定义放在主调函数(此处为main函数)之后,在主调函数中调用被调函数时,则被调函数就要进行声明。

但有一种情况除外:即被调函数的返回值类型为Int类型。

所以此处的sum函数其实可以不进行声明,但会产生警告。

而在dvc中则会产生以下错误:

"fun"was not declared int this scope

其中的declared就是声明的意思。

 函数声明的一般形式为:

返回值类型 函数名(类型 形参1,类型 形参2···)
int sum(int a,int b);

函数声明的作用在于扩展函数的作用域,使得函数可以在定义之前被调用。

通常将函数的声明都写在头文件中,使得程序员不必考虑函数的调用顺序。

函数声明的本质告知计算机该函数的名称、参数个数、参数类型、返回值类型。

浮游塔第36层:数组元素作为函数参数

在数组的学习中,我们知道简单提到数组可以作为函数的参数,本期来进一步讲讲。

数组作为函数参数有两种方法:

  • 数组元素作为实际参数
  • 数组名作为实际参数和形式参数

数组元素就是下标变量,它与普通变量并无区别。

 因此它作为函数实参使用与普通变量是完全相同的,在发生函数调用时,会把实际参数的值传递给形式参数,是“数值传递”的方式,即所谓的——单向传递

而数组元素之所以不能用作形式参数,是因为形式参数在函数被调用时临时分配存储单元的,不可能为一个数组元素单独分配存储单元。

#include <stdio.h>
void input(int v)
{
    if(v>0)
        printf("%d\n",v); //若数据大于零,则输出本身
    else
        printf("%d\n",0); //小于等于零,则输出零
}

int main( )
{
    int i,arr[5];
    printf("请输入5个数据\n");
    for(i=0;i<5;i++)
	{
        scanf("%d",&arr[i]);  //键盘接收数据
        input(arr[i]);        //将数组元素作为实际参数
		printf("\n");
    }
    return 0;
}

浮游塔第37层:数组名作为函数参数

除了可以用数组元素作为函数参数外,还可以 用数组名作为函数参数(包括实参和形参)。

不过,用数组元素作为实参时,向形参变量传递的是数组元素的值,而用数组名作为函数实参时,向形参(数组名或指针变量)传递的是数组首元素的地址。

当使用数组名作为函数参数时,需要注意一下几点;

一、要求形式参数和相对应的实际参数都必须是类型相同的数组,都必须有明确的数组说明。

当形参和实参二者不一致时,会发生错误。

二、形参数组和实参数组的长度可以不相同,因为在调用时,只传送首地址而不检查形参数组的长度。

当形参数组的长度与实参数组不一致时,虽然不会报错,但程序执行结果将与实际不符。

 下面用一段代码简单展示以下。

#include<stdio.h>
float average(float array[5])    //定义average函数,在主调函数前,故不需要声明
{
	int i;
	float aver,sum=array[0];
	for(i=1;i<5;i++)
	{
		sum=sum+array[i];  //累加学生成绩
	}
	aver=sum/5;
	return aver;
}

int main()
{
	float score[5],aver;
	int i;
	printf("请输入5个成绩:\n");
	for(i=0;i<5;i++)
	{
		scanf("%f",&score[i]);
	}
	printf("\n");
	aver=average(score);            //调用average函数
	printf("平均成绩为:%5.2f\n",aver);
	return 0;
}

浮游塔第38层:变量的作用域

所谓的作用域(Scope),就是从上到下的一段代码区间,就是变量的有效范围,表明了变量的使用范围,就是变量可以在哪个范围以内使用。

变量的作用域由变量的定义位置决定,在不同位置定义的变量,它的作用域是不一样的。

void fun( )
{
	int age=0;//定义变量age
	age=100;
}
int main( )
{
	age=100; //错误,超出age的作用域,无法使用age
	return 0;
}

作用域分为很多种类:

在同一个作用域中不能出现相同名称的标识符(变量、函数、数组、结构体类型名...),

否则程序提示错误。

int age=0;
void fun( )
{
	int age=0;
	{
		int age=20;
		printf("%d",age);  //输出语句块作用域的age
	}
	printf("%d",age); //函数作用域变量age
}
int main( )
{
	age=100;//修改全局作用域变量age的值
	printf("%d",age); //输出全局作用域age,100
	return 0;     
}

变量的作用域有两类:

  • 局部变量:定义在函数内部的变量拥有一个局部作用域,被叫做局部变量
  • 全局变量:定义在函数外的拥有全局作用域的变量,被称为全局变量。

而所谓的局部变量并不是一成不变的,它是相对的。局部变量也有可能是更小范围内的变量的外部变量。

​
int g_age=0; //全局变量,尽量少用或者别用全局变量
void fun(int arg) //局部变量
{
	int age=0; //局部变量
	{
		int age=20; //局部变量
	}
}
int main(void)
{
    int age=100; //局部变量
    return 0;
}

​

浮游塔第39层:函数的作用域

C语言中,根据函数能否被其他源文件调用,分为内部函数和外部函数

  • 内部(静态)函数:只在定义的本文件中有效。
  • 外部函数:可以被其他源文件调用的函数。

外部函数

在多人同时开发大型项目时,可能包含很多源文件来分别实现,最终,再整合在一起,有时,一个源文件中,需要调用其他源文件中的函数
如果在定义函数时,在函数的首部的最左端加关键字 extern,则此函数是外部函数,可供其它文件调用。
示例代码:

extern int fun(int argument1,int argument2)
{

}

这样,编译器通过extern关键字就可以知道,fun()函数是定义在其他文件中的外部函数。

需要注意的时,在C语言中,如果在定义时,省略了extern,会被默认成外部函数。
即:

int fun(int argument1,int argument2)
{

}

声明外部函数时,无论有没有关键字extern,外部函数与原函数定义的返回值类型、函数名称和参数列表必须一致

内部(静态)函数

外部函数,只要声明一个函数原型,就可以调用其他源文件中的函数,

但是,当多人开发时,可能出现函数重名的情况,不同源文件中的同名函数会相互干扰
此时,就需要一些特殊函数,只在定义的文件中有效,这类函数称为内部函数。

如果在定义函数时,在函数的首部的最左端加关键字 static,也称内部函数(静态)函数。

static 类型名 函数名 (形参表)

浮游塔第40层:结构化程序设计

在上程序课的时候,很多老师都会提到一个概念——结构化设计

它是进行以模块功能和处理过程设计为主的详细设计的基本原则。

其主要任务:

  1. 定义满足需求所需要的结构
  2. 确定“怎么做”的问题
  3. 划分两个阶段

总体设计:整 个系统为研究对象
详细设计:系统当中的某个模块为研究对象,来设计这个模块的详细处理流程。

结构化设计采用自顶向下、逐步求精的方法

  1. 把大问题分解成若干小问题,小问题再进一步分解成若干更小的问题,直到问题足够容易解决。
  2. 编写函数解决小问题,然后编写复杂函数,调用小问题解决复杂问题。
  3. 最后在main函数里通过调用复杂函数解决整个问题。

结构化设计强调高内聚低耦合

示例:编写一个程序,打印一个四则运算的菜单,用户选择其中一种运算,接着提示用户输入两个整数,并在窗口打印运算结果。

  • 对输入选项和输入整数都要进行数据合法性校验;
  • 计算完成后,并不退出程序,二十再次打印菜单,直到用户选择“0”,才退出;
  • 需要考虑除数为零的情况,以及计算精确度的问题;

呼,有关函数的章节我们也已经学习了,下一期将会是C语言中的难点开始——指针。

因为最近有公务在身,所以下一期的内容可能会很久才会和大家见面,希望大家能记住我,555.

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

渡过晚枫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值