深入理解指针(1)

一 , 内存和地址

 1.1  内存

从生活出发,如果有一栋房子,里面有100间房间,但是房间没有门牌号,你们朋友就居住在其中一间,如果你要找到你的朋友,就得挨个房间挨个房间的去找,但是如果我们根据楼层,还有楼层房间的排序,给每一个房间编上号码,我们就可以快速定位一个房间,提高查找效率。

一楼 : 101,  102 , 103...

二楼:201,202,203...

....................................................

把上面的例子对应到我们的计算机中:

我们可以知道,CPU(中央处理器)在处理数据的时候,是在内存中读取数据,然后经过处理后,再放回内存中。我们知道,电脑中的内存有8G/16G/32G,内存的空间很大,为了更好的使用和管理内存,我们把这些内存空间被划分成一个个内存单元,每一个内存单元的大小取一个字节。 

计算机常见单位:

1个比特位可以存储一个2进制的位1或者0

1 Byte = 8Bit

1 KB   = 1024Byte

1 MB   = 1024KB

1 GB   = 1024MB

1 TB   = 1024GB

1 PB   = 1024TB

           ........

每个内存单元,可以看作一个学生宿舍,一个字节的空间可以放8个比特位,就相当于一间宿舍可以住8个人,每一个人是一个比特位。

每个内存单元也有一个编号,通过这个编号,CPU可以快速找到一个内存空间。生活中,我们把门门牌号称为地址,在计算机中,我们也把内存单元的编号成为地址C语言中给地址取了一个新名字:指针。 

                               内存单元编号 = 地址 = 指针

1.2   编址

CPU访问内存中的某个字节空间,必须知道该字节空间在内存中的什么位置,因为内存中的字节很多,所以需要给内存单元进行编址。计算机中的编址,并不是把每个字节的地址记录下来,而是通过硬件完成的

计算机内是由很多的硬件单元,而硬件单元是要互相协同合作的。所谓协同,至少互相之间能够进行数据传递。但是硬件与硬件之间是互相独立的,如何通信?如何进行数据传递?其实是通过“线”连起来。 而CPU和内存之间也是有大量的数据交互的,所以二者也有通过线连接起来。

     

地址信息被下达给内存在内存上,就可以找到该地址对应的数据,将数据在通过数据总线传⼊
CPU内寄存器。

 

二 , 指针变量和地址

2.1   取地址操作符(   &  )

变量创建的本质:  向内存中申请一块空间

int main()
{
	int a = 0x1223344;
	return 0;
}

上述代码创建了整形变量a,内存中申请4个字节,用于存放数据,其中每个字节都有地址,如何取出a的地址呢?C语言中提供了一个操作符  &  ,用于取地址,取出的地址可以使用 %p 进行打印

int main()
{
	int a = 0x11223344;
	&a;  //取出a的地址
	printf("%p\n", &a);
	return 0;
}

 

经过调试,打印,我们发现  &a  取出的是a所占4个字节中地址较小的字节的地址

虽然整型变量占4个字节,但是只要知道了第一个地址,一个内存单元占1个字节,就可顺藤摸瓜的可以访问到4个字节的数据。 

2.2  指针变量

 我们使用 & 取出一个数值的地址,为了方便以后使用,我们需要把它存储起来,用什么存储? 存在指针变量中。指针变量也是一中变量,这种变量是专门用来存放地址的,而存在指针变量中的值相应的会被理解成地址。

int main()
{
	int a = 10;
	int* pa = &a;//取出a的地址并存储在指针变量pa中
	printf("%p\n", &a);
	printf("%p\n", pa);
	return 0;
}

 这里的pa是指针变量,里面的存放的是地址。

2.3  指针变量的类型

int n = 10;

char m = 'a ' ;

int * pa = &a;

我们知晓 n 是 整型变量(int) , m 是字符型变量(char) , 推而得之 ,pa的类型是(int * );

我们如何理解指针类型   int * ? 

2.4  解引用操作符( * )

前面我们知道,用指针变量来存储数据地址,方便未来使用,那如何使用呢?

现实生活中,我们是通过地址找到房间,然后再房间里拿物品,存物品

C语言中也一样,我们拿到了地址(指针),通过地址找到地址所指向的对象。 

int main()
{
	int a = 20;
	int* pa = &a;
	printf("%d\n", *pa);//通过地址找a
	*pa = 30;    //通过地址找到a并修改a
	printf("%d\n", a);//通过地址找a
	return 0;
}

 这里我们使用了解引用操作符*pa的意思就是用过pa中存放的地址,找到指向的空间,而*pa所对应的就是a变量;所以*pa=30;这个操作就是把a改成了30;

