函数 -- C / C++




概述


函数分类

C 程序是由函数组成的, 我们写的代码都是由主函数 main() 开始执行的. 函数是 C 程序的基本模块, 是用于完成特定任务的程序代码单元.


从函数定义的角度看, 函数可分为系统函数和用户自定义函数两种:

  • 系统函数, 即库函数: 标准 C库, libc, 这是由编译系统提供的, 用户不必自己定义这些函数, 可以直接使用它们, 如我们常用的打印函数 printf().
    • 1. 引入头文件 -- 声明函数
    • 2. 根据函数原型调用
  • 用户定义函数: 用以解决用户的专门需要.


函数的作用

封装

提高代码的复用率

提高程序模块化组织性


函数的定义


包含

  • 函数原型 (返回值类型、函数名、形参列表) 和
  • 函数体 (大括号一对, 具体代码实现)


int add (int a, int b, int c) 
{
    return a + b + c;
}



函数的调用


包含 函数名(实参列表);

int ret = add(10, 4, 28);

函数在调用时, 传参必须严格按照形参填充. (参数的个数、类型、顺序必须一样)



函数的声明


要求在函数调用之前, 编译必须见过函数定义. 否则, 需要函数声明.


函数声明包含 函数原型 (返回值类型、函数名、形参列表)  + ‘;’


~ cat test.c
#include <stdio.h>

int add(int a, int b)
{
    return a + b;
}

int main(void)
{
    printf("%d\n", add(3, 5));

    return 0;
}
~ gcc test.c -o test && ./test
8


~ cat test.c
#include <stdio.h>

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

int main(void)
{
    printf("%d\n", add(3, 5));

    return 0;
}

int add(int a, int b)
{
    return a + b;
}
~ gcc test.c -o test && ./test
8


~ cat test.c
#include <stdio.h>

int main(void)
{
    printf("%d\n", add(3, 5));

    return 0;
}

int add(int a, int b)
{
    return a + b;
}
~ gcc test.c -o test && ./test
test.c:5:20: error: implicit declaration of function 'add' is invalid in C99 [-Werror,-Wimplicit-function-declaration]
    printf("%d\n", add(3, 5));
                   ^
1 error generated.



main 函数与 exit 函数


return: 返回当前函数调用

exit() 函数: 退出当前程序


指针和函数


栈帧

p先有栈, 后有栈帧


当函数调用时, 系统会在 stack 空间上申请一块内存区域, 用来供函数调用, 主要存储形参和局部变量.

当函数调用结束, 这块在 stack 空间中申请的内存区域会自动被释放.


传值和传址


传值: 函数调用期间, 实参将自己的值, 拷贝一份给形参.

传址: 函数调用期间, 实参将自己的地址, 拷贝一份给形参.


传值


~ cat test.c
#include <stdio.h>

int swap(int a, int b);

int main()
{
    int m = 23;
    int n = 57;

    printf("--before-- m = %d, n = %d\n", m, n);

    // 函数调用
    swap(m, n);

    printf("--after-- m = %d, n = %d\n", m, n);

    return 0;
}

// 函数定义
int swap(int a, int b)
{
    int tmp = 0;

    tmp = a;
    a = b;
    b = tmp;

    return 0;
}
~ gcc test.c -o test && ./test
--before-- m = 23, n = 57
--after-- m = 23, n = 57


程序从上往下执行:

  • main 函数是入口函数, 执行 main 函数时, 会在栈空间中为 main 函数创建一个栈帧, 随着代码的执行, 局部变量 m 和 n 会被存入到 main 函数的栈桢中.
  • 当执行到 swap 函数时, 会紧随 main 函数栈帧之后创建 swap 函数的栈帧, 首先会将形参 a 和 b 存储在 swap 函数的栈帧中, 然后通过值拷贝的方式, 将实参 m 和 n 的值分别拷贝给形参 a 和 b. 随着 swap 函数的顺序执行, 将局部变量 tmp 也存储到 swap 函数的栈帧中. 然后根据函数逻辑修改 tmp、a、b 的值, 最后返回 0 退出 swap 函数, swap 函数退出后, swap 函数的栈帧空间也随之被释放.
  • 程序又回到 main 函数中继续向下执行.

由于在 swap 函数中, m 和 n 的值没有被修改, 所以 m 和 n 的值在 swap 函数执行前后没有发生改变.




传址


~ cat test.c
#include <stdio.h>

int swap(int *, int *);

int main()
{
    int m = 23;
    int n = 57;

    printf("--before-- m = %d, n = %d\n", m, n);

    // 函数调用
    swap(&m, &n);

    printf("--after-- m = %d, n = %d\n", m, n);

    return 0;
}

