当前位置: 首页 > news >正文

C语言之函数

提出问题:
从n个数据中取出m(m<=n) 个数据组成一组,总共可以形成多少种组合?

求组合数的公式如下:

程序:

#include  <stdio.h>int main( ) 
{int  m, n, x=1, y=1, z=1, i, cmn; scanf("%d%d", &m, &n);for(i=1; i<=n; i++)      /* 求 n! */ x = x * i;for(i=1; i<=m; i++)       /* 求 m! */ y = y * i;for(i=1; i<=n-m; i++)  /* 求 (n-m)! */ z = z * i;cmn = x / (y * z); printf("Cmn = %d", cmn);return 0;
}

由程序可以看出,求阶乘都是相似类型的操作,那是否可以减少相似类型的操作而使程序变得简洁一些?这样就引出了函数这个概念

一, 函数概述:

   1.1  什么是函数?

          函数就是完成一定功能的程序代码。

    1.2  模块化(结构化)程序设计:

将一个复杂的功能划为为不同的功能模块,把复杂问题分解为若干较小的、功能简单的、相互独立又相互关联的模块来进行设计,最终通过功能模块的"组合"实现复杂功能,这样的一种程序设计方法。

在C语言中,函数(function)是构成程序的基本模块。 一个C程序由一个或多个函数组成,有且仅有一个主函数,即main()函数。 每个函数完成一个相对独立的且功能明确的任务。 由主函数调用其他函数,其他函数也可以互相调用。 同一个函数可以被一个或多个函数调用任意多次

    1.3  使用函数的目的?  

          1. 方便功能模块可被别人使用,就像我们使用别人编写的功能一样;
2. 减少程序中重复性的代码
3. 实现模块化程序设计

    1.4  函数分类:

         1.  函数的实现方式:
标准库函数:例如  scanf()、printf()、fabs()、sqrt()等
用户自定义函数 :例如add()。
2.  函数的形式:
有参函数:主调函数通过参数向被调函数传递数据。 一般执行被调函数会得到一个返回值,供主调函数使用。
无参函数:主调函数不向被调函数传递数据。用来执行指定的一组操作,一般不返回值。

相关概念: 主调函数: 调用其他函数的函数
被调函数: 被调用的函数

  二, 函数定义:


函数组成:  1. 函数首部(函数头)
2. 函数体

        定义格式:  类型标识符  函数名(形式参数列表)
{
函数体语句;
}
函数名:一个有效的标识符。
类型标识符:返回值的类型说明符。
void :表示函数不返回任何值。
形式参数列表: 参数是用于接收主调函数传递的数据的。参数列表中的参数可以有多个,多个参数之间,隔开,并且每一个参数必须要指定参数类型。
参数列表可以省略,但是函数名后的()不能省略,省略了参数列表的函数称为无参函数,它表明函数被调用时,不接收主调函数传递的任何数据。

定义无参函数:

定义无参函数的一般形式为:
类型标识符  函数名( )
{      
函数体
}
函数不接受主调函数传递过来的数据,所以没用参数。没有了参数,所以称为无参函数。

定义有参函数:

定义有参函数的一般形式为:
类型标识符  函数名( 形式参数列表)
{  
函数体
}

如果省略函数的类型标识符,则默认为是int型。

主调函数通过参数向被调函数传递数据。

定义空函数:

定义空函数的形式为:
类型标识符  函数名( )
调用此函数时,什么都不做。常用来在准备扩充功能的地方先写上一个空函数,以方便以后使用。

在定义C函数时要注意以下几点:

1.函数类型标识符变量类型说明符相同,它表示返回的函数值的类型。
2.在C语言中还可以定义无类型(即void类型)的函数,这种函数不返回函数值,只是完成某种功能。
3.如果省略函数的类型标识符,则默认为是int型。
4.函数中返回语句的形式为 return(表达式);或 return 表达式;其作用是将表达式的值作为函数值返回给调用函数。其中表达式的类型应与函数类型一致。
5.如果“形参表列”中有多个形式参数,则它们之间要用  ,分隔。
6.如果形参表中有多个形参,即使它们的类型是相同的,在形参表中也只能逐个进行说明。

