C语言指针

虚拟内存

  虚拟内存作为一个中间层,避免程序直接与物理内存交互,因而程序中访问的内存地址不再是真实的物理内存地址,而是每个进程独有的虚拟内存地址,虚拟内存地址由操作系统映射到适当的物理内存地址上,至于如何映射与开发者无关。

  在只启动一次的情况下,一个程序只可以拥有一个进程,多个程序的多个进程拥有结构相同的虚拟内存,在典型的32位系统中,虚拟内存空间总大小为4GB64位系统远大于4G),其中包括用户空间内核空间

  1. 用户空间,占用虚拟内存空间3G,包括
    • 代码段(Code Segment):也称为文本段,存储程序的机器码指令。通常是只读的,因为程序在运行时不应该修改自己的指令。
    • 数据段(Data Segment):存储全局变量和静态变量。数据段分为初始化的数据段(Initialized Data Segment)和未初始化的数据段(Uninitialized Data Segment,也称为BSS段)。初始化的数据段包含在程序中明确初始化的全局和静态变量,而未初始化的数据段包含未初始化的全局和静态变量,这些变量会在程序运行时被初始化为零或空。
    • 堆(Heap):动态内存分配的区域,由程序员进行手动管理。使用malloc()calloc()realloc()等函数分配内存,并使用free()函数释放内存。
    • 栈(Stack):存储局部变量、函数参数和函数调用的返回地址。栈是一种后进先出(LIFO)的数据结构,它在函数调用时分配局部变量的空间,当函数返回时释放这些空间。
    • 常量区(Constant Area):存储常量字符串,例如字符串字面量。这部分内存通常是只读的。
    • 内存映射区域(Memory-mapped Region):存储与文件关联的内存映射区域。这包括共享内存等。
  2. 内核空间,占用虚拟内存空间1G (0xC000_0000 ~ 0xFFFF_FFFF)

指针概念

  在典型的32位系统中,虚拟内存空间总大小为4GB,因为4GB4,096MB4,194,304KB4,294,967,296byte,即虚拟内存空间共有4,294,967,296个字节;
  816进制从0x0000_00000xffff_ffff刚好4,294,967,296个数,每个数对应虚拟内存中的一个字节,将816进制中的每个数都作为虚拟内存的字节的内存地址;于是虚拟内存的内存地址范围从0x0000_00000xffff_ffff

  对于一个多字节的变量,例如一个占用4字节内存的int变量,假设其第一个字节储存在虚拟内存中的地址为0x7fc5_3001,于是其最后一个字节的地址就为0x7fc5_3004,那么它编号最小的内存地址0x7fc5_3001就代表它本身的地址

1
2
3
4
5
6
7
8
9
10
// int 占用4个字节
// 假设第一个字节的内存地址是 0x7fc5_3001
// 那么变量 num 的内存地址就是 0x7fc5_3001
int num = 1;

// 取 num 的指针,也就是内存地址
int* pointer = &num

// 将会输出 0x7fc5_3001
printf("pointer = %p", pointer);

指针变量

  定义指针变量

1
数据类型* 指针变量名;

  取变量所在的内存地址

1
2
3
4
int num = 100;
int* pointer = #
// 将会输出 num 的内存地址
printf("pointer = %p",pointer);

  取指针变量所指向的内存地址储存的值

1
2
3
4
5
6
7
int num1 = 100;
int* pointer = &num1;

// 取指针变量所指向的内存地址储存的值
int num2 = *pointer;
// 将会输出 100 ,num1 = num2
printf("pointer = %d",num2);

  不同数据类型的指针只能存储对应类型的内存地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 字符指针
char ch = 'A';
char* ch_pointer = &ch;

// 短整型指针
short int sh = 10;
short int* sh_pointer = &sh;

// 整形指针
int in = 10;
int* in_pointer = ∈

// 长整型指针
long int lo = 10;
long int* lo_pointer = &lo;

// 浮点型指针
float fl = 10.0f;
float* fl_pointer = &fl;

// 双浮点型指针
double dou = 10.0;
double* dou_pointer = &dou;

  通用指针,可以储存任何变量类型的指针

1
2
int num = 100;
void* pointer = #

指针和数组的关系

