指针的学习总结(一)

推荐使用电脑端阅读

内存和地址

  CPU在处理数据时,需要从内存中取出数据,使用完后再放回内存。那么内存就需要被划分为一个一个小的内存单元,每个内存单元的大小取为 1 个字节。
  常见计算机常见的单位:

  1. bit - 比特位
  2. byte - 字节   1byte = 8bit
  3. KB      1KB = 1024byte
  4. MB      1MB = 1024KB
  5. GB      1GB = 1024MB
  6. TB       1TB = 1024GB

  把内存划分为多个内存单元,一个内存单元室一个字节,一个字节包含八个比特位,这样CPU就可以快速找到所对应的内存空间了。
  可以想象,在内存被划分之后,那么每一个内存单位我们都可以给它一个编号,这个编号在计算机里就是它所对应的地址。在C语言里,地址也有一个新的名字叫:指针
所以我们可以理解为:内存单元的编号 == 地址 == 指针
当我们创建了一个变量,就是给这个变量寻找了一个内存空间,这个变量就拥有了自己的内存和地址(即内存单位编号)。

指针变量和地址

指针变量

  指针变量顾名思义就是用来存放地址的变量。之前我们知道内存单元的编号 == 地址 == 指针。

所以指针变量的值就是地址,换句话来说指针变量是一个变量,这个变量的值是一个地址。

对指针变量进行拆解:
[变量类型名]* [变量名] 例如: int a = 0;  int* pa = &a;

方便理解我们可以认为 * 代表变量 pa 是一个指针变量 ,int 是 pa 作为指针变量所指向的变量数据类型,因为 pa 指向 a 的地址,变量 a 是 int 类型,所以 pa 是指向 int 类型的指针变量

  那么我们保存这个值(里面是地址)后能怎么办呢?
  我们得到指针变量保存的这个值后,只知道我们储存的地址是多少,暂时还没有用。请接着往下看。

取地址操作符(&)

  首先是我们怎么得到一个变量的地址,那就是使用取地址操作符(&),取地址符(&)可以获取变量的地址。

#include <stdio.h>
int main()
{
	int a = 10;

	printf("a的地址是%p\n", &a);

	return 0;
}

如图所示

解引用操作符(*)

  那么得到了变量的地址,我们就可以通过指针变量的解引用来找到指针变量所指向的那块内存空间,并对它进行需要的修改操作。

#include <stdio.h>
int main()
{
	int a = 10;

	printf("a的地址是%p\n", &a);

	printf("修改前,a的值为%d\n", a);
	int* p = &a;
	*p = 20;
	printf("修改后,a的值为%d\n", a);

	return 0;
}

如图所示
  细心的伙伴可能会发现变量a的地址发生了改变,其实是每次程序运行结束后变量会自动销毁,下一次程序执行时,为变量a所寻找的内存空间地址不一定相同,所以会显示地址不相同。

指针变量的大小

指针变量的大小取决于地址的大小
32位平台下地址是32个bit位(即4字节)
64位平台下地址是64个bit位(即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(float*));
	printf("%zd\n", sizeof(double*));
	return 0;
}

如图所示

指针变量类型的意义

  指针变量的大小都一样,那么为什么指针变量有那么多种类型呢?比如整形类型指针变量,字符类型指针变量等等。其实不同类型的指针变量有着其他的意义。

指针的解引用

左图
右图

  char*类型的指针和int*类型的指针所实现的效果不同,char*类型指针只改变1个字节的内存,而int*类型指针则改变了4个字节的内存,所以这也是为什么指针类型会存在多种的原因。就是通过赋不同类型给指针变量,让指针变量拥有不同的内存单位操作能力,比如char*类型指针就负责操作1个字节的内存操作,int*类型指针就负责整体4个字节的内存操作。

即给指针变量赋什么类型,那么指针变量就能拥有该类型变量的内存大小的操作能力,比如int*类型的指针拥有改变四个字节的内存操作能力,char*类型的指针拥有改变一个字节的内存操作能力。

#include <stdio.h>
int main()
{//图1代码
	int a = 0x11223344;
	int* p = &a;
	*p = 0;

	printf("a的值%X", a);
	return 0;
}

#include <stdio.h>
int main()
{//图2代码
	int a = 0x11223344;
	char* p = (char*) &a;
	*p = 0;

	printf("a的值%X", a);
	return 0}

指针 + - 整数

  先来看一张图片:
图片3
结合上面指针类型的意义,大家可以看到不同类型的指针±1后得到的是指针的值(即所指的地址)会±1个该类型指针的大小,
如:char*指针+1,那么值(即地址)就会增加1个字节。如图从FB24变为FB25。
  int*指针+1,那么值(即地址)就会增加4个字节。如图为FB24变为FB28。
  以此类推,指针加1就会在所包含的值的基础上加上该类型的字节大小。