例子:
计算并输出两个圆面积之和。

#include  "stdio.h"
double q(double r)            /*计算圆面积的函数,为双精度实型*/{ double  s;s=3.1415926*r*r;return(s);}int main(){ double r1,r2;printf("input  r1 ,r2: ");  /*输入前的提示*/scanf("%lf,%lf",&r1,&r2); /*输入r1与r2*/printf("s=%f\n",q(r1)+q(r2));return 0;}

运行结果:

   三,形参与实参


形参(形式参数):  定义函数时,函数名后括号中的变量称为形式参数。
函数定义时,系统是不会为参数分配内存,所以说参数是形式上的,形参只有当函数被调用时才会分配内存;

      实参(实际参数): 函数被调用时,传递给函数的数据,实际参数与形式参数可以同名,但两者占据不同的内存空间。

关于形参与实参的说明:
1.在函数调用时才给形参分配存储空间,且调用结束后所占空间立即释放。
2.实参可以是常量、变量、表达式,只要有确定的值。
3.实参和形参必须类型相同。若不同时,按赋值规定自动进行类型转换。
4.在C语言中,实参向形参的数据传递是“值传递”,是单向传递,即只由实参传给形参,而不能由形参传回来给实参。在内存中,实参、形参占不同的存储单元,因此形参值的改变不会影响实参值。

   四,函数的返回值:

通过调用函数使主调函数得到一个确定的值,这就是函数的返回值

       函数的返回值: 返回给主调函数的结果数据。
1)函数的返回值是通过 return 语句来实现的。

1.若不需要返回值,函数中可以没有return语句。
2.一个函数中可以有多个return语句,但任一时刻只有一个return语句被执行。
3.return后面的值可是常量、变量或表达式,且圆括号可有可无,返回的是其值。如:

int max(int x,int y)      {  return (x>y ? x:y);  }  /*完成了计算和返回两个功能*/

       return 语句:

              格式: return   (表达式) ;
功能: return 语句不仅仅是为了给主调函数返回数据,而是结束函数。
2)函数值的类型
若需要返回值,定义函数时一定要说明函数的类型;
凡不加类型说明符的函数,C语言按int型对待,但C++必须要说明函数类型;

3)定义函数时指定的函数类型一般应和return语句中的表达式类型相同。若函数值的类型和 return语句中表达式的值不一致时,以函数类型为准。对数值型数据可以自动进行类型转换。

4)对不要求返回值的函数,应用“void”定义为无类型(空类型),以保证函数不返回任何值,而不论是否有return          


五,函数使用(函数调用)


函数的调用过程: 当主调函数调用被调函数时,主调函数暂停运行,转而执行被调函数,当被调函数返回,主调函数再从之前暂停处继续执行。

       函数的调用方式:
1.   常见调用方式:
1)   函数名(实参列表);
2)   函数表达式;  将函数调用放置在一个表达式语句中;
3)   函数参数,    将函数调用作为另一个函数的参数。

          2.   函数的嵌套调用
被调函数有作为主调函数调用其他函数。这样的函数调用形式,称为函数嵌套调用

          3.   函数的递归调用
在函数内部直接或者间接的调用了自身,这样的函数调用形式,称为函数递归调用

C语言函数调用、定义与声明的核心原则:

在C语言中,程序的基本构成单位是函数。要正确地使用函数,必须遵循“先声明(或定义),后调用”的黄金法则。编译器在从上到下解析代码时,遇到一个函数调用,它必须提前知道这个函数的信息,即函数的返回类型和参数类型、个数。

        1.函数定义:
函数定义是指函数体的具体实现,它包含了函数要执行的所有代码。一个完整的函数定义包括:
        函数头 (Function Header): 函数返回类型 函数名(参数类型 参数名1, ...)
        函数体 (Function Body): { 和 } 之间包裹的代码块
