美丽的外貌千篇一律,有趣的灵魂万里挑一

指针是C语言的灵魂

应该有不少小伙伴对指针还是比较迷迷糊糊的吧

本期 阿紫 带大家一起过关斩将,擒 “ 指针 ”


目录

1.指针是什么 ?

2.指针变量和指针变量类型

2.1指针变量+-整数

2.2指针变量的解引用

3.野指针

3.1野指针的成因

3.2如何避免野指针

4.指针变量的运算

4.1指针+-整数

4.2指针-指针

4.3指针的关系运算

5.指针变量和数组

6.二级指针


1.指针是什么 ?

指针是什么?

我们知道计算机内存中的基本存储单位是字节,计算机对每个字节都会进行编号,我们把对字节的编号称为地址,那么这个地址也就称为指针

地址:地址是唯一标识一块内存空间的,我们可以通过地址找到对应的内存空间

注:我们通常所说的指针是指的指针变量 ,是用来存放地址的变量。

  • 指针:地址
  • 指针变量:存放地址的变量

内存编号图:

我们如何将变量的地址放入到一个指针变量中?

我们通常用 & (取地址符),将一个变量的地址放入到指针变量中

 int a = 0;int* p = &a;

注:a 是 int 类型,所以它占4个字节,将 4个字节中的的第 1 个字节地址放入指针 p 中。指针 p 的类型是int*,但并不代表指针 p 也占4个字节,而是代表着 p 找到了变量 a 的第一个字节的地址后,应该访问多少个字节。

我们现在知道了计算机会对内存中的每一个存储单元(字节)进行编址,那我们现在思考一下计算机是如何对进行编址的?

对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电平(低电压)就是(1或者0), 那么32根地址线产生的地址就会是: 就会产生2的32次方个地址,每个地址由32位二进制进行编码

同样的方法对64位的机器,就会产生 2 的 64 次方个地址,每个地址由64位二进制进行编码

  • 32位的机器:一个地址由32位二进制组成,8个二进制等于一个字节,那么32位的机器产生的地址也就是4个字节 
  • 64位的机器:一个地址由64位二进制组成,8个二进制等于一个字节,那么64位的机器产生的地址也就是8个字节

2.指针变量和指针变量类型

我们知道变量有不同的类型,那么指针变量有没有类型呢?

准确来说:指针变量是有类型的,但它跟变量的类型却有所不同。变量的类型一般大多数是用来开辟多大的空间的,而指针变量的类型并不是用来开辟空间的,而是表示它能访问多大空间

接下来我们一起来看看指针变量类型的作用吧! 

2.1指针变量+-整数

变量 a 是一个int类型的,打印 &a 打印的是变量 a 起始位置字节空间的地址,pi 和 pc 里面存放的都是 a 起始位置的地址 ,所以它们打印的也是 a 起始位置的地址。但是 pi 是 int* 类型的所以它可以访问 4 个字节 ,所以pi + 1跳过了四个字节的地址。pc 是char* 类型的所以它可以访问 1 个字节,所以pc + 1跳过了 1 个字节的地址

  • 指针类型决定了指针向前或向后走一步有多大距离

2.2指针变量的解引用

将n的地址强制类型转换为 char* 赋值给 char* pc 字符指针变量。因为每个变量的地址都是起始位置的地址,那么 pc 中也就存放了 n 变量的起始位置的地址,然后给 pc解引用 找到 pc 指向的地址对应的空间,因为 pc 是 char* 类型的它只能访问一个字节的空间大小,所以它只改变了起始位置空间里面的内容。

n 是一个整型变量,将 n 的地址赋值给 int* pi 整型指针变量。因为每个变量的地址都是起始位置的地址,那么 pi 中也就存放了 n 变量的起始位置的地址,然后给 pi解引用 找到 pi 指向的地址对应的空间,因为 piint* 类型的它可以访问四个字节的空间大小,变量n是整型占四个字节空间大小,所以它改变了n所有的空间里面的内容

  • 指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)

比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节

3.野指针

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

3.1野指针的成因

① 指针未初始化 

局部变量指针未初始化,默认为随机值 。如果不知道给指针初始化为什么,建议初始化为NULL

② 指针越界访问 

数组下标是从 0 开始的,arr 数组中有 10 个元素,所以下标到 9 就结束了。但它依然可以访问改变下标为 10 的空间里面的内容,这就是越界访问,当程序运行时程序会直接崩溃并提示崩溃原因。当指针指向的范围超出数组 arr 的范围时,p 就是野指针

③ 指针指向的空间释放 