数组名代表数组首元素的内存地址,因此数组名可以直接赋值给指针变量,而实际上赋值时传递的是数组的地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main() {
int arr[] = { 1,2,3,4,5 };

// 将数组名直接赋值给指针变量
int* pointer_1 = arr;

// 取数组首元素的内存地址
int* pointer_2 = &arr[0];

// 二者相同
printf("pointer_1 = %p\n", pointer_1);
printf("pointer_2 = %p\n", pointer_2);

return 0;
}

数组名只可作为不可变的左值,因为数组名表示数组的起始地址,而指针声明时是左值,但也可以作为右值

  • 左值(lvalue) 是指表达式结束后依然存在的持久化对象,通常是一个标识符(变量、数组元素、结构体成员等)。左值可以出现在赋值运算符的左边,表示一个可以被赋值的位置。在int x = 42;中,x是左值,因为它是一个标识符,可以出现在赋值运算符的左边,
  • 右值(rvalue) 是指表达式结束后不再存在的临时对象,通常是一个常量、表达式的结果或临时计算得到的值。在int y = 10 + 5;中,10 + 5是右值,因为它是一个临时计算得到的值,不能被直接赋值;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int main() {
int arr1[3] = { 1,2,3 };

int num = 10;
int* pointer1 = #

// 错误,因为数组名只可作为不可变的左值
arr1 = pointer1;

// 错误,因为数组无法接受直接赋予地址值,而arr1也不能作为初始值赋予arr2
int arr2[] = arr1;

// 指针可以作为左值,接受arr1传递的地址值
int* pointer2 = arr1;
// pointer2指针可以作为右值,对pointer1重新赋予地址值
pointer1 = pointer2;

return 0;
}

数组名不能进行自增或自减运算,而指针变量可以

  数组首元素的指针自加可以移动到数组第二个元素,此时指针指向第二个元素,两元素地址十六进制值相差为一个数组元素的类型所占用字节的大小,例如int类型数组,那么两元素地址的十六进制值将相差4字节。注意,指针自加+整数的方式只有在数组元素中才有意义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
int main() {
int arr[] = { 1,2,3,4,5,6,7,8 };
// 将数组名代表的数组首元素地址赋值给指针
int* pointer = arr;

// pointer此时指向数组的首元素地址,将会输出 1 及第一个元素的地址
printf("num = %d, pointer = %p\n", *pointer, pointer);

// 指针自加,即向下移动一个元素
pointer++;
// pointer此时指向数组第二个元素的地址,将会输出 2 及第二个元素的地址
printf("num = %d, pointer = %p\n", *pointer, pointer);

// 自加2,向下移动两个元素
pointer+=2;
// pointer此时指向数组第四个元素的地址,将会输出 4 及第四个元素的地址
printf("num = %d, pointer = %p\n", *pointer, pointer);

// 自减,向上移动一个元素
pointer--;
// pointer此时指向数组第三个元素的地址,将会输出 3 及第三个元素的地址
printf("num = %d, pointer = %p\n", *pointer, pointer);

return 0;
}

  同一数组两元素的两个指针可以相减,将会得到两个元素之间相差的元素的个数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int main() {
int arr[] = { 1,2,3 };

// 数组第一个元素的指针
int* pointer1 = &arr[0];

// 数组第二个元素的指针
int* pointer2 = &arr[1];

// 数组第三个元素的指针
int* pointer3 = &arr[2];

// 第三个元素和第一个元素下标相差 2 ,将会输出2
int minus_1 = (int)(pointer3 - pointer1);
printf("minus_1 = %d\n", minus_1);

// 第二个元素和第一个元素下标相差 1 ,将会输出1
int minus_2 = (int)(pointer2 - pointer1);
printf("minus_2 = %d\n", minus_2);

return 0;
}

sizeof操作符对指针和数组名使用的结果不同,

  • 对数组名使用sizeof得到的是整个数组的大小,
  • 对指针变量使用sizeof得到的是指针本身的大小;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main() {
// 数组中有 8 个 int 类型元素,因此数组的大小应该是 32 字节
int arr[] = { 1,2,3,4,5,6,7,8 };
// 在32位系统 int 类型指针通常占用 4 字节的内存空间。
// 在64位系统 int 类型指针通常占用 8 字节的内存空间。
int* pointer = arr;

// 将会输出 32
printf("%zu\n", sizeof(arr));

// 在32位系统中输出 4 ,在64位系统中输出 8
printf("%zu\n", sizeof(pointer));

return 0;
}