这是一个完整的函数定义:

int add(int a, int b)  // 函数头
{                      // 函数体开始int sum = a + b;return sum;
}                      // 函数体结束

        2.函数调用:
函数调用就是使用已经定义好的函数执行特定的任务
例如:

int main()
{int result = add(5, 3); // 这就是对add函数的调用return 0;
}

         3.函数声明:
当在一个函数(如 main)中调用另一个函数(如 add)时,如果 add 函数的完整定义出现在 main 函数的后面,编译器在读到 main 里面的 add(5, 3) 时,就不知道 add 是什么。它不知道 add 需要几个参数、参数是什么类型、返回值又是什么类型。

为了解决这个问题,C语言引入了函数声明(也常被称为函数原型)。

作用:函数声明就像一个“预告”,它提前告诉编译器某个函数的基本信息,让编译器在没有看到完整函数定义的情况下,也能正确地检查函数调用是否合法。

声明格式
函数声明本质上就是函数头加上一个分号 ;。

它提供了三项关键信息给编译器:

  1. 函数名:要调用的函数叫什么。

  2. 参数列表:函数需要接收什么类型、多少个参数。

  3. 返回类型:函数执行完毕后会返回一个什么类型的值。

具体的声明方式:

1.包含形式参数名:
函数返回类型 函数名(参数类型1 形参名1, 参数类型2 形参名2, ...);

int add(int a, int b);

2.不包含形式参数名:
函数返回类型 函数名(参数类型1, 参数类型2, ...);

int add(int, int);

当函数没有参数,则应该使用void关键字。
示例:

void print_hello(void);

对于函数定义与调用声明的两种情况:

1.先定义后调用(无需额外声明)

#include <stdio.h>// 1. 函数定义在main函数之前
int add(int a, int b) {return a + b;
}int main() {// 2. 直接调用,此时编译器已经知道add函数的一切int result = add(5, 3);printf("Result is: %d\n", result);return 0;
}

2.先调用,后定义(必须进行声明)

#include <stdio.h>// 1. 函数声明 (Function Prototype)
// 提前告诉编译器,后面会有一个叫'add'的函数
int add(int, int); // 或者 int add(int a, int b);int main() {// 2. 函数调用// 编译器根据上面的声明来检查这次调用是否正确int result = add(5, 3);printf("Result is: %d\n", result);return 0;
}// 3. 函数定义 (Function Definition)
// 函数的具体实现
int add(int a, int b) {return a + b;
}

    六,函数的嵌套调用和递归调用

1.嵌套调用

C语言虽不允许嵌套定义函数,但可以嵌套调用函数。

例如:输入4个整数,找出其中最大的数。用函数的嵌套调用来处理
分析:在主函数中调用一个max_4函数来求4个整数中的最大数。然后在max_4函数中再调用一个max_2函数来求2个整数中的最大数。最后在主函数中输出结果。

编写程序如下:

#include <stdio.h>
int main()
{ int max_4(int a,int b,int c,int d); /*函数max_4原型声明*/int a,b,c,d,max;printf("Please enter 4 interger numbers:");scanf("%d%d%d%d",&a,&b,&c,&d);max=max_4(a,b,c,d);            /*调用函数max_4*/printf("max=%d \n",max);return 0;
} int max_4(int a,int b,int c,int d)    /*定义函数max_4*/
{ int max_2(int a,int b);                 /*函数max_2原型声明*/int m; m=max_2(a,b);      /*调用函数max_2,求a、b中的大者存入m*/m=max_2(m,c);     /*                                求m、c中的大者存入m*/m=max_2(m,d);     /*                                求m、d中的大者存入m*/return (m);             /*返回m值便是4个中的大者*/
}int max_2(int a,int b)           /*定义函数max_2*/
{  if (a>b)    return a;else    return b;                /* 函数返回值是a和b中的大者 */
}

2.递归调用

