C函数高级
发布日期:2021-05-14 23:42:23 浏览次数:25 分类:精选文章

本文共 7216 字,大约阅读时间需要 24 分钟。

C语言高级函数指南

一、函数返回

函数返回值

函数的返回值是指函数被调用后,执行函数体中的程序段所取得的并返回给主调函数的值。

return语法

return 表达式;

或者 return (表达式);

return语句的实现机制

执行return语句时,先计算return后面的表达式的值,再将值返回给主调函数。

函数中允许有多个return语句,但每次调用只能有一个return语句被执行,因此只能返回一个函数值。实际上,函数的右花括号具有return功能。
返回值的数据类型应该与函数原型中返回值的数据类型匹配,若两者不一致,则以函数原型中的返回值类型为准,自动进行类型转换,但可能会出现精度损失。
当遇到return语句时,函数执行将终止,程序控制流将立即返回调用函数。
不返回函数值的函数,可以明确定义为“空类型”,类型说明符为“void”。若要终止函数执行可用“return;”语句。

二、Linux内存管理机制

冯诺依曼体系结构

五个设备:控制器、运算器、存储器、输入设备、输出设备。

三条总线:控制总线、地址总线、数据总线。

Linux内存

地址从高到低的四种虚拟内存:

  • 栈:存储局部变量、参数、函数返回地址等。
  • 堆:也称动态内存分配,由程序员用malloc等函数向系统申请任意指定大小的内存,并由程序员自己调用free等函数来释放内存。
  • 数据段:包括静态存储区和常量区。
  • 代码段(正文段):存储程序的函数代码。
  • 内存的管理

    进程隔离:保护独立的进程,防止互相干涉数据和存储空间。

    进程中使用的地址是虚拟地址,分配有4个G。
    段页式内存管理:进程在虚拟内存中分为代码段、数据段、堆栈段。
    进程在段中由许多固定大小的块组成,这些块称为页。
    虚拟地址由段号、页号、页中偏移量构成。
    虚拟地址和内存中物理地址的动态映射。
    按需调页,消除了进程全部载入内存中。称为LPU。

    三、变量的存储类型和生命周期

    作用域的分类

  • 文件作用域:变量从它定义的位置开始直到这个程序文件的末尾都有效。
  • 函数作用域:变量仅在一个函数中都有效,是一个局部变量。
  • 代码块作用域:变量位于一对花括号{}中(函数体或语句块),从它定义的位置开始到右}括号之间有效。
  • 函数原型作用域:变量出现在函数原型中,这个函数原型只是一个声明而不是定义(没有函数体),那么变量从声明的位置开始到末尾之间有效。
  • 变量的存储类型

    存储类型 变量 说明
    auto 自动变量 局部变量在缺省存储类型的情况下归为自动变量,默认不会初始化,会产生随机值。
    static 静态变量 在程序执行时存在,并且只要整个程序在运行,就可以继续访问该变量,默认会初始化为0且仅初始化一次。可以是全局也可以是局部变量。
    extern 外部变量 作用域是整个程序,包含该程序的各个文件。生存期非常长,它在该程序运行结束后,才释放内存,默认会初始化并仅初始化一次。
    register 寄存器变量 一般变量存储在内存中,而寄存器变量存放在CPU的寄存器中。只能是局部变量,不能是全局变量并且不能加static修饰,默认不会初始化。会占用CPU,所以不推荐使用。

    变量的内存分配方式

  • 静态存储区(属于数据段)上分配:针对全局变量和static变量,程序在编译时编译器就已对其分配好内存地址,这些变量的值在程序运行期间一直存在,直到程序运行结束才会被释放。
  • 栈上分配:针对局部变量(包括参数),在每次调用函数时,系统会动态为局部变量在栈中分配内存空间,在函数调用后这些局部变量占有的内存空间会被自动释放。
  • 堆上分配:或称动态内存分配,由程序员主动使用malloc,free等函数向系统申请指定大小内存或释放,生命周期由程序员决定。
  • 变量的存储方式

  • 静态存储方式:程序在编译时分配的固定存储空间(静态存储区)的方式,程序执行完毕就释放。全局变量和静态变量属于静态存储方式,存放在静态存储区中。
  • 动态存储方式:程序运行期间根据需要进行动态的分配存储空间(动态存储区)的方式。
    包括函数的形参、自动变量(带auto或者不带auto的局部变量)、函数调用时的现场保护和返回地址等。
  • 变量的作用域和生命周期

    变量 作用域 生命周期
    自动变量 一个函数内有效 一个函数,函数调用结束后被释放
    寄存器变量 同自动变量 同自动变量
    静态局部变量 一个函数内有效 整个程序的生命周期,程序运行结束才会被释放
    静态全局变量 一个源文件 同静态局部变量
    全局变量(外部变量) 一个源文件范围,也可以通过extern声明扩展到程序中的多个源文件 同静态局部变量

    链接属性

  • External:存储类型为extern,作用域可以在当前文件和其他文件使用。
  • Internal:存储类型为static,仅限于当前文件或函数使用。
  • None:存储类型为auto,仅限于函数使用。
    static可以修改链接属性,存储类型,作用域和生命周期。
  • 内部函数和外部函数

  • 内部函数:一个函数只能被本文件中的其他函数调用。
    内部函数的声明:static 类型说明符 函数名(型参列表);
    内部函数也称为静态函数,仅限于本文件内调用。
  • 外部函数:函数声明时在函数前面使用extern修饰称为外部函数,省略extern默认就是外部函数。
  • 四、extern

    extern的使用

  • 在一个文件内声明外部变量:在外部变量定义之前若函数想使用该变量,则应在使用之前用extern对该变量作外部变量的声明,那么就可以从使用extern声明处起使用该外部变量。
  • 在多个文件的程序中声明外部变量:一个c程序可以由多个源文件组成,在一个文件中通过extern可以使用另外一個文件中已经定义的外部变量。
    若c程序中的两个源文件都要用到同一个外部变量,那就不能分别在两个文件中各自定义这个外部变量,否则在程序链接时会出现重复定义的错误。正确做法:在一个文件中用extern作为外部变量声明,这样此文件就可以使用该外部变量。
  • extern使用注意点

    • 外部变量值的修改对所有使用该变量的文件都有影响。
    • 编译器遇到extern时,先从本文件中找到外部变量的定义,若找到就在本文件中扩展作用域。若没有找到就在链接时从其他文件中找到外部变量定义,若找到就将作用域扩展到本文件,若找不到按出错处理。

    五、函数栈

    主调函数和被调函数

    • 在函数调用时,被调函数名后面必须有括号。
    • 一个函数只能返回一个值。

    函数栈的调度流程

    • 函数的调用过程实际上是对栈空间的操作过程,调用函数是使用对栈空间来保存信息,过程如下:
    • 建立被调用函数的栈空间
    • 保护调用函数的运行状态和返回地址
    • 传递函数的实参给形参
    • 执行被调用函数的函数体内语句
    • 将控制权或返回值转交给调用函数
    • 函数调用完毕,释放被调用函数的栈空间。
      不同函数的栈区互相独立,函数间通过参数传递、返回值等方式进行数据传递。

    六、递归调用

    递归调用概念

    • 一个函数在它的函数体内调用它自身称为递归调用,这种函数称为递归函数。
    • C语言中支持运行时通过堆栈支持递归函数的实现。
    • 在递归调用中,主调函数又是被调函数。执行递归函数将反复调用其自身,每调一次就进入新的一层。调用太深会造成溢出。
    • 尾部递归最好采用迭代方式以提高效率。

    递归所需特性

  • 存在限制条件,当符合限制条件递归不再继续。
  • 每次递归调用后越来越接近该限制条件。
  • 递归案例

    //将数值转换成字符
    void bin2asc(int value) {
    int temp;
    temp = value / 10;
    if (temp != 0) bin2asc(temp); //递归调用
    putchar(value % 10 + '0');
    }
    int main(void) {
    bin2asc(4267); //返回"4268"
    printf("\n");
    return 0;
    }

    递归计算斐波那契数列

    long fibonacci(int n) {
    if (n < 2) return 1;
    return fibonacci(n - 1) + fibonacci(n - 2);
    }
    long factorial(int n) {
    if (n <= 0) return 1;
    else return n * factorial(n - 1);
    }

    七、函数指针与指针函数

    指针函数

    • 指针函数是指带指针的函数,其本质是一个函数。
    • 指针函数返回类型是某一类型的指针。
    • 指针函数的定义:返回类型标识符 *函数名(形参列表){函数体}

    指针函数案例

    int sum = 0; //也可以作为全局变量
    int *fun(int n) {
    static int sum = 0; //局部静态变量,将sum存储在数据段,所以在函数清空后还得以保存
    int *p = ∑
    int i;
    for (i = 1; i <= n; i++) sum += i;
    return p; //返回的指针指向的是局部变量,函数释放后局部变量会清空,不推荐使用
    }
    int main() {
    int *p_sum = fun(100); //指针赋值操作
    sleep(1); //函数调用完毕,这1秒内fun()函数栈空间被清空,使得其中的值都变随机值。但是若使用static变量则避免了随机值。
    printf("sum:%d\n", *p_sum);
    return 0;
    }

    函数指针

    • C语言在编译时,每一个函数都有一个入口地址,该入口地址就是函数指针。
    • 通过函数指针可以调用它所指向的函数。
    • 函数指针也称函数指针变量。
    • 函数指针的定义和初始化:
      返回类型说明符 (*函数指针变量名)(参数列表);
      函数指针初始化:函数指针变量 = 函数名;

    函数指针调用方法

    int max(int x, int y); //函数声明
    int (*p)(int, int); //函数指针
    p = max; //函数名即为函数入口地址
    result = p(a, b); //函数指针调用所指函数
    //或者
    result = (*p)(a, b);

    回调函数

    • 回调函数就是一个通过函数指针调用的函数。
    • 把函数指针作为参数传递给另一个函数,当这个指针被用来调用其他所指向的函数时,我们就说这是回调函数。
    • 回调函数不是由该函数的实现方(如main函数)直接调用,而是在特定的时间或条件发生时由另外的一方(如下process函数)调用的,用于对该事件或条件进行响应。

    回调函数实现流程

  • 定义一个回调函数(如get_grade函数)。
  • 提供函数调用的另一方(如process函数)在初始化的时候,将回调函数的函数指针(如get_grade)传递给调用者(如process(score, get_grade))。
  • 当特定的事件或条件发生时,调用者使用函数指针调用所指向的回调函数对事件进行处理。
  • 回调函数优点

    • 实现了代码的通用性。可以方便地调用各种算法。

    回调函数案例

    /*文件:callback.h*/
    char process(int score, char (*p)(int));
    /*文件:callback.c*/
    #include "callback.h"
    char process(int score, char (*p)(int)) {
    char result = p(score);
    return result;
    }
    /*文件:callback_test.c*/
    void get_grade(int score) {
    }
    int main(void) {
    int score;
    printf();
    scanf();
    printf("result:%c\n", process(score, get_grade));
    }

    回调函数排序算法

    /*文件:callback_sort.h*/
    extern void data_sort(int data[], int n, void (*p)(int*, int), void (*out)(int*, int));
    /*文件:callback_sort.c*/
    void data_sort(int data[], int n, void (*p)(int*, int), void (*out)(int*, int)) {
    p(data, n);
    out(data, n);
    }
    /*文件:callback_sort_test.c*/
    #include
    #include "callback_sort.h"
    void bubble_sort(int *p, int n) {
    int i, j;
    for (i = 0; i < n - 1; i++) {
    for (j = n - 1; j > i; j--) {
    if (*p + j < *p + j - 1) {
    int temp;
    temp = *p + j;
    *p + j = *p + j - 1;
    *p + j - 1 = temp;
    }
    }
    }
    }
    void selection_sort(int *p, int n) {
    int i, j;
    for (i = 0; i < n - 1; i++) {
    // 选择最小的一个数到排好序的数列前面
    }
    }

    八、可变参数列表

    可变参数列表的概念

    • 参数个数可变的函数,如scanf()其函数原型:
      int scanf(const char* format, ...);
      除了参数format固定外,后面跟的参数个数和类型是可变的(用三个点"..."做参数占位符)。
    • “…”称为可变参数列表,可以用来接受个数和类型不确定的实参。

    可变参数列表的实现

    通过三个宏(va_start, va_arg, va_end)和一个类型(va_list)实现,它们都定义在头文件stdarg.h中。

    可变参数列表的使用

  • 宏va_start的原型:void va_start(va_list ap, paramN)

    参数:va_list:存储参数的类型;ap:可变参数列表地址;paramN:确定的参数。
    功能:初始化可变参数列表。

  • 宏va_arg的原型:type va_arg(va_list ap, type)

    功能:返回下一个参数的值。

  • 宏va_end的原型:type va_end(va_list ap, type)

    功能:关闭初始化列表(将可变参数列表清空)。

  • 使用方式:

    • 用va_start初始化可变参数列表,用va_arg逐个获取参数的值,最后用va_end将可变参数列表清空。
  • 可变参数列表的实现方式

    #include 
    #include
    float average(int n_values, ...) {
    va_list vary_arg;
    int count;
    float sum = 0;
    va_start(vary_arg, n_values);
    for (count = 0; count < n_values; count++) {
    float value = va_arg(vary_arg, float);
    sum += value;
    }
    va_end(vary_arg);
    return sum / n_values;
    }

    使用可变参数列表实现平均值计算

    #include 
    #include
    float average2(int n_values, ...) {
    int *p = &n_values;
    float sum = 0.0f;
    int i;
    for (i = 1; i <= n_values; i++) {
    float value = *p++;
    sum += value;
    }
    return sum / n_values;
    }

    九、函数指针数组

    函数指针数组概念

    • 数组元素是函数指针的数组称为函数指针数组,也称为__转移表__。

    函数指针数组的定义和初始化

    • 返回类型说明符(*函数指针数组名[])(参数列表)={函数指针/函数名1,……,函数指针/函数名n}

    函数指针数组的使用

    • 函数指针数组名;
    • 或者(*函数指针数组名[下标])(实参列表);

    函数指针数组的应用场景

  • 通过使用switch语句可以获得类似的效果,但是使用函数指针数组可以有更大的灵活性,因为数组元素可以在程序运行时发生改变。
  • 可应用在命令菜单的选择处理。
  • 函数指针数组的示例

    int (*p[])(int); //这是一个函数指针数组
    int (*p)[4]; //这是一个数组指针,或称行指针
    int (*p)(int); //这是一个函数指针
    int *p(int); //这是一个指针函数声明
    上一篇:2021-03-23 循环运用
    下一篇:C语言指针基础

    发表评论

    最新留言

    不错!
    [***.144.177.141]2025年04月09日 19时01分46秒