第六章 C语言函数简介

时间:2024-04-10 08:47:56

1.函数定义

函数定义就是函数体的实现,函数体就是一个代码块,它在函数调用时执行,与函数定义相反,函数声明出现在函数被调用的地方。

函数定义的语法:

类型 函数名(形式参数)

代码块

代码块就是一对花括号,里面包含了一些声明和语句,因此最简单的函数大致如下:

function_name()

{

}

当这个函数被调用时,它简单的返回,然而,它可以实现一种有用的存根目的,未那些此时尚未实现的代码保留一个位置。

形式参数列表包括变量名和它们的类型声明。代码块包含了局部变量的声明和函数调用时需要执行的语句。

在K&R C中,(K&R C是C语言程序设计的一种标准,1978年由丹尼斯·里奇创造。)形式参数的类型以单独的列表进行声明,并出现在参数列表和函数体的花括号之间,如下:

int* find_int(key, array, array_len)

int key;

int array;

int array_len;

{

这种声明形式仍旧为标准所允许,但是我们应该按照新的风格来写代码

return语句

当执行流执行到函数定义的末尾的时候,函数就将返回(return),也就是说,执行流返回到函数被调用的地方。return语句允许你从函数体的任何位置返回,语法如下:

return expression;

表达式experission时可以选的,如果函数无需返回值experssion就可以被省略。这种没有返回值的函数在声明的时候应该把函数的类型声明为void

在C中,子程序无论是否存在返回值,均被称为函数,调用一个整函数(即返回一个值的函数),从表达式中调用一个过程类型的函数(无返回值)是一个严重的错误,因为这样一来表达式的求值中会产生一个不可预测的值。

2.函数声明

2.1原型

向编译器提供一些关于函数的特定信息更为安全,两种方法实现,

首先,如果同一源文件的前面已经出现了该函数的定义,编译器就会记住它的参数数量和类型,以及函数的返回值类型,接着编译器就可以该函数的所有后续调用(在同一个源文件),确保它们是正确的。

第二种向编译器提供函数信息的是方法是使用函数原型,函数原型总结了函数定义起始部分的声明,向编译器提供有关该函数应该如何调用完整的信息。

函数原型例子如下:

int* find_int(int key, int array[], int len);

注意最后的分号,它区分了函数原型和函数定义的起始部分。编译器见过原型之后,就可以检查该函数的调用,确保参数正确,返回值无误。

下面代码说明了一种使用函数原型的更好的方法:

#include"fun.h"

void a() {

    ...;

}

void b() {

    ...;

}

文件fun.h包含了下面的函数原型

int* func(int* value, int len);

从几个方面看,这个技巧更好:

1.现在函数原型具有文件作用域,所以原型的一份拷贝可以作用于整个源文件,较之在该函数每次调用前单独书写一份函数原型容易的多。

2.现在函数原型只书写一次

3.如果函数的定义进行了修改,只需要修改函数原型并且重新编译所有包含了该函数的原型的源文件即可。

4.如果函数的原型同时也被#include指令包含到定义函数的文件中,编译器就可以确认函数原型与函数定义的匹配。

一个没有参数的函数的原型应该下面这样:

int* func(void);

2.2函数的缺省认定

当程序调用一个无法见到原型的函数时,编译器就认为该函数返回一个整型值,这个可能引起错误,因为有些函数并不返回整型值。

所以所有的函数都应该具有原型,尤其是那些返回值不是整型的函数。

3.函数的参数

C函数所有参数均以“传值调用”方式进行传递,这意味函数将获得参数值的一份拷贝。

C的规则很简单:所有参数都是传值调用,但是如果被传递的参数是一个数组名,并且在函数中使用下标引用该数组的参数,那么在函数中对数组元素的修改实际上修改的是调用程序中的数组元素。函数将访问调用程序的数组元素,数组并不会复制,这个行为称为“传址调用”。

数组名的值实际上是一个指针,传递给函数的是这个指针的一份拷贝。下标引用实际上是间接访问的另一种形式,它可以对指针指向间接访问操作,访问指针指向的内存位置。

所以:1.传递给函数的标量参数是传值调用的

           2.传递给函数的数组在行为上就像它们是通过传址调用那样。

4.ADT和黑盒

C可以用于设计和实现抽象数据类型,因为它可以限制函数和数据定义的作用域,这个技巧也可以被称为黑盒。抽象数据类型的基本想法很简单——模块具有功能说明和接口说明,前者说明模块所执行的任务,后者定义模块的使用,模块的用户并不需要知道模块实现的任何细节。

5.递归

C通过运行时堆栈支持递归函数的实现。递归函数就是直接或间接调用自身的函数。

//递归求阶乘

int f(int n)

{

    int x;

    //递归出口

    if(n==1||n==0)

        x=1;

    else

        x=n*f(n-1);//递归

    return x;

}

遇到递归问题需要根据规律解决。

1、找到所谓的终止条件,即让递归停止的条件

2、找到递推的关系式

3、递归的方向要搞清楚,一般是向最终的终止条件不断递归

4、递归 分为回推和递推两个阶段,当递推到终止条件时,程序会反向推回(回推)。

6.可变参数列表

在函数原型中,列出了函数期望接受的函数,但是原型只能显示固定数目的参数,所以就出现了可变参数

6.1stdarg宏

可变参数列表是通过宏来实现的,这些宏定义于stdarg.h头文件,它是标准库的一部分,这个头文件声明了一个类型va_list三个宏——va_start、va_arg、va_end。我们可以声明一个类型为va_list的变量,配合使用

为了访问参数,需要使用va_arg,这个宏接受两个参数:va_list变量和参数列表中下一个参数的类型,在这个例子中,所有的可变参数都是整型,在有些函数中,可能需要通过前面获得的数据来判断下一个参数的类型,va_arg返回这个参数的值,并使用va_arg指向下一个可变参数。

最后,当访问完毕最后一个可变参数列表之后,我们需要调用va_end。

6.2可变参数的限制

注意,可变参数必须从头到尾按照顺序逐个访问,如果访问了几个参数之后不想访问了可以不再访问,但是如果一开始就访问参数列表中间的参数那是不行的。而且所有作为可变参数传递给函数的值都 将执行缺省参数类型提升。

//计算指定数量的平均值

#include<stdarg.h>

float average(int n_values,...) {

    va_list va_arg;

    int count;

    float sum = 0;

    //准备访问可变参数

    va_start(va_arg, n_values);

    //添加取自可变参数列表的值

    for(count = 0; vount < n_values; count += 1) {

        sum += va_arg(var_arg, int);

    }

    //完成处理可变参数

    va_end(var_arg);

    return sum / n_values;

}

注意:这些宏无法判断实际存在的参数的数量,而且也无法判断每个参数的类型。
获取编程学习资料的两种方式:1.申请加入企鹅裙(C语言C++编程学习14)2.关注私信我!正有一大波资料即将抵达,请做好准备!

一些警告:

1.错误的在其他函数的作用域内编写函数原型

2.没有为那些返回值不是整型的函数编写原型

3.把函数原型和旧式风格的函数定义混合使用

4.在va_arg中使用错误的参数类型导致未定义的结果

“我是一名从事了10年开发的老程序员,最近我花了一些时间整理关于C语言、C++,自己有做的材料的整合,一个完整的学习C语言、C++的路线,学习材料和工具。进企鹅裙学习(<C语言C++编程学习14>)免费送给大家。这里是编程爱好者的聚集地,欢迎初学和进阶中的小伙伴。希望你也能凭自己的努力,成为下一个优秀的程序员。

第六章 C语言函数简介

第六章 C语言函数简介

第六章 C语言函数简介

第六章 C语言函数简介