在调用一个函数的过程中又出现直接或间接地调用该函数本身,称为函数的递归调用。C语言的特点之一就在于允许函数的递归调用。

          递归本质: 递归的本质是一种循环;
实现递归主要事项: 
1.  递归必须要有结束的条件。
2.  递归需要慢慢向结束条件进行逼近,最终达到结束递归;
3.  先进行递归结束条件的判断,然后才进行递归。

例:直接递归

int f ( int x)
{ int y,z;z=f(y);              /* 在执行f函数的过程中又要调用f函数自己 */return(2*z);}

间接递归
通过别的函数调用自身。例如,两个函数之间的调用关系就属于间接递归调用。

例:用递归方法求n!
分析:
求n!也可以用递归方法,即:5!=4!×5   , 4!=3!×4        ……       1!=1 
可用下面的递归公式表示:
n!=1                (n=0,1)
n!=n*(n-1)!      (n>1)

#include <stdio.h>long fac(int n);   int main() {int n;long y;printf("input an integer number: ");scanf("%d", &n);y = fac(n);// 在主函数中检查fac函数返回的错误信号if (y == -1) {// 如果返回-1,说明输入有误,fac函数内部已经打印了原因// main函数就不再打印阶乘结果了} else {printf("%d! = %ld\n", n, y);}return 0;
}long fac(int n) {long f;// 1. 处理无效输入if (n < 0) {printf("n<0, data error!\n");return -1; // 立即返回一个错误码,不再继续执行}// 2. 正确处理基本情况 (Base Case)// 使用逻辑或 || if (n == 0 || n == 1) {f = 1;}// 3. 递归步骤 (Recursive Step)else {f = fac(n - 1) * n;}return f; // 返回计算好的值
}

   七,数组做函数参数:

        数组做函数参数:
注意事项:
1.  数组做实参,形参也需要是数组;
2.  数组做实参,不代表传递的是数组中所有元素,而传递是第一个元素在
内存的地址;
3.  形参数组接收了实参数组地址,此时,形参数组与实参数组共同占据
相同的内存空间。
4.  因为形参数组与实参数组共同占据相同的内存空间,则形参数组元素的修改
会更新实参数组的元素。
5.  因为实参数组传递的是首元素的地址,相当于传递了数组中元素的开始位置,
所以在数组做参数时,往往是要额外提供一个表示数组元素个数的参数,以便
表示形参在访问数组元素时,该何时结束。

例:
编写一个函数,用来分别求数组score_1(有5个元素)和数组score_2(有10个元素)各元素的平均值 。

分析:要求两个不同大小的数组中元素的均值,在函数中设一个整型形参接受调用时传递过来的元素个数;用数组名作函数的实参时,传递数组的首地址,使形参数组与实参数组占用同一段内存单元。当形参数组元素的值变化时,对应的实参数组元素的值也发生了改变。

#include <stdio.h>
void main()
{ float average(float array[ ],int n);float score_1[5]={98.5,97,91.5,60,55};float score_2[10]={67.5,89.5,99,69.5,77,89.5,76.5,54,60,99.5};printf("The average of class A is %6.2f\n",average(score_1,5));printf("The average of class B is %6.2f\n",average(score_2,10));}
float average(float array[ ],int n){ int i;       float aver,sum=array[0];for(i=1;i<n;i++)sum=sum+array[i];aver=sum/n;return (aver);
}


    八,变量的作用域(作用范围)

        变量的作用域: 变量的作用范围,

         引入问题: 我们定义函数,往往是要考虑函数参数的设计,我们知道参数是用于传递数据的,如果数据可以在多个函数中都能被使用,则我们就无需提供参数来传递数据。那么什么样的数据能够被多个函数访问,这就涉及到变量的作用域问题的。

         根据变量的作用域不同,变量分为两大类:

         1. 局部变量:
函数内定义的变量,称为局部变量,局部变量的作用范围仅限于函数内,
函数外就无法访问该变量了。

         2. 全局变量(外部变量)
函数外定义的变量,称为外部变量(全局变量),该变量的作用从变量定义处到
本文件的末尾。

         使用全局变量的优缺点:

           优点: 
