一. 相似性
- 内存访问机制
- 从内存的角度来看,数组是一块连续的内存空间,用于存储相同类型的数据元素(单变量多值)。例如声明
int arr[5] = {1,2,3,4,5};
时,这意味着在内存中分配了一块连续的空间,足以存储5个int类型的数据
- 指针则是存储一个内存地址的变量。当将指针指向数组的名字(首元素),例如
int *ptr = arr;
实际上是将数组的首元素的内存地址存储在ptr中 本质是指针常量退化解引用
- 无论是通过数组的下标操作符[] 还是指针的解引用操作符 * ,在底层都是通过地址偏移来访问内存。当你使用
arr[2];
时,编辑器会将其转化为*(arr+2)
,这里arr会被隐式地视为指向首元素的指针。对于指针prt,ptr[2]
也会被转换为*(ptr+2)
。在汇编层面,这种操作通常是通过将基地址(即arr或prt的地址)加上偏移量(这里是 2 * sizeof(int)) , 然后计算出的地址处读写数据
- 这种地址偏移操作利用内存的线性特性,利用指针或数组都可以灵活地访问和 操作内存中的数据元素
- 函数参数传递
- 当数组作为函数参数传递时,会发生数组到指针的转化,这是因为在c/c++中,函数调用是通过栈来传递参数的,将数组完整地复制到栈上效率低且不灵活,所以数组会
退化
为指针。例如函数void func(int arr[]);
实际在调用时,传递的是数组arr的首元素地址,等同于void func(int *arr);
在函数内部,使用arr时,编辑器将其视为指针
非常量指针
,相当于包含了所有数组元素的数组指针解一次引用即int(*ptr)[5]=&arr;后再*ptr
,通过这个指针再解引用*ptr
可以访问原数组元素,这使得函数可以处理不同大小的数组,但也带来了一个问题,即函数内部无法直接指导数组的实际大小,需要额外的参数来传递数组长度
- 退化:
int(*ptr)[5]=&arr;ptr=*ptr
等价于 int *ptr = arr
前者ptr指向包含所有元素数组的指针,所以*ptr指针是数组名,又因为数组名是指向首元素的地址, 便可指针法
二. 特殊关系
- 数组名的本质
- 数组名在大多数情况下被视为常量指针无法修改但
退化后可解引用
。它代表了数组在内存中的固定起始地址,不能被修改,退化后解引用后通过指针法
指针运算
线性访问
- 但数组名与指针有本质区别。
sizeof(arr)
时,编辑器可以确定数组的大小,因为arr也代表整个数组。然而int *ptr = arr; sizeof(ptr)
, 计算的是指针本身的大小 除非int *ptr[5] = arr;
相当于数组本身传递给了数组指针。
- 在编译期间,数组名的这种特性被用来确定数组的边界,防止越界访问,例如访问
arr[5]
,编辑器会检查这个下标是否超出了数组的范围,因为它知道arr是大小为5的数组,属于受检异常编辑器可避免。编辑器无法轻易得知其指向的范围,非受检异常避免指针越界,因为指针可以指完数组之后可指向任意地址,可能出现严重的内存错误
- 指针运算和数组边界
- 指针可以进行算数运算,如
ptr++
底层是将指针的值增加sizeof(int)。对于数组名,退化前无法arr++
, 因为它是个常量指针
- 指针元素在处理数组时很有用,如
for(int *ptr = arr;ptr < arr+5;ptr++)
却可能访问为定义引发内存崩溃
- 数组和指针在底层的这种关系,反映了c/c++的灵活性和对性能的追求。数组提供了方便的方式存储和访问连续的数据,而指针提供了对内存的灵活操作能力,同时这种灵活性也带来了风险,需要开发者理解内存布局和指针操作,避免
野指针
- 收敛优化
- 数组和指针的特殊性质是由底层的内存访问和操作机制决定的
- 在某些编辑器中,遍历简单数组,使用指针可能会比使用下标操作性能稍快
- 多维数组
int arr[3][4]
其内存布局也是线性的,可利用指针将其视为一维数组*((int *)arr + i * 4 + j)
三. 字符串与数组
- 存储形式
- 字符串在c/c++中通常以
字符数组
的形式存储。例如 char str[]="Hello";
定义了字符数组str,编辑器会其分配一块连续的内存空间,依次存储'H','e','l','l','o'以及字符串结束标识 \0
。字符串本质就是特殊的字符数组
- 由于字符串以数组形式存储,具有数组的特性,如通过
str[2]
下标就能访问字符元素,在编译期间,编辑器知道数组大小,即sizeof(str)
会返回字符串数组占用的内存空间,包含\0
, 上述就是大小6子节
四. 字符串与指针
- 指针指向
- 当定义
char *ptr = str/"Hello"
,指针ptr指向字符串str的首字符
地址。 也可通过解引用
后指针法访问字符,因其底层也是通过基地址偏移实现字符串的访问
- 对于字符串指针,通常不需要指定长度,而是通过判断字符串是否达到结束标识
\0
来确定是否遍历到字符串末尾,避免野指针
五. 字符串约定
- 字符串约定,引用字符串本身时只需传递字符串起始地址(指针常量),便会自动读取到'\0'结束符号
- 当字符串作为函数参数时,和普通数组一样,实际传递的是字符串的首字符地址,此时可指针法访问
char str[]="hello";
等价于 char *str ="hello";
str
都是指向首字符指针,便于字符串约定
六. 字符串和数组
- 字符串本质就是特殊字符数组,特殊字符数组本质又是首元素地址(常量指针)
- 也就是说,当
字符串本身
或数组名
被传递时,会退化为起始地址
的指针(非数组指针),因为其本身代表起始地址
的指针常量, 都可指针法, 前者存在字符串约定
, 后者存在数组越界
,都存在[]
的隐式解引用
- 常量指针,由于数组或字符串定义后它的起始地址就固定了,拥有常量的特性,故为常量指针,而仅有退化后才能进行解引用
退化前/后都行
int *ptr=arr前退化
int (*ptr)[5]=&arr;ptr=*ptr后退化
- 单变量存储多字符串.字符指针数组
char *ptr[2]={"nagisb","nagisb2"};
prinf(%s,ptr[0]);
每个元素存储对应首字符指针
七. 线性退化
- 数组就是指针,仅仅是它们的
表现方式
不同,数组(默认名)是指针对象的成员属性 起始地址
, 数组索引或字符串引用是指针直接操作内存
的实例属性,即数组名/字符串
直接拿来作为指针法,如行列式
直接拿来作为方阵的值
- 指针法的关键在于
起始地址
和 指针步长
而数组名恰恰符合及其
线性存储特性恰恰符合,体现了上层到底层的灵活性
, 底层到上层的安全性
- 普通数组(数组名)退化
*array
等价于 array[0]
数组指针(数组本身)退化 *array
等价于 array本身
再退化 *(*array+i)
直接退化
- 数组传递 数组名传递:
*array = array
直接可退化, 数组本身传递:*array=&array
间接可退化
- 数组下标
array[0]
等价于 *array
. 与 array[i]
等价于 *(array+i)
间接退化
- 数组指针 , 引用时根据是
前指向&temp
整个数组解一次引用,或new直接分配已解一次引用,但 array[i]
不等价于*(array+i)
- 下标运算
[]
语法糖支持数组
指针
字符串
. 根据指针法指向注意步长 需要为起始地址指针
数组指针的步长为数组大小(本身)
只能*(*array+i)
- 下标运算
[]
属于隐式退化
,即下标运算[]
是 指针法*(array+i)
的充分条件,充分条件(前者能推出后者,反过来数组指针
是特例)
#include <iostream>
using namespace std;
int main()
{
int temp[5] = {1,2,3,4,5};
for(int i=0; i<5; i++) {
cout << *(temp+i) << endl;
cout << temp[i] << endl;
}
}
#include <iostream>
using namespace std;
int main()
{
char str[] = "Hello,world";
int a = sizeof(str)/sizeof(str[0]);
for(int i=0; i<a; i++) {
cout << *(str+i) << endl;
cout << str[i] << endl;
}
}
#include <stdio.h>
int main() {
int temp[5] = {1,2,3,4,5};
int (*p2)[5] = &temp;
for(int i=0; i<5; i++) {
printf("%d\n", *(*p2+i));
}
return 0;
}
#include <iostream>
using namespace std;
int main()
{
int *array=new int [5];
for( int i=0; i<5; i++ )
{
array[i] = i;
cout << array[i] << endl;
cout << *(array+i) << endl;
}
delete [] array;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义