C基础之指针

指针概述

内存基本原理

内存示意图:

1bit

□□□□□□□□ □□□□□□□□ □□□□□□□□ □□□□□□□□ □□□□□□□□ □□□□□□□□ □□□□□□□□ □□□□□□□□
↑     ↑
0x00000000|0x00000001

 
内存中最小的物理单位是【比特(bit)】,1个比特可以存储两种状态,即低电平与高电平,用数字模拟也就是0和1。因为1个bit只能存储两种状态,无法满足更大的数据存储的需求,所以计算机将8个bit作为一组存储单位,这样的存储单位1个就可以存储2的8次方(256)种状态,这样的存储单位就叫做【字节(byte)】,也是计算机内存中最小最基本的存储单位。C++中最小的数据类型char占用一个字节,计算机系统里的内存4G指的是4G个字节 , 内存地址也是以字节为单位而不是以bit为单位。

指针

指针是用来存储变量内存地址的变量。

□□□□□□□□ □□□□□□□□ □□□□□□□□ □□□□□□□□ □□□□□□□□ □□□□□□□□ □□□□□□□□ □□□□□□□□
↑     ↑
0x00000000|0x00000001

要操作内存,需要知道两个要素:
(1)要操作的内存地址 (从哪开始)
(2)要操作的内存大小 (到哪结束)

指针的语法

数据类型* 变量名称

int* pStudent{};

pStudent为一个int类型的指针
数据类型解决要操作内存的大小问题,int类型表示该指针要操作int大小的内存也就4个字节。pStudent则存储了内存地址。

取址运算符 &

利用取之运算符可以获得一个变量的内存地址

int a{ 5000 };
int* pa{&a};
std::cout << pa<<char(10);
008FF8F8

间接运算符 *

利用间接运算符可以操作指针所表示的内存。

int a{ 5000 };
int* pa{&a};
*pa = 5001;  //写入
std::cout << *pa<<char(10); //读取
5001

指针与数组

int studentId[5]{ 1001,1002,1003,1004,1005 };
int* ptrStudentId[5];
for (int x = 0; x < 5; x++)
{
       ptrStudentId[x] = &studentId[x];
       std::cout << ptrStudentId[x] <<":"<<*ptrStudentId[x]<< char(10);
}
0095F6EC:1001
0095F6F0:1002
0095F6F4:1003
0095F6F8:1004
0095F6FC:1005

指针的大小

既然指针是一个变量,那么它就会占用内存空间,指针的大小是多少呢?
首先,指针类型要大到能够描述操作系统所支持的所有内存地址,而操作系统是根据CPU的寻址能力来设计的,32位操作系统与32位CPU相匹配,32位CPU的寻址能力为2的32次方,也就是4,294,967,296,为 unsigned int的大小,也就是4个字节。所以,32位操作系统的指针需占用4个字节的内存。64位CPU的寻址能力为2的64次方,也就是18,446,744,073,709,551,615,为unsigned long int的大小,也就是8个字节,所以64位操作系统的需要占用8个字节的内存。

寻址能力与内存大小

拿32位CPU为例,32位CPU是指CPU的总线的数量为32根,CPU运行时每次最多可以通过32根线来传输数据,而每根线是通过高低电来传输电平信号,也就是0和1,那么每根线2种状态,32根线,那么最多就可以模拟出2的32次方种状态,也就是4,294,967,296种状态,用16进制表示就是FFFF FFFF,也就是说,32位CPU一次最多也就能访问这么大的内存。4,294,967,296种状态用4个字节就能描述,那么32位操作系统的指针的大小用4字节也就够了。

指针类型转换

指针的类型是可以强制转换的。

unsigned ua{ 9999 };
int* ptr{ (int*)&ua };
std::cout << *ptr << char(10);
*ptr = -1;
std::cout << *ptr << char(10);
std::cout << ua << char(10);
9999
-1
4294967295

上例代码中讲usigned类型指针&ua转换成了int类型的指针ptr,然后修改了内存中的内容为-1,尽管*ptr与ua都是获取到同一块内存区域的内容(也就是-1),但是由于类型不同,描述内存内容的方式也就不同,输出的结果也就不同。
*ptr = -1;这行代码是将int类型的-1存储到了内存中,int的-1在内存中存储为FFFFFFFF(-1=1的补码+1),当使用unsigned类型描述FFFFFFFF时则会变成unsigned类型的最大值,也就是结果中所打印的4294967295。

