指针详解(一)

1.内存和地址

1.1内存

在讲内存和地址之前,我们先引入一个例子。

有的人住在小区,那么数据住在哪里呢?就住在内存里。

想在小区里找到你家,需要知道你家在几号楼几层几号,也就是要知道一个你家地址的编号;那么,找数据也需要知道数据在内存的什么位置,也就是说,我们也需要给内存编号。

专业的说,CPU在处理数据的时候,需要的数据是在内存当中读取的,处理后的数据也会放回内存中,而内存划分为了一个个的内存单元,每个单元的大小取1个字节,也就是8个比特位,而每个内存单元都有一个编号,有了这个编号,我们就可以快速的找到这个内存单元。而在C语言中,我们给它取了一个新的名字:指针

所以我们可以这样理解:内存单位的编号==地址==指针

1.2 究竟该如何理解编制?

CPU访问内存中的某个字节空间,必须知道这个字节空间在内存的什么位置。而因为内存中字节很多,所以我们需要给内存进行编址(编写地址)。

但,有一个问题是,我们的硬件都是相互独立的,但是实际需要我们对他们进行大量的数据交互。那么我们应该如何让它们不独立呢?很简单!用“线”连起来就可以了。 今天我们关注一组名为“地址总线”的线。

我们可以简单理解为,32位机器有32根地址总线,每根线都有两种状态,分别是0和1,一根线就可以表示出两种含义,两根线就有2^2种含义,32根线就有2^32种含义。而每种含义都代表一个地址。

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

2.指针变量和地址

2.1取地址操作符(&)

理解了内存和地址的关系,那么我们就应该能够理解,我们创造一个变量,便是将一个变量放到了一块内存空间内。专业的说,创建变量其实就是向内存申请空间。

下面我们通过调试内的内存窗口来观察一下

#include <stdio.h>
int main()
{
	int a = 10;
	printf("%p", &a);
	return 0;
}

可以看出,这个整型占据的四个字节都有地址,而且刚好占据了四个地址。

那么,我们如何得到a的地址呢? 在前面学习scanf函数时学到,我们要使用取地址操作符(&),它的作用正是把地址取出来。  也就是说,我们只需要打印出&a的值即可。按照刚刚的代码打印即可。但,不同的编译器不同的电脑可能会把这个数据放在内存的不同位置,因此大家得到的结果是不一样的。

&a取出的是a所占4个字节中较小的地址,我们只需要顺藤摸瓜即可找到四个地址。

2.2指针变量和解引用操作符(*)

2.2.1指针变量

我们通过取地址操作符(&)拿出的地址是一个数据,比如:0x006FFD70,这个地址也是需要我们保存起来,方便以后使用的。那么我们把地址值存放在哪里呢?答案是:指针变量

#include <stdio.h>
int main()
{
	int a = 10;
	int* pa = &a;//取出a的地址并存储在指针变量pa中。
	printf("%p", &a);
	return 0;
}

上图中的指针变量为pa

指针变量也是一种变量

指针变量是用来存放地址的

存放指针变量中的值会被理解为地址

2.2.2如何拆解指针变量?

int a = 10;
int* pa = &a;//取出a的地址并存储在指针变量pa中。

我们应该知道,变量名前面即是变量的类型,那么代码中pa的类型应该是int*,*是在说明pa是一个指针变量,而前面的int是指针指向的内存空间中存储数据的类型。

2.2.3解引用操作符

我们将地址保存起来,未来是要使用的,那应该如何使用呢?

在现实生活中,我们使用地址找到一个房间,可以在房间内存放或者拿取物品。

C语言中也是一样的,我们只要拿到了地址(指针),就可以通过地址(指针)找到地址(指针)存放的变量(存放的变量),这里需要我们学习一个操作符-->解引用操作符(*)。

#include <stdio.h>
int main()
{
	int a = 10;
	int* pa = &a;//取出a的地址并存储在指针变量pa中。
	printf("%p", &a);
	return 0;
}

上面代码的第五行就使用了解引用操作符,*pa的意思就是通过pa中存放的地址找到这块空间的内容。也就是说*pa就是a变量了。

2.3指针变量的大小

刚刚我们说过,地址总线产生的二进制序列就是一个地址,那么一个32位的机器总共有32条地址线,也就是32个二进制序列,即32Byte,也就是四个字节;如果是64位机器的话,那么就是八个字节了。也就是说,一个指针变量在32位环境下是4个字节,在64位环境下是8个字节

又因为和其大小有关系的只是地址总线,而不是它指向的内容,因此,无论指向的是什么类型的数据,都不会影响指针变量的大小。那么我们现在来测一测。

#include <stdio.h>
//指针变量的⼤⼩取决于地址的⼤⼩
//32位平台下地址是32个bit位(即4个字节)
//64位平台下地址是64个bit位(即8个字节)
int main()
{
	printf("%zd\n", sizeof(char*));
	printf("%zd\n", sizeof(short*));
	printf("%zd\n", sizeof(int*));
	printf("%zd\n", sizeof(double*));
	return 0;
}

