文章目录
一、普通数组与指针数组
- 普通数组 :
- 例如
int arr[5];
,这是一个简单的一维数组,它包含5个int
类型的元素。从类型角度看,arr
的类型是int [5]
,表示一个包含5个int
元素的数组。在内存中,这些元素是连续存储的,数组名arr
在大多数情况下代表数组的首地址。 - 当我们使用
arr
作为表达式时,在很多情况下它会"衰减"为一个指向数组首元素的指针,类型变为int *
。例如,当把arr
传递给一个函数时,函数接收到的实际是一个int *
类型的指针,指向数组的首元素。
- 例如
- 指针数组 :
- 定义形式为
类型 *数组名[数组大小];
,例如int *pArr[3];
。这里pArr
是一个数组,它的元素类型是int *
,即每个元素都是一个指向int
类型的指针。 - 假设我们有三个
int
变量a = 1, b = 2, c = 3
,可以这样使用pArr
:int *pArr[3] = {&a, &b, &c};
,此时pArr[0]
指向a
,pArr[1]
指向b
,pArr[2]
指向c
。这种数组主要用于存储多个指针,方便对这些指针所指向的对象进行操作。
- 定义形式为
二、二维数组与数组指针(指向数组的指针)
- 二维数组 :
- 例如
int twoDArr[3][4];
,这是一个二维数组,它的类型是int [3][4]
,可以看作是一个包含3个元素的数组,每个元素又是一个包含4个int
元素的数组。在内存中,它的存储方式是按行存储,即先存储第一行的4个元素,再存储第二行的元素,以此类推。 - 当我们使用
twoDArr
作为表达式时,它也会衰减,不过它衰减后的类型是int (*)[4]
(指向包含4个int
元素的数组的指针)。例如,在函数参数传递中,如果有一个函数接收一个二维数组作为参数,形式参数可以写成void func(int (*p)[4])
,这里p
就是一个数组指针,它可以接收二维数组twoDArr
作为实参。
- 例如
- 数组指针(指向数组的指针) :
- 定义形式为
类型 (*指针变量名)[数组大小];
,如int (*p)[4];
。这种指针是专门用于指向一个包含特定数量元素(这里是4个int
元素)的数组。 - 假设我们有一个二维数组
int arr[3][4]
,可以通过以下方式使用数组指针:
- 定义形式为
c
int (*p)[4];
p = arr;
for(int i = 0; i < 3; i++){
for(int j = 0; j < 4; j++){
// 访问二维数组元素
printf("%d ",(*(p + i))[j]);
}
printf("\n");
}
- 这里`p`指向`arr`的首行(即一个包含4个`int`元素的数组),`p + i`指向第`i`行,`(*(p + i))[j]`就相当于`arr[i][j]`,
- 通过这种方式可以使用数组指针来访问二维数组。
三、字符数组与字符指针
- 字符数组 :
- 例如
char str1[] = "hello";
,这是一个字符数组,它的类型是char [6]
(包括字符串结束符\0
)。字符数组在内存中有自己的存储空间,用于存储字符序列。 - 可以通过下标来访问数组中的字符,如
str1[0]
表示字符'h'
。而且字符数组可以通过strcpy
等函数来修改其中的内容。
- 例如
- 字符指针 :
- 定义形式为
char *str2;
,如果str2 = "world";
,这里str2
是一个字符指针,它指向一个字符串常量(在C语言中,字符串常量存储在只读数据区)。其类型是char *
。 - 虽然可以通过
str2
来访问字符串中的字符,如printf("%c", str2[0]);
会输出'w'
,但是不能通过str2
来修改字符串常量的内容(在标准C语言环境下,这样做是未定义行为)。如果想修改字符串内容,需要通过字符数组来实现。例如,char str3[] = "test";
,可以通过str3[0]='T';
来修改。
- 定义形式为
四、数组名和&数组名的区别
1、数组名
- 代表首元素地址(大部分情况) :在C和C++语言中,数组名在大多数情况下会"衰减"为指向数组首元素的指针。例如,对于
int arr[5];
,arr
通常等价于&arr[0]
,它们的类型都是int*
。这意味着可以使用数组名来访问数组中的元素,就像使用指针一样。例如,可以通过*(arr + 1)
来访问arr[1]
。 - 类型信息(数组类型) :尽管数组名在很多操作中表现得像指针,但它本身的类型是"数组类型"。对于
int arr[5];
,arr
的类型是int [5]
。这个类型信息在一些特定的场景下会体现出来,比如在使用sizeof
运算符时,sizeof(arr)
返回的是整个数组所占用的字节数(在这个例子中是5 * sizeof(int)
),而不是指针的大小。 - 作为函数参数 :当数组名作为函数参数传递时,它会被转换为指向数组首元素的指针。例如,有一个函数
void func(int a[])
,实际上它等价于void func(int *a)
。这是因为在函数调用过程中,传递的是数组的首元素地址,而不是整个数组的副本。
2、取地址数组名(&数组名)
- 代表整个数组的地址 :对于
int arr[5];
,&arr
返回的是整个数组的地址。它的类型是int (*)[5]
,这是一个指向包含5个int
元素的数组的指针。与arr
(大部分情况下等价于&arr[0]
)不同,&arr
的地址值在数值上虽然和&arr[0]
相同,但它们的类型和含义是不同的。 - 在指针运算中的差异 :因为
&arr
是整个数组的地址,所以在指针运算中会有不同的行为。例如,如果p
是一个指向整个数组的指针(类型为int (*)[5]
),那么p + 1
会跳过整个数组的长度(即5 * sizeof(int)
字节),而如果q
是一个指向数组首元素的指针(类型为int*
),那么q + 1
只会跳过一个元素的长度(即sizeof(int)
字节)。 - 在函数参数传递中的特殊用途 :当想要传递整个数组(而不是仅仅传递数组的首元素地址)给一个函数时,可以使用指向数组的指针作为参数类型。例如,
void func(int (*p)[5])
这个函数可以接收一个指向包含5个int
元素的数组的指针。在调用这个函数时,可以使用func(&arr)
,这里&arr
就是整个数组的地址。这种方式在处理多维数组等复杂情况时非常有用,能够更准确地操作数组。
3、举例
- 示例代码
c
#include <stdio.h>
int main() {
int arr[5] = {1, 2, 3, 4, 5};
int *p1 = arr;
int (*p2)[5] = &arr;
// 输出数组名和取地址数组名的值(地址值)
printf("arr: %p\n", (void *)arr);
printf("&arr: %p\n", (void *)&arr);
// 指针运算的区别
printf("p1 + 1: %p\n", (void *)(p1 + 1));
printf("p2 + 1: %p\n", (void *)(p2 + 1));
// 通过数组名和取地址数组名访问数组元素
printf("*(arr + 2): %d\n", *(arr + 2));
printf("(*(&arr))[2]: %d\n", (*(&arr))[2]);
return 0;
}
- 运行结果:
- 详细讲解
- 指针定义部分
int *p1 = arr;
:这里将p1
定义为一个int
类型的指针,并将arr
赋值给p1
。由于数组名arr
在这种情况下会衰减为指向首元素的指针,所以p1
现在指向arr
的第一个元素1
。int (*p2)[5] = &arr;
:p2
被定义为一个指向包含5个int
元素的数组的指针,然后将&arr
(整个数组的地址)赋值给p2
。
- 输出地址值部分
printf("arr: %p\n", (void *)arr);
和printf("&arr: %p\n", (void *)&arr);
:这两行代码分别输出数组名arr
的值(也就是首元素的地址)和取地址数组名&arr
(整个数组的地址)。在很多情况下,这两个地址值在数值上是相同的,但它们的类型不同。
- 指针运算部分
printf("p1 + 1: %p\n", (void *)(p1 + 1));
:因为p1
是指向int
类型的指针,所以p1 + 1
会使指针向后移动一个int
类型元素的大小。在这个例子中,如果int
类型占4个字节,那么p1 + 1
指向的地址会比p1
的地址增加4个字节,也就是指向数组中的第二个元素2
。printf("p2 + 1: %p\n", (void *)(p2 + 1));
:p2
是指向包含5个int
元素的数组的指针,所以p2 + 1
会使指针向后移动一个包含5个int
元素的数组的大小。如果int
类型占4个字节,那么p2 + 1
指向的地址会比p2
的地址增加5 * 4 = 20
个字节,这就跳过了整个arr
数组。
- 访问数组元素部分
printf("*(arr + 2): %d\n", *(arr + 2));
:这里利用数组名arr
衰减为指向首元素的指针的特性,arr + 2
表示指向数组中第三个元素的指针,然后通过*(arr + 2)
来获取这个元素的值,即3
。printf("(*(&arr))[2]: %d\n", (*(&arr))[2]);
:首先&arr
是整个数组的地址,*(&arr)
就相当于arr
(整个数组),然后(*(&arr))[2]
就相当于arr[2]
,所以输出的值也是3
。
- 指针定义部分