1. 使用全局变量可以达到一个函数对外输出多个数据的效果;
2. 使用全局变量可以减少函数参数,而且降低因数据传递而造成的时间消耗
缺点:
1.  全局变量会在程序运行期间一直占据内存空间,直到程序结束;
2.  全局变量的使用会增加模块(函数)之间的耦合性,与程序设计要求
"高内聚,低耦合"是违背的;
3.  降低程序的通用性,函数使用全局变量,则在移植函数时就需要连同全局变量
一起移植,影响程序的可靠性与通用性

           总结:使用全局变量弊大于利,所以尽量减少甚至不使用全局变量。

   九,变量的生存期与存储类型


变量的生存期:变量在程序中存在的时间;
存储类型:  往往是表明变量存储位置的;

       变量定义完整的格式:
[存储类型]  类型 变量列表;

        变量存储类型:
1.  auto     自动存储类型
auto   只能修饰局部变量;这也是变量默认的存储类型
注意:auto是不能用于修饰形参,原因是形参存储类型是由编译系统
内部隐式管理的。
2.  static   静态存储类型
static 修饰局部变量: 延长局部变量的生存期,但不改变局部变量的作用域 
static 修饰全局变量: 限制全局变量仅在本文件内使用,
3.  extern   外部存储类型 
extern 修饰全局变量: 说明修饰的全局变量是其他文件或者在本文件下面定义的
对变量仅是声明,不是定义。
变量的作用域会被扩展, 但生命周期不变
4.  register 寄存器存储类型                    
register 修饰局部变量:局部变量将不存在于内存中,而是存储在CPU的寄存器中,
register常常是用于修饰循环变量的。



十,内部函数与外部函数:

         1.内部函数(静态函数):
格式:   static  类型 函数名(形参列表)
{
//函数体
}
例如:   static int fun ( int a , int b );
内部函数(静态函数)仅限定义的文件内使用,无法被其他文件的函数调用

         2.外部函数:
格式:   [extern]  类型 函数名(形参列表)
{
//函数体
}
extern 关键字修饰的函数称为外部函数,当我们省略extern,函数默认也是外部函数
外部函数可被其他文件的函数调用,但在其他文件中调用外部函数时,需要进行
外部函数声明,

例:有一个字符串,内有若干个字符,今输入一个字符, 要求程序将字符串中该字符删去。用外部函数实现。
File.c(文件1)

#include <stdio.h>
void main()                   
{ extern void enter_string(char str[]);                                extern void detele_string(char str[],char ch);extern void print_string(char str[]);/*以上3行声明在本函数中将要调用的在其它文件中定义的3个函数*/char c;char str[80]; enter_string(str);scanf("%c",&c);detele_string(str,c);print_string(str);  
}

file2.c(文件2)

#include <stdio.h>
void enter_string(char str[80])  /* 定义外部函数 enter-string*/{   fgets(str,80,stdin);                  /*向字符数组输入字符串*/} 

file3.c(文件3)

void delete_string(char str[],char ch)/*定义外部函数elete_string */
{  int i,j;for(i=j=0;str[i]!='\0';i++)if(str[i]!=ch)str[j++]=str[i];str[j]='\0';
}

file4.c(文件4)

#include <stdio.h>
void print_string(char str[])
{printf("%s\n",str);
}

小结:

1.C语言中函数是用来完成一定功能的;

2.C语言中有两种函数:库函数和用户自定义函数;

3.函数的定义和声明含义是不同;

4.函数处于调用它的函数之后时,要进行原型声明。函数原型声明有两种形式;

5.调用函数是要注意:实参与形参个数应相同、类型应一致(或兼容);数据传递是从实参到形参的单向值传递;

6.函数可以嵌套调用,也可以递归调用;

7.数组元素作实参其用法与普通变量相同,传递的是元素的值。而数组名作实参,向形参传递的是数组的首地址,而不是全部元素的值;

