第六章 函数

1.函数介绍

概述

函数是指功能。 每一个函数用来实现一个特定的功能。 函数的名字应反映其代表的功能。
在设计一个较大的程序时, 往往把它分为若干个程序模块, 每一个模块包括一个或多个函数, 每个函数实现一个特定的功能。
注意:

  1. 一个 C 程序可由一个主函数和若干个其他函数构成。
  2. 由主函数调用其他函数。
  3. 其他函数也可以互相调用, 但不可嵌套定义。
  4. 同一个函数可以被一个或多个函数调用任意多次。
  5. C 程序的执行是从 main 函数开始,在 main 函数中结朿。
  6. 在 main 函数中调用其他函数, 在调用后流程返回到 main 函数。

分类

用户使用的角度
  • 库函数
    库函数是由系统提供的, 用户不必自己定义, 可直接使用它们。

不同的 C 语言编译系统提供的库函数的数量和功能会有一些不同。

  • 为什么会有库函数呢?
  1. 将信息按照一定的格式打印到屏幕上(printf)
  2. 在编程的过程中我们会频繁的做一些字符串的拷贝工作(strcpy)
  3. 在编程是我们也计算,总是会计算n的k次方这样的运算(pow)

像上面我们描述的基础功能,它们不是业务性的代码。我们在开发的过程中每个程序员都可能用的到,
为了支持可移植性和提高程序的效率,所以C语言的基础库中提供了一系列类似的库函数,方便程序员
进行软件开发。

参考库函数:
www.cplusplus.com
http://en.cppreference.com
使用库函数,必须包含 #include 对应的头文件

  • 用户自己定义的函数
    它是用以解决用户专门需要的函数
函数形式
  • 无参函数
    在调用无参函数时, 主调函数不向被调用函数传递数据。 、

无参函数一般用来执行指定的一组操作。
无参函数可以带回或不带回函数值, 但一般以不带回函数值的居多。

  • 有参函数
    主调函数在调用被调用函数时, 通过参数向被调用函数传递数据。

一般情况下, 执行被调用函数时会得到一个函数值, 供主调函数使用。

2. 函数定义

C 语言要求, 在程序中用到的所有函数, 必须“先定义, 后使用”。

内容

  1. 指定函数的名字, 以便以后按名调用。
  2. 指定函数的类型, 即函数返回值的类型。
  3. 指定函数的参数的名字和类型, 以便在调用函数时向它们传递数据。 对无参函数不需要这项。
  4. 指定函数应当完成什么操作, 也是指函数是做什么的, 即函数的功能。

方法

  • 函数定义格式
ret_type fun_name(para1, * )
{
statement;//语句项
}
ret_type 返回类型
fun_name 函数名
para1  函数参数

也可以定义空函数:如
void dummy ()
{}

  • get_max
#include <stdio.h>
//get_max函数的设计
int get_max(int x, int y)
{
        return (x>y)?(x):(y);
}
int main()
{
    int num1 = 10;
    int num2 = 20;
    int max = get_max(num1, num2);
    printf("max = %d\n", max);
    return 0;
}
#include <stdio.h>
void Swap1(int x, int y)
{
int tmp = 0;
tmp = x;
x = y;
y = tmp;

  • 交换变量
#include <stdio.h>
void Swap1(int x, int y)
{
    int tmp = 0;
    tmp = x;
    x = y;
    y = tmp;
}
void Swap2(int *px, int *py)
{
    int tmp = 0;
    tmp = *px;
    *px = *py;
    *py = tmp;
}
int main()
{
    int num1 = 1;
    int num2 = 2;
    Swap1(num1, num2);
    printf("Swap1::num1 = %d num2 = %d\n", num1, num2);
    Swap2(&num1, &num2);
    printf("Swap2::num1 = %d num2 = %d\n", num1, num2);
    return 0;
}

3.函数参数

在调用有参函数时,主调函数和被调用函数之间有数据传递关系。

形式参数

形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配
内存单元),所以叫形式参数。( 简称“形参”) 或“虚拟参数’。

形式参数当函数调用完成之后就自动销毁了。因此形式参数只在函数中有效 。

实际参数

真实传给函数的参数,叫实参。实参可以是:常量、变量、表达式、函数等。无论实参是何种类
型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参

