#C++ Day16 October 26 2025

//9 – 6 – 1 the relationship between pointer and array

#include <iostream>

using namespace std;

/*

———————————

|  5   |  4   |  3  |  2  |  1  | 

———————————

4   4 4   4 4   bytes

int

*/

//pointer is an address and the array’s name is an address too

//

int main() {

//1.To access the array’s elements with pointer

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

cout << “array’s first element:” << a[0] << endl;

int* p = a;

cout << “array element’s first address:” << a << endl;

cout << “the address that pointer pointed to:” << p << endl;

cout << “the pointer access: the first element in the array:” << *p << endl;

//the shift number of int is 4 bytes

cout << “——————-” << endl;

cout << “the second element’s address of the array:” << &a[1] << endl;

cout << “pointer + 1  pointed address ” << p + 1 << endl; 

cout << “the second element of the pointer accessed to:” << *(p + 1) << endl;

cout << “the third element’s address of the array:” << &a[2] << endl;

cout << “pointer + 2  pointed address ” << p + 2 << endl;

cout << “the third element of the pointer accessed to:” << *(p + 2) << endl;

cout << “——————-” << endl;

p++;

cout << “pointer access: array’s second element:” << *p << endl;

cout << “——————-” << endl;

//a++;//this place of the array can be the same as pointer

//the array can’t add the sequence after initiation

p = a;

for (int i = 0; i < 5; ++i) {

cout << “the array’s ” << (i + 1) << ” element is ” << *p << endl;

p++;

}

cout << “——————-” << endl;

double b[4] = { 4,3,2,1 };

double* pd = b;

cout << “the first element’s address:” << pd << endl;

pd++;

cout << “the second element’s address:” << pd << endl;

//the shift number of double is 8 bytes

//59 5A 5B 5C 5D 5E 5F 60

//hexadecimal 十六进制

//指针的+1,实际上是偏移了 sizeof(对应的指针类型)个字节

// when a pointer is incrementes by 1, it acctuall offsets by a number of bytes equal to the size of the data type that the pointer points to

//char,short, long long, float

cout << “——————-” << endl;

char c[4] = { ‘4’,’3′,’2′,’1′ };

char* pc = c;

cout << sizeof(char) << endl;

printf(“the first element’s address:%#X\n”, pc);

pc++;

printf(“the second element’s address:%#X\n”, pc);

cout << “——————-” << endl;

short d[4] = { 4,3,2,1};

short* ps = d;

cout << sizeof(short) << endl;

cout << “the first element’s address:” << ps << endl;

ps++;

cout << “the second element’s address:” << ps << endl;

cout << “——————-” << endl;

long long e[4] = { 4,3,2,1 };

long long* pll = e;

cout << sizeof(long long) << endl;

cout << “the first element’s address:” << pll << endl;

pll++;

cout << “the second element’s address:” << pll << endl;

//99 9A 9B 9C 9D 9E 9F A0

cout << “——————-” << endl;

float f[4] = { 4,3,2,1 };

float* pf = f;

cout << sizeof(float) << endl;

cout << “the first element’s address:” << pf << endl;

pf++;

cout << “the second element’s address:” << pf << endl;

return 0;

}

//9 – 6 – 2 pointer array

//指针数组

#include <iostream>

using namespace std;

int main() {

char a[] = “I”;

char b[] = “love”;

char c[] = “you”;

//pointer array

char* p[3];// have 3 elements, each element is a pointer

p[0] = a;

p[1] = b;

p[2] = c;

for (int i = 0; i < 3; ++i) {

cout << p[i] << ‘ ‘;

}

cout << endl;

//two dimensional array(matrix)

//二维数组 是 矩阵

int mat[3][4]{

{1,2,3,4},

{5,6,7,8},

{9,10,11,12}

};

int* pmat[3];

//[rows][columns]

pmat[0] = mat[0];//mat[0] represents the one dimensional array’s first address

//mat[0]代表一维数组的首地址

pmat[1] = mat[1];

pmat[2] = mat[2];

for (int i = 0; i < 3; ++i) {

for (int j = 0; j < 4; ++j) {

cout << *(pmat[i] + j) << ‘ ‘;

//cout << pmat[i] + j << ‘ ‘;

// +j and then dereference it

}

cout << endl;

}

return 0;

}

//9 – 6 – 3 array pointer

//数组指针

#include <iostream>

using namespace std;

int main() {

int (*p)[5];

//数组指针可以类比成二维数组

int a[4][5]{

{4,3,2,1,0},

{9,8,7,6,5},

{6,7,8,9,0},

{5,6,7,8,9}

};

p = a;

cout << p << endl; // hex 70  -> 7F -> 84

//15 + 5 = 20 bytes

// 20 = 4 * 5 = sizeof(int) *5(columns)

//shift value

cout << p+1 << endl; //hex 84 

cout << p << ‘:’ << &a[0] << endl;

cout << p+1 << ‘:’ << &a[1] << endl;

//理解成数组指针指向一个二维数组的首地址,所以数组指针可以类比理解成二维数组

//我们可以利用这个数组指针操作一个二维数组上的数组

return 0;

}

//9 – 6 – 4 pointer array and array pointer

//指针数组和数组指针

#include <iostream>

using namespace std;

string getHex(int x) {

char buff[10];

sprintf_s(buff, “%X”, (x & 0xFFFF));

return (string)buff;

}

int main() {

int a[3][4]{

{1,2,3,4},

{9,10,11,12},

{13,14,15,16}

};

for (int i = 0; i < 3; i++) {

for (int j = 0; j < 4; j++) {

if (j) {

cout << ‘,’;

}

int* p = &(a[i][j]);

//cout << (void*)p ;

cout << getHex((int) p);

}

cout << endl;

}

//指针数组

int* q[3] = { &a[0][0],&a[1][0],&a[2][0] }; //这个3代表数组长度

//the same as  int* q[3] = { a[0],a[1],a[2] } ;

//数组指针

int (*p)[4];

//这个4代表你指向的是一个长度为4的数组

p = &a[0];//即{1,2,3,4}, 这一行

cout << “1.指针 + i” << endl;

//q+i\p+i

cout << “数组指针 ” << endl;

for (int i = 0; i < 3; ++i) {

string s = getHex((int)(p + i)) ;

cout << “第” << i << “个[4]数组的地址是” << s << endl;

}

cout << “指针数组(没啥用),此为装指针的二重指针地址 ” << endl;

for (int i = 0; i < 3; ++i) {

string s = getHex((int)(q + i));

cout << “第” << i << “q元素的地址是” << s << endl;

}

cout << “2.*(指针 + i)” << endl;

//== *(指针 + i)+0

//*(q+i) \ *(p+i)

cout << “数组指针 ” << endl;

for (int i = 0; i < 3; ++i) {

string s = getHex((int)*(p + i));

cout << “a数组的第” << i << “行的第0个元素的地址是” << s << endl;

}

cout << “指针数组 ” << endl;

for (int i = 0; i < 3; ++i) {

string s = getHex((int)*(q + i));

cout << “a数组的第” << i << “行的第0个元素的地址是” << s << endl;

}

cout << “3.*(指针 + i)+j” << endl;

cout << “数组指针 ” << endl;

for (int i = 0; i < 3; ++i) {

string s = getHex((int)(*(p + i) + 1));

cout << “a数组的第” << i << “行的第1个元素的地址是” << s << endl;

}

cout << “指针数组 ” << endl;

for (int i = 0; i < 3; ++i) {

string s = getHex((int)(*(q + i)+2));

cout << “a数组的第” << i << “行的第2个元素的地址是” << s << endl;

}

return 0;

}

AI:

这是 C 语言中数组与指针关系的前置知识,用于理解数组元素的指针访问方式,以下是逐行解释:

  • int a[1024];:定义了一个长度为 1024 的整型数组a,数组在内存中是连续存储的。
  • int *p = a;:定义了一个整型指针p,并将数组a的首地址赋值给p。因为数组名a本质上是数组首元素的地址(即&a[0]),所以p指向了数组a的第一个元素a[0]。
  • a ≡ &(a[0]):数组名a等价于数组首元素a[0]的地址,这是 C 语言的语法规则。
  • p ≡ &(a[0]):因为p被赋值为a(数组首地址),所以p也等价于a[0]的地址。
  • p + 0 ≡ &(a[0]):指针p加 0,还是指向自身,所以等价于a[0]的地址。
  • p + i ≡ &(a[i]):指针的算术运算规则,p是指向int类型的指针,p + i表示在p的基础上偏移i个int类型的大小,最终指向的是数组第i个元素a[i]的地址。
  • *(p + i) ≡ *(&(a[i])) ≡ a[i]:
    • *(p + i):对p + i(a[i]的地址)解引用,得到的是该地址存储的内容,即a[i]。
    • *(&(a[i])):先取a[i]的地址(&(a[i])),再对该地址解引用,结果就是a[i]本身。
  • *(p + i) ≡ p[i]:这是指针的下标访问语法糖,p[i]等价于*(p + i),本质上是通过指针偏移和解引用操作来访问数组元素。

总结来说,这些规则揭示了数组和指针在访问元素时的等价性,理解它们能帮助你灵活运用指针操作数组,是 C 语言中数组与指针知识的核心基础。

这段代码通过具体的地址打印,直观展示了指针数组数组指针的本质区别,尤其是两者在指针运算(+i)时的不同行为。我们逐部分解析:

一、前置知识:getHex函数

作用:将整数(这里实际是指针强制转换的结果)的低 16 位转换为大写十六进制字符串,方便以地址形式展示(简化地址显示,只看低 16 位差异)。

二、二维数组a的地址打印

首先定义了一个3行4列的二维数组a,然后通过嵌套循环遍历每个元素,打印每个元素的地址(转换为十六进制)。

例如,假设a在内存中连续存储(二维数组在内存中是 “按行连续” 的),输出的地址会呈现规律:

  • 同一行内,元素地址依次递增(间隔sizeof(int),通常 4 字节);
  • 行与行之间,地址间隔4*sizeof(int)(一行 4 个元素,共 16 字节)。

三、指针数组q与数组指针p的定义

1. 指针数组q

定义:int* q[3] = { &a[0][0], &a[1][0], &a[2][0] };

  • 本质:q是一个数组,包含 3 个元素,每个元素都是int*指针(指向int类型)。
  • 初始化:3 个指针分别指向二维数组a的第 0 行、第 1 行、第 2 行的首元素(&a[i][0]等价于a[i],因为数组名a[i]即首元素地址)。

2. 数组指针p

定义:int (*p)[4];

  • 本质:p是一个指针,专门指向 “包含 4 个int元素的数组”(即二维数组a中的一行)。
  • 赋值:p = &a[0]; 表示p指向a的第 0 行(a[0]是一行 4 个元素的数组,&a[0]是整个行的地址)。

四、核心对比:p+iq+i的区别

代码通过循环打印p+i和q+i的地址,直观展示两者的差异:

1. 数组指针p+i

  • 含义:p指向 “4 个int的数组”,p+i表示指针向后移动i个 “行”(每个行是 4 个int)。
  • 地址变化:每次移动的步长 = 4 * sizeof(int)(一行的总大小,通常 16 字节)。
  • 输出结果:p+0指向第 0 行地址,p+1指向第 1 行地址,p+2指向第 2 行地址,与前面二维数组a每行首元素的地址完全一致(因为行地址即首元素地址)。

2. 指针数组q+i

  • 含义:q是数组(元素为int*),q作为数组名表示首元素地址,q+i表示数组q中第i个元素的地址(即指针数组自身存储元素的位置)。
  • 地址变化:每次移动的步长 = sizeof(int*)(一个指针的大小,64 位系统中是 8 字节)。
  • 输出结果:打印的是指针数组q自身在内存中的元素地址(比如q+0是q[0]这个指针变量的地址,q+1是q[1]这个指针变量的地址),与二维数组a的地址无关(除非巧合),因此注释说 “没啥用”(因为这里想观察的是指向a的地址,而非q自身的元素地址)。

总结

这段代码的核心目的是通过地址打印,让我们看到:

  • 数组指针p 是 “指向行的指针”,p+i会跳到第i行的地址(步长为一行大小),用于定位二维数组的行;
  • 指针数组q 是 “存储行首指针的数组”,q+i是数组中第i个指针的地址(步长为指针大小),与二维数组的行地址无直接关联(除非访问q[i]才是行首指针)。

通过地址的十六进制变化,能更直观理解两者的本质差异:一个是 “指向数组的指针”,一个是 “存储指针的数组”。

AI2:

这张图是用来解释二维数组的地址与元素值的对应关系,核心是展示二维数组在内存中的连续存储特性,以下是分部分解释:

1. 二维数组的 “值”(a[i][j]

上方的表格是二维数组中每个元素的具体数值,比如:

  • 第 0 行(i=0)的元素是 a[0][0]=1、a[0][1]=2、a[0][2]=3、a[0][3]=4;
  • 第 1 行(i=1)的元素是 a[1][0]=5、a[1][1]=4、a[1][2]=6、a[1][3]=7;
  • 第 2 行(i=2)的元素是 a[2][0]=9、a[2][1]=10、a[2][2]=11、a[2][3]=12。

2. 二维数组的 “地址”(&a[i][j]

下方的表格是对应元素在内存中的地址(以十六进制表示,比如F18、F1C等),需要注意以下几点:

  • 内存连续性:二维数组在内存中是按行连续存储的。比如第 0 行最后一个元素a[0][3]的地址是F24,第 1 行第一个元素a[1][0]的地址是F28,两者相差4个字节(因为int类型占 4 字节),这体现了 “第一行的最后一个元素的地址偏移了 4 个字节” 的说明。000
  • 地址与元素的对应:每个地址&a[i][j]唯一对应一个元素a[i][j],比如地址F18对应值1(即a[0][0]),地址F2C对应值4(即a[1][1])。

3. 补充说明(底部文字)

“a[i][j]的地址(也就是&a[i][j])并没有存储在二维数组里面,PPT 这么连续的画是为了清楚的和二维数组中的值,进行一一对应而已” 这句话是强调:内存中只存储数组的元素值,“地址表格” 是为了辅助理解而抽象出来的对应关系,实际内存里不会额外存储这些地址的表格,地址是通过数组的存储规则(连续、按类型偏移)计算得到的。

总结来说,这张图通过 “值” 和 “地址” 的对照,直观展示了二维数组按行连续存储的内存特性,以及元素值和其地址的一一对应关系,帮助理解二维数组的指针访问和内存布局。

AI3:

这是关于指针数组的知识讲解图,核心信息如下:

1. 指针数组的定义

int *p[3]; 表示定义了一个指针数组p,它包含 3 个元素,每个元素都是一个指向int类型的指针。

2. 指针数组与二维数组的关联

指针数组p的每个元素p[i]存储了二维数组a对应行的首地址:

  • p[0]的值为F18,指向二维数组a第 0 行的首元素a[0][0](值为 1);
  • p[1]的值为F28,指向二维数组a第 1 行的首元素a[1][0](值为 5);
  • p[2]的值为F38,指向二维数组a第 2 行的首元素a[2][0](值为 9)。

3. 二维数组的内存呼应

右侧的 “二维数组a的值” 和 “二维数组a的地址” 表格,与之前讲解的二维数组内存布局一致,体现了元素值a[i][j]和其地址&a[i][j]的一一对应关系,同时也说明指针数组p是通过存储每行首地址来间接访问二维数组元素的。

这种结构常用于灵活管理多个一维数组(或二维数组的行),相比直接操作二维数组,指针数组在某些场景(如行的重新排序、动态分配行内存等)下更具灵活性。

AI4:

存储地址的地址,指针的指针

从十六进制地址A38到A40,核心是偏移 8 个字节,需结合内存编址规则和数据存储特性解读:

1. 先明确地址差值:8 个字节

  • 十六进制计算:A40(十六进制) – A38(十六进制) = 8(十六进制),而十六进制的8对应十进制的8,因此地址跨度为 8 个字节(内存以字节为最小编址单位,1 个十六进制单位对应 1 个字节)。
  • 直观验证:从A38开始,逐字节递增依次是A39、A3A、A3B、A3C、A3D、A3E、A3F、A40,共 8 个字节间隔。

2. 与 “数据类型存储” 的关联(结合此前场景)

结合之前讨论的 “4 字节偏移(对应 32 位数据)”,A38到A40的 8 字节偏移,通常对应以下场景:

  • 双精度数据类型:若存储double(双精度浮点数,占 8 字节),从A38开始存储 1 个double数据,其结束地址就是A40(A38为起始地址,占用A38~A3F共 8 字节,下一个数据的起始地址为A40)。
  • 多个 32 位数据的连续存储:若存储 2 个int(32 位整数,每个占 4 字节),第一个int从A38开始(占A38~A3B),第二个int从A3C开始(占A3C~A3F),两个数据存储完成后,下一个数据的起始地址就是A40,整体体现为从A38到A40的 8 字节跨度。
  • 指针数组的元素间隔:若A38是指针数组p中某元素(如p[0])的地址(指针占 8 字节,64 位系统),则下一个元素p[1]的地址就是A40,正好对应A38到A40的 8 字节偏移(64 位系统中指针占 8 字节,元素间偏移 8 字节)。

3. 内存对齐的体现

若系统遵循 “8 字节对齐”(如 64 位环境下部分数据类型要求),A38到A40的跨度也符合对齐规则:

  • 例如long long(64 位整数,占 8 字节)需 8 字节对齐,其起始地址必须是 8 的倍数(A38是 8 的倍数:A38转十进制为 2616,2616÷8=327,无余数),存储后下一个对齐地址就是A40(A40转十进制为 2624,2624÷8=328,同样是 8 的倍数),符合对齐要求。

综上,A38到A40的核心是 8 字节地址跨度,其本质是由数据类型的存储大小(如 8 字节double、2 个 4 字节int)或内存对齐规则决定的,是计算机内存连续存储逻辑的典型体现。

这是关于指针数组的知识图解,核心信息如下:

1. 指针数组的定义与存储

  • 定义:int *p[3]; 表示一个指针数组p,包含 3 个指向int类型的指针元素。
  • 指针数组的值(p[i]):存储二维数组a各行的首地址,如p[0]=F18(指向a[0][0])、p[1]=F28(指向a[1][0])、p[2]=F38(指向a[2][0])。
  • 指针数组的地址(&p[i]):指针数组本身也存储在内存中,其元素的地址为A38、A40、A48,体现了指针数组自身的内存布局。

2. 与二维数组的关联

右侧的二维数组a的 “值(a[i][j])” 和 “地址(&a[i][j])” 表格,与指针数组p的元素形成映射:指针数组通过存储行首地址,间接实现对二维数组元素的访问。这种结构常用于灵活管理多维数组的行,在行的动态分配、排序等场景中具有优势。

3. 内存特性说明

指针数组的 “值” 存储的是目标数组的地址,而其自身的 “地址” 是指针数组元素在内存中的存储位置,清晰区分了 “指针存储的地址” 和 “指针本身的地址” 这两个易混淆的概念,帮助理解指针数组的双层内存结构。

sprintf_s(buff, “%X”, (x & 0xFFFF));

这段代码的作用是将变量 x 的低 16 位数据以大写十六进制形式格式化到缓冲区 buff 中,具体解析如下:

  • 核心操作:提取低 16 位
    (x & 0xFFFF) 是一个位运算:
    • 0xFFFF 是十六进制常量,对应二进制的 16 个连续的 1(即 0b1111111111111111)。
    • 与 x 进行 “按位与” 运算后,会保留 x 的低 16 位数据,并将高于 16 位的部分清零(即只保留 x 中范围为 0~0xFFFF 的值)。
  • 格式化写入缓冲区
    sprintf_s(buff, “%X”, …) 是安全的字符串格式化函数:
    • %X 是格式控制符,表示以大写十六进制形式输出整数(例如 10 会被转换为 “A”,255 会被转换为 “FF”)。
    • sprintf_s 相比 sprintf 增加了安全性检查(需要指定缓冲区大小,通常用法为 sprintf_s(buff, sizeof(buff), “%X”, …)),可避免缓冲区溢出风险。

总结:这段代码的功能是将 x 的低 16 位数值转换为大写十六进制字符串,并存储到 buff 缓冲区中,同时通过 sprintf_s 保证操作的安全性。

AI5:

指针数组和数组指针是 C/C++ 中容易混淆的两个概念,核心区别在于:指针数组是 “数组”(元素为指针),数组指针是 “指针”(指向数组)。下面通过定义、本质、示例详细说明:

一、指针数组(Array of Pointers

本质:一个数组,数组中的每个元素都是指针(同类型指针)。

声明形式:类型 *数组名[数组长度](注意:[]优先级高于*,所以先构成数组,元素为指针)。

示例:

c

运行

int a = 1, b = 2, c = 3;

int *ptr_arr[3];  // 指针数组:数组有3个元素,每个元素是int*指针

// 给数组元素(指针)赋值

ptr_arr[0] = &a;

ptr_arr[1] = &b;

ptr_arr[2] = &c;

// 访问:先通过数组下标取指针,再解引用取数据

printf(“%d, %d, %d”, *ptr_arr[0], *ptr_arr[1], *ptr_arr[2]);  // 输出:1, 2, 3

特点:

  • 数组的大小 = 元素个数 × 单个指针的大小(如 32 位系统指针占 4 字节,64 位占 8 字节)。
  • 用途:常用于存储多个同类型数据的地址(最典型的是字符串数组,因为字符串本质是 char*)。
    例如:c

    运行




    char *strs[3] = {“hello”, “world”, “cpp”};  // 每个元素是char*指针,指向字符串常量




二、数组指针(Pointer to Array

本质:一个指针,该指针专门指向一个固定大小的数组(数组的元素类型和长度固定)。

声明形式:类型 (*指针名)[数组长度](注意:()强制*先与指针名结合,所以是指针,指向数组)。

示例:

c

运行

int arr[3] = {10, 20, 30};

int (*arr_ptr)[3];  // 数组指针:指针指向“包含3个int元素的数组”

arr_ptr = &arr;  // 赋值:指向数组arr的地址(数组名arr是首元素地址,&arr才是整个数组的地址)

// 访问:先解引用指针得到数组,再通过下标取元素

printf(“%d, %d, %d”, (*arr_ptr)[0], (*arr_ptr)[1], (*arr_ptr)[2]);  // 输出:10, 20, 30

特点:

  • 指针的大小 = 系统指针的大小(与指向的数组大小无关,32 位 4 字节,64 位 8 字节)。
  • 指针的 “步长” = 指向的数组总大小(例如int (*p)[3],p+1会跳过3×sizeof(int)字节)。
  • 用途:常用于处理多维数组(尤其是作为函数参数传递多维数组时,明确数组第二维的长度)。
    例如:c

    运行




    int matrix[2][3] = {{1,2,3}, {4,5,6}};
  • int (*p)[3] = matrix;  // p指向matrix的第0行(每行是含3个int的数组)
  • printf(“%d”, p[1][2]);  // 等价于(*(p+1))[2],输出6(第1行第2列)




三、核心区别对比

类型本质声明示例关键特征
指针数组数组(元素为指针)int *arr[3];arr是数组,arr[i]是指针
数组指针指针(指向数组)int (*p)[3];p是指针,*p是数组

记忆技巧

  • 看声明中*和[]的结合顺序:
    • 无括号时,[]优先结合,所以类型 *名[]是指针数组(先成数组,元素为指针)。
    • 有括号时,*优先结合,所以类型 (*名)[]是数组指针(先成指针,指向数组)。

好问题!😊

“指针数组”和“数组指针”名字很像,但意思完全不一样。

我们可以先从直觉上去理解,然后再看看它们的定义。

一、指针数组(array of pointers

定义:

一个指针数组就是数组的每个元素都是一个指针

🔹语法例子:

int *p[3];

这里:

• p 是一个数组;

• 这个数组里有 3 个元素;

• 每个元素都是 int * 类型的指针。

🧠 你可以想象它是:

p[0]p[1]p[2] → 都是指针,分别可以指向不同的 int 变量。

比如:

int a = 1, b = 2, c = 3;

int *p[3] = {&a, &b, &c};

那么:

• *p[0] == 1

• *p[1] == 2

• *p[2] == 3

二、数组指针(pointer to an array

定义:

一个数组指针就是指向整个数组的指针

🔹语法例子:

int (*p)[3];

这里:

• p 是一个指针;

• 它指向一个“包含 3 个 int 元素的数组”。

比如:

int a[3] = {1, 2, 3};

int (*p)[3] = &a;

那么:

• (*p)[0] == 1

• (*p)[1] == 2

• (*p)[2] == 3

🧩 小技巧记忆:

• “指针数组” → 数组大,里面装指针。

• “数组指针” → 指针大,它指向数组。

要不要我帮你画个内存图,用图来区分它们?这样更容易看出谁指向谁。

没关系~这是 C 语言里非常容易混淆的一对概念,我们慢慢来。

我用比喻 + 视觉化的方式帮你彻底分清。

💡先想象两个世界

名称 直观比喻 语法例子

指针数组 一个“装指针的盒子” int *p[3];

数组指针 一根“指向整个数组的指针” int (*p)[3];

🧱 1. 指针数组 int *p[3];

“p 是一个数组,数组中有 3 个指针,每个指针都能指向一个 int。”

图示:

p[0] —> &a

p[1] —> &b

p[2] —> &c

代码例子:

int a = 10, b = 20, c = 30;

int *p[3] = { &a, &b, &c };

printf(“%d\n”, *p[1]);  // 输出 20

🧠 小记忆:

👉 方括号 [] 优先级高,所以 p[3] 是数组,*p[3] 说明数组里是指针。

🧭 2. 数组指针 int (*p)[3];

“p 是一个指针,指向一个含有 3 个 int 的数组。”

图示:

a —> [ 1 | 2 | 3 ]

p —> a

代码例子:

int a[3] = {1, 2, 3};

int (*p)[3] = &a;

printf(“%d\n”, (*p)[1]);  // 输出 2

🧠 小记忆:

👉 () 的优先级强制让 *p 先结合,所以 (*p)[3] 表示 p 指向一个数组。

🌈一句话区分法:

名称 口诀

指针数组 “数组里装指针”

数组指针 “指针指向数组”

想试试我出两道小题,让你判断哪个是“指针数组”,哪个是“数组指针”吗?

我们边做边练就能完全掌握。

非常好!👏完全正确。

✅ int *p[5];

意思是:“p 是一个包含 5 个元素的数组,每个元素都是 int * 类型的指针。”

你记住口诀 “数组里装指针” 就对啦。

我们来升一点难度👇

第 2 题:

int (*p)[5];

请你想一想:这回 p 是指针数组还是数组指针?

非常好!👏完全正确。

✅ int *p[5];

意思是:“p 是一个包含 5 个元素的数组,每个元素都是 int * 类型的指针。”

你记住口诀 “数组里装指针” 就对啦。

我们来升一点难度👇

第 2 题:

int (*p)[5];

请你想一想:这回 p 是指针数组还是数组指针?

完美!👍

你不仅答对了,而且解释也非常到位。

✅ int (*p)[5];

意思是:p 是一个指针,它指向一个包含 5 个 int 元素的数组。

举个小例子帮助你巩固:

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

int (*p)[5] = &a;

printf(“%d\n”, (*p)[2]); // 输出 3

来,我们再来一道稍微绕一点的题,看看你能不能一眼判断出来👇

第 3 题:

char *strs[3];

这次呢?是“指针数组”还是“数组指针”?

(提示:想想 char * 是什么,再看 []。)

这次有点小小偏啦~😉

其实这一题的答案是:

指针数组

我们来拆开看看为什么:

char *strs[3];

• 方括号 [] 优先级比 * 高。

• 所以它先说明 strs 是一个数组

• 然后数组里的每个元素类型是 char *,也就是“字符指针”。

🧠 这表示:

strs 是一个“装有 3 个字符串指针”的数组。

比如:

char *strs[3] = {“Hello”, “World”, “C”};

printf(“%s\n”, strs[1]); // 输出 “World”

你想继续练 2~3 道稍微进阶一点的例子吗?

我可以加上一些你在字符串或二维数组场景中会真用到的那种。