拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

【C语言进阶】指针和数组(指针与数组名;指针数组,数组指针;数组传参,指针传参;字符指针,函数指针,函数指针数组,回调函数)

【C语言进阶】指针和数组(指针与数组名;指针数组,数组指针;数组传参,指针传参;字符指针,函数指针,函数指针数组,回调函数)

目录

一、数组

1.数组名

2.数组的内存布局

二、指针

三、指针与数组

1.使用方法上的相似点:

2.指针与数组名的区别:

3.数组传参

4.C语言为什么要将指针和数组的访问方式设计成通用的?

5.多维数组与指针

6.两道经典例题

四、字符指针

五、指针数组

六、数组指针

七、数组传参、指针传参

八、函数指针

九、函数指针数组——无限套娃

十、回调函数


一、数组

1.数组名

1.大多数情况数组名都代表的是数组首元素的地址,除了:

>>>Sizeof(数组名)—— 数组名表示整个数组 ——计算的是整个数组的大小,单位是字节

>>>&数组名—— 数组名表示整个数组 ——取出的是整个数组的地址

举例验证:

2.数组只能够进行整体初始化,不能整体赋值(数组名是地址常量不能作为左值)。

2.数组的内存布局

1.普通单一局部变量的地址从高往低发展(栈区空间的特点)。

2.数组整体开辟空间,不同数组的地址从高往低发展。

3.数组内部元素的地址连续且随下标递增。


二、指针

1.32位电脑有32根地址线,每根地址线有通(1)断(0)两种状况。因此,32位电脑最多可以访问2^32个地址,每个地址需要32bit == 4byte 存放。又因为一个地址对应一个字节内存,所以32位电脑最大可以访问 2^32byte == 2^10*2^10*2^10*2^2 == 4GB 大小的内存空间。

2.指针类型的意义:

>>>决定指针解引用的权限有多大(能操作几个字节)

>>>决定了指针走一步的步长有多大

3.指针-指针得到的是两个指针之间元素的个数

4.变量的地址是所占内存空间地址最低的字节的地址

5.指针的多种表示形式

Int arr[10]={0};

Int* p=arr;

//arr[2]-->*(arr+2)-->*(2+arr)-->2[arr]

//p[2]-->*(p+2)-->*(2+p)-->2[p]


三、指针与数组

首先声明一点,指针与数组是两套不同的概念,只是因为在使用方法上有相似之处所以经常被初学者搞混。

1.使用方法上的相似点:

指针与数组,在访问多个连续元素的时候,既可以采用指针解引用的方式,也可以采用 [中括号]的 方式。

2.指针与数组名的区别:

>>>数据类型不同:指针是保存地址的变量;数组是相同类型数据的集合。

>>>访问数据的方式:

利用指针访问数据属于间接寻址,本质上是先取p的内容然后加上i*sizeof(类型)字节作为数据真正的地址;

数组名访问数组元素是直接寻址,本质上是arr所代表数组首元素的地址(地址常量)加上i*sizeof(类型)字节作为数据真正的地址。

>>>常用场合:指针通常用于动态数据结构;数组通常用于存储固定数目且数据类型相同的元素。

3.数组传参

1.数组传参,需要降维成指针。如果不降维,就要发生数组拷贝,函数调用效率降低。

2.所有的数组,传参都会降维成指针,降维成为指向其内部元素类型的指针。

3.函数形参列表中的数组会被解释成指向其内部元素类型的指针,因此最高维[中括号]中的数字会被忽略,其他维度是指针的类型,不能省略。

4.C语言为什么要将指针和数组的访问方式设计成通用的?

C语言是面向过程的语言,函数是其核心概念。而在函数调用的过程中数组传参为提高效率,会降维成指针。

假设指针和数组访问元素的方式不通用,程序员需要不断在不同的代码片段处进行习惯的切换,增加代码出错的概率。

为了让程序员统一使用数组,减少出错概率,数组和指针的访问方式才设计成通用的。

5.多维数组与指针

1.在理解上,我们甚至可以将所有的数组都当成“一维数组”。三维数组的元素是二维数组,二维数组的元素是一维数组......

2.多维数组的内存布局也 是“线性连续且递增的”

6.两道经典例题

1.二维数组的元素是一维数组

a[i] <---> *(a+i)

1. **基础大小:**
* `sizeof(a)` 为 **48**:整个二维数组有 3 行 4 列,共 12 个 `int` 元素(12 * 4 = 48 字节)。
* `sizeof(a[0][0])` 为 **4**:单个 `int` 类型的大小。
* `sizeof(a[0])` 为 **16**:`a[0]` 是二维数组的第一行,本质是一个包含 4 个 `int` 的**一维数组**,所以是 4 * 4 = 16 字节。