数组元素的指针加下标的方式也能调用数组中的元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main() {
int arr[] = { 1,2,3,4,5 };

// 数组名代表了数组首元素的内存地址
// 将数组首元素的内存地址赋值给指针
int* pointer = arr;

// 使用指针加下标的方式调用数组第二个元素
int num = pointer[1];

// 将会输出 2
printf("num = %d\n", num);

return 0;
}

  如果取数组中第二个元素arr[1]的指针,并以arr[1]的指针加下标的方式调用数组中的元素,那么调用arr[1]将使用下标0,调用其他元素的下标均在原数组下标基础上-1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main() {
int arr[] = { 1,2,3,4,5 };

// 数组中第二个元素的内存地址
int* pointer = &arr[1];

// 将会输出 arr[0] 的值 1
printf("value1 = %d\n", pointer[-1]);
// 将会输出 arr[1] 的值 2
printf("value2 = %d\n", pointer[0]);
// 将会输出 arr[2] 的值 3
printf("value3 = %d\n", pointer[1]);

return 0;
}

  同理,如果取第三个元素arr[2]的指针,用arr[2]的指针加下标的方式调用数组中的元素,那么调用arr[2]将使用下标0,调用其他元素的下标均在元素组下标基础上-2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main() {
int arr[] = { 1,2,3,4,5 };

// 数组中第三个元素的内存地址
int* pointer = &arr[2];

// 将会输出 arr[1] 的值 2
printf("value1 = %d\n", pointer[-1]);
// 将会输出 arr[2] 的值 3
printf("value2 = %d\n", pointer[0]);
// 将会输出 arr[3] 的值 4
printf("value3 = %d\n", pointer[1]);

return 0;
}

  其实我发现如果指针指向变量,也可以用像数组一样,取指针指向的变量的值

1
2
3
4
5
6
7
8
9
int main() {
int num = 10;
int* pointer = #

// 将会输出 10
printf("pointer = %d\n", pointer[0]);

return 0;
}

指针数组

  由指针组成的数组,其本质上还是一个数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int main() {
// 指针数组
int* p[5];

int arr[5] = { 1,2,3,4,5 };

// 循环将int类型数组中元素的地址值存进指针数组
for (size_t i = 0; i < 5; i++) {
p[i] = &arr[i];
}

// 输出指针数组中指针的地址值和地址中的值
for (size_t i = 0; i < 5; i++) {
printf("pointer = %p\n", p[i]);
printf("num = %d\n", *p[i]);
}

return 0;
}

二级指针(多重指针)

  指针作为一个占用4字节内存的数据,它也有自己的内存地址,那么指针的内存地址也可以用另一个指针记录,如下代码pointer_pointer指向指针pointer的地址

1
2
3
4
5
6
7
8
9
10
11
12
13
int main() {

int num = 100;

int* pointer = &num;
printf("pointer = %p\n", pointer);

// 指针的指针
int** pointer_pointer = &pointer;
printf("pointer_pointer = %p\n", pointer_pointer);

return 0;
}

字符指针

  字符串有下面四种常见写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
int main() {
// char数组以 \0 结尾,构成字符串
char str1[] = { 'h','e','l','l','o','\0' };
// 直接在大括号中写字符串
char str2[] = {"hello"};
// 去掉大括号
char str3[] = "hello";
// 字符串直接赋值给指针,无法修改指定某个元素
char* str4 = "hello";

// 将输出 4 个 hello
printf("str1=%s, str2=%s, str3=%s, str4=%s\n", str1, str2, str3, str4);

// 数组名代表的内存地址无法修改,但是可以修改元素值
str1[0] = 'H';
str2[0] = 'H';
str3[0] = 'H';

// 字符串直接赋值的指针,无法修改指定某个元素
// 但是可以修改指向的内存地址
str4 = "Hello";

// 将输出 4 个 Hello
printf("str1=%s, str2=%s, str3=%s, str4=%s\n", str1, str2, str3, str4);

return 0;
}

  字符串指针即便转换成char数组,依然不能够修改指定的某个元素值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int main() {
char* str = "hello";

// 将字符串指针作为参数传递给 test1
// test1 所接收的形参是 char 数组
test1(str);

return 0;
}