// 函数定义
int swap(int *a, int *b)
{
    int tmp = 0;

    tmp = *a;
    *a = *b;
    *b = tmp;

    return 0;
}
~ gcc test.c -o test && ./test
--before-- m = 23, n = 57
--after-- m = 57, n = 23


程序从上往下执行:

  • main 函数是入口函数, 执行 main 函数时, 会在栈空间中为 main 函数创建一个栈帧, 随着代码的执行, 局部变量 m 和 n 会被存入到 main 函数的栈桢中.
  • 当执行到 swap 函数时, 会紧随 main 函数栈帧之后创建 swap 函数的栈帧, 首先会将形参 a 和 b 存储在 swap 函数的栈帧中, 然后通过地址拷贝的方式, 将实参 m 和 n 的地址分别拷贝给形参 a 和 b. 随着 swap 函数的顺序执行, 将局部变量 tmp 也存储到 swap 函数的栈帧中. 然后根据函数逻辑修改 tmp、a、b 的值, 最后返回 0 退出 swap 函数, swap 函数退出后, swap 函数的栈帧空间也随之被释放.
  • 程序又回到 main 函数中继续向下执行.

由于在 swap 函数中, m 和 n 的值被修改, 所以 m 和 n 的值在 swap 函数执行前后发生改变.


一级指针做函数参数时的输入输出特性



指针做函数参数

函数调用时, 传递有效的地址值


数组做函数参数

函数传参传递的不再是整个数组, 而是数组的首地址 (一个指针).

所以, 当整型数组做函数参数时, 我们通常在函数定义中封装两个参数, 一个表示数组首地址, 一个表示数组元素个数.


语法:

void bubble_sort(int arr[10]) 
== void bubble_sort(int arr[]) 
== void bubble_sort(int *arr)


~ cat test.c
#include <stdio.h>

void bubble_sort(int arr[], int n) // void bubble_sort(int *arr, int n)
{
    // sizeof(arr) 是函数首地址的大小, 也就是指针的大小, 在 64 位系统中大小为 8
    // sizeof(arr[0]) 是函数首地址中存储的数据的大小, 数据是 int 类型, 所以为 4
    int len = sizeof(arr) / sizeof(arr[0]);

    printf("bubble_sort: len = %d\n", len);
    printf("bubble_sort: sizeof(arr) = %lu\n", sizeof(arr));
    printf("bubble_sort: sizeof(arr[0]) = %lu\n", sizeof(arr[0]));

    for (int i=0; i<n-1; i++)
    {
        for (int j=0; j<n-1-i; j++)
        {
            if (arr[j] > arr[j+1])
            {
                int temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
            }
        }
    }
}

int main()
{
    int arr[] = {5,12,6,15,23,65,1,7};

    printf("main: sizeof(arr) = %lu\n", sizeof(arr));

    int n = sizeof(arr) / sizeof(arr[0]);

    bubble_sort(arr, n);

    for (int i=0; i<n; i++)
    {
        printf("%d ", arr[i]);
    }

    printf("\n");

    return 0;
}
~ gcc test.c -o test && ./test
test.c:5:21: warning: sizeof on array function parameter will return size of 'int *' instead of 'int []' [-Wsizeof-array-argument]
    int len = sizeof(arr) / sizeof(arr[0]);
                    ^
test.c:3:22: note: declared here
void bubble_sort(int arr[], int n) // void bubble_sort(int *arr, int n)
                     ^
test.c:5:27: warning: 'sizeof (arr)' will return the size of the pointer, not the array itself [-Wsizeof-pointer-div]
    int len = sizeof(arr) / sizeof(arr[0]);
              ~~~~~~~~~~~ ^
test.c:3:22: note: pointer 'arr' declared here
void bubble_sort(int arr[], int n) // void bubble_sort(int *arr, int n)
                     ^
test.c:8:54: warning: sizeof on array function parameter will return size of 'int *' instead of 'int []' [-Wsizeof-array-argument]
    printf("bubble_sort: sizeof(arr) = %lu\n", sizeof(arr));
                                                     ^
test.c:3:22: note: declared here
void bubble_sort(int arr[], int n) // void bubble_sort(int *arr, int n)
                     ^
3 warnings generated.
main: sizeof(arr) = 32
bubble_sort: len = 2
bubble_sort: sizeof(arr) = 8
bubble_sort: sizeof(arr[0]) = 4
1 5 6 7 12 15 23 65


指针做函数返回值

指针作函数返回值时, 不能返回函数中局部变量的地址值.


