Skip to content

LV010-函数与数组

很多时候,我们处理数据并不是单一的处理一个或者几个数据,这个时候我们就可以使用数组来传参,便于数据处理。

一、形参为数组

1. 基本格式

实参为数组名(也可以说是数组的指针),形参为数组名(本质是一个指针变量)。

这种方式传入数组时,形参并没有赋值实参所有的元素,而是复制了实参的首地址,这也就意味着我们 传入的参数实际上是一个地址。这也说明了我们在函数中对数组元素进行操作,原来数组中的元素会相应发生变化。

【注意】形参是数组形式时,本质是同级别的指针。例如,

c
void selectSort(int arr[], int n);
/* 里边的 int arr [] 相当于下边的写法 */
void selectSort(int *arr, int n);

C 语言规定:数组作为参数时,只传递首元素地址,这就是为什么形参会自动变成指针,并且,这里的 arr 可以进行自加操作(原来的数组名是不可以进行自加的)。

2. 大小未定义

  • 形式参数是一个未定义大小的数组
c
#include <stdio.h>
 
/* 函数声明 */
void selectSort(int arr[], int n);
 
int main(int argc, const char *argv[]) 
{
	int a[5] = {1, 7, 3, 2, 5};
	int i = 0;
	printf("a = ");
	for(i = 0; i < 5; i++)
	{
		printf("%d\t",a[i]);
	}
	printf("\n");
    
	selectSort(a, sizeof(a)/sizeof(int));
	printf("a = ");
	for(i = 0; i < 5; i++)
	{
		printf("%d\t",a[i]);
	}
	printf("\n");
    
   return 0;
}
 
void selectSort(int arr[], int n)
{
    int i = 0;
	int j = 0;
	int temp = 0;
    for(i = 0; i < n; i++)
    {
        for(j = 0; j < n - 1; j++)
        {
            if(arr[j] > arr[j+1])
            {
                temp=arr[j];
                arr[j]=arr[j+1];
                arr[j+1]=temp;
            }
        }
    }
}

然后在终端运行以下命令:

shell
gcc test.c -Wall # 编译程序
./a.out          # 执行可执行程序

接着我们会在终端中看到以下输出信息:

shell
a = 1   7       3       2       5
a = 1   2       3       5       7

3. 已定义大小

  • 形式参数是一个已定义大小的数组
c
#include <stdio.h>
 
/* 函数声明 */
void selectSort(int arr[5]);
 
int main(int argc, const char *argv[]) 
{
	int a[5] = {1, 7, 3, 2, 5};
	int i = 0;
	printf("a = ");
	for(i = 0; i < 5; i++)
	{
		printf("%d\t",a[i]);
	}
	printf("\n");
	selectSort(a);
	printf("a = ");
	for(i = 0; i < 5; i++)
	{
		printf("%d\t",a[i]);
	}
	printf("\n");
    
   return 0;
}
 