void test1(char arr[]) {
// 字符串指针即便转换成 char 数组
// 依然不能够修改指定的某个元素值
arr[0] = 'H';

printf("arr[0] = %c\n", arr[0]);
}

  给char类型的指针直接赋值字符串,相当于将一个char数组构成的字符串赋值给指针,不同的是这样的指针是可以修改指定元素值的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int main() {
// char数组以 \0 结尾,构成字符串
char str1[] = { 'h','e','l','l','o','\0' };

// char数组赋值给指针
// 相当于 char* str4 = "hello"
char* str4 = str1;

// 将输出两个 hello
printf("str1=%s, str2=%s\n", str1, str4);

// 需要注意的是,如果将char数组构成的字符串赋值给指针变量
// 是可以修改指定的元素值的
str4[0] = 'H';

// 将输出两个 Hello
printf("str1=%s, str2=%s\n", str1, str4);

return 0;
}

数组指针

  数组指针不同于指针数组,指针数组是一个数组,其中的每个元素都是指针,数组指针是指向数组的指针,它指向数组的首元素地址

  数组指针通常以下面这种格式定义

1
数据类型(*数组指针变量名)[长度] = &数组名

  由于数组名代表数组首元素的内存地址,所以也可以省略取地址符号&

1
数据类型(*数组指针变量名)[长度] = 数组名

一维数组的指针

  (*pointer)表示pointer是一个指向数组的指针,指向的数组包含4int类型元素;解引用*pointer将提供整个数组,(*pointer)[0]将给出数组的第一个元素;不过其实一维数组完全没必要用这样的数组指针定义,直接把数组名赋值给一个与数组元素相同类型的指针就行了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int main() {
int arr[5] = { 1,2,3,4 };

// 数组指针,指向一个数组
int(*pointer)[] = &arr;

// 打印下面三个值,输出的内容是一致的

// 1.数组指针指向的内存地址
printf("pointer = %p\n", pointer);
// 2.数组指针指向的内存地址中储存的数组
printf("*pointer = %p\n", *pointer);
// 3.数组指针指向的内存地址中储存的数组的首元素地址
printf("*pointer[0] = %p\n", &((*pointer)[0]));

// 将输出 1, 2, 3, 4
printf("%d, %d, %d, %d\n", (*pointer)[0], (*pointer)[1], (*pointer)[2], (*pointer)[3]);

return 0;
}

  用指针调用数组中的元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int main() {
int arr[5] = { 1,2,3,4 };

// 数组指针,指向一个数组
int(*pointer)[] = &arr;

// 解引用,得到数组arr的指针,其实等同于将arr代表的地址赋值给arr_p
int* arr_p = *pointer;
// 将输出 1
printf("%d\n", *arr_p);

// 指针向下移动
arr_p++;

// 将输出 2
printf("%d\n", *arr_p);

return 0;
}

二维数组的指针

  当arr是如下的二维数组时,pointer依然是一个指向包含4int类型元素的数组的指针;解引用*pointer将提供二维数组第一行的数组,(*pointer)[0]将给出第一行数组的第一个元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
int main() {
int arr[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};

// 数组指针,指向二维数组中的某一行
int(*pointer)[4] = arr;

// pointer的起始位置在二维数组的第一行
// 所以arr_0将指向 {1, 2, 3, 4} 数组
int* arr_0 = *pointer;
// 将输出 1, 2, 3, 4
printf("%d, %d, %d, %d\n", arr_0[0], arr_0[1], arr_0[2], arr_0[3]);

// 指针向下移动
pointer++;

// pointer目前移动到二维数组的第二行
// 所以arr_1将指向 {5, 6, 7, 8} 数组
int* arr_1 = *pointer;
// 将输出 5, 6, 7, 8
printf("%d, %d, %d, %d\n", arr_1[0], arr_1[1], arr_1[2], arr_1[3]);

// 指针继续向下移动
pointer++;

// pointer目前移动到二维数组的第三行
// 所以arr_2将指向 {9, 10, 11, 12} 数组
int* arr_2 = *pointer;
// 将输出 9, 10, 11, 12
printf("%d, %d, %d, %d\n", arr_2[0], arr_2[1], arr_2[2], arr_2[3]);

return 0;
}

  如果不使用移动指针的方式,也可以用指针直接调用二维数组中的元素,通过pointer[i][j]的方式,访问不同数组中的元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
