C语言学习(六)函数

函数最重要的概念就是模块化编程。模块化程序设计。

函数结构:

一个函数的基本结构:
        1、函数声明:声明函数的返回类型、名称和参数类型;
        2、函数定义:编写函数的实际代码实现;
        3、函数调用,早需要的地方调用函数。

声明与定义:

        程序中的声明可以理解为预先告诉编译器实体的存在,如变量,函数等;
        程序中的定义是明确指示编译器实体的意义。

函数示例:

#include <stdio.h>

// 函数声明
int add(int a, int b);

int main() {
    int sum;
    // 函数调用
    sum = add(5, 3);
    printf("Sum: %d\n", sum);
    return 0;
}

// 函数定义
int add(int a, int b) {
    return a + b;
}

函数参数:

        函数参数在本质上与局部变量相同,都是子啊栈上分配空间;
        函数参数的初始值是函数调用时的实参值;

顺序点概念:

        顺序点(Sequence Point) ,它定义了表达式中的某些时刻,在这些时刻之前的所有副作用(如修改变量、IO操作等)都已完成,且这些副作用的结果已经生效。

  • 每条语句结束时是一个顺序点。在这之前的所有副作用都已生效。
  • 逻辑与(&&)和逻辑或(||)运算符的左侧表达式求值完成后是一个顺序点。如果左侧表达式决定了逻辑结果,右侧表达式可能不再求值(短路求值)
  • 条件表达式(?:)的条件部分求值完成后是一个顺序点;
  • 在函数调用中,所有参数的求值和副作用完成后是一个顺序点,然后才开始执行函数体。
  • 逗号运算符的左侧表达式求值完成并应用副作用后是一个顺序点,然后才开始右侧表达式的求值。

函数概念小结:

  1. c语言是一种面向过程的语言;
  2. 函数可以理解为解决问题的步骤;
  3. 函数的实参并没有固定的计算次序;
  4. 顺序点是c语言中变量改变的最晚时机;
  5. 函数定义是参数个返回值的缺省类型为int;

可变参数列表:

        在c语言中可以定义参数可变的函数;参数可变函数的实现依赖于stdarg.h头文件;va_list变量与va_start、va_arg和va_end配合使用能够访问参数值。

示例:

#include <stdio.h>
#include <stdarg.h>

// 定义一个可变参数函数,计算参数的总和
int sum(int count, ...) {
    va_list args;
    int total = 0;

    // 初始化参数列表
    va_start(args, count);

    // 逐个获取参数并累加
    for (int i = 0; i < count; i++) {
        total += va_arg(args, int);
    }

    // 结束参数处理
    va_end(args);

    return total;
}

int main() {
    // 调用可变参数函数
    int result1 = sum(3, 1, 2, 3); // 传递3个参数
    int result2 = sum(5, 10, 20, 30, 40, 50); // 传递5个参数

    printf("Sum of 3 numbers: %d\n", result1);
    printf("Sum of 5 numbers: %d\n", result2);

    return 0;
}
  1. 包含<stdarg.h>头文件。
  2. 使用省略号表示可变参数。
  3. 使用va_list类型变量来存储参数列表。
  4. 使用va_start宏初始化参数列表。
  5. 使用va_arg宏获取参数列表中的参数。
  6. 使用va_end宏结束参数处理。

在示例代码中:
va_list:声明一个变量,用于存储参数列表;
va_start:初始化参数列表。第一个参数是va_list类型的变量,第二个参数是最后一个固定参数,即可变参数之前的那个参数。
va_arg:获取参数列表中的下一个参数。第一个参数是va_list类型的变量,第二个参数是要获取的参数类型。
va_end:结束参数处理,通常在函数返回前调用。


函数 VS 宏

        1、宏是由预处理器直接在程序中替换展开的,编译器不知道宏的存在;
        2、函数是由编译器直接编译的实体,调用行为由编译器决定;
        3、多次使用宏会导致程序代码量增加;
        4、函数是跳转执行的,因此代码量不会增加;
        5、宏的效率比函数要高,因为是直接展开,无调用开销;
        6、函数调用时会创建活动记录,效率不如宏。

宏定义的详细作用请参考前面章节。总之宏的好处是代码替换,但是缺点是不安全且难以调试。


函数的调用行为:

        在程序执行过程中,调用函数会用活动记录,也称为栈帧,这是一个基于栈的数据结构,用于存储函数调用期间所需的所有信息。这些信息包括:
返回地址、参数、局部变量、保存的寄存器、静态链、帧指针。

参考上一节中栈的学习记录,给出一个示例:
在示例中主函数调用函数A,函数A中调用函数B:

#include <stdio.h>

void functionB(int b) {
    int y = b * 2;
    printf("functionB: y = %d\n", y);
}

void functionA(int a) {
    int x = a + 5;
    functionB(x);
}

int main() {
    int n = 10;
    functionA(n);
    return 0;
}

则有:

main 函数调用 functionA

+-----------------+ <--- 栈顶(Stack Top)
|  Return Address |   <--- `main`的返回地址
|  Saved FP       |   <--- `main`的帧指针
|  n = 10         |   <--- `main`的局部变量
+-----------------+

functionA 调用 functionB

+-----------------+ <--- 栈顶(Stack Top)
|  Return Address |   <--- `functionA`的返回地址
|  Saved FP       |   <--- `functionA`的帧指针
|  x = 15         |   <--- `functionA`的局部变量
+-----------------+
|  Return Address |   <--- `functionB`的返回地址
|  Saved FP       |   <--- `functionB`的帧指针
|  b = 15         |   <--- `functionB`的参数
|  y = 30         |   <--- `functionB`的局部变量
+-----------------+

递归函数:

        递归函数是指在函数定义过程中直接或者间接调用自身的函数。

        递归函数的重要思想是分而治之。

        递归必须有一个基准条件,确保递归不会无限进行。当满足基准条件时,递归停止。


函数设计技巧:

        1、不要在函数中使用全局变量,尽量让函数从意义上是一个独立的功能模块;

        2、函数名要能够体现参数的意义;

        3、不要省略返回值的类型,如果函数没有返回值,那么应该声明为void类型;

        4、在函数体的入口处,对参数的有效性进行检查,对指针的检查尤为重要;

        5、语句不可范围指向“栈内存”的“指针”,因为该内存在函数体结束时被自动销毁;这也就是内存管理中的一个错误;

  • 22
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值