int *pa = &a;     -------  pa是指针变量,a的地址

*pa                    --------   是变量a 

使用 *pa  ,是把对a的操作交给了pa,这样对a的修改,就有多了一种途径,写代码会更加灵活!

2.5  指针变量的大小

          指针变量有多大?

== >  指针放的是地址   == >地址存放要多大空间?

== > 指针变量的大小取决于地址的大小

32位机器假设有32根地址总线,每根地址线出来的电信号转换成数字信号后是1或者0,那我们把32根地址线产⽣的2进制序列当做⼀个地址,那么⼀个地址就是32个bit位,需要4个字节才能存储;
同理64位机器,假设有64根地址线,⼀个地址就是64个⼆进制位组成的⼆进制序列,存储起来就需要8个字节的空间,指针变量的⼤⼩就是8个字节

 

注意指针变量的大小和类型是无关的,只要指针类型的变量,再相同平台下,都是相同的;

nt main()
{
	printf("%zd\n", sizeof(short*));
	printf("%zd\n", sizeof(int*));
	printf("%zd\n", sizeof(long*));
	printf("%zd\n", sizeof(char*));
	printf("%zd\n", sizeof(float*));
	printf("%zd\n", sizeof(double*));
	return 0;
}

 

32位平台下地址是32个bit位,指针变量⼤⼩是4个字节
64位平台下地址是64个bit位,指针变量⼤⼩是8个字节
注意指针变量的大小和类型是⽆关的,只要指针类型的变量,在相同的平台下,大小都是相同的。

三 , 指针变量类型的意义

指针变量的大小和类型无关,只要是指针变量,在同一个平台下,大小都是一样的,那么为什么还需要有各种各样的指针类型? 定义不同的指针类型有什么意义? 

3.1  指针的解引用 

运行如下代码,进入调试界面,观察内存的变化

//代码一:
int main()
{
	int a = 0x11223344;
	int* pa = &a;
	*pa = 0;
	return 0;
}

 

//代码2:
int main()
{
	int a = 0x11223344;
	char* pa = &a;
	*pa = 0;
	return 0;
}

 

通过调试我们可以发现,代码1会把4个字节全部改为0,但是代码2只是把1个字节改为0。

所以,指针类型决定了,对指针解引用时有多大的权限(一次能操作多少个字节)。int *类型的指针解引用可以访问4个字节,但char*类型的指针解引用只能访问以个字节。 

 3.2 指针+-整数

int main()
{
	int a = 10;
	int* pa = &a;
	char* pb = &a;

	//pa
	printf("pa-1 = %p\n", pa - 1);
	printf("pa   = %p\n", pa);
	printf("pa+1 = %p\n", pa+1);

	printf("\n");
	//pb
	printf("pb-1 = %p\n", pb - 1);
	printf("pb   = %p\n", pb);
	printf("pb+1 = %p\n", pb + 1);
	return 0;
}

我们可以看出,char * 类型的指针变量+1跳过一个字节 。int * 类型的指针变量+1跳过了4个字节。

指针的类型决定了指针向前或者向后走一步有多大(距离) 

3.3 void* 指针

在指针类型中,有一种特殊的类型 --- void* 类型  ,可以理解为无具体类型的指针(泛型指针) ,可接受任何类型的地址,但是不可以直接进行指针的+ - 整数和解引用运算!

int main()
{
	int a = 10;
	int* pa = &a;
	char* pb = &a;
	return 0;
}

 但是当我们使用void * 型来接受地址的话,不兼容的问题解决了:

 

void* 类型的指针可以接受不同类型的地址,但是不能进行运算!一般我们会把void * 类型的指针用来接受其他人传来的地址,因为不知道地址的类型,然后在使用指针的时候进行强制类型转化即可,使得函数来处理多种类型的数据。 

四  ,const修饰操作符

const 常属性 ,被const 修饰后变量就具有常属性,变成了常变量,数值不可被修改了;

1.const 修饰普通变量

2.const 修饰指针变量

4.1 const 修饰普通变量

int main()
{
	const int n = 10;
	n = 10;
	printf("%d\n", n);
	return 0;
}

 上述代码中n是不可以被修改的,这里的  n  在 C 语言中  n 属于常变量,但是n本质上还是变量,因为有const 的修饰,编译器在语法上是不允许修改这个变量的;

但是如果我们绕过 n , 使用 n 的地址,然后修改 n 就可以做到了,但是这样是打破语法规则

int main()
{
	const int n = 10;
	printf("%d\n", n);
	int* pn = &n;
	*pn = 30;
	printf("%d\n", n);
	return 0;
}

