LV010-函数与数组
很多时候,我们处理数据并不是单一的处理一个或者几个数据,这个时候我们就可以使用数组来传参,便于数据处理。
一、形参为数组
1. 基本格式
实参为数组名(也可以说是数组的指针),形参为数组名(本质是一个指针变量)。
这种方式传入数组时,形参并没有赋值实参所有的元素,而是复制了实参的首地址,这也就意味着我们 传入的参数实际上是一个地址。这也说明了我们在函数中对数组元素进行操作,原来数组中的元素会相应发生变化。
【注意】形参是数组形式时,本质是同级别的指针。例如,
void selectSort(int arr[], int n);
/* 里边的 int arr [] 相当于下边的写法 */
void selectSort(int *arr, int n);C 语言规定:数组作为参数时,只传递首元素地址,这就是为什么形参会自动变成指针,并且,这里的 arr 可以进行自加操作(原来的数组名是不可以进行自加的)。
2. 大小未定义
- 形式参数是一个未定义大小的数组
#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;
}
}
}
}然后在终端运行以下命令:
gcc test.c -Wall # 编译程序
./a.out # 执行可执行程序接着我们会在终端中看到以下输出信息:
a = 1 7 3 2 5
a = 1 2 3 5 73. 已定义大小
- 形式参数是一个已定义大小的数组
#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;
}
}
}
}然后在终端运行以下命令:
gcc test.c -Wall # 编译程序
./a.out # 执行可执行程序接着我们会在终端中看到以下输出信息:
a = 1 7 3 2 5
a = 1 2 3 5 7这里为什么不通过 sizeof 计算数组大小呢,是因为它会报以下警告:
warning: ‘sizeof’ on array function parameter ‘arr’ will return size of ‘int *’ [-Wsizeof-array-argument]函数的形参即便是一个已经定义了大小的数组,但是这个形参的数组名被认为是一个指针类型了。
二、形参为指针
1. 基本格式
既然传入的参数是一个地址,那么当然可以用指针来接收地址了。一般形式如下:
dataType functionName(dataType *arg1, int length)
{
/* 省略 */
}2. 使用实例
#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;
}
}
}
}然后在终端运行以下命令:
gcc test.c -Wall # 编译程序
./a.out # 执行可执行程序接着我们会在终端中看到以下输出信息:
a = 1 7 3 2 5
a = 1 2 3 5 7三、二维数组传参
二维数组也是类似的传参方式。
1. 未定义大小的二维数组形式
1.1 基本格式
形式参数是一个未定义大小的二维数组(第一维的大小可以不指定,第二维的大小 必须指定):
dataType functionName(int arr[][列数])
{
/* 省略 */
}
// func(arr)1.2 使用实例
#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;
}然后在终端运行以下命令:
gcc test.c -Wall # 编译程序
./a.out # 执行可执行程序接着我们会在终端中看到以下输出信息:
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 = 242. 行指针形式
2.1 基本格式
形式参数是一个行指针:
dataType functionName(int (*arr)[列数])
{
/* 省略 */
}
//func(arr)2.2 使用实例
#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;
}然后在终端运行以下命令:
gcc test.c -Wall # 编译程序
./a.out # 执行可执行程序接着我们会在终端中看到以下输出信息:
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 = 243. 一级指针
3.1 基本格式
列数在编译期不确定,需要运行时传入的场景可以使用下面的形式:
dataType functionName(int *arr, int rows, int cols)
{
/* 省略 */
}
//func(&arr [0][0], rows, cols)3.2 使用实例
#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 基本格式
需要注意的是,这种形式只能是用于动态分配的二维数组。
dataType functionName(int **arr, int rows, int cols)
{
/* 省略 */
}
//func(arr, rows, cols)4.2 使用实例
#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 常见错误
// 错误声明
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 **。
静态二维数组的内存布局: 动态二级指针的内存布局:
matrix[0][0] matrix[0][1] ... matrix --> [指针1, 指针2]
↓ 连续存储 ↓ ↓
[ 1, 2, 3, 4, 5, 6 ] [1,2,3] [4,5,6]
各行可能不连续5. 写法小结
// 静态二维数组 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) 参数特性,我们还可以这样写:
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 可选) |