void selectSort(int arr[5])
{
    int i = 0;
	int j = 0;
	int temp = 0;

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

然后在终端运行以下命令:

shell
gcc test.c -Wall # 编译程序
./a.out          # 执行可执行程序

接着我们会在终端中看到以下输出信息:

shell
a = 1   7       3       2       5
a = 1   2       3       5       7

这里为什么不通过 sizeof 计算数组大小呢,是因为它会报以下警告:

shell
warning: ‘sizeof’ on array function parameter ‘arr’ will return size of ‘int * [-Wsizeof-array-argument]

函数的形参即便是一个已经定义了大小的数组,但是这个形参的数组名被认为是一个指针类型了。

二、形参为指针

1. 基本格式

既然传入的参数是一个地址,那么当然可以用指针来接收地址了。一般形式如下:

c
dataType functionName(dataType *arg1, int length)
{
    /* 省略 */
}

2. 使用实例

c
#include <stdio.h>
 
/* 函数声明 */
void selectSort(int *arr, int n);
 
int main(int argc, const char *argv[]) 
{
	int a[5] = {1, 7, 3, 2, 5};
	int i = 0;
	
	printf("a = ");
	for(i = 0; i < 5; i++)
	{
		printf("%d\t",a[i]);
	}
	printf("\n");

	selectSort(a, 5);
	printf("a = ");
	for(i = 0; i < 5; i++)
	{
		printf("%d\t",a[i]);
	}
	printf("\n");
    
   return 0;
}
 
void selectSort(int *arr, int n)
{
    int i = 0;
	int j = 0;
	int temp = 0;
	
    for(i = 0; i < 5; i++)
    {
        for(j = 0; j < 5 - 1; j++)
        {
            if(arr[j] > arr[j+1])
            {
                temp=arr[j];
                arr[j]=arr[j+1];
                arr[j+1]=temp;
            }
        }
    }
}

然后在终端运行以下命令:

shell
gcc test.c -Wall # 编译程序
./a.out          # 执行可执行程序

接着我们会在终端中看到以下输出信息:

shell
a = 1   7       3       2       5
a = 1   2       3       5       7

三、二维数组传参

二维数组也是类似的传参方式。

1. 未定义大小的二维数组形式

1.1 基本格式

形式参数是一个未定义大小的二维数组(第一维的大小可以不指定,第二维的大小 必须指定):

c
dataType functionName(int arr[][列数])
{
    /* 省略 */
}

// func(arr)

1.2 使用实例

c
#include <stdio.h>

int fun1(int n, int m, int arr[][m]);

int main(int argc, const char *argv[]) 
{
	int a[2][3] = {{1, 3, 2}, {5, 7 ,6}};
	int (*p)[3] = a;
	int i = 0;
	int j = 0;
	int sum = 0, n, m;
	for(i = 0; i < 2; i++)
	{
		for(j = 0; j <3; j++)
		{
			printf("a[%d][%d] = %d \t", i, j, *(*(p + i) + j));
		}
		printf("\n");
	}

	n = sizeof(a)/sizeof(a[0]);
	m = sizeof(a[0])/sizeof(int);
	sum = fun1(n, m, a);
	printf("sum = %d\n", sum);

	return 0;
}

int fun1(int n, int m, int arr[][m])
{
	int i = 0;
	int j = 0;
	int sum = 0;
	for(i = 0; i < n; i++)
	{
		for(j = 0; j < m; j++)
		{
			sum += arr[i][j];
		}
	}
	return sum;
}

然后在终端运行以下命令:

shell
gcc test.c -Wall # 编译程序
./a.out          # 执行可执行程序

接着我们会在终端中看到以下输出信息:

shell
a[0][0] = 1     a[0][1] = 3     a[0][2] = 2 
a[1][0] = 5     a[1][1] = 7     a[1][2] = 6 
sum = 24

2. 行指针形式

2.1 基本格式

形式参数是一个行指针:

c
dataType functionName(int (*arr)[列数])
{
    /* 省略 */
}

//func(arr)

2.2 使用实例

c
#include <stdio.h>

int fun1(int n, int m, int (*arr)[m]);

int main(int argc, const char *argv[]) 
{
	int a[2][3] = {{1, 3, 2}, {5, 7 ,6}};
	int (*p)[3] = a;
	int i = 0;
	int j = 0;
	int sum = 0, n, m;
	for(i = 0; i < 2; i++)
	{
		for(j = 0; j <3; j++)
		{
			printf("a[%d][%d] = %d \t", i, j, *(*(p + i) + j));
		}
		printf("\n");
	}

	n = sizeof(a)/sizeof(a[0]);
	m = sizeof(a[0])/sizeof(int);
	sum = fun1(n, m, a);
	printf("sum = %d\n", sum);

	return 0;
}

int fun1(int n, int m, int (*arr)[m])
{
	int i = 0;
	int j = 0;
	int sum = 0;
	for(i = 0; i < n; i++)
	{
		for(j = 0; j < m; j++)
		{
			sum += *(*(arr + i) + j);
		}
	}
	return sum;
}

然后在终端运行以下命令:

shell
gcc test.c -Wall # 编译程序
./a.out          # 执行可执行程序

接着我们会在终端中看到以下输出信息:

shell
a[0][0] = 1     a[0][1] = 3     a[0][2] = 2 
a[1][0] = 5     a[1][1] = 7     a[1][2] = 6 
sum = 24

3. 一级指针

3.1 基本格式

列数在编译期不确定,需要运行时传入的场景可以使用下面的形式:

c
dataType functionName(int *arr, int rows, int cols)
{
    /* 省略 */
}

//func(&arr [0][0], rows, cols)

3.2 使用实例

c
#include <stdio.h>

// 把二维数组当作一维数组处理,手动计算偏移
void printArray_flat(int *arr, int rows, int cols) {
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            // 关键:手动计算偏移量
            printf("%d ", arr[i * cols + j]);
        }
        printf("\n");
    }
}