void指针

  void*类型指针是无具体类型指针,也叫泛型指针。它可以接受任意类型变量的地址,但是不能进行指针+ - 整数和指针解引用的操作。

我们之前提过指针前加上类型名,可以获得一次指针操作可以操作几个字节的能力,因为void*类型没有规定是多少字节,所以void*不能这样使用,给void*指针强制类型转换后可以做自己想要完成的操作。
如图所示

但是void*还是很有用的,在函数上可以用来作为参数来接收一些传址调用,如qsort函数中第一个传入的参数就是void*指针变量。更具体的操作,在后面的学习总结中再继续说明。

const修饰指针

  开始前先记住 前定值后定向 。那么开始:
const修饰在(*)的前面:const [类型名]*[指针名] 是指指针所指的内存区域的值不可以改变,但是指针的值(即指向的地址,也就是指针的指向)可以改变。从下图可以知道会发生错误。
报错
const修饰在(*)的后面:[类型名]* const[指针名]是指指针的值(即指针的指向)不变,但是指针所指的内容区域可变。如下图所示:
错误
再强调一次:前定值后定向

指针运算

指针的基本运算有三种,分别是:
• 指针+ - 整数
• 指针 - 指针
• 指针的关系运算

指针+ - 整数

  比较典型的应用是遍历数组,只要知道了数组的长度和首地址,那么我们就可以用指针来遍历数组。
如图所示

指针 - 指针

  这里的一个典型应用也是用于统计字符串的长度。
  在下图中我们设置了长度为13,不算上休止符(\0)共12个字符的字符串,我们用末地址去减掉首地址,可以得到12的一个数字。我突发奇想,使用末地址减去首地址,但是指针变量是 int 类型的,得到了3的这样一个数字,我就怀疑,计算机在得到地址长度(即末地址减去首地址)后,用这个长度去除指针变量类型所对应的长度(即 int 对应4个字节)。
如图所示
  不知道大家对 int* 指针减去 char* 指针得到的结果感不感兴趣,可以自己动手试一下。

#include <stdio.h>
int main()
{
	char str[13] = "Hello World!";//12个字符,加上终止符,共13个字符
	char* p_head = str;
	char* p_tail = str;
	while (*p_tail != '\0')
		p_tail++;//走到字符串的末尾
	printf("p_tail - p_head 的值:%lld\n", p_tail - p_head);


	int* p1 = (int*)p_head;
	int* p2 = (int*)p_tail;
	printf("p2 - p1 的值:%lld\n", p2 - p1);

	return 0;
}

指针的关系运算

  这个在数组上也有应用,也可以用来遍历数组,方式还是比较简单的,如下图:
如图所示

野指针

  概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
野指针形成的原因:

  1. 指针未初始化   创建指针变量,如果不赋初值,得到的是一个随机值。
  2. 指针越界访问  越界访问会损害到其他内存区域的数据。
  3. 指针指向的空间释放  空间释放,导致指针指向无意义。

如何规避野指针:

  1. 指针初始化  

若明确知道指针指向哪里,可以给它赋值相应的地址;若还不清楚,则可以给指针赋值NULL。NULL 是C语音中定义的一个标识符常量,值是0,0也是地址,这个地址是无法使用的,读写该地址会报错。

  1. 小心越界访问   

明确申请的内存区域范围,指针只能在这些范围里活动,超出了这个区域,就是越界访问。

  1. 指针变量不再使用时,及时置NULL,指针使用之前要检查有效性  
  2. 避免返回局部变量的地址  

例如在函数中创建的局部变量,不要使用返回值来返回局部变量的地址,因为在函数退出后,函数中的局部变量也会被销毁,导致返回的局部变量已经失去原本的价值。

指针的使用和传址调用

  这里使用一个例子来说明这个使用,Swap函数来完成两个变量之间值的置换:图中可以看到Swap_1函数没有完成值的置换这项任务,但是Swap_2函数完成了值的置换这项任务。

是因为函数本身是值的拷贝,main函数给Swap_1函数传参,Swap_1中的变量只是拷贝了main函数中的参数的值。这两个函数(main函数和Swap_1函数)中的变量不是同一块内存区域,所以值的置换仅仅是置换了Swap_1函数中的两个变量的值,并没有影响到main函数中的两个变量的值。

而Swap_2函数接受的参数是两个 int* 类型的指针变量,指针变量可以通过(*)解引用来得到所指向的内容,并对其进行修改。原因是指针变量保留着变量a和变量b的内存单元编号,从而通过解引用可以直接修改这部分内存区域的数据,最后完成了值的置换这项任务。

如图所示

结束

  学习总结完毕!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值