我们可以发现,这里的n指确实被修改了,但是 const 就失去的使用的意义, 使用const 修饰就是为了让 n 值不被修改 , 如果 pn 拿到 n 的地址 ,就能修改 n ,打破了 const 的限制 , 这是不合理的 , 所以应该改让  pn 可以拿到 n 的地址 , 但是不能修改 n ;

4.2 const 修饰指针变量

 一般来讲 const 修饰指针变量 , 可以放在 * 的左边,也可以放在 * 右边 ,放在不同位置,他们的意义是不一样的。

int* p;                              //没有const 修饰
int const* p;
const int* p;                    //const 放在*的左边做修饰
int* const p;                    //const 放在*的右边做修饰

//左边  --  表示指针指向的内容不能通过指针来改变了,但是指针变量本身的值(地址)是可以改的
int main()
{
	const int num = 10;
	int n = 100;
	const int* p = #
	//p=  &n;
	//*p = n;
	//const 放 * 左边修饰的是 *p  但是对p不限制;
	return 0;
}

//右边  --- const 限制的是p(指针变量),但是指针指向的内容是可以通过指针变量来改变的
int main()
{
	const int num = 10;
	int n = 100;
	int* const p = #
	*p = 20;  //ok
	//p = &n;
	printf("%d\n", num);
	return 0;
}

//左右两边    --- 指针变量(p)(地址)不可以被修改,指针变量指向的内容也不可以被修改
int main()
{
	const int num = 10;
	int n = 100;
	const int* const p = #
	//p = &n;
	//p = 200;
	return 0;
}
  •  所以,const如果放在 * 的左边,修饰的是指针所指向的内容,保证指针所指向的内容不能通过指针来改变,但是指针变量本身的内容可变。(地址可变,值不变)
  • const 放在 * 右边,修饰的是指针变量本身,保证了指针变量的内容不能修改,但是指针所指向的内容,可以通过指针改变的。(值可变,地址不可变)
  • const 放在 * 左右两边,都不可以改变;

五  ,指针运算

指针的基本运算有三种,

  • 指针 + - 整数
  • 指针 - 指针
  • 指针的关系运算 

5.1 指针 + - 整数 

数组在内存中是连续存放的,所以只要知道第一个元素的地址,就可以顺藤摸瓜的找到后面的元素。

 

//指针 +- 整数
//采用指针的方式打印数组元素---通过起始地址
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	int* p = &arr[0];
	for (i = 0; i < sz; i++)
	{
		printf("%d ",*(p+i));
	}
	return 0;
}

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	int* p = &arr[0];
	for (i = sz-1; i >=0; i--)
	{
		printf("%d ", *(p + i));
	}
	return 0;
}

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	int* p = &arr[0];
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *p );
		p ++;
		//缺陷就是,p值改变
	}
	return 0;
}

5.2 指针 - 指针

//指针 - 指针(地址 - 地址 )得到指针和指针之间的元素个数(绝对值)
//运算的前提条件是,两个指针指向同一块空间;
//int main()
//{
//	int arr[10] = { 0 };
//	printf("%d\n", &arr[9] - &arr[0]);//9
//	printf("%d\n", &arr[0] - &arr[9]);//-9
//
//	return 0;
//}

//求字符串长度
//#include <stdio.h>
//int main()
//{
//	char arr[] = "abcdef";
//	size_t len = strlen(arr);
//	printf("%d \n", len);
//	//strlen 统计的是字符串中\0之前的字符个数
//	return 0;
//}

//size_t my_strlen(char* s)
//{
//	int count = 0;
//	while (*s != '\0')
//	{
//		count++;
//		s++;
//	}
//	return count;
//}
//
//int main()
//{
//	char arr[] = "abcdef";
//	size_t len = my_strlen(arr);
//	printf("%zd\n", len);
//	return 0;
//}

//size_t my_strlen(char* s)
//{
//	char* start = s;
//	while (*s != '\0')
//		s++;
//	return s - start;
//}
//int main()
//{
//	char arr[] = "abcdef";
//	size_t len = my_strlen(arr);
//	printf("%zd\n", len);
//	return 0;
//}

 5.3 指针的关系运算

//指针关系运算
//int main()
//{
//	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//	int* p = arr;
//	int sz = sizeof(arr) / sizeof(arr[0]);
//	while (p < &arr[sz])
//	{
//		printf("%d ", *p);
//		p++;
//	}
//	return 0;
//}

//int main()
//{
//	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//	int sz = sizeof(arr) / sizeof(arr[0]);
//	int* p = &arr[sz - 1];
//	while (p >= &arr[0])//p >= arr
//	{
//		printf("%d ", *p);
//		p--;
//	}
//	return 0;
//}

  • 10
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值