int main(void) {
    int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}};
    
    // 传递首元素地址
    printArray_flat(&matrix[0][0], 2, 3);
    // 等价写法:printArray_flat((int *)matrix, 2, 3);
    
    return 0;
}

4. 二级指针

4.1 基本格式

需要注意的是,这种形式只能是用于动态分配的二维数组。

c
dataType functionName(int **arr, int rows, int cols)
{
    /* 省略 */
}

//func(arr, rows, cols)

4.2 使用实例

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

void printArray_dynamic(int **arr, int rows, int cols) {
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }
}

int main(void) {
    int rows = 2, cols = 3;
    
    // 动态分配
    int **matrix = malloc(rows * sizeof(int *));
    for (int i = 0; i < rows; i++) {
        matrix[i] = malloc(cols * sizeof(int));
    }
    
    // 赋值
    matrix[0][0] = 1; matrix[0][1] = 2; matrix[0][2] = 3;
    matrix[1][0] = 4; matrix[1][1] = 5; matrix[1][2] = 6;
    
    printArray_dynamic(matrix, rows, cols);
    
    // 释放
    for (int i = 0; i < rows; i++) {
        free(matrix[i]);
    }
    free(matrix);
    
    return 0;
}

4.3 常见错误

c
// 错误声明
void wrong_func(int **arr, int rows, int cols) {
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }
}

int main(void) {
    int matrix[2][3] = {{1,2,3}, {4,5,6}};
    
    wrong_func(matrix, 2, 3);  // 编译警告,运行崩溃!
    return 0;
}

原因: 静态二维数组 int matrix[2][3] 在内存中是连续的,类型是 int (*)[3],不是 int **

md
静态二维数组的内存布局:             动态二级指针的内存布局:

matrix[0][0] matrix[0][1] ...    matrix --> [指针1, 指针2]
    ↓ 连续存储                          ↓       ↓
[ 1, 2, 3, 4, 5, 6 ]              [1,2,3] [4,5,6]
                                  各行可能不连续

5. 写法小结

c
// 静态二维数组 int arr [3][4] 的传参方式:

void func(int arr[][4], int rows);       // ✅ 推荐
void func(int (*arr)[4], int rows);      // ✅ 等价写法
void func(int *arr, int rows, int cols); // ✅ 需要传 &arr [0][0]
void func(int **arr);                    // ❌ 错误!类型不匹配

另外,在 C99 标准引入的 变长数组(VLA) 参数特性,我们还可以这样写:

c
int fun1(int n, int m, int arr[][m]);    // 写法 1
int fun1(int n, int m, int arr[n][m]);   // 写法 2(行数可写可不写)
int fun1(int n, int m, int (*arr)[m]);   // 写法 3(指针形式)

这种写法中,参数 m 必须在 arr 之前 声明,编译器需要先知道 m 的值才能确定数组的列宽。对比传统写法:

特性传统写法 int arr[][4]变长数组 int arr[][m]
列数编译期固定运行时确定
灵活性只能处理固定列数可处理任意列数
标准支持C89 及以上C99 起(C11 可选)