在函数调用过程中,每个形参都拥有自己的空间,同时拥有了和实参一模一样的内容。
所以我们可以简单的认为:形参实例化之后其实相当于实参的一份临时拷贝

调用理解

  • 传值调用
    函数的形参和实参分别占有不同内存块,对形参的修改不会影响实参。

  • 传址调用
    传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式。
    这种传参方式可以让函数和函数外边的变量建立起正真的联系,也就是函数内部可以直接操作函数外部的变量。

4.函数返回值

  • 函数的返回值是通过函数中的 return 语句获得的
  • 在定义函数时指定函数值的类型。
  • 定义函数时指定的函数类型一般应该和 return 语句中的表达式类型一致
  1. 如果函数值的类型和 return 语句中表达式的值不一致, 则以函数类型为准。
  2. 对数值型数据, 可以自动进行类型转换。
  3. 即函数类型决定返回值的类型
  • 对于不带回值的函数, 应当用定义函数为“void 类型”(或称“空类型”)

系统可以不使函数带回任何值, 此时在函数体中不得出现return 语句.

5.函数声明

  • **先声明后使用 **

声明的作用是把函数名、 函数参数的个数和参数类型等信息通知编译系统, 以便在遇到函数调用时, 编译系统能正确识别函数并检査调用是否合法 。

函数声明中参数名可以省略,编译系统只关心和检査参数个数和参数类型, 而不检査参数名。

  • 函数声明可以直接写入头文件中,在使用时添加相应头文件
#ifndef __TEST_H__
#define __TEST_H__
//函数的声明
int Add(int x, int y);
#endif //__TEST_H__
  • 函数声明也可以放在程序中
