初学者小白复盘13之——指针(2) - 实践

1.数组名的理解

我们在上次说过,数组名其实就是首元素的地址,那么今天我们来验证一下。

#include <stdio.h>
  int main()
  {
  int arr[] = { 1,2,3,4,5 };
  int* p = &arr[0];
  printf("%p\n", p);
  return 0;
  }

在这里插入图片描述
我们通过&arr[0]得到了数组首元素的地址,接下来我们来对比下直接通过arr会是什么样子的。

#include <stdio.h>
  int main()
  {
  int arr[] = { 1,2,3,4,5 };
  int* p1 = &arr[0];
  int* p2 = arr;
  printf("%p\n", p1);
  printf("%p\n", p2);
  return 0;
  }

在这里插入图片描述
我们可以发现数组首元素的地址确实就是数组名,所以我们现在证实了这件事。
那可能有的人会有疑问,如果数组名是数组首元素的地址,那么下面的代码该如何解释呢?

int main()
{
int arr[5] = { 1,2,3,4,5 };
int sz = sizeof(arr);
printf("%d\n", sz);
return 0;
}

在这里插入图片描述
在这里可能有人会说,这是怎么回事呢?如果数组名是数组首元素的地址,那这里应该是4而不是20呀,20就很明显是整个数组了。
其实数组名就是数组首元素(第⼀个元素)的地址是对的,但是有两个例外:
1.sizeof中单独放数组名,这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节。
2.&数组名,这里的数组名表示整个数组,取出的是整个数组的地址(整个数组的地址和数组首元素的地址是有区别的)。
除此之外,任何地方使用数组名,数组名都表示首元素的地址。

我们再来几段代码来加深下我们对于数组名的理解:

int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%p\n", arr);
printf("%p\n", &arr);
printf("%p\n", &arr[0]);
return 0;
}

在这里插入图片描述
单纯从数值上来看,这几个值都是相同的,那么我们来进一步分析探究下究竟有什么不同。

int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%p\n", arr);
printf("%p\n", arr+1);
printf("%p\n", &arr[0]);
printf("%p\n", &arr[0] + 1);
printf("%p\n", &arr);
printf("%p\n", &arr+1);
return 0;
}

在这里插入图片描述
根据我们之前说的,数组名是数组首元素的地址,那么跳过一个元素是4个字节,没问题。&arr[0]跳过一个元素也当然是4个字节,而&arr是整个数组,所以在16进制下,570-548=28,而0x28就等于40,40个字节正好是整个数组,所以这里我们就验证了&arr确实是整个数组。
下面为0x28转化为10进制计算过程:
在这里插入图片描述
到这里大家应该搞清楚数组名的意义了吧。
数组名是数组首元素的地址,但是有2个例外

2.使用指针访问数组

有了前面知识的支持,再结合数组的特点,我们就可以很方便的使用指针访问数组了。
我们先来举一个例子:

int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
int* p = arr;
for (int i = 0; i < sz; i++)
{
scanf("%d", p + i);
}
for (int i = 0; i < sz; i++)
{
printf("%d ", *(p + i));
}
return 0;
}

在这里插入图片描述
我们可以看到,代码正常运行,那么既然我们把arr赋给了指针变量p,也就是数组首元素地址,那么在这里我们不去使用指针变量p而是去用arr,那这种操作是否可行呢?

int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
int* p = arr;
for (int i = 0; i < sz; i++)
{
scanf("%d", arr + i);
}
for (int i = 0; i < sz; i++)
{
printf("%d ", *(arr + i));
}
return 0;
}

在这里插入图片描述
可以发现,完全没问题。毕竟就是把数组首元素地址传给指针变量p的嘛!
这个代码搞明白后,我们再试⼀下,如果我们再分析⼀下,数组名arr是数组首元素的地址,可以赋值给p,其实数组名arr和p在这里是等价的。那我们可以使用arr[i]可以访问数组的元素,那p[i]是否也可以访问数组呢?我们来看一下代码:

int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
int* p = arr;
for (int i = 0; i < sz; i++)
{
scanf("%d", p + i);
}
for (int i = 0; i < sz; i++)
{
printf("%d ", p[i]);
}
return 0;
}

在这里插入图片描述
在这里我们可以看到代码也是可以正常运行的,说明我们的这个操作没有问题,确实可以把arr换成p;那么我们直到[ ]叫做下标引用操作符。既然是操作符,那会不会跟加减法类似可以交换顺序呢?
比方说1+2=2+1,让我们来拭目以待。

int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
int* p = arr;
for (int i = 0; i < sz; i++)
{
scanf("%d", p + i);
}
for (int i = 0; i < sz; i++)
{
printf("%d ", i[p]);
}
return 0;
}

在这里插入图片描述
欸!我们可以发现,代码依然正常运行,我们的想法是正确的,实际上编译器会自动转化为解引用的形式,比方说p[i]== *(p+i),也就是说,我们调换个顺序,i[p] == *(i+p)。他们其实是等价的。
不过不推荐大家这么写哦,如果在公司里这么写估计会给别人带来一些困扰,你就要离被公司优化掉不远了(狗头)

3.一维数组传参的本质

数组我们学过了,之前也讲了,数组是可以传递给函数的,这个小节我们讨论⼀下数组传参的本质。首先从一个问题开始,我们之前都是在函数外部计算数组的元素个数,那我们可以把数组传给一个函数后,函数内部求数组的元素个数吗?

void Print(int arr[10])
{
int sz2 = sizeof(arr) / sizeof(arr[0]);
printf("%d\n", sz2);
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int sz1 = sizeof(arr) / sizeof(arr[0]);
printf("%d\n", sz1);
Print(arr);
return 0;
}