3.指针变量类型的意义

3.1指针的解引用

对比下面两段代码,在调试时观察内存的变化。

//代码1
#include <stdio.h>
int main()
{
int n = 0x11223344;
int *pi = &n;
*pi = 0;
return 0;
}

我们在调试中的内存窗口中发现,当代码运行到*pi=0;这一行时,n的四个字节都变为了0.

//代码2
#include <stdio.h>
int main()
{
	int n = 0x11223344;
	char* pc = (char*)&n;
	*pc = 0;
	return 0;
}

在运行这段代码时,我们发现它只将n的第一个字节变为了0

通过对比这两段代码我们可以发现,第一段代码的指针变量类型为int*,而第二段代码的指针变量类型为char*。同时我们发现,第一段代码解引用之后一次操作四个字节,第二段代码解引用之后一次操作一个字节,我们发现它们操作的字节数等于他们指向的数据的数据类型的大小。因此,我们可以得到如下结论:

指针的类型决定了,对指针解引⽤的时候有多⼤的权限(⼀次能操作⼏个字节)。
⽐如: char* 的指针解引⽤就只能访问⼀个字节,⽽ int* 的指针的解引⽤就能访问四个字节。

3.2 指针+-整数

刚刚我们已经知道了一次解引用的权限和其指向的数据类型有关系。那么指针的加减整数和这个有没有关系呢?答案是肯定的。我们再来观察一段代码。

我们可以发现,char* 类型的指针变量+1跳过1个字节, int* 类型的指针变量+1跳过了4个字节。
这就是指针变量的类型差异带来的变化。也就是说,指针指向的数据的类型决定了指针加减整数所移动的比特位。得到如下结论:

                            指针的类型决定了指针向前或者向后⾛⼀步有多⼤(距离)。

3.3 void*指针

在指针类型中有⼀种特殊的类型是 void* 类型的,可以理解为⽆具体类型的指针(或者叫泛型指
针),这种类型的指针可以⽤来接受任意类型地址。但是也有局限性, void* 类型的指针不能直接进⾏指针的+-整数和解引⽤的运算,因为并不知道它一步能走多远。

这类指针一般用于函数参数的部分,用于接受不同类型的数据地址,这样的设计可以实现泛型编程的效果。

4.const修饰指针

4.1 const修饰变量

如果我们希望⼀个变量加上⼀些限制,不能被修改,怎么做呢?这就是const的作⽤。

也就是说,被const修饰的变量不能被修改

可以看到,在使用了这段代码之后,我们无法通过编译。

4.2 const修饰指针变量

const在修饰指针变量时有两种情况。

	int m = 10;
	int const* pm = m;
	int* const pm = m;

第一种情况放在*的左边,修饰的是*pm,即确保*pm指向的内容不能变化,但是pm本身是可变的。即,我们不可以修改*pm,但是可以修改pm。

第二种情况放在*的右边,修饰的是pm,即确保指针变量本身的内容不能改变,但是*pm指向的内容是可以变化的,即,我们不可以修改pm,但是可以修改*pm。

现在打个比方帮助大家理解

有一个小男孩是*pm,而pm代表小男孩的家财万贯。

第一种情况是一个小女孩和这个家财万贯小男孩谈恋爱,const放在*的左边,就可以让小女孩对这个小男孩死心塌地了,即便pm变了,也就是说小男孩家里没钱了,小女孩依旧爱他。

第二种情况还是这个小女孩和这个家财万贯的小男孩谈恋爱,const放在了*的右边,即,*pm是可以变的,但是pm不能变。也就是说,和小女孩谈恋爱的小男孩是可以变的,家境是不能变的,你要是没钱了我就要和你分手说再见。

相信大家看了刚刚的例子后都可以理解const放在*左右两边的情况。现在我们来做一个总结:

const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。
但是指针变量本⾝的内容可变。
const如果放在*的右边,修饰的是指针变量本⾝,保证了指针变量的内容不能修改,但是指针指
向的内容,可以通过指针改变。

5.指针运算

指针的基本运算有三种,分别是

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

5.1指针+-整数

刚刚我们已经介绍过了指针+-整数,现在我们通过打印数组来示范一下。

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

5.2指针-指针

值得一提的是,指针只有减法,没有加法。下面我们通过指针相减来实现一个strlen函数。

int strlen(char* s)
{
	char* p = s;
	while (*p != '\0')
	{
		p++;
	}
	return p - s;
}

int main()
{
	printf("%d",strlen("abc"));
}

5.3指针的关系运算

指针的关系运算即指针比较大小。

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	while (p < arr + sz)
	{
		printf("%d ", *(p + i));
		p++;
	}
	return 0;
}
  • 19
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值