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
。这样一来,arr
和 p
在访问数组元素时可以说是等价的。
既然可以使用 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]
则访问该一维数组中的具体元素。
上述代码模拟了二维数组的效果,但实际上并非真正的二维数组,因为每一行的存储空间并不连续。