首页 > 编程笔记 > C语言笔记 阅读:21

C语言指针作为函数参数详解(附带实例)

在 C 语言程序中,整型变量、实型变量、字符型变量、数组名和数组元素等均可作为函数参数。此外,指针型变量也可以作为函数参数。

看一个例子,用指针做函数参数,交换两个变量的值。在主函数中,调用自定义函数,利用指针将两个数交换,具体代码如下:
#include <stdio.h>
void swap(int *a, int *b)  /* 自定义 swap 函数,形参为两个指针 */
{
    int tmp;
    tmp = *a;
    *a = *b;
    *b = tmp;
}

int main()
{
    int x, y;
    int *p_x, *p_y;
    printf("请输入两个数:\n");
    scanf("%d", &x);  /* 输入两个数 */
    scanf("%d", &y);
    p_x = &x;
    p_y = &y;
    swap(p_x, p_y);  /* 调用 swap */
    printf("x=%d\n", x);
    printf("y=%d\n", y);
    return 0;
}
程序运行结果为:

请输入两个数:
3 5
x=5
y=3

swap() 函数是用户自定义的函数,在 main() 函数中调用该函数交换变量 a 和 b 的值,swap() 函数的两个形参被传入了两个地址值,也就是传入了两个指针变量。

在 swap() 函数的函数体内使用整型变量 tmp 作为中间变量,将两个指针变量所指向的数值进行交换。在 main() 函数内首先获取输入的两个数值,分别传递给变量 x 和 y,调用 swap() 函数将变量 x 和 y 的数值互换。

如果 swap() 函数的形参不是指针而是普通整型,调用 swap() 函数后能实现两数互换吗?将前述程序改成如下形式:
#include <stdio.h>
void swap(int a, int b)  /* 自定义 swap 函数,形参为两个整数 */
{
    int tmp;
    tmp = a;
    a = b;
    b = tmp;
}

int main()
{
    int x, y;
    printf("请输入两个数:\n");
    scanf("%d", &x);
    scanf("%d", &y);
    swap(x, y);  /* 调用 swap 函数 */
    printf("x=%d\n", x);
    printf("y=%d\n", y);
    return 0;
}
程序运行结果为:

请输入两个数:
3 5
x=3
y=5

可见,程序并没有交换 x 和 y 的值,这是为什么呢?在函数调用过程中,主调用函数与被调用函数之间有一个数值传递过程,这种数值传递是单向的,只能把实参的值传递给形参。

也就是说,在自定义函数体中,即便此时形参的值发生了改变,也无法再传递回来,因此实参的值不会发生变化。这就是为什么普通整型作为函数形参时不能实现 x 和 y 值互换的原因。

通过指针传递参数,可以减少值传递带来的开销,也可以使函数调用不产生值传递。

【实例 1】将 3 个数按降序输出。首先定义交换函数 swap(),参数是指针变量;然后再定义比较大小的函数 exchange(),参数也是指针变量,并且在 exchange() 函数中嵌套交换函数 swap()。在主函数中,调用 exchange() 函数将输入的 3 个数按降序输出,具体代码如下:
#include <stdio.h>
void swap(int *p1, int *p2)  /* 自定义交换函数 swap */
{
    int temp;
    temp = *p1;
    *p1 = *p2;
    *p2 = temp;
}

void exchange(int *pt1, int *pt2, int *pt3)  /* 自定义排序函数 exchange */
{
    if (*pt1 < *pt2)
        swap(pt1, pt2);  /* 调用 swap 函数 */
    if (*pt1 < *pt3)
        swap(pt1, pt3);
    if (*pt2 < *pt3)
        swap(pt2, pt3);
}

int main()
{
    int a, b, c, *q1, *q2, *q3;
    puts("Please input three key numbers you want to rank:");
    scanf("%d,%d,%d", &a, &b, &c);
    q1 = &a;  /* 将变量的地址赋给指针变量 */
    q2 = &b;
    q3 = &c;
    exchange(q1, q2, q3);  /* 调用 exchange 函数 */
    printf("\n%d,%d,%d\n", a, b, c);
    return 0;
}
程序运行结果为:

Please input three key numbers you want to rank:
78,96,56

96,78,56

程序创建了一个自定义函数 swap(),用于交换两个变量的值。还创建了一个 exchange() 函数,其作用是将 3 个数由大到小排序,在 exchange() 函数中调用 swap() 函数。这里,swap() 和 exchange() 函数都是以指针变量作为形参。

