前言: 本文内容相较于初学者可能会有些难度,适合已经对C语言指针有过一点了解,或者指针部分基础薄弱的老铁们学习!耐心看完一定会有所收获的。
1,指针数组
关于指针数组,我们首先一定要先认清它的含义是什么?从名字中不难得出,指针数组,是一个存放指针的数组,指针是这个数组的类型,就像整形数组和字符数组一样
int arr[10];-—》整形数组
char ch[20];—》字符数组
那么既然指针数组也是数组,那它也一定有自己的命名格式
int* arr[10];
char* ch[10];
指针类型加上数组名就是指针数组的命名格式,arr是数组名,由于[]的优先级比* 高,所以arr先和[]组成一个数组,数组的类型是int *
我们参考数组,数组有一维和二维之分,那么指针数组也有一级和二级,甚至三级之分
int** arr[10];—》二级指针数组
没错,在一级的基础上多加一个 * 便是二级指针的命名
那么C语言中指针数组是怎么使用的呢?
#include <stdio.h>
int main()
{
int arr[] = { 0, 1, 2 };
int arr1[] = { 1, 2, 3 };
int arr2[] = { 2, 3, 4 };
int arr3[] = { 3, 4, 5 };
int arr4[] = { 4, 5, 6 };
int* p[5] = { arr, arr1, arr2, arr3, arr4};
int i = 0;
for (i = 0; i < 5; i++)
{
int j = 0;
for (j = 0; j < 3; j++)
{
printf("%d ", *(*(p + i) + j));
//*(p+i)找到是指针数组的元素,再加j找到的是对应数组的元素
//*(*(p + i) + j)再进行解引用找到对应数组的元素
}
printf("\n");
}
return 0;
}
当我们要使用多个数组时,一个一个的进行打印的话,代码会很臃肿,代码量也会增多,这时创建一个指针数组,利用指针数组去找到对应的数组会提高代码的灵活性,也会方便日后的修改
运行结果:
2,数组指针
2.1数组指针的定义
数组指针的概念也是从名字就可以得知,是一个指向数组的指针,大家一定不要和指针数组搞混了
数组指针 | 指向数组的一个指针 |
---|---|
指针数组 | 存放指针的一个数组 |
他俩的本质区别还是很大的,我们先来回归一个指针
指向int类型的指针是int *p;
指向char类型的指针是char *p;
指向floar类型的指针是float* p;
那么同样的,指向数组的指针如下:
int (*p)[10];
char (*p)[10]
p是数组名,由于()的优先级最高,所以p先和 * 结合组成一个指针,指针指向的内容是int [10]类型的数组。
为了防止大家搞混指针数组和数组指针的概念,这里我们再次将它们俩放在一起看一下
指针数组:int* arr[10];
是一个大小为10的数组,数组存放int*的指针
数组指针:int (*p)[10];
是一个指针,指向的是一个大小为10的int类型的数组
2.2数组名详解
我们通过一段代码来观察数组名
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int(*p)[10] = arr;
printf("arr: %p\n", arr);
printf("arr+1: %p\n", arr+1);
printf("&arr: %p\n", &arr);
printf("&arr+1:%p\n", &arr+1);
printf("P: %p\n", p);
printf("P+1: %p\n", p+1);
return 0;
}
运行结果
我们知道,数组名是首元素的地址,所以arr+1跳过4个字节(int类型的字节大小是4)指向数组中的第二个元素。
通过运行结果来看&arr与arr的地址是相同的,但是加一之后的结果却是不同的,从004FF89C到004FF8C4,增加了10,即数组的大小,所以虽然arr和&arr的地址相同,但是本质却是不同的
&arr表示的是整个数组的地址
arr表示的是数组首元素的地址
那么我们再看,p和p+1的结果与&arr和&arr+1的结果是相同的,这也证明了,数组指针指向的是数组的地址,并非首元素的地址
了解完以上内容之后,我们再来看一下,关于数组指针到底是怎么使用的?
2.3数组指针的使用
数组指针的使用,多用在二维数组传参时,当然一维数组传参时,也是可以使用的,我们先从一维数组开始讲起
void print(int (*p)[5], int sz)//使用数组指针来接收
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", (*p)[i]);
}
}
//打印数组内容
int main()
{
int arr[5] = { 1,2,3,4,5 };
print(arr, 5);
return 0;
}
这里我们不再使用数组或者指针作为形参,而是改用我们刚学的数组指针。
这里int (*p)[5]中的5可有可无,不过最好还是写上。
然后就是在打印数组内容的时候要注意一点的是,要先对p解引用(*p)之后再通[]找到对应的元素下标,千万不能写成 *(p+i),因为数组指针指向的是数组的地址,而不是首元素地址,这样写将造成不可预料的结果
如果在上面的代码中想要不使用[]也是可以的,应该写成
printf("%d ", *((*p) + i));
先对p解引用找到数组,然后再加上i,再解引用找到对应的元素,输出的结果也是一样的
接下来我们再来看一段二维数组的代码
#include <stdio.h>
void print_arr1(int arr[3][5], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
void print_arr2(int(*arr)[5], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
//printf("%d ", arr[i][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);
//数组名arr,表示首元素的地址
//但是二维数组的首元素是二维数组的第一行
//所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
//可以数组指针来接收
print_arr2(arr, 3, 5);
return 0;
}
print_arr1是使用同等大小的二维数组作为形参接受,没什么好说的,唯一值得注意的一点是,二维数组作为形参时,列的大小是不能省略的也就是
void print_arr1(int arr[3][5], int row, int col)
行(3)可以省略,但是列(5不能省略)
这和二维数组的创建是一个道理,编译器可以通过你有几列来自动确定行数,但是不能不知道列数
print_arr2便是使用了一个数组指针作为形参
void print_arr2(int(*arr)[5], int row, int col)
arr是数组指针变量名,指向的是一个大小为10的int类型的数组
要注意的是,数组指针在指向一个二维数组的时候,指向的其实是第一行的地址,并非第一个元素的地址
所以我们在接下来打印的时候,就要格外注意一下
printf("%d ", arr[i][j]);基础写法--没问题
我们来讲一下这个写法
printf("%d ", *(*(arr + i) + j));
首先*(arr+i)找到对应的行数,之后再再加上列(j)找到对应元素的地址,之后再解引用找对应的元素。
知道之后逻辑就是很简单的了
3,数组参数&指针参数
在写代码的时候,难免会将【数组】或者【指针】作为实参传给函数,那么函数的参数该如何设计呢?
3.1一维数组传参
先来看一下一维数组的情况
我们来判断一下test函数的形参设计的是否合理
#include <stdio.h>
void test(int arr[])//ok?
{}
void test(int arr[10])//ok?
{}
void test(int* arr)//ok?
{}
int main()
{
int arr[10] = { 0 };
test(arr);
return 0;
}
arr是一个大小为10的一维数组
所以函数的形参同样设计成一维数组时肯定可以,形参arr的大小可有可无。
一维数组使用一级指针也是ok的,本身test(arr)传的就是函数的地址,使用一级指针接受,没毛病。
void test2(int* arr[20])//ok?
{}
void test2(int** arr)//ok?
{}
int main()
{
int* arr2[20] = { 0 };
test2(arr2);
return 0;
}
这里创建的是一个大小为20的一级指针数组。
同样使用一个大小相同的一级指针数组来接受,肯定也是没问题的,道理和使用数组来接受数组一样。
那么使用
void test2(int** arr)
是否也行呢?
不难看出int** arr是一个二级的指针,test2传过去的是一个一级指针,那么使用一个二级指针来接受也是可以的
3.2二维数组传参
接下来看一下二维数组的情况
void test(int arr[3][5])//ok?1
{}
void test(int arr[][])//ok?2
{}
void test(int arr[][5])//ok?3
{}
void test(int* arr)//ok?4
{}
void test(int* arr[5])//ok?5
{}
void test(int(*arr)[5])//ok?6
{}
void test(int** arr)//ok?7
{}
int main()
{
int arr[3][5] = { 0 };
test(arr);
}
第一个test和第三个test肯定是可以的没问题,第二个test因为缺少列数,所以是错误的。
第四个test函数参数是一个int类型的一级指针,可以使用一级指针来接受二维数组的地址吗?
并不可以,因为我们知道,二维数组在传参的时候,数组名代表的是一行的地址(一个一维数组的地址),你用一个int类型的指针来接受一个数组肯定是行不通的
第五个test是个指针数组,指针数组存放的是指针,而我们二维数组名代表的是一个一维数组的地址,所以这种写法肯定也是不行,类型不匹配!
第六个test参数是数组指针,我们知道二维数组其实可以看成是由多个一维数组构成的,那么这里的arr就可以看成是由三个一维数组,每个数组有五个元素构成的二维数组,而二维数组名代表的就是第一个数组的地址,所以使用数组指针作为函数参数也是可以的。
第七个test的形参是一个二级指针,首先我们要搞清楚二级指针是用来存放一级指针的地址的,而我们test传过去的是一个二维数组名(一个一维数组的地址),那么肯定也是会发生类型不匹配。
所以在使用指针接收二维数组时,只有第六个test是可以的,其他都不行。
3.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;
}
这里我们在设计函数的参数时,一级指针传参就还是用一级指针来接收一级指针就可以了, 代码不会有任何毛病,然后遍历整个数组就可以将数组的内容全部打印出来。但是有一个很值得思考的问题
当我们函数形参是一级指针的时候,函数的实参可以是什么?
假设我们设计了一个函数如下:
int test(int* pt);
大家可以思考几分钟…
该函数的实参可以是一下几种类型:
1.一个整形变量的地址
int a = 0;
test(&a);
2.一个一级指针(如上面代码所示)
3.一个数组名
int arr[10];
test(arr);
数组名其实是首元素的地址,所以我们用一个一级的指针来接受也是可以的
3.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;
}
二级指针用来存放一级指针的地址,所以test(&p)也是没问题的
同样的一个问题:当函数的形参设计成二级指针时,函数的形参可以是什么呢?
首先二级指针和一级指针的地址肯定是没问题的(见上面代码区)
那么还有什么呢?
我们知道二级指针可以用来存放一级指针的地址,那联合我们上面讲过的指针数组,我们说指针数组是用来存放指针的数组,那么数组名便是首元素(第一个指针)的地址,所以形参还可以是同类型的指针数组名,如下图:
4,函数指针
因为内容太多,所以有关函数指针的内容,后期我会单出一篇博客来讲解,见谅!
希望这边文章能给大家带来帮助,欢迎大家评论留言Bey!