8.变量的作用域是指变量有效的范围。根据定义变量的位置不同,分为局部变量和全局变量;

9.变量的存储类别共有4个:auto、static、register、extern 前3个用于局部变量,可改变变量的生存期。 extern只能用于全局变量,可改变变量的作用域;

10.函数有内部和外部之分。本质上是外部的,但在其它文件调用时,要用extern对其声明。若不想让调用,应在定义时加上static,将其屏蔽起来;

11.变量的生存期是指变量存在的时间。全局变量的生存期是程序运行的整个期间,局部变量则不同。Static类为程序运行的整个期间, auto和register则与所在函数调用的时间段相同,函数调用结束就不存在了。


文章转载自:

http://4Cj091m3.gyfwy.cn
http://qU9ZtfYW.gyfwy.cn
http://xqKGp7xU.gyfwy.cn
http://5UQggbbY.gyfwy.cn
http://3DDQPn1t.gyfwy.cn
http://rp0BwOnV.gyfwy.cn
http://mndh5DYS.gyfwy.cn
http://a2Q2AhDp.gyfwy.cn
http://2D6YgOio.gyfwy.cn
http://ZyCrh3QF.gyfwy.cn
http://YR9E67GL.gyfwy.cn
http://eqEWWbJ9.gyfwy.cn
http://af06wIZb.gyfwy.cn
http://XetV0uq7.gyfwy.cn
http://9QCc7e1W.gyfwy.cn
http://vEUmkRfp.gyfwy.cn
http://vX6Avnx3.gyfwy.cn
http://RSTEDcZr.gyfwy.cn
http://UWuqRsNH.gyfwy.cn
http://A4ehBOPT.gyfwy.cn
http://1I2sZMmj.gyfwy.cn
http://x9Dul84G.gyfwy.cn
http://McWFa0Oy.gyfwy.cn
http://vvlApe63.gyfwy.cn
http://FPZAzYC6.gyfwy.cn
http://k6RrOG2s.gyfwy.cn
http://zNtLN07W.gyfwy.cn
http://FGYgSYB9.gyfwy.cn
http://3t1SWeRt.gyfwy.cn
http://EtjI1yab.gyfwy.cn
http://www.dtcms.com/a/383734.html

相关文章:

  • A050基于博途西门子1200PLC智能交通灯控制系统
  • shell文本处理三核心:grep(过滤匹配)、sed(流编辑)、awk(结构化分析)
  • 【WIT】编程百问一
  • ros2-tf树查看
  • 速通ACM省铜第四天 赋源码(G-C-D, Unlucky!)
  • MFC仿真
  • Leetcode 19 java
  • Vue3 响应式核心 API
  • linux故障排查
  • 为什么哈希表能 O(1) 查找?——C++ 哈希基础入门
  • [CISCN2019 华北赛区 Day1 Web2]ikun
  • 算法——递推求解
  • stm32之点灯
  • Qt---内存管理 对象树(Object Tree)机制
  • 人工智能通识与实践 - 计算机视觉技术
  • GAMES101:现代计算机图形学入门(Chapter1 计算机图形学概述)学习笔记
  • MATLAB 常用函数汇总大全和高级应用总结
  • Knockout.js 任务调度模块详解
  • LeetCode 2414.最长的字母续连续子字符串的长度
  • 当环保遇上大数据:生态环境大数据技术专业的课程侧重哪些领域?
  • 【Ansible】使用角色和Ansible内容集合简化Playbook知识点
  • init / record / required:让 C# 对象一次成型
  • BigemapPro快速添加历史影像(Arcgis卫星地图历史地图)
  • 树莓派操作第一章常用指令
  • Altium Designer(AD24)工作面板的切换与定制
  • 【WebSocket✨】入门之旅(七):WebSocket 的未来发展趋势
  • MySQL——库的操作
  • Redis缓存的常见问题及其解决方案
  • Conda 安装 CUDA Toolkit 解决 nvcc 找不到的问题
  • (二)Django框架常用配置