程序运行时,通过键盘输入 3 个数 a、b、c,分别将 a、b、c 的地址赋给 q1、q2、q3,调用 exchange() 函数,将指针变量作为实参,将实参变量的值传递给形参变量,此时 q1 和 pt1 都指向变量 a,q2 和 pt2 都指向变量 b,q3 和 pt3 都指向变量c;在 exchange() 函数中又调用了 swap() 函数,当执行 swap(pt1, pt2) 时,pt1 也指向了变量 a,pt2 指向了变量 b,这一过程如下图所示:


图 1 嵌套调用时指针的指向情况

C语言中实参变量和形参变量之间的数据传递是单向的“值传递”方式。指针变量作函数参数也是如此,调用函数不可能改变实参指针变量的值,但可以改变实参指针变量所指向变量的值。

下面介绍如何使用指向数组的指针变量作为函数参数。这里,形参和实参均为指针变量。

【实例 2】计算新生总人数。利用指针变量作函数参数编写程序,根据输入的各班人数求刚入学的初中新生的总人数。
#include <stdio.h>
void SUM(int *p, int n)  /* 自定义 SUM 函数,用于求和 */
{
    int i, sum = 0;
    for (i = 0; i < n; i++)  /* 循环各班人数并求和 */
        sum = sum + *(p + i);  /* 相加 */
    printf("新生总人数是: %d\n", sum);
}

int main()
{
    int *pointer, a[10], i;
    pointer = a;
    printf("请输入每个班级人数:\n");
    for (i = 0; i < 10; i++)  /* 输入各班的人数 */
        scanf("%d", &a[i]);
    SUM(pointer, 10);  /* 调用 SUM 函数求总人数 */
    return 0;
}
程序运行结果为:

请输入每个班级人数:
42 24 56 60 66 45 54 34 65 50
新生总人数是: 496

在自定义函数 SUM() 中使用指针变量作形式参数,在主函数中实际参数 pointer 是一个指向一维数组 a 的指针,虚实结合,被调用函数 SUM() 中的形式参数 p 得到 pointer 的值,指向了内存中存放的一维数组。

冒泡排序是 C语言中比较经典的例子,也是读者应该牢牢掌握的一种算法,下面分析如何使用指针变量作为函数参数来实现冒泡排序。

【实例 3】实现冒泡排序。冒泡排序的基本思想:如果要对 n 个数进行冒泡排序,则要进行 n−1 轮比较,在第一轮比较中要从后到前进行 n−1 次两两比较,在第 j 轮比较中要进行 n−j 次两两比较。使用指针实现冒泡法排序,代码如下:
#include <stdio.h>
void order(int *p, int n)  /* 自定义冒泡排序函数 order */
{
    int i, t, j;
    for (i = 0; i < n - 1; i++)
        for (j = 0; j < n - 1 - i; j++)  /* 判断相邻两个元素的大小 */
            if (*(p + j) > *(p + j + 1))
            {
                t = *(p + j);  /* 借助中间变量 t 进行值互换 */
                *(p + j) = *(p + j + 1);
                *(p + j + 1) = t;
            }
    printf("排序后的数组:");
    for (i = 0; i < n; i++)
    {
        if (i % 5 == 0)
            printf("\n");  /* 以每行 5 个元素的形式输出 */
        printf("%5d", *(p + i));  /* 输出数组中排序后的元素 */
    }
    printf("\n");
}

int main()
{
    int a[20], i, n;
    printf("请输入数组元素的个数:\n");
    scanf("%d", &n);  /* 输入数组元素的个数 */
    printf("请输入各个元素:\n");
    for (i = 0; i < n; i++)
        scanf("%d", a + i);  /* 给数组元素赋初值 */
    order(a, n);  /* 调用 order 函数 */
    return 0;
}
程序运行结果为:
请输入数组元素的个数:
10
请输入各个元素:
27 25 23 29 21 39 34 38 35 37
排序后的数组:
   21   23   25   27   29
   34   35   37   38   39
前面两个实例都是用一个指向数组的指针变量作函数参数。接下来通过一个实例介绍如何用指向指针的指针作函数参数。

【实例 4】水果首字母升序排名。大福超市员工布置水果区,店长要求按照水果名称升序的顺序摆放。各水果的名称及对应单价如下:
编写程序,按照水果名称的首字母将水果升序排序:
#include <stdio.h>
#include <string.h>