int main() {
int arr[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};

// 数组指针,指向二维数组中的某一行
int(*pointer)[4] = arr;

// 指针直接调用二维数组的第一行
int* arr_0 = pointer[0];
// 将输出 1, 2, 3, 4
printf("%d, %d, %d, %d\n", arr_0[0], arr_0[1], arr_0[2], arr_0[3]);

// 指针直接调用二维数组的第二行
int* arr_1 = pointer[1];
// 将输出 5, 6, 7, 8
printf("%d, %d, %d, %d\n", arr_1[0], arr_1[1], arr_1[2], arr_1[3]);

// 指针直接调用二维数组的第三行
int* arr_2 = pointer[2];
// 将输出 9, 10, 11, 12
printf("%d, %d, %d, %d\n", arr_2[0], arr_2[1], arr_2[2], arr_2[3]);

return 0;
}

三维数组的指针

  当arr是三维数组时,pointer是一个指向二维数组的指针,指向的二维数组中包含了3个一维数组,每个一维数组又包含4int类型的元素;通过pointer++可以移动指针到下一个二维数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
int main() {
int arr[2][3][4] = {
{
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
},
{
{13, 14, 15, 16},
{17, 18, 19, 20},
{21, 22, 23, 24}
}
};

// 数组指针,指向三维数组中的某个二维数组
int(*pointer)[3][4] = arr;

// pointer的起始位置在三维数组的第一个元素
// (*pointer)[0]是第一个元素的第一行
int* arr_0_0 = (*pointer)[0];
// 将输出 1, 2, 3, 4
printf("%d, %d, %d, %d\n", arr_0_0[0], arr_0_0[1], arr_0_0[2], arr_0_0[3]);

// (*pointer)[1]是第一个元素的第二行
int* arr_0_1 = (*pointer)[1];
// 将输出 5, 6, 7, 8
printf("%d, %d, %d, %d\n", arr_0_1[0], arr_0_1[1], arr_0_1[2], arr_0_1[3]);

// (*pointer)[2]是第一个元素的第三行
int* arr_0_2 = (*pointer)[2];
// 将输出 9, 10, 11, 12
printf("%d, %d, %d, %d\n", arr_0_2[0], arr_0_2[1], arr_0_2[2], arr_0_2[3]);

// 指针向下移动
pointer++;

// pointer目前移动到三维数组的第二个元素
// (*pointer)[0]是第二个元素的第一行
int* arr_1_0 = (*pointer)[0];
// 将输出 13, 14, 15, 16
printf("%d, %d, %d, %d\n", arr_1_0[0], arr_1_0[1], arr_1_0[2], arr_1_0[3]);

// (*pointer)[1]是第二个元素的第二行
int* arr_1_1 = (*pointer)[1];
// 将输出 17, 18, 19, 20
printf("%d, %d, %d, %d\n", arr_1_1[0], arr_1_1[1], arr_1_1[2], arr_1_1[3]);

// (*pointer)[2]是第二个元素的第三行
int* arr_1_2 = (*pointer)[2];
// 将输出 21, 22, 23, 24
printf("%d, %d, %d, %d\n", arr_1_2[0], arr_1_2[1], arr_1_2[2], arr_1_2[3]);

return 0;
}

  通过pointer[i][j][k]的方式,直接访问不同二维数组中的元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
int main() {
int arr[2][3][4] = {
{
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
},
{
{13, 14, 15, 16},
{17, 18, 19, 20},
{21, 22, 23, 24}
}
};

// 数组指针,指向三维数组中的某个二维数组
int(*pointer)[3][4] = arr;

// 指针直接调用三维数组第一个元素的第一行
int* arr_0_0 = pointer[0][0];
// 将输出 1, 2, 3, 4
printf("%d, %d, %d, %d\n", arr_0_0[0], arr_0_0[1], arr_0_0[2], arr_0_0[3]);

// 指针直接调用三维数组第一个元素的第二行
int* arr_0_1 = pointer[0][1];
// 将输出 5, 6, 7, 8
printf("%d, %d, %d, %d\n", arr_0_1[0], arr_0_1[1], arr_0_1[2], arr_0_1[3]);

// 指针直接调用三维数组第一个元素的第三行
int* arr_0_2 = pointer[0][2];
// 将输出 9, 10, 11, 12
printf("%d, %d, %d, %d\n", arr_0_2[0], arr_0_2[1], arr_0_2[2], arr_0_2[3]);

// 指针直接调用三维数组第二个元素的第一行
int* arr_1_0 = pointer[1][0];
// 将输出 13, 14, 15, 16
printf("%d, %d, %d, %d\n", arr_1_0[0], arr_1_0[1], arr_1_0[2], arr_1_0[3]);

// 指针直接调用三维数组第二个元素的第二行
int* arr_1_1 = pointer[1][1];
// 将输出 17, 18, 19, 20
printf("%d, %d, %d, %d\n", arr_1_1[0], arr_1_1[1], arr_1_1[2], arr_1_1[3]);

// 指针直接调用三维数组第二个元素的第三行
int* arr_1_2 = pointer[1][2];
// 将输出 21, 22, 23, 24
printf("%d, %d, %d, %d\n", arr_1_2[0], arr_1_2[1], arr_1_2[2], arr_1_2[3]);

return 0;
}