malloc 开辟一块 int 类型大小的空间,让指针变量 p 指向这块空间。当释放了这块空间时应该让 p 指向 NULL ,要是 p 没有指向 NULL, p 依旧记得这块空间地址。但是这块空间已经释放了也就等于还给了操作系统,那就很可能被其他分配的动态内存所占用。但是 p 还是指向了这块空间,当使用者忘记了前面已经释放这块空间时,就很有可能误改变了已经属于其他空间的值。所以当指针指向一块动态内存开辟的空间时,当这块动态内存被释放,指针应该置为 NULL。

3.2如何避免野指针

  1. 指针初始化
  2. 小心指针越界
  3. 指针指向空间释放即使置NULL
  4. 避免返回局部变量的地址
  5. 指针使用之前检查有效性

4.指针变量的运算

4.1指针+-整数

#include<stdio.h>
int main()
{int values[6] = {0};int *vp = &values[0];//指针+-整数;指针的关系运算while (vp < &values[6]){*vp++ = 1;}return 0;
}

*vp++ = 1++ 是一个后置。所以 vp  先和 * 结合找到 vp 指向的地址空间,将里面的内容改为1,然后再 vp++ ,指向下一个地址。  *vp++ = 1;相当于   *vp = 1;vp += 1; 

4.2指针-指针

#include<stdio.h>
int main()
{int arr[7] = { 0 };int* begin = arr;//数组名代表第一个元素下标地址int* end = &arr[6];printf("%d", end - begin);return 0;
}

指针 - 指针 = 它们中间元素的个数

begin 指针变量指向的是数组第一个元素的地址,数组中每个元素的类型都为 int 型占4个字节大小,每个元素的地址都为4个字节中第一个字节的地址,那 &arr[6] 则是最后一个元素所占的4个字节的第一个字节的地址,所以我们 end - begin = 6 

4.3指针的关系运算

#include<stdio.h>
int main()
{int arr[7] = { 0 };for (int* vp = &arr[6]; vp >= &arr[0]; vp--){*vp = 1;}return 0;
}

实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行

标准规定: 允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与 指向第一个元素之前的那个内存位置的指针进行比较

5.指针变量和数组

首先我们来看一个例子:

从上面的代码运行结果中我们可以看出:数组名跟数组首元素的地址是一样的 

那我们就可以得出一个结论:数组名表示数组首元素地址 

那我们再看两个例子:

例1:

当数组名 和 数组首元素放在 sizeof 中求大小时,却不同了。数组名放在 sizeof 中求大小时,求的是整个数组的大小。而数组首元素放在 sizeof 中求大小时,求的是首元素的大小。

例2:

arr 表示数组首元素地址,&arr 表示整个数组的地址,但是它们打印出来的结果是一样的地址。但是通过给它们分别 +1 就能看出区别,arr + 1 跳过的是一个元素大小,而 &arr + 1 跳过的是整个数组的大小

结论:sizeof(数组名)和 &数组名 表示整个数组大小,其余的 数组名 都表示数组首元素的地址

既然可以把数组名当成地址存放到一个指针中,我们使用指针来访问数组就成为可能。

所以 p+i 其实计算的是数组 arr 下标为i的地址 ,那我们就可以直接通过指针来访问数组。

如下:

#include<stdio.h>
int main()
{char arr[6] = { 'a', 'b', 'c', 'd', 'e', 'f' };char* p = arr; //指针存放数组首元素的地址int sz = sizeof(arr) / sizeof(arr[0]);for (int i = 0; i<sz; i++){printf("%c\n", *(p + i));}return 0;
}

打印的时候也可以写成:printf("%c\n", p[i])。  我们知道 arr 表示数组首元素地址arr[0] 表示数组第一个元素,那 p 指针变量中也是 arr 数组中首元素地址,那它同样 p[0] 表示数组第一个元素地址。

6.二级指针

二级指针:指针变量也是变量,那它也就有地址,存放一级指针地址的指针就是二级指针。

注:存放一级指针地址的指针称二级指针,存放二级指针地址的指针称三级指针...

a 的地址放在 p 中,p 就是一级指针。把 p 的地址放在 pp 中,pp 就是二级指针

对二级指针的运算: 

  • *pp 通过对 pp 中的地址进行解引用,这样找到的是 p , *pp 其实访问的就是 p
  • **pp 先通过 *pp 找到 p ,然后对 p 进行解引用操作: *p ,那找到的是 a .