~ cat test.c
#include <stdio.h>

int m = 100;

int *test_func()
{
    return &m;
}

int main()
{
    int *ret = NULL;

    ret = test_func();

    printf("%d\n", *ret);

    return 0;
}
~ gcc test.c -o test && ./test
100


局部变量在函数调用结束后, 会随着函数栈帧的消失而被释放掉.

~ cat test.c
#include <stdio.h>

int *test_func()
{
    int n = 20;
    return &n;
}

int main()
{
    int *ret = NULL;

    ret = test_func();

    printf("%d\n", *ret);

    return 0;
}
~ gcc test.c -o test && ./test
test.c:6:13: warning: address of stack memory associated with local variable 'n' returned [-Wreturn-stack-address]
    return &n;
            ^
1 warning generated.
20



数组做函数返回值


C 语言中, 不允许.

只能写成指针形式.


二级指针函数参数的输入输出特性



输入特性


~ cat test.c
#include <stdio.h>
#include <stdlib.h>

void printArray(int **pArray, int len)
{
    for (int i=0; i<len; i++)
    {
        printf("%d\n", *pArray[i]);
    }
}

int main()
{
    int **pArray = malloc(sizeof(int *) * 5);

    // 在栈上创建 5 个数据
    int a1 = 10;
    int a2 = 20;
    int a3 = 30;
    int a4 = 40;
    int a5 = 50;

    pArray[0] = &a1;
    pArray[1] = &a2;
    pArray[2] = &a3;
    pArray[3] = &a4;
    pArray[4] = &a5;

    // 打印数组
    printArray(pArray, 5);

    if (pArray != NULL)
    {
        free(pArray);
        pArray = NULL;
    }

    return 0;
}
~ gcc test.c -o test && ./test
10
20
30
40
50



~ cat test.c
#include <stdio.h>
#include <stdlib.h>

void printArray(int **pArray, int len)
{
    for (int i=0; i<len; i++)
    {
        printf("%d\n", *pArray[i]);
    }
}

void freeSpace(int **pArray, int len)
{
    for (int i=0; i<len; i++)
    {
        free(pArray[i]);
        pArray[i] = NULL;
    }
}

int main()
{
    // 创建在栈区
    int *pArray[5];

    for (int i=0; i<5; i++)
    {
        pArray[i] = malloc(4);
        *(pArray[i]) = 10 + i;
    }

    // 打印数组
    printArray(pArray, 5);

    // 释放堆区
    freeSpace(pArray, 5);

    return 0;
}
~ gcc test.c -o test && ./test
10
11
12
13
14


输出特性

被调函数分配内存, 主调函数使用


~ cat test.c
#include <stdio.h>
#include <stdlib.h>

void allocateSpace(int **p)
{
    int *temp = malloc(sizeof(int) * 10);
    for (int i=0; i<10; i++)
    {
        temp[i] = 100 + i;
    }

    *p = temp;
}

void printArray(int **p, int len)
{
    for (int i=0; i<len; i++)
    {
        printf("%d\n", (*p)[i]);
    }
}

void freeSpace(int *p)
{
    if (p != NULL)
    {
        free(p);
        p = NULL;
    }
}

int main()
{
    int *p = NULL;
    allocateSpace(&p);

    printArray(&p, 5);

    // 同级指针不能将 p 修改为空指针
    freeSpace(p);

    if (p == NULL)
    {
        printf("空指针\n");
    }
    else
    {
        printf("野指针\n");
    }

    p = NULL;

    return 0;
}
~  gcc test.c -o test && ./test
100
101
102
103
104
野指针


~ cat test.c
#include <stdio.h>
#include <stdlib.h>

void allocateSpace(int **p)
{
    int *temp = malloc(sizeof(int) * 10);
    for (int i=0; i<10; i++)
    {
        temp[i] = 100 + i;
    }

    *p = temp;
}

void printArray(int **p, int len)
{
    for (int i=0; i<len; i++)
    {
        printf("%d\n", (*p)[i]);
    }
}

void freeSpace(int **p)
{
    if (*p != NULL)
    {
        free(*p);
        *p = NULL;
    }
}

int main()
{
    int *p = NULL;
    allocateSpace(&p);

    printArray(&p, 5);

    // 高级指针可以将 p 修改为空指针
    freeSpace(&p);

    if (p == NULL)
    {
        printf("空指针\n");
    }
    else
    {
        printf("野指针\n");
    }

    return 0;
}
~ gcc test.c -o test && ./test
100
101
102
103
104
空指针




const 修改函数参数