个人一点小看法

  如果将一维数组视作是一个仅包含一个元素的二维数组,即将int arr[] = { 1,2,3,4,5 }视作int arr[] = { { 1,2,3,4,5 } },那么二维数组和一维数组的用法以及定义就相互吻合了
  而且如果不这样的话,无法解释pointer[i]不能调用其指向的一维数组int arr[5] = { 1,2,3,4 }中的元素的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int main() {
int arr[1][4] = {
{ 1,2,3,4 }
};

// 数组指针,指向一个二维数组
int(*pointer)[4] = arr;

// pointer的起始位置在二维数组的第一行(arr中只有一行)
// 所以arr_0将指向 { 1,2,3,4 } 数组
int* arr_0 = *pointer;
// 将输出 1, 2, 3, 4
printf("%d, %d, %d, %d\n", arr_0[0], arr_0[1], arr_0[2], arr_0[3]);

// 如果再使用 pointer++ 移动指针,将会溢出
// 因为arr中只有一行

return 0;
}

  用指针直接调用的方式,使用pointer[i][j]访问一维数组中的元素;即使arr是个一维数组,也要通过这种方式,而不是pointer[i]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main() {
int arr[1][4] = {
{ 1,2,3,4}
};

// 数组指针,指向一个二维数组
int(*pointer)[4] = arr;

// 指针直接调用二维数组的第一行
int* arr_0 = pointer[0];
// 将输出 1, 2, 3, 4
printf("%d, %d, %d, %d\n", arr_0[0], arr_0[1], arr_0[2], arr_0[3]);

return 0;
}

  同样二维数组也可以视作仅包含一个元素的三维数组,只不过这种情况下二维数组的指针(*pointer)[0]不是一个左值,所以无法通过移动指针位置的方式选择二维数组中的一维数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
int main() {
int arr[1][3][4] = {
{
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
}
};

// 数组指针,指向三维数组中的某个二维数组
int(*pointer)[3][4] = arr;

// pointer的起始位置在三维数组的第一个元素
// (*pointer)[0]是第一个元素的第一行
int* arr_0 = (*pointer)[0];
// 将输出 1, 2, 3, 4
printf("%d, %d, %d, %d\n", arr_0[0], arr_0[1], arr_0[2], arr_0[3]);

// (*pointer)[1]是第一个元素的第二行
int* arr_1 = (*pointer)[1];
// 将输出 5, 6, 7, 8
printf("%d, %d, %d, %d\n", arr_1[0], arr_1[1], arr_1[2], arr_1[3]);

// (*pointer)[2]是第一个元素的第三行
int* arr_2 = (*pointer)[2];
// 将输出 9, 10, 11, 12
printf("%d, %d, %d, %d\n", arr_2[0], arr_2[1], arr_2[2], arr_2[3]);

return 0;
}

  通过pointer[i][j][k]的方式,直接访问不同二维数组中的元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
int main() {
int arr[1][3][4] = {
{
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
}
};

// 数组指针,指向三维数组中的某个二维数组
int(*pointer)[3][4] = arr;

// pointer的起始位置在三维数组的第一个元素
// pointer[0][0]是第一个元素的第一行
int* arr_0 = pointer[0][0];
// 将输出 1, 2, 3, 4
printf("%d, %d, %d, %d\n", arr_0[0], arr_0[1], arr_0[2], arr_0[3]);

// pointer[0][1]是第一个元素的第二行
int* arr_1 = pointer[0][1];
// 将输出 5, 6, 7, 8
printf("%d, %d, %d, %d\n", arr_1[0], arr_1[1], arr_1[2], arr_1[3]);

// pointer[0][2]是第一个元素的第三行
int* arr_2 = pointer[0][2];
// 将输出 9, 10, 11, 12
printf("%d, %d, %d, %d\n", arr_2[0], arr_2[1], arr_2[2], arr_2[3]);

return 0;
}