#include<stdio.h>
int main()
{ 
    float add(float x, float y)//对 add 函数作声明
        float a = 1;
        float b = 2;
        float c;
    c=add(a,b);
printf ("%f \n",c);

float add(float x,float y) //定义 add 函数
{ 
    float z;
    z= x+ y;
    return(z) ;
}

6.函数调用

一般形式

函数名( 实参表列)

如果是调用无参函数, 则“ 实参表列” 可以没有, 但括号不能省略。
如果实参表列包含多个实参, 则各参数间用逗号隔开。

函数调用方式

  1. 函数调用语句
    把函数调用单独作为一个语句。

如“printf.starO;”, 这时不要求函数带回值, 只要求函数完成一定的操作。

  1. 函数表达式
    函数调用出现在另一个表达式中。

例如: c=2*max (a, b);

  1. 函数参数
    函数调用作为另一个函数调用时的实参。

例如 m = max (a, max (b, c));

嵌套调用

在定义函数时, 一个函数内不能再定义另一个函数( 不能嵌套定义)
但可以嵌套调用函数, 即在调用一个函数的过程中, 又调用另一个函数

#include <stdio.h>
void new_line()
{
    printf("hehe\n");
}
void three_line()
{
    int i = 0;
    for(i=0; i<3; i++)
    {
        new_line();
    }
}
int main()
{
    three_line();
    return 0;
}

链式访问

把一个函数的返回值作为另一个函数的参数

#include <stdio.h>
#include <string.h>
int main()
{
    char arr[20] = "hello";
    int ret = strlen(strcat(arr,"bit"))
    printf("%d\n", ret);
    return 0;
}


#include <stdio.h>
int main()
{
    printf("%d", printf("%d", printf("%d", 43)));
    return 0;
}

7.函数递归

一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法
它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。
递归的主要思考方式在于:把大事化小

注意:

  1. 程序中不应出现这种无终止的递归调用, 而只应出现有限次数的、有终止的递归调用。
  2. 存在限制条件,当满足这个限制条件的时候,递归便不再继续。
  3. 每次递归调用之后越来越接近这个限制条件。
  • 接收一个整型值(无符号),按顺序打印其每一位
#include <stdio.h>
void print(int n)
{
    if(n>9)
    {
        print(n/10);
    }
    printf("%d ", n%10);
}
int main()
{
    int num = 1234;
    print(num);
    return 0;
}
  • 不允许创建临时变量,求字符串长度
#incude <stdio.h>
int Strlen(const char*str)
{
    if(*str == '\0')
        return 0;
    else 
        return 1+Strlen(str+1);
}
int main()
{
    char *p = "abcdef";
    int len = Strlen(p);
    printf("%d\n", len);
    return 0;
}
  • 求n!
int factorial(int n)
{
    if(n <= 1)
        return 1;
    else
        return n* factorial(n-1);
}
  • 求斐波那契(不考虑溢出)
int fib(int n)
{
    if (n <= 2)
        return 1;
    else
        return fib(n - 1) + fib(n - 2);
}

在调试 factorial 函数的时候,如果你的参数比较大,那就会报错: stack overflow(栈溢出) 这样的信息。

系统分配给程序的栈空间是有限的,但是如果出现了死循环,或者(死递归,这样有可能导致一直开辟栈空间,最终产生栈空间耗尽的情况,这样的现象我们称为栈溢出。


改进方法

  1. 将递归改写成非递归。
  2. 使用static对象替代nonstatic局部对象。在递归函数设计中,可以使用static对象替代nonstatic局部对象(即栈对象),这不仅可以减少每次递归调用和返回时产生和释放nonstatic对象的开销,而且static对象还可以保存递归调用的中间状态,并且可为各个调用层所访问。
  • n!
//求n的阶乘
int factorial(int n)
{
    int result = 1;
    while (n > 1)
    {
        result *= n ;
        n -= 1;
    }
    return result;
}
  • 斐波那契
//求第n个斐波那契数
int fib(int n)
{
    int result;
    int pre_result;
    int next_older_result;
    result = pre_result = 1;
    while (n > 2)
    {
        n -= 1;
        next_older_result = pre_result;
        pre_result = result;
        result = pre_result + next_older_result;
    }
    return result;
}

注意:

  1. 许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰。
  2. 但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些。
  3. 当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开销。

8.数组作为函数参数

数组元素作函数实参


数组元素可以用作函数实参, 不能用作形参。
因为形参是在函数被调用时临时分配存储单元的, 不可能为一个数组元素单独分配存储单元。
在用数组元素作函数实参时, 把实参的值传给形参, 是“值传递”方式。
数据传递的方向是从实参传到形参, 单向传递 。

数组名作函数参数

数组名作函数参数( 包括实参和形参)。
用数组名作函数实参时, 向形参( 数组名或指针变量) 传递的是数组首元素的地址。

  1. sizeof(数组名),计算整个数组的大小,sizeof内部单独放一个数组名,数组名表示整个数组。
  2. &数组名,取出的是数组的地址。此处,数组名表示整个数组。

除此1,2两种情况之外,所有的数组名都表示数组首元素的地址。

  • 冒泡排序
#include <stdio.h>
void bubble_sort(int arr[],int sz)
{
    int i = 0;
    for(i=0; i<sz-1; i++)
    {
        int j = 0;
        for(j=0; j<sz-i-1; j++)
        {
            if(arr[j] > arr[j+1])
            {
                int tmp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = tmp;
            }
        }
    }
}

int main()
{
    int arr[] = {3,1,7,5,8,9,0,2,4,6};
    int sz = sizeof(arr)/sizeof(arr[0]);
    bubble_sort(arr, sz);//需要把数组长度传入
    for(i=0; i<sz; i++)
    {
        printf("%d ", arr[i]);
    }
    return 0;
}

9.内部函数和外部函数

内部函数

  • 如果一个函数只能被本文件中其他函数所调用, 它称为内部函数。

  • 在定义内部函数时, 在函数名和函数类型的前面加 static。

  • 即:static 类型名函数名( 形参表);

  • 表示该函数是一个内部函数, 不能被其他文件调用。

  • 内部函数又称静态函数, 因为它是用 static 声明的。

  • 使用内部函数, 可以使函数的作用域只局限于所在文件。

  • 在不同的文件中即使有同名的内部函数, 也互不干扰。 这就使它对外界“屏蔽”了。

  • 通常把只能由本文件使用的函数和外部变景放在文件的开头, 前面都冠以 static 使之局部化, 其他文件不能引用。 这就提高了程序的可靠性。

外部函数


如果在定义函数时, 在函数首部的最左端加关键字 extern, 则此函数是外部函数, 可供其他文件调用。
即:extern int fun ( int a, intb);

  • C 语言规定, 如果在定义函数时省略 extern, 则默认为外部函数。
  • 在需要调用此函数的其他文件中, 需要对此函数作声明( 不要忘记, 即使在本文件中调用一个函数, 也要用函数原型进行声明)。
  • 在对此函数作声明时, 要加关键字 extern, 表示该函数“是在其他文件中定义的外部函数。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值