void sort(char *strings[], int n)  /* 自定义排序函数 sort */
{
    char *temp;
    int i, j;
    for (i = 0; i < n; i++)
    {
        for (j = i + 1; j < n; j++)
        {
            if (strcmp(strings[i], strings[j]) > 0)  /* 比较两个字符串的大小 */
            {
                temp = strings[i];  /* 如果前面的字符串比后面的大,则互换 */
                strings[i] = strings[j];
                strings[j] = temp;
            }
        }
    }
}

int main()
{
    int n = 8;
    int i;
    char **p;  /* 定义字符型、指向指针的指针 */
    char *name[] = {
        "apple",
        "tangerine",
        "grapefruit",
        "banana",
        "orange",
        "pineapple",
        "grape",
        "pitaya"
    };
    p = name;
    sort(p, n);  /* 调用排序函数 sort */
    printf("排序后的水果单如下:\n");
    for (i = 0; i < n; i++)
        printf("%s\n", name[i]);  /* 输出排序后的字符串 */
    return 0;
}
程序运行结果为:

排序后的水果单如下:
apple
banana
grape
grapefruit
orange
pineapple
pitaya
tangerine


下面将通过一个二维数组使用指针变量作为函数参数的实例,加深读者对该部分知识的理解:
【实例 5】找出数组每行中最大数并求和。利用指针作函数参数,找出一个数组每行最大的数,并将这些数相加求和。具体代码如下:
#include <stdio.h>
#define N 4

void max(int (*a)[N], int m)  /* 自定义 max 函数,求数组每行的最大元素 */
{
    int value, i, j, sum = 0;
    for (i = 0; i < m; i++)
    {
        value = *(*(a + i));  /* 将一行中的首个元素赋给 value */
        for (j = 0; j < N; j++)
            if (*(*(a + i) + j) > value)  /* 判断其他元素是否小于 value */
                value = *(*(a + i) + j);  /* 把比 value 大的数重新赋给 value */
        printf("第%d 行:最大数是:%d\n", i, value);
        sum = sum + value;
    }
    printf("\n");
    printf("每行中最大数相加之和是:%d\n", sum);
}

int main()
{
    int a[3][N], i, j;
    int (*p)[N];
    p = &a[0];
    printf("please input:\n");
    for (i = 0; i < 3; i++)
        for (j = 0; j < N; j++)
            scanf("%d", &a[i][j]);  /* 给二维数组元素赋值 */
    max(p, 3);  /* 调用 max 函数,指针变量作函数参数 */
    return 0;
}
程序运行结果为:

please input:
96 56 75 85
12 25 84 88
76 65 62 66
第0 行:最大数是:96
第1 行:最大数是:88
第2 行:最大数是:76

每行中最大数相加之和是:260

现在来总结一下:
1) 数组名就是数组的首地址,因此例 3 中可以将数组名作为实参传递给指针型形式参数。

2) 当形参为数组时,实参也可以为指针变量。可将例 3 改写成如下形式:
#include <stdio.h>
void order(int a[], int n)
{
    int i, t, j;
    for (i = 0; i < n - 1; i++)
        for (j = 0; j < n - 1 - i; j++)  /* 判断相邻两个元素的大小 */
            if (*(a + j) > *(a + j + 1))
            {
                t = *(a + j);  /* 借助中间变量 t 进行值互换 */
                *(a + j) = *(a + j + 1);
                *(a + j + 1) = t;
            }
    printf("排序后的数组:");
    for (i = 0; i < n; i++)
    {
        if (i % 5 == 0)
            printf("\n");  /* 以每行 5 个元素的形式输出 */
        printf("%5d", *(a + i));  /* 输出数组中排序后的元素 */
    }
    printf("\n");
}

int main()
{
    int a[20], i, n;
    int *p;
    p = a;  /* 定义 p 为数组 a 的首地址 */
    printf("请输入数组元素的个数:\n");
    scanf("%d", &n);  /* 输入数组元素的个数 */
    printf("请输入各个元素:\n");
    for (i = 0; i < n; i++)
        scanf("%d", p++);  /* 给数组元素赋初值 */
    p = a;  /* 再次定义 p 为数组 a 的首地址 */
    order(p, n);  /* 调用 order 函数 */
}
上述程序中,形参是数组,实参是指针变量。注意倒数第 3 行语句:
p = a;  /* 再次定义 p 为数组 a 的首地址 */
该语句不可少,如果将其省略,则后面调用 order() 函数时,参数 p 指向的就不再是数组 a 的首地址,从而导致程序运行出错,这点需要读者加以注意。

相关文章