2. **指针运算与类型退化(关键点):**
* `sizeof(a[0] + 1)` 为 **4**:`a[0]` 作为数组名,在表达式中会退化为指向首元素的指针(`int*`)。`+1` 表示向后移动一个 `int` 的位置,所以结果是一个指针的大小(在 32 位系统中为 4 字节)。
* `sizeof(*(a[0] + 1))` 为 **4**:对上述指针进行解引用,得到的是具体的 `int` 元素。
* `sizeof(a + 1)` 为 **4**:这里的 `a` 是二维数组名,代表指向**行数组**的指针(`int(*)[4]`)。`a + 1` 指向第二行,依然是一个指针,大小为 4。
* `sizeof(*(a + 1))` 为 **16**:解引用 `a + 1`,得到的是第二行这个一维数组,大小为 16 字节。

3. **取地址符 `&` 的用法:**
* `sizeof(&a[0] + 1)` 为 **4**:`&a[0]` 获取的是第一行整个数组的地址(类型为 `int(*)[4]`)。`+1` 依然是指针移动,所以大小是 4。
* `sizeof(*(&a[0] + 1))` 为 **16**:解引用后,得到的是第二行的数组整体,大小为 16。

4. **补充说明:**
* `sizeof(*a)` 为 **16**:`*a` 等价于 `a[0]`,即第一行数组。
* `sizeof(a[3])` 为 **16**:虽然数组 `a` 只有 3 行(下标 0, 1, 2),但 `sizeof` 是编译时求值的。它只关心类型,`a[3]` 在类型上依然是 `int[4]`,所以大小为 16 字节。**注意:这里只是计算大小,如果真的去访问 `a[3]` 会导致数组越界。**

**💡 补充提醒:**
图片中的打印结果(如 4 字节)是在 **32 位系统**下运行的。如果你在 64 位系统上运行这段代码,指针类型(如 `sizeof(a + 1)`、`sizeof(&a[0] + 1)`)的大小会变成 **8 字节**,其余结果(48、16、4)保持不变。

2.指针加 i 跳过 i*sizeof(类型) 个字节

指针相减,代表两个指针之间元素的个数

答案:FFFFFFFC,-4


四、字符指针

1.两个不同的字符数组在内存中占两块不同的存储空间,数组名指向的首元素的地址就不同

2.完全相同的常量字符串只开辟一块存储空间,因此不同的字符指针指向的地址相同。

3.字符数组中的元素是可以改变的,而字符串常量是不能改变的


五、指针数组

1.指针数组的定义

指针数组是数组——元素是指针的数组

Int *arrp[10];——arrp就是一个指针数组,其中的每一个元素都是指针。

2.让指针数组中每一个元素都指向一个字符串常量,统一处理这些字符串。

3..让指针数组中每一个元素都指向一个数组首元素的地址,就可以模拟二维数组,访问这些数组中的元素。将相同类型的一维数组串联起来。


六、数组指针

1.数组指针的定义

数组指针是指针——执向数组的指针

Int (*parr)[10]=&arr;——parr就是一个数组指针,其中存放的是数组的地址

4.数组指针的使用

所有的数组,传参都会降维成指针,降维成为指向其内部元素类型的指针。

在理解上,我们甚至可以将所有的数组都当成“一维数组”。三维数组的元素是二维数组,二维数组的元素是一维数组......多维数组传参,降维成数组指针。


七、数组传参、指针传参

1.一维数组传参

实参

形参

Int arr1[10];

Int arr[10](形式)

Int arr1[10];

Int arr[](形式)

Int arr1[10];

Int* arr(本质)

Int* arr2[20];

Int* arr[20](形式)

Int* arr2[20];

Int* arr[](形式)

Int* arr2[20];

Int** arr(本质)

2.二维数组传参

实参

形参

Int arr[3][5];

Int arr[3][5](形式)

Int arr[3][5];

Int arr[][5](形式)

Int arr[3][5];

Int (*arr) [5](本质)

3.当函数的参数为一级指针的时候,可以接受的参数:

相应变量的地址(&a)

相应一维数组的数组名(arr)

一级指针(p)

4.当函数的参数为二级指针的时候,可以接受的参数:

一级指针的地址(&p)

二级指针(pp)

指针数组的数组名(arrp)


八、函数指针

1.存放函数地址的指针

2.数组名VS函数名

>>>&数组名 != 数组名

>>>&函数名 == 函数名

3.定义函数指针:

返回类型 (*pf)(参数类型……)

Int (*pf)(int ,int)=&add(或add);

4.使用函数指针调用函数

>>>Int ret = add(3,5);//使用函数名调用函数

>>>Int ret = (*pf)(3,5);//'*'无实际意义

>>>Int ret = pf(3,5);//常用

5.有趣的代码:

对类型重定义简化函数声明


九、函数指针数组——无限套娃

1.存放函数指针的数组;

int(* pfarr[5])(int,int);(在函数指针的基础上加"[ ]")

2.指向函数指针数组的指针


十、回调函数

举例:库函数qsort

返回列表