深入理解指针Part3——指针与数组

1 数组名的理解

在C/C++中,数组名在表达式中使用时,通常会转换为指向数组首元素的指针(区别数组指针) 。此规则有两个例外

  • 作为 sizeof 运算符的操作数:sizeof(arr) 返回的是整个数组所占的字节大小,而非指针大小。
  • 作为 & 运算符的操作数:&arr 产生的是一个指向整个数组的指针(类型为 int(*)[10]),而非指向首元素的指针(类型为 int*)。

实例分析(假设有 int arr[10]):

arr&arr[0] 的类型是 int*。对它们 +1,地址增加一个 int 的大小(4字节)。

&arr 的类型是 int(*)[10]。对它 +1,地址会增加 10 * sizeof(int) = 40字节,即跳过整个数组。

2 使用指针访问数组

有了前面知识的支持,再结合数组的特性,我们就可以很方便地使用指针来访问数组了。

我们可以将数组名 arr(即数组首元素的地址)赋值给指针变量 p。这样一来,arrp 在访问数组元素时可以说是等价的。

既然可以使用 arr[i] 来访问数组元素,那么是否也能使用 p[i] 呢?答案是肯定的,将 *(p + i) 替换为 p[i] 后,程序仍然可以正常打印结果。这说明 p[i] 本质上等价于 *(p + i)。同理,arr[i] 也等价于 *(arr + i)

实际上,在编译器处理数组元素的访问时 ,也会将其转换为"首元素地址 + 偏移量"的方式,先计算出元素的地址,再通过解引用来访问该元素。

3 一维数组传参的本质

我们已经学习过数组,也知道数组可以作为参数传递给函数。现在我们将探讨数组传参的本质。首先从一个问题开始:我们之前通常是在函数外部计算数组的元素个数,那么是否可以在数组传给函数之后,在函数内部求出它的元素个数呢?

实际测试发现,在函数内部并不能正确获取数组的元素个数。这就引出了数组传参的本质问题。

我们之前学过:数组名在大多数情况下表示数组首元素的地址。在数组传参时,传递的正是数组名,也就是说,数组传参本质上传递的是数组首元素的地址

因此,函数的形参部分实际上是用一个指针变量来接收这个首元素地址 。在函数内部使用 sizeof(arr) 时,计算的是该指针变量的大小(以字节为单位),而不是整个数组的大小。正因为参数本质上是一个指针,所以在函数内部无法直接求出原数组的元素个数。

总结来说,一维数组传参时,形参既可以写成数组的形式,也可以写成指针的形式,但它们的本质 都是指针

4 冒泡排序

c 复制代码
void Bubble_sort1(int* arr, size_t 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;
			}
		}
	}
}

//A Better Version,在数组有序后就跳出循环
void Bubble_sort2(int* arr, size_t sz)
{
	for (int i = 0; i < sz - 1; i++)
	{
		int 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)//这一趟没交换就说明已经有序,不用再排了
			break;
	}
}

void Print(int* arr, size_t sz)
{
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", *(arr + i));
	}
	printf("\n");
}

int main()
{
	int arr[] = { 3,1,7,5,8,9,0,2,4,6 };
	size_t sz = sizeof(arr) / sizeof(arr[0]);
	Print(arr, sz);
	Bubble_sort2(arr, sz);//从小到大
	Print(arr, sz);
	return 0;
}

5 二级指针

c 复制代码
int main() 
{
    int a = 10;
    int* pa = &a;   // 一级指针,保存a的地址
    int** ppa = &pa; // 二级指针,保存pa的地址

    // 通过二级指针修改变量a的值
    **ppa = 30;     // 步骤1: *ppa 找到 pa
    // 步骤2: *(*ppa) 即 *pa 找到 a
    // 步骤3: 将a的值改为30

    printf("a = %d\n", a);  // 输出: a = 30

    // 通过二级指针改变一级指针的指向
    int b = 50;
    *ppa = &b;      // 让pa转而指向b
    *pa = 100;      // 现在修改的是b的值

    printf("b = %d\n", b);  // 输出: b = 100

    return 0;
}

6 指针数组

指针数组的本质是一个数组,其元素类型是指针类型。若一个数组的每个元素都是用来存放地址(指针)的,则该数组称为指针数组。

例如,int* arr[5] 声明了 arr 是一个包含5个元素的数组,每个元素都是一个 int*(整型指针)。

拆开看,int* [5] 是变量 arr 的类型,它是一个数组类型,包含了5个 int* 类型的元素。

特性:指针数组的每个元素本身是一个地址,而这个地址又可以作为入口,指向另一块连续的内存区域(如另一个数组或一个数据结构)。这使得它成为管理多个指针、构建复杂数据结构(如字符串数组、二维动态数组)的基础。

7 指针数组模拟二维数组

c 复制代码
#include <stdio.h>

int main()
{
    int arr1[] = {1, 2, 3, 4, 5};
    int arr2[] = {2, 3, 4, 5, 6};
    int arr3[] = {3, 4, 5, 6, 7};
    
    // 数组名是首元素的地址,类型是int*,可以存放在parr数组中
    int* parr[3] = {arr1, arr2, arr3};
    int i = 0;
    int j = 0;
    
    for (i = 0; i < 3; i++)
    {
        for (j = 0; j < 5; j++)
        {
            printf("%d ", parr[i][j]);
        }
        printf("\n");
    }
    return 0;
}

数组名是数组首元素的地址,其类型为 int*,因此可以存放在 parr 这个指针数组中。

parr[i] 访问的是 parr 数组的元素,该元素是一个指向整型一维数组的指针。parr[i][j] 则访问该一维数组中的具体元素。

上述代码模拟了二维数组的效果,但实际上并非真正的二维数组,因为每一行的存储空间并不连续。


正文完

相关推荐
要做朋鱼燕8 小时前
【AES加密专题】1.AES的原理详解和加密过程
运维·网络·密码学·c·加密·aes·嵌入式工具
煤球王子1 天前
学而时习之:C语言中的Error处理
c
qq_437896435 天前
unsigned 是等于 unsigned int
开发语言·c++·算法·c
Lonble6 天前
C语言篇:预处理
c语言·c
BlackQid8 天前
深入理解指针Part1——C语言
c++·c
Lonble20 天前
C语言篇:宏
c语言·c
Lonble20 天前
C语言篇:翻译阶段
c语言·c
空山新雨(大队长)1 个月前
C 语言第一课:hello word c
c++·c·exe
饭碗的彼岸one1 个月前
C++ 并发编程:异步任务
c语言·开发语言·c++·后端·c·异步