菜鸟日记 之函数

  • 在C语言中,函数至关重要,函数意味着功能模块,一个典型的C语言程序就是由一个个的功能模块拼接而成的整体。也因为如此,C语言被称为模块化语言。
  • 函数的概念

  • 对于函数的使用者,可以将函数理解为黑箱子,使用者只管按照规定给黑箱一些输入,就会得到一些输出,而不必理会黑箱子里面的运行细节。

  • 函数的定义

函数由函数头和函数体两部分组成。

函数头:相当于函数对外的接口

语法说明

返回值类型 函数名称(输入参数列表)
{
   C语句,函数功能的具体的实现(函数体)
                       ......
}
1.函数名称:命名规则与变量一致,一般取与函数功能相符合的、顾名思义的名称。

2.参数列表:即黑箱的输入数据列表,一个函数可有一个或多个参数,也可以不需要参数。

3. 返回类型:即黑箱的输入数据类型,一个函数可不返回数据,但最多只能返回一个数据

函数体:函数功能的内部实现

  • 示例
写一个函数:求两个给定整数的最大

/*

     max : 求给定两个整数的最大值

    @ a

    @ b

    返回值:

         最大值

*/

int max(int a, int b)

{

      return a > b ? a : b;

}

  • 语法汇总:
1. 当函数的参数列表为 void 时,表示该函数不需要任何参数。
2. 当函数的返回类型为 void 时,表示该函数不返回任何数据。
3. 关键字 return 表示退出函数。①若函数头中规定有返回数据类型,则 return 需携带一个类型与之
匹配的数据;②若函数头中规定返回类型为 void ,则 return 不需携带参数
  • 函数调用

语法说明

函数名(实参列表)//实参:传给函数的值

以取两个数的最大值为例:

#include <stdio.h>
int max(int a, int b)
{
    return a > b ? a : b;
}
int main()
{
    int max_val = max(3, 5);
    printf("最大值是:%d\n", max_val);
    return 0;
}
  • 行参与实参

实参:函数调用时传给被调函数的参数,如上述例子中max(3,5),3,5就是实参。

形参:用于接受调用函数所传数据的参数,如上述例子中max(int a,int b),a,b就是形参。

  • note
  • 形参与实参的类型和个数必须一一对应。
  • 形参的值由实参初始换。
  • 形参与实参位于不同的内存空间,彼此独立。
  • 内存分布

每个C语言进程都拥有一片结构相同的虚拟内存,所谓的虚拟内存,就是从实际物理内存映射出来的地址规范范围,最重要的特征是所有的虚拟内存布局都是相同的,极大的方便内核管理不同的进程。而每个虚拟内存都拥有以下四个区域:

  • 栈(stack)

  • 堆(heap)

  • 数据段 .rodata .data .bss

  • 代码段: .init .text

  • 栈内存

存在于栈内存的有:环境变量,命令行参数,局部变量。

处于栈内存里的变量,空间随函数的释放而释放,栈内存的分配和释放都是由系统规定的,我们无法干预。

  • 静态数据

c语言中,静态数据有两种:

  • 全局变量:定义在函数外的变量

  • 静态局部变量:定义在函数内部,且被static修饰的变量

  • note:

  • 若定义时未初始化,则系统会将所有的静态数据自动初始化为0

  • 静态数据初始化语句,只会执行一遍。

  • 静态数据从程序开始运行时便已存在,直到程序退出时才释放

  • 数据段和代码段
  • 数据段细分成如下几个区域:

    • .bss 段:存放未初始化的静态数据与全局变量,它们将被系统自动初始化为0

    • .data段:存放已初始化的static静态数据与全局变量

    • .rodata段:存放常量数据

  • 代码段细分成如下几个区域:

    • .text段:存放用户代码

    • .init段:存放系统初始化代码

数据段和代码段内存的分配和释放,都是由系统规定的,我们无法干预。

  • 堆内存
  • 堆内存基本特征:

    • 相比栈内存,堆的总大小仅受限于物理内存,在物理内存允许的范围内,系统对堆内存的申请不做限制。

    • 相比栈内存,堆内存从下往上增长。

    • 堆内存是匿名的,只能由指针来访问。

    • 自定义分配的堆内存,除非开发者主动释放,否则永不释放,直到程序退出。

  • 相关函数:

    • 申请堆内存:malloc() / calloc()

    • 清零堆内存:bzero()

    • 释放堆内存:free()

  • 函数指针与指针函数数组