过关斩将,擒“指针”(上)相关推荐

  1. Boost:验证atomic <>不会在成员指针上提供算术运算

    Boost:验证atomic <>不会在成员指针上提供算术运算 实现功能 C++实现代码 实现功能 验证atomic <>不会在成员指针上提供算术运算 C++实现代码 #inc ...

  2. c语言指针改良,重新认识C语言指针(上)(示例代码)

    ? 独创性并不是首次观察某种新事物,而是把旧的.很早就是已知的,或者是人人都视而不见的事物当新事物观察,这才证明是有真正的独创头脑 -尼采 本文已经收录至我的GitHub,欢迎大家踊跃star 和 i ...

  3. C++ 笔记(14)— 指针(指针声明、取地址、取值、new/delete、NULL指针、指针运算、指针数组、数组指针、指针传递给函数、从函数返回指针)

    1. 声明指针 指针是一个变量,其值为另一个变量的地址,即,内存位置的直接地址.就像其他变量或常量一样,您必须在使用指 针存储其他变量地址之前,对其进行声明. 指针变量声明的一般形式为: type * ...

  4. 指针的底层原理与使用

    1. 前言 如果问C语言中最重要.威力最大的概念是什么,答案必将是指针!威力大,意味着使用方便.高效,同时也意味着语法复杂.容易出错.指针用的好,可以极大的提高代码执行效率.节约系统资源:如果用的不好 ...

  5. 弄清指针-如何深入了解指针

    弄懂这些指针基础知识,再遇C指针咱就不慌了 一.前言 二.变量与指针的本质 三.指针的几个相关概念 四.指向不同数据类型的指针 五.总结 一.前言 如果问C语言中最重要.威力最大的概念是什么,答案必将 ...

  6. c语言智能指针是什么,C ++中的智能指针

    原标题:C ++中的智能指针 考虑以下带有普通指针的简单C ++代码. MyClass *ptr = new MyClass(); ptr->doSomething(); // We must ...

  7. 恼人的函数指针(二)

    原文链接:http://www.cnblogs.com/AnnieKim/archive/2011/12/04/2275589.html 前面曾写过一篇恼人的函数指针(一).总结了普通函数指针的声明. ...

  8. 可以比较两个指针是否相等_算法一招鲜——双指针问题

    什么是双指针(对撞指针.快慢指针) 双指针,指的是在遍历对象的过程中,不是普通的使用单个指针进行访问,而是使用两个相同方向(快慢指针)或者相反方向(对撞指针)的指针进行扫描,从而达到相应的目的. 换言 ...

  9. 9.QT事件机制源码时序分析(上)

    通过上两篇博客https://blog.csdn.net/Master_Cui/article/details/109093845和https://blog.csdn.net/Master_Cui/a ...

最新文章

  1. 网页制作知识:XHTML 和 DOCTYPE 切换
  2. python简单装饰器_python装饰器的简单示例
  3. python写文件读文件-python--文件流读写
  4. python能在excel运行吗-使用PyXLL在Excel中执行Python脚本
  5. LeetCode Implement Trie (Prefix Tree)(字典树)
  6. Autolisp:利用AuoCAD之Lisp编程案例之自动智能绘制枫叶玫瑰
  7. 在MAC系统的eclipse里打开android sdk manager
  8. Android 短信数据库重要table字段解释
  9. 60-130-336-源码-source-kafka相关-Flink读取kafka
  10. 物理层传输介质(双绞线、光纤等)
  11. python怎么筛选excel数据_Python操作三个excel并过滤数据,python,筛选,其中
  12. MySQL循环结构例题_Mysql:循环结构
  13. Elementui tabs组件内添加组件
  14. pytorch打包exe出现WARNING: file already exists but should not: C:\Users\workAI\AppData\Local\Temp\_MEI13
  15. iphone型号表_iPhone12各版本有什么区别 苹果12四款机型配置表一览
  16. 计算机工资管理软件是,计件工资管理软件
  17. python 余弦定理_自己实现文本相似度算法(余弦定理)
  18. k8s数据持久化之statefulset的数据持久化,并自动创建PV与PVC
  19. win10正确清理C盘
  20. 固态硬盘和机械硬盘的区别

热门文章

  1. xinxin -制作网页
  2. IIS设置http自动跳转https
  3. iuni-summary
  4. readLine()与read()
  5. foss测试_值得关注的5个人道主义FOSS项目
  6. 程序员:大师,有些东西我放不下...
  7. 都在说00后是躺平的一代,公司刚来的00后却把我卷哭了
  8. Python安装教程(Windows10)
  9. python 撞库_python 脚本撞库国内“某榴”账号
  10. docker中使用$PWD: 提示 ‘:‘ was not followed by a valid variable name character. 解决方案