指针运算之自增与自减

指针的运算基于指针的类型。

int a[5]{ 1001,1002,1003,1004,1005 };
int* ptr{ &a[0] };
std::cout << ptr++ << char(10);//先显示ptr,再++
std::cout << ptr-- << char(10);//先显示ptr,再--
std::cout << ptr << char(10);
00CFFA94
00CFFA98
00CFFA94

上例所示,指针加n等于将指针增加n个该指针类型的大小。

多级指针

代码变量内存地址
int a{500}a5000x50000
int* ptr{&a}ptr0x500000x50100
int** pptr{&ptr}pptr0x501000x50200
int*** ppptr{&pptr}ppptr0x502000x50300
int a[5]{ 1001,1002,1003,1004,1005 };
int* ptr{ &a[0] };
int** pptr{ &ptr };
std::cout << *ptr << char(10);
std::cout << ptr << char(10);
std::cout << pptr << char(10);
*pptr = &a[1];
std::cout << *ptr << char(10);
1001
0058FDEC
0058FDE0
1002

上例中,ptr是a[0]的指针,pptr是ptr的指针,将pptr的值(也就是ptr所存储的指针)变为a[1]的指针后,ptr就变成了a[1]的指针。这样,就可以在不操作ptr的情况下,完成对ptr的修改。

一些道理简单却容易搞混的概念:

常量指针

常量指针就是指向一个常量的内存地址的指针,常量指针中,不能对其指向的内存进行改变,但是指针指向的地址可以改变。

const int a{ 1000 };
const int b{ 2000 };
const int* ptrA{ &a };
std::cout << *ptrA << char(10);
ptrA = &b;
std::cout << *ptrA<<char(10);
//*ptrA=500; 编译出错
1000
2000
指针常量

指针常量是指这个指针变量为一个常量,一旦初始化就不可以再指向其他内存地址,但它指向的内存里呢数据可以读写。

int a{ 1000 }
int b{ 2000 };
int* const ptrA{ &a };
std::cout << *ptrA << char(10);
//ptr = &b; 编译出错
*ptrA = 9999;
std::cout << a << char(10);
1000
9999
指向常量的常量指针

这种类型的指针,基本能修改指针的地址,也不能修改指针所指向的内存的数据。

const int a{ 1000 };
const int b{ 2000 };
const int* const ptrA{ &a };
std::cout << *ptrA << char(10);
//ptr = &b; 编译出错
//*ptrA = 500;//编译出错
1000

指针与数组

数组的底层逻辑

数组的底层实现是利用了指针,指针和数组是同一个方法的不同表达,而数组名本身是数组第0个元素的指针,数组元素是这个指针按照一定偏移量(下标*数组类型) 偏移后所对应的指针的内存内容。

下标取值的底层逻辑

a[x]的底层逻辑是将指针a向右偏移x个数组类型的长度,然后取值。

int a[3]{1,2,3};
std::cout<<a[2]<<char(10);
2

上例中a[2]的值的计算方式为: *(&a[0] + 2),或者 *(a+2);

int a[5]{1000,1001,1002,1003,1004};
int* ptrA{ a};
std::cout << a << char(10);
std::cout << &a[0] << char(10);
std::cout << ptrA << char(10);
std::cout << a[3] << char(10);
std::cout << ptrA[3] << char(10);
004CFC98
004CFC98
004CFC98
1003
1003

指针与二维数组

int a[2][4]{
	{1001,1002,1003,1004},
	{2001,2002,2003,2004}
};
int(*ptra)[4]{a};
std::cout << ptra << char(10);
std::cout << a << char(10);
std::cout << a[0] << char(10);
std::cout << ptra[0] << char(10); 
006FFD7C
006FFD7C
006FFD7C
006FFD7C

既然是二维数组,那就先把a[2][4]拆分描述成两个维度,第一维度是4,第二个维度是2。
那么,
ptra是一个指向了a的指针,该指针类型为int[5]。
a表示二维数组a的内存地址,也就是a[2][4]的首个元素地址,即a[0][0]的地址。
a[0]表示在第二维度中的第0个元素,那就是{1001,10002,1003,1004}这组数据的内存地址。也就是{1001}的内存地址。
ptra[0]与a[0]相同,a的实质就是ptra。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值