概念:

函数指针变量就是用来保存函数地址的变量。

int func(int a, int b);
// 函数指针,指向的函数类型为返回值为int 参数为(int,int)的函数
int (*pfunc)(int a, int b);

给函数改别名:
以上用来保存函数地址的变量看起来非常冗余复杂,采用改别名的方式可以增强代码的易读性。

eg:
// 此时fptr就相当于void (*fptr)(int *a, int *b)的类型
typedef void (*fptr)(int *a, int *b);// 类似于 int类型 int a = 10

// 函数指针数组,数组存放的是指向返回值为 int 参数为int int类型的函数地址
int (*pfbuf[]) (int,int);

相关习题:

typedef 定义函数指针数组,初始化 sub(减法) div(除法) rem(取余数)

#include <stdio.h>
typedef int (*fpbuf[3])(int, int);
int sub(int a, int b)
{
    return a - b;
}
int div(int a, int b)
{
    return a / b;
}
int rem(int a, int b)
{
    return a % b;
}
int main(int argc, char const *argv[])
{
    int(*pbuf[3])(int ,int) = {sub, div, rem};
    int a, b;
    fpbuf fbuf={sub, div, rem};
    printf("请输入:");
    scanf("%d%d", &a, &b);
    printf("sub=%d\n", pbuf[0](a, b));
    printf("div=%d\n", fbuf[1](a, b));
    printf("rem=%d\n", pbuf[2](a, b));
    return 0;
}

  • 回调函数(钩子函数)

调用一个函数,这个函数传递函数指针,被调用的的这个函数通过这个函数指针进行调用其他函数,我们把这种方式称为回调

eg:

将两数相加设计成回调函数:

callback.c:

#include <stdio.h>
#include "callback.h"
int callback(p_callback_add func)
{
    int a = 10, b = 20;
    return func(10, 20);
}

callback.h:
 

typedef int ( (*p_callback_add)(int,int));//改别名
extern int callback(p_callback_add func);

main.c:

#include <stdio.h>
#include "callback.h"
int add(int a,int b)
{
    return a+b;
}
int main(int argc, char const *argv[])
{
    int ret=callback(add);
    printf("ret=%d\n",ret);
    return 0;
}

  • 内联函数

内联函数的核心在于用空间换时间,当多次调用同一个函数时,很耗时间,这时就可将被调用的函数设置为内联函数以节省调用时间,加快运行效率。

inline注意事项

  • 内联函数在头文件实现,其它函数不要在头文件实现

  • 函数声明和函数实现都需要添加关键字inline,如果函数声明没有添加extern 和 inline 关键字,会报错

demo:
 通过内联函数实现获取两个数的最小值:

main.h:

int min(int a, int b);
inline int min(int a, int b)
{
    return a < b ? a : b;
}

main.c:

#include <stdio.h>
#include "main.h"
int main()
{
    int a=27,b=23;
    printf("min=%d\n",min(a,b));
    printf("min=%d\n",min(a,b));
    printf("min=%d\n",min(a,b));
    printf("min=%d\n",min(a,b));
    return 0;
}

  • 递归函数

概念:如果一个函数内部,包含了对自身的调用,则该函数称为递归函数。

note:

递归函数注意要有明显的结束条件,不然函数会进入无限递归,结束不了。

习题:
幂运算问题:

首先可以写一个非递归实现幂运算:
 

#include <stdio.h>
int main()
{
    int x, n;
    int ret = 1;
    int count = 0;//用以计数与幂指数比较
    printf("请输入:");
    scanf("%d%d", &x, &n);
    if (n == 0)
    {
        ret = 1;
    }
    else
    {
        while (1)
        {

            ret *= x;
            count++;
            if (count == n)
                break;
        }
    }

    printf("ret=%d\n", ret);
    return 0;
}

递归实现幂运算:
 

#include <stdio.h>
float Pow(int x, int n)
{

    if (x == 1 || n == 0)//结束条件
        return 1;
    else if (n == 1 || x == 1)//特殊情况
        return x;
    else
        return x * Pow(x, n - 1);
}
int main()
{
    int x, n;
    float ret;
    printf("请输入:");
    scanf("%d%d", &x, &n);
    if (n < 0)
    {
        ret = 1 / Pow(x, -n);
    }
    else
        ret = Pow(x, n);
    printf("ret=%.4f\n", ret);
    return 0;
}

通过对比递归比非递归,可以发现,用递归实现幂运算更为方便。

  • 10
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值