调用函数时, 参数的传递方式如果是值传递, 会重新从实参中拷贝一份给形参

如果同时调用 n 次, 就会在内存中存在 n 份实参的拷贝

~  cat test.c
#include <stdio.h>
#include <stdlib.h>

struct Person
{
    char name[64]; // 0 - 63
    int age;       // 64 - 67
    int id;        // 68 - 71
    double score;  // 72 - 79
};

void showPerson(struct Person p)
{
    printf("name: %s, age: %d, id: %d, score: %f\n", p.name, p.age, p.id, p.score);
}

int main()
{
    struct Person p = {"point", 18, 12, 100};

    showPerson(p);

    return 0;
}
~ gcc test.c -o test && ./test
name: point, age: 18, id: 12, score: 100.000000


将 struct Person p 改为 struct Person *p 节省资源

调用函数时, 如果传引用, 则只会拷贝实参的地址给形参, 而指针的大小是固定的 (4 bytes)

如果同时调用 n 次, 就会在内存中存在 n 份实参地址的拷贝, 相对于实参数据很大的场景来说, 内存得到优化

~ cat test.c
#include <stdio.h>
#include <stdlib.h>

struct Person
{
    char name[64]; // 0 - 63
    int age;       // 64 - 67
    int id;        // 68 - 71
    double score;  // 72 - 79
};

// 将 struct Person p 改为 struct Person *p 节省资源
// void showPerson(struct Person p)
void showPerson(struct Person *p)
{
    //printf("name: %s, age: %d, id: %d, score: %f\n", p.name, p.age, p.id, p.score);
    printf("name: %s, age: %d, id: %d, score: %f\n", p->name, p->age, p->id, p->score);
}

int main()
{
    struct Person p = {"point", 18, 12, 100};

    //showPerson(p);
    showPerson(&p);

    return 0;
}
~ gcc test.c -o test && ./test
name: point, age: 18, id: 12, score: 100.000000


传引用给函数, 若函数内存修改了引用的数据, 函数外部的原始数据也会被修改

~ cat test.c
#include <stdio.h>
#include <stdlib.h>

struct Person
{
    char name[64]; // 0 - 63
    int age;       // 64 - 67
    int id;        // 68 - 71
    double score;  // 72 - 79
};

// 将 struct Person p 改为 struct Person *p 节省资源
// void showPerson(struct Person p)
void showPerson(struct Person *p)
{
    p -> age = 100;
    //printf("name: %s, age: %d, id: %d, score: %f\n", p.name, p.age, p.id, p.score);
    printf("name: %s, age: %d, id: %d, score: %f\n", p->name, p->age, p->id, p->score);
}

int main()
{
    struct Person p = {"point", 18, 12, 100};

    printf("call showPerson before: p.age = %d\n", p.age);

    //showPerson(p);
    showPerson(&p);

    printf("call showPerson after: p.age = %d\n", p.age);

    return 0;
}
~ gcc test.c -o test && ./test
call showPerson before: p.age = 18
name: point, age: 100, id: 12, score: 100.000000
call showPerson after: p.age = 100


const 修饰形参, 防止误操作

将 struct Person *p 改为 const struct Person *p 节省资源, 且可以防止数据被修改

~ cat test.c
#include <stdio.h>
#include <stdlib.h>

struct Person
{
    char name[64]; // 0 - 63
    int age;       // 64 - 67
    int id;        // 68 - 71
    double score;  // 72 - 79
};

// 将 struct Person p 改为 struct Person *p 节省资源
// 将 struct Person *p 改为 const struct Person *p 节省资源, 且可以防止数据被修改
// void showPerson(struct Person p)
// void showPerson(struct Person *p)
void showPerson(const struct Person *p)
{
    p -> age = 100;
    //printf("name: %s, age: %d, id: %d, score: %f\n", p.name, p.age, p.id, p.score);
    printf("name: %s, age: %d, id: %d, score: %f\n", p->name, p->age, p->id, p->score);
}

int main()
{
    struct Person p = {"point", 18, 12, 100};

    printf("call showPerson before: p.age = %d\n", p.age);

    //showPerson(p);
    showPerson(&p);

    printf("call showPerson after: p.age = %d\n", p.age);

    return 0;
}
~ gcc test.c -o test && ./test
test.c:18:14: error: cannot assign to variable 'p' with const-qualified type 'const struct Person *'
    p -> age = 100;
    ~~~~~~~~ ^
test.c:16:38: note: variable 'p' declared const here
void showPerson(const struct Person *p)
                ~~~~~~~~~~~~~~~~~~~~~^
1 error generated.


递归函数