在这里插入图片描述
我们可以看到,两者的结果并不相同,那么这是为什么呢?
先来看一下是怎么得到这两种结果的,10当然是10个元素一共40个字节/一个元素4个字节=10;那么2是从何而来呢?
真相只有一个!在x64系统下指针为8字节,所以这里是传过去了一个指针变量为8字节/4字节=2,这就是结果。
实际上arr只是传过去了一个指针变量而已,那么这种在函数里头计算元素个数的形式并不可取,我们最好是在函数外头计算完元素个数,然后再传给函数,这样是最好的,如下:

void Print(int arr[10],int sz1)
{
printf("%d\n", sz1);
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int sz1 = sizeof(arr) / sizeof(arr[0]);
Print(arr,sz1);
return 0;
}

在这里插入图片描述
可以看到,我们成功的把数组的元素个数传了过去。
我们直到arr实际上是数组首元素地址,所以在传参时参数最好写成指针形式,如下:

void Print(int* arr,int sz1)
{
printf("%d\n", sz1);
}

总结:一维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式

4.冒泡排序

冒泡排序的核心思想就是:两两相邻的元素进行比较。
如下:

void MaoPao(int* arr, int sz)
{
for (int i = 0; i < sz - 1; i++)
{
for (int j = 0; j < sz - 1 - i; j++)
{
if (arr[j] > arr[j + 1])
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
int main()
{
int arr[10] = { 9,1,2,3,4,5,6,7,8,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
MaoPao(arr, sz);
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}

在这里插入图片描述
我们可以看到,数组内元素已经排好顺序了,说明我们的代码没毛病,不过其实我们的代码还可以再优化一下,比方说我们上面的这个代码,这个代码只需要一趟排序就可以了,后面还要再次进行比较,浪费时间。
所以我们采取一种办法令数组排好序后立马跳出来结束运行,如下:

void MaoPao(int* arr, int sz,int flag)
{
for (int i = 0; i < sz - 1; i++)
{
flag = 1;
for (int j = 0; j < sz - 1 - i; j++)
{
if (arr[j] > arr[j + 1])
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
flag = 0;
}
}
if (flag == 1)
{
break;
}
}
}
int main()
{
int arr[10] = { 9,1,2,3,4,5,6,7,8,10 };
int flag = 1;
int sz = sizeof(arr) / sizeof(arr[0]);
MaoPao(arr, sz,flag);
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}

在这里插入图片描述
这里的flag就是当flag为1时,即数组内元素已经有序,有序就直接break即可。

5.二级指针

指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里?
那就是二级指针。

int main()
{
int a = 10;
int* p = &a;
int** pp = &p;
printf("%p\n", p);
printf("%p\n", pp);
printf("%d\n", *p);
printf("%d\n", **pp);
return 0;
}

在这里插入图片描述
那么其实就是套娃的一个过程,我们来看,首先指针变量p&a的地址,*p自然就是a的值,然后二级指针&一级指针变量p的地址,解引用星号p自然也得到a的值,同理,还有三级指针等等到n级指针,其实都是一样的道理。

6.指针数组

指针数组是指针还是数组?我们类比一下,整型数组,是存放整型的数组,字符数组是存放字符的数组。那指针数组呢?是存放指针的数组。
在这里插入图片描述
指针数组的每个元素都是用来存放地址(指针)的。
如下:
在这里插入图片描述
指针数组的每个元素是地址,又可以指向一块区域。

7.指针数组模拟二维数组

int main()
{
// 三个一维数组
int arr1[5] = {1,2,3,4,5};
int arr2[5] = {2,3,4,5,6};
int arr3[5] = {3,4,5,6,7};
// 指针数组 - 存储三个数组的地址
int* arr4[3] = {arr1, arr2, arr3};
// 双重循环打印
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 5; j++)
{
printf("%d ", arr4[i][j]);
}
printf("\n");
}
return 0;
}

在这里插入图片描述
arr4[i]是访问arr4数组的元素,arr4[i]找到的数组元素指向了整型一维数组,arr4[i][j]就是整型一维数组中的元素。上述的代码模拟出二维数组的效果,实际上并非完全是二维数组,因为每一行并非是连续的。
arr4[i][j] 相当于 *(arr4[i] + j);因为arr4[i]是一个指针,指向第i个数组的首元素,然后通过j偏移访问后续元素。
我们可以用另一种方式理解:
arr4[0] 等于 arr1(即arr1的首地址),所以arr4[0][j] 就是 arr1[j]。
arr4[1] 等于 arr2,所以arr4[1][j] 就是 arr2[j]。
arr4[2] 等于 arr3,所以arr4[2][j] 就是 arr3[j]。

arr4[指针数组]
┌─────┬─────┬─────┐
│ 0x1000 │ 0x1014 │ 0x1028 │ ← 存储的是地址
└─────┴─────┴─────┘
│ │ │
▼ ▼ ▼
arr1 arr2 arr3
┌─┬─┬─┬─┬─┐ ┌─┬─┬─┬─┬─┐ ┌─┬─┬─┬─┬─┐
│1│2│3│4│5│ │2│3│4│5│6│ │3│4│5│6│7│
└─┴─┴─┴─┴─┘ └─┴─┴─┴─┴─┘ └─┴─┴─┴─┴─┘
这个例子展示了:

1.数组名就是指针的概念
2.指针数组的用法:用数组来存储多个指针
3.如何用指针数组模拟二维数组
4.arr[i][j] 这种写法的指针运算本质

感谢大家的收看,如果认为这篇文章对你有所帮助,可以给一个点赞哦~~

posted @ 2025-10-21 15:39  yjbjingcha  阅读(3)  评论(0)    收藏  举报