文章目录:
前言
在之前的学习中,我们已经初步了解了指针的概念。如果你还没有阅读过相关内容,可以先移步到【C语言】初阶指针详解。接下来,我们将深入探讨指针的更多细节。由于内容较长,我们将分为上、中、下三篇进行讲解。现在,让我们进入深入了解指针的第一篇。
一、 字符指针
在指针的类型中,有一种特殊的指针类型叫做字符指针 char*
。
一般的使用方式如下:
int main()
{
char ch = 'w';
char* pc = &ch;
*pc = 'h';
return 0;
}
另一种使用方式如下:
int main()
{
char arr[20] = "hello bit";
char* pc = arr;
return 0;
}
需要注意的是,上面的代码只是将字符数组的首元素地址赋给了字符指针,而不是将整个字符数组的内容存储在字符指针中。
下面有一道题可以尝试一下:
输出结果如下:
这里 str3
和 str4
指向的是同一个字符串常量。C/C++ 会将常量字符串存储在单独的内存区域,当多个指针指向同一个字符串时,它们实际上指向的是同一个内存单元。然而,使用相同字符串初始化的不同数组会开辟自己独立的空间,因此 str1
和 str2
不同,而 str3
和 str4
相同。
二、 指针数组
指针数组是一个存放指针的数组。我们在指针初阶已经了解过这一点。下面我们来复习一下,以下指针数组的意思:
int* arr1[10]; // 整形指针的数组
char *arr2[4]; // 一级字符指针的数组
char **arr3[5]; // 二级字符指针的数组
三、 数组指针
3.1、 数组指针的定义
数组指针本质上是一个指向数组的指针。下面哪一个是指向数组的指针?
int *p1[10];
int (*p2)[10];
p1
和 p2
分别是什么?
我们先回顾一下操作符的优先级:
p1
先与[]
结合,说明p1
是一个数组,数组元素类型为int*
,所以p1
是指针数组。p2
先与*
结合,说明p2
是一个指针,指向的数据类型为int[10]
,所以p2
是数组指针。
需要注意的是,[]
的优先级高于 *
,所以要用 ()
保证 p2
先与 *
结合。
3.2、 数组名和&数组名
对于数组 int arr[10];
,&arr
和 arr
有什么区别呢?
我们知道 arr
是数组首元素的地址,那么 &arr
是什么呢?
我们来看代码:
#include <stdio.h>
int main()
{
int arr[10] = {0};
printf("%p\n", arr);
printf("%p\n", &arr);
return 0;
}
输出结果如下:
我们可以看到 &arr
和 arr
的地址值是一样的。那么 arr
和 &arr
就是一样的吗?我们通过算术运算来看一下。
#include <stdio.h>
int main()
{
int arr[10] = {0};
printf("arr = %p\n", arr);
printf("&arr= %p\n", &arr);
printf("arr+1 = %p\n", arr+1);
printf("&arr+1= %p\n", &arr+1);
return 0;
}
输出结果如下:
我们可以看到 arr
和 &arr
的地址还是一样的,但 arr+1
和 arr
相差 4 个字节,&arr+1
和 &arr
相差 40 个字节。从这里我们可以看出 arr
和 &arr
值一样但意义不一样。
实际上,arr
表示数组首元素的地址,&arr
表示整个数组的地址,所以 arr+1
跳过一个元素的大小,而 &arr+1
跳过整个数组的大小。
3.3、 数组指针的使用
我们讲了数组指针的定义,接下来讲如何使用。
过去我们有⼀个⼆维数组的需要传参给⼀个函数的时候,我们是这样写的:
#include <stdio.h>
void print_arr1(int arr[3][5], int row, int col)
{
int i = 0;
for(i=0; i<row; i++)
{
int j=0;
for(j=0; j<col; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
int main()
{
int arr[3][5] = {1,2,3,4,5,6,7,8,9,10};
print_arr1(arr, 3, 5);
return 0;
}
这⾥实参是⼆维数组,形参也写成⼆维数组的形式,那还有什么其他的写法吗?
这里我们先分析一下二维数组,二维数组可以看成每个元素都为一维数组的数组,也就是⼆维数组的每个元素是⼀个⼀维数组。那么⼆维数组的⾸元素就是第⼀⾏,是个⼀维数组。如下图:
所以,根据数组名是数组⾸元素的地址这个规则,⼆维数组的数组名表⽰的就是第⼀⾏的地址,是⼀维数组的地址。根据上⾯的例⼦,第⼀⾏的⼀维数组的类型就是 int [5]
,所以第⼀⾏的地址的类型就是数组指针类型 int(*)[5]
。那就意味着⼆维数组传参本质上也是传递了地址,传递的是第⼀⾏这个⼀维数组的地址,那么形参也是可以写成指针形式的。如下:
void print_arr2(int (*arr)[5], int row, int col)
{
int i = 0;
for(i=0; i<row; i++)
{
int j=0;
for(j=0; j<col; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
int main()
{
int arr[3][5] = {1,2,3,4,5,6,7,8,9,10};
print_arr2(arr, 3, 5);
return 0;
}
总结:二维数组传参,可以写成二维数组的形式,也可以写成数组指针的形式。
四、 指针和数组传参
我们学习了数组和指针后,在写代码的时候难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计呢?
4.1、 一维数组传参
#include <stdio.h>
void test(int arr[])//ok?
{}
void test(int arr[10])//ok?
{}
void test(int *arr)//ok?
{}
void test2(int *arr[20])//ok?
{}
void test2(int **arr)//ok?
{}
int main()
{
int arr[10] = {0};
int *arr2[20] = {0};
test(arr);
test2(arr2);
}
对于
arr
:
arr
是一个一维数组,形参可以写成一维数组的形式。arr
表示数组首元素的地址,是一个指向整形的指针常量,形参可以写成一级指针的形式。
对于
arr2
:
arr2
是一个指针数组,其本质也是一个一维数组,形参也可以写成一维数组的形式。arr2
是数组首元素的地址,数组元素类型为int*
的指针类型,arr2
表示为二级指针,形参可以写成二级指针的形式。
4.2、 二维数组传参
void test(int arr[3][5])//ok?
{}
void test(int arr[][])//ok?
{}
void test(int arr[][5])//ok?
{}
void test(int *arr)//ok?
{}
void test(int* arr[5])//ok?
{}
void test(int (*arr)[5])//ok?
{}
void test(int **arr)//ok?
{}
int main()
{
int arr[3][5] = {0};
test(arr);
}
void test(int* arr[5])
和 void test(int **arr)
上面黄色的代码可以接收二维数组吗?
我们分析一下形参的类型:
arr
先与*
结合,说明是一个指针,指向的数据类型为int*
,所以形参的数据类型为二级指针,但我们传的是一个二维数组,数据类型不匹配,所以二级指针不可以接收二维数组的传参。arr
先与[5]
结合,说明是一个数组,各元素的数据类型为int*
,所以形参的数据类型为指针数组,但我们传的是一个二维数组,数据类型不匹配,所以指针数组不可以接收二维数组的传参。
总结:
- 二维数组传参,函数形参的设计只能省略第一个
[]
的数字。 因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素,这样才方便运算。- 二维数组传参的指针形式写成
type (*)[ ]
。
4.3、 一级指针传参
#include <stdio.h>
void print(int *p, int sz)
{
int i = 0;
for(i=0; i<sz; i++)
{
printf("%d\n", *(p+i));
}
}
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9};
int *p = arr;
int sz = sizeof(arr)/sizeof(arr[0]);
//一级指针p,传给函数
print(p, sz);
return 0;
}
思考:
当一个函数的参数为一级指针时,可以传什么参数?
答案:
- 一维数组的数组名
- 一级指针
4.4、 二级指针传参
#include <stdio.h>
void test(int** ptr)
{
printf("num = %d\n", **ptr);
}
int main()
{
int n = 10;
int*p = &n;
int **pp = &p;
test(pp);
test(&p);
return 0;
}
思考:
当函数的参数为二级指针的时候,可以接收什么参数?
答案:
- 二级指针
- 指针数组的数组名
结束
通过本文的学习,我们深入了解了指针和数组的相关知识,包括字符指针、指针数组、数组指针以及指针和数组的传参方式。