代码改变世界

C 语言编程 — 函数

2020-04-02 22:38  云物互联  阅读(288)  评论(0编辑  收藏  举报

目录

前文列表

程序编译流程与 GCC 编译器
C 语言编程 — 基本语法
C 语言编程 — 基本数据类型
C 语言编程 — 变量与常量
C 语言编程 — 运算符
C 语言编程 — 逻辑控制语句

函数

函数的本质就是针对变量的操作过程,同时可能也会改变当前程序的状态。它接受多个输入值,计算并返回一个输出值。

函数大体上分为 3 类:

  1. 主函数:每个 C 程序都至少有一个 main()
  2. 内置函数:由 C 标准库提供。例如,strcat() 用来连接两个字符串,memcpy() 用来复制内存到另一个位置。
  3. 自定义函数

函数的声明

函数声明就是告诉 C 编译器函数的名称、返回类型和参数以及如何调用函数。函数的实际主体可以单独定义,但当你在一个源文件中定义函数且在另一个源文件中调用函数时,函数声明是必需的。

声明函数时,首先将返回值的类型写在前面,后面紧跟函数的名字,而后的一对圆括号里面包裹函数的输入参数,参数之间用 , 进行分割。

函数声明包括以下几个部分:

return_type function_name(parameter list);

在函数声明中,参数的名称并不重要,只有参数的类型是必需的,因此下面两种都是有效的声明:

int max(int num1, int num2);
int max(int, int);

函数的定义

函数体部分紧跟其后,

函数定义除了定义函数的名称、返回类型、形参列表之外,最重要的是编写函数的主题,即函数体。包裹在 {} 里,里面包含了函数执行的所有语句,语句之间使用 ; 分隔。return 语句用来结束函数的执行,并返回一个值。

C 程序中函数的结构如下:

return_type function_name(parameter list){
   body of the function
}
  • 返回类型:一个函数可以返回一个值,返回类型定义了函数返回的值的数据类型。有些函数不需要返回值,在这种情况下,return_type 是关键字 void
  • 函数名(函数标识符):函数名和参数列表一起构成了函数签名。
  • 形式参数:形参就像是占位符。当函数被调用时,向形参传递一个值,而这个值被称为实际参数。形参列表包括函数参数的类型、顺序、数量。形参是可选的,即形参列表可以为。
  • 函数主体:包含了一组定义函数执行任务的语句。
/* 函数返回两个数中较大的那个数 */
int max(int num1, int num2) 
{
   /* 局部变量声明 */
   int result;
 
   if (num1 > num2)
      result = num1;
   else
      result = num2;
 
   return result; 
}

函数的形参与实参

如果函数要使用参数,则必须声明接受参数值的变量。这些变量称为函数的形式参数。形式参数就像函数内的其他局部变量,在进入函数时被创建,退出函数时被销毁。当调用函数时,有两种向函数传递参数的方式:

  • 值传递:该方法把参数的实际值复制给函数的形式参数。在这种情况下,修改函数内的形式参数不会影响实际参数。
  • 引用传递:通过指针传递方式,形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作。

值传递

默认情况下,C 语言使用传值调用方法来传递参数。一般来说,这意味着函数内的代码不会改变用于调用函数的实际参数。

  • swap.c
/* 函数定义 */
void swap(int x, int y)
{
   int temp;

   temp = x; /* 保存 x 的值 */
   x = y;    /* 把 y 赋值给 x */
   y = temp; /* 把 temp 赋值给 y */
  
   return;
}
  • main.c
#include <stdio.h>
 
/* 函数声明 */
void swap(int x, int y);
 
int main ()
{
   /* 局部变量定义 */
   int a = 100;
   int b = 200;
 
   printf("交换前,a 的值: %d\n", a );
   printf("交换前,b 的值: %d\n", b );
 
   /* 调用函数来交换值 */
   swap(a, b);
 
   printf("交换后,a 的值: %d\n", a );
   printf("交换后,b 的值: %d\n", b );
 
   return 0;
}


引用传递

通过引用传递方式,形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作。传递指针可以让多个函数访问指针所引用的对象,而不用把对象声明为全局可访问。

  • swap.c
/* 函数定义 */
void swap(int *x, int *y)
{
   int temp;
   temp = *x;    /* 保存地址 x 的值 */
   *x = *y;      /* 把 y 赋值给 x */
   *y = temp;    /* 把 temp 赋值给 y */
  
   return;
}
  • main.c
#include <stdio.h>
 
/* 函数声明 */
void swap(int *x, int *y);
 
int main ()
{
   /* 局部变量定义 */
   int a = 100;
   int b = 200;
 
   printf("交换前,a 的值: %d\n", a );
   printf("交换前,b 的值: %d\n", b );
 
   /* 调用函数来交换值
    * &a 表示指向 a 的指针,即变量 a 的地址
    * &b 表示指向 b 的指针,即变量 b 的地址
   */
   swap(&a, &b);
 
   printf("交换后,a 的值: %d\n", a );
   printf("交换后,b 的值: %d\n", b );
 
   return 0;
}

可变长形参列表

有时候我们需要函数带有可变数量的参数,而不是预定义数量的参数。C 语言提供了 stdarg.h 头文件,该文件提供了实现可变参数功能的函数和宏。

int func(int, ... ) {}
 
int main() {
   func(1);
   func(1, 2);
   func(1, 2, 3);
   return 0;
}
  • 定义一个函数,int 形参代表了要传递的可变参数的总数,... 可变长形参运算符标识函数的形参数量是可变的。
  • 在函数定义中创建一个 va_list 类型变量,该类型是在 stdarg.h 头文件中定义的。
  • 使用 int 参数和 va_start 宏来初始化 va_list 变量为一个参数列表。宏 va_start 是在 stdarg.h 头文件中定义的。
  • 使用 va_arg 宏和 va_list 变量来访问参数列表中的每个项。
  • 使用宏 va_end 来清理赋予 va_list 变量的内存。
#include <stdio.h>
#include <stdarg.h>

double Avg(int num, ...) {
    va_list valist;
    double sum = 0.0;
    int i;

    /* 初始化 valist,数量为 num 个 */
    va_start(valist, num);
    for (i = 0; i < num; i++) {
        /* 访问所有赋给 valist 的参数 */
        sum += va_arg(valist, int);
    }
    /* 清理为 valist 保留的内存 */
    va_end(valist);

    return sum / num;

}

int main() {
    printf("Average of 2, 3, 4, 5 = %f\n", Avg(4, 2, 3, 4, 5));
    printf("Average of 5, 10, 15 = %f\n", Avg(3, 5, 10, 15));
    return 0;
}

运行:

$ ./main
Average of 2, 3, 4, 5 = 3.500000
Average of 5, 10, 15 = 10.000000

函数的调用

定义 C 函数时,会定义函数做什么,然后通过调用函数来完成已定义的任务。当程序调用函数时,程序控制权会转移给被调用的函数。被调用的函数执行已定义的任务,当函数的返回语句被执行时,或到达函数的结束括号时,会把程序控制权交还给主程序。

调用函数时,传递所需参数,如果函数返回一个值,则可以存储返回值。

#include <stdio.h>
 
/* 函数声明 */
int max(int num1, int num2);
 
int main ()
{
   /* 局部变量定义 */
   int a = 100;
   int b = 200;
   int ret;
 
   /* 调用函数来获取最大值 */
   ret = max(a, b);
 
   printf( "Max value is : %d\n", ret );
 
   return 0;
}
 
/* 函数返回两个数中较大的那个数 */
int max(int num1, int num2) 
{
   /* 局部变量声明 */
   int result;
 
   if (num1 > num2)
      result = num1;
   else
      result = num2;
 
   return result; 
}

函数的指针

函数名本质就是一个地址,也就是一个指针。函数指针是指向函数的指针类型变量,函数指针可以像一般函数一样,用于调用函数、传递参数。也就是说,函数指针其实可以看做就是一个函数的 “别名”。

函数指针很方便,可以作为实参传入函数,在函数体中,仅仅通过指针就可以调用函数,这也是为了提高代码的效率。函数指针和普通指针差不多,主要的区别是函数指针指向了特定的函数。

声明一个函数指针:

typedef int (*fun_ptr)(int, int);

e.g.

#include <stdio.h>
 
int max(int x, int y){
    return x > y ? x : y;
}
 
int main(void){
    /* 定义并初始化一个函数指针 */
    int (*p)(int, int) = &max;  // 可以省略地址运算符 &
    int a, b, c, d;
 
    printf("Input three numbers:");
    scanf("%d %d %d", &a, &b, &c);

    d = p(p(a, b), c); 
    printf("MAX: %d\n", d);
    
    return 0;
}

回调函数

回调函数就是将函数指针作为实参传入到某个函数,即:回调函数就是一个通过函数指针调用的函数。

#include <stdlib.h>
#include <stdio.h>

void populate_array(int *array, size_t arraySize, int (*getNextValue)(void)) {

    size_t i;

    for(i = 0; i < arraySize; i++) {
        array[i] = getNextValue();
    }
}

int getNextRandomValue(void) {
    return rand();
}

int main() {
    int i;
    int myarray[10];

    populate_array(myarray, 10, &getNextRandomValue);  // & 可以省略,因为函数名的本质就是一个指针

    for(i = 0; i < 10; i++) {
        printf("%d ", myarray[i]);
    }

    printf("\n");
    return 0;
}

递归函数

递归,指的是在函数的定义中调用函数自身

在这里插入图片描述

void recursion()
{
   statements;
   ... ... ...
   recursion(); /* 函数调用自身 */
   ... ... ...
}
 
int main()
{
   recursion();
}

但在使用递归时,一定要注意编写退出条件,否则会进入死循环。递归函数在解决许多数学问题上起了至关重要的作用,比如计算一个数的阶乘、生成斐波那契数列,等等。

数的阶乘

#include <stdio.h>
 
double factorial(unsigned int i) {
   if(i <= 1)
   {
      return 1;
   }
   return i * factorial(i - 1);
}

int main() {
    int i = 15;
    printf("%d 的阶乘为 %f\n", i, factorial(i));
    return 0;
}

斐波那契数列

#include <stdio.h>
 
int fibonaci(int i) {
   if (i == 0) {
      return 0;
   }
   if (i == 1) {
      return 1;
   }
   return fibonaci(i-1) + fibonaci(i-2);
}
 
int main() {
    int i;
    for (i = 0; i < 10; i++) {
       printf("%d\t\n", fibonaci(i));
    }
    return 0;
}

构造函数(Constructor)

构造函数 ,是一种特殊的方法。主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值。例如:

  • Java 中的 new 运算符
  • Python 中的 __init__ 函数

通常的,一个类中只有一个构造函数;特别的,一个类可以有多个构造函数 ,可根据其参数个数的不同或参数类型的不同来区分它们即构造函数的重载。

析构函数(Destructor)

析构函数与构造函数相反,当对象结束其生命周期,如对象所在的函数已调用完毕时,系统自动执行析构函数。析构函数往往用来做 “清理善后” 的工作。

以 C++ 为例:析构函数名也应与类名相同,只是在函数名前面加一个位取反符~,例如:~stud( ),以区别于构造函数。它不能带任何参数,也没有返回值(包括 void 类型)。只能有一个析构函数,不能重载。如果用户没有编写析构函数,编译系统会自动生成一个缺省的析构函数(即使自定义了析构函数,编译器也总是会为我们合成一个析构函数,并且如果自定义了析构函数,编译器在执行时会先调用自定义的析构函数再调用合成的析构函数),它也不进行任何操作。所以许多简单的类中没有用显式的析构函数。