C语言—深入理解指针(1)

1.内存和地址

1.1 内存

计算机上CPU(中央处理器)在处理数据的时候,需要的数据是在内存中读取的,处理后的数据也会放回内存中,那我们买电脑的时候,电脑上内存是8GB/16GB/32GB 等,其实管理方式也是把内存划分为一个个的内存单元,每个内存单元的大小取1字节

每个内存单元都有一个地址,C语言中给地址起了一个新的名字:指针

所以可以理解为:内存单元的编号 == 地址 == 指针

1.2 理解编址

首先,必须理解,计算机内是有很多的硬件单元,而硬件单元是要相互协同工作的。所谓的协同,至少相互之间要能够进行数据传递。但是硬件与硬件之间是相互独立的,那么如何通信呢?这就需要用“线”链接起来。而CPU和内存之间也是有大量的数据交互的,所以,两者必须也用线连起来。

本文先探讨一下“地址总线”,CPU访问内存中的某个字节空间,必须知道这个字节空间在内存的什么位置,而因为内存中字节很多,所以需要给内存进行编址。

计算机中的编址,并不是把每个字节的地址记录下来,而是通过硬件设计完成的。

可以简单理解为,32位机器有32根地址总线,每根线只有两态,表示0,1【电脉冲有无】,那么一根线,就能表示2种含义,2根线就能表示4种含义,每一种含义都代表一个地址。

2.指针变量和地址

2.1 取地址操作符(&)和指针变量

理解了内存和地址的关系,我们再回到C语言,在C语言中创建变量其实就是向内存申请空间

2.2 解引用操作符(*)

2.3 指针变量的大小

32位机器假设有32根地址总线,由前文所解释,不难知道一个地址就是32个bit位,需要4个字节才能存储。

如果指针变量是用来存放地址的,那么指针变量的大小就得是4个字节的空间才可以。

同理,64位机器就需要8个字节。

3. 指针变量类型的意义

3.1 指针的解引用

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

代码一:

代码二:

通过调试我们可以看到,代码1会将n的4个字节全部改为0,但是代码2只是将n的第一个字节改为0。

结论:指针的类型决定了对指针解引用的时候有多大的权限(一次能操作几个字节)。

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

3.2 指针+-整数

char*类型的指针变量+1,可以跳过1个字节;int*类型的指针变量+1,可以跳过4个字节。这就是指针变量的类型差异带来的变化,指针+1,其实跳过1个指针指向的元素。指针可以+1,也可以-1。

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

3.3 void* 指针

在指针类型中有一种特殊的类型是 void * 类型的,可以理解为无具体类型的指针(或者叫泛型指针),这种类型的指针可以用来接受任意类型地址。但是也有局限性,void*类型的指针不能直接进行指针的+-整数和解引用的运算。

在上面的代码中,将一个int类型的变量的地址赋值给一个char*类型的指针变量。编译器给出了一个警告,是因为类型不兼容,而使用void*类型就不会有这样的问题。

4.const修饰指针

4.1 const修饰普通变量

变量是可以修改的,如果把变量的地址交给一个指针变量,通过指针变量也可以修改这个变量。

但是如果我们希望一个变量加上一些限制,不能被修改,那么就需要const。

但是下述代码却能改变const修饰的值。

此处指针p拿到了m的地址,这样就打破了const的限制,这是不合理的,所以应该让p拿到n的地址也不能修改n,那接下来怎么做呢?

4.2 const 修饰指针变量

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

5. 指针运算

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

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

5.1 指针+-整数

5.2 指针-指针

指针-指针:结果是两个指针之间元素的个数

5.3 指针的关系运算

6.野指针

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

6.1 野指针成因

1.指针未初始化

  

一个局部变量不初始化,它的值是随机的。此时解引用就会形成非法访问。(在VS中直接报错,dev里面给你一个随机值)。

2.指针越界访问

 

当p->arr[10]时,这时候就变成了野指针。

3.指针指向的空间释放

注意看上述想要返回n的地址,但是n是一个局部变量,当函数结束后,内存就被释放了。此时p就变成了野指针。

6.2 如何避免野指针

6.2.1 指针初始化

如果明确知道指针指向哪里就直接赋值地址,如果不知道指针应该指向哪里,可以给指针赋值NULL,NULL是C语言中定义的一个标识符常量,值是0,0也是地址,这个地址是无法使用的,读写地址会报错。

6.2.2 小心指针越界

一个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是越界访问。

6.2.3 指针变量不再使用时,及时置NULL,指针使用之前检查有效性

当指针变量指向一块区域的时候,我们可以通过指针访问该区域,后期不再使用这个指针访问空间的时候,我们可以把该指针置为NULL。因为约定俗成的一个规则就是:只要是NULL指针就不去访问,同时使用指针之前要去判断指针是否为NULL。

我们可以把野指针想象成野狗,野狗放任不管是非常危险的,所以我们可以找一棵树把野狗拴起来,就相对安全了,给指针变量及时赋值为NULL,其实就类似把野狗拴起来,就是把野指针暂时管理起来。

不过野狗即使拴起来我们也要绕着走,不能去挑逗野狗,有点危险;对于指针也是,在使用之前,我们也要判断是否为NULL,看看是不是被拴起来的野狗,如果是,则不能直接使用,如果不是,我们再去使用。

6.2.4 避免返回局部变量的地址

上述例子已经说明

7. assert断言

<assert.h>头文件定义了宏assert(),用于在运行时确保程序符合指定条件,如果不符合,就报错终止运行。这个宏常常被称为“断言”。

例:     assert(p!=NULL);

上面代码在程序运行到这一行语句时,验证变量p是否等于NULL。如果确实不等于NULL,程序继续运行,否则就会终止运行,并且给出报错信息提示。

assert()宏接受一个表达式作为参数。如果该表达式为真(返回值为非0),assert()不会产生任何作用,程序继续运行。如果该表达式为假(返回值为0),assert()就会报错,在标准错误流stderr(就是屏幕上)中写入一条错误信息,显示没有通过的表达式,以及包含这个表达式的文件名和行号。

assert()的好处:它不仅能自动标识文件和出问题的行号,还有一种无需更改代码就能开启或关闭assert()的机制。如果已经确认程序没有问题,不需要再做断言,就在#include <assert.h>语句的前面,定义一个宏NDEBUG(no debug)。

然后,重新编译程序,编译器就会禁用文件中所有的assert()语句。如果程序又出现问题,可以移除这条#define NDEBUG 指令(或者把它注释掉)再次编译,这样就重新启用了assert()语句。

assert()的缺点是,因为引入了额外的检查,增加了程序的运行时间。

一般我们可以在debug中使用,在release版本中选择禁用assert就行,在vs这样的集成开发环境中,在release版本中,直接就是优化掉了。这样在debug版本中有利于我们排查问题,在release版本中不影响用户使用时程序的效率。

8.指针的使用和传址调用

8.1 strlen的模拟实现

库函数strlen的功能是求字符串的长度,统计的是字符串中\0之前的字符的个数。

8.2 传值调用和传址调用

例:写一个函数,来交换整型变量的值。

还是那个老问题,函数调用后,内存会被释放,但是当传地址,然后改变值后,就没事了。

作者自述:本文主要针对C语言的指针知识。内容中包含了很多总结内容。本文制作不易,求求动动你们发财的小手点个赞和关注,这是对我创造最大的动力。后续我也会跟进内容,尽量一周至少一次,保证内容的质量。如果有想知道的内容或者有建议的地方,欢迎后台私信或者在本文留言哦。感谢各位的支持捏Thanks♪(・ω・)ノ。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/9078.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python从0到POC编写--实用小脚本02

爆破脚本&#xff1a; 爆破脚本也是我们经常使用的东西 这里就简单讲讲后台爆破脚本的编写吧 在编写之前&#xff0c;我们先通过访问网站去看看情况 首先我们可以先登录看看 输入账号 admin &#xff0c;密码 12345 后 登录失败&#xff0c;提示 用户名或密码错误 在输入…

探秘Tailwind CSS:前端开发的加速器(Tailwind CSS让CSS编写更简洁)

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 Tailwind CSS 📒📝 快速体验📝 深入学习⚓️ 相关链接 ⚓️📖 介绍 📖 在这个快速迭代的互联网时代,前端开发效率和设计质量的双重要求,使得开发者们不断寻求更高效的工具和方法。今天,我们要介绍的是一个能够极大…

java面向对象实现文字格斗游戏细节完善版

为了完善上一篇的文字格斗游戏的细节&#xff0c;所以加了些代码&#xff0c;使得交互更加的具体有趣! 效果 大家可以多运行几次代码&#xff0c;得到不同的战况&#xff01;&#xff01; 代码实现 1.bean类 import java.util.Random;public class TextGame {private Strin…

[性能优化] ScrollView视图优化为循环列表

问题描述&#xff1a; 原先商城的物品栏中的item 是load在一个scrollView 下&#xff0c;用于滑动查看。仅仅在父级panel下是使用了NGUI原生的scrollview 组件&#xff0c;随着商场物品列表中新物品的增多。panel下加载的实例也非常庞大。而大部分的实例用户也无法看到&#x…

python代码无法点击进入,如何破???

python代码无法点击进入&#xff0c;如何破&#xff1f;&#xff1f;&#xff1f; 举个栗子&#xff1a; model.chat是无法进入的&#xff0c;这时可以使用如下的命令进行操作&#xff1a; ?model.chat

Day31:单元测试、项目监控、项目部署、项目总结、常见面试题

单元测试 保证独立性。 Assert&#xff1a;断言&#xff0c;一般用来比较是否相等&#xff0c;比如 Assert.assertEquals 在JUnit测试框架中&#xff0c;BeforeClass&#xff0c;Before&#xff0c;After和AfterClass是四个常用的注解&#xff0c;它们的作用如下&#xff1a; …

Vue--》从零开始打造交互体验一流的电商平台(一)

今天开始使用 vue3 ts 搭建一个电商项目平台&#xff0c;因为文章会将项目的每处代码的书写都会讲解到&#xff0c;所以本项目会分成好几篇文章进行讲解&#xff0c;我会在最后一篇文章中会将项目代码开源到我的github上&#xff0c;大家可以自行去进行下载运行&#xff0c;希…

Pyserini

文章目录 关于 Pyserini安装&#x1f3ac;如何搜索&#xff1f;&#x1f64b;如何给我的语料集建立索引 关于 Pyserini github : https://github.com/castorini/pyseriniPyserini: An Easy-to-Use Python Toolkit to Support Replicable IR Research with Sparse and Dense Re…

第09章 局域网技术(拓扑结构设计+FDDI工作机制)

9.1 本章目标 了解IEEE 802局域网标准掌握局域网拓扑结构了解10Base以太网了解快速以太网熟悉交换式以太网了解千兆位以太网了解其它种类的局域网局域网中的常用技术 9.2 局域网概述 罗伯特梅特卡夫个人简介 罗伯特梅特卡夫&#xff08;Robert Metcalfe&#xff0c;1…

【镜像仿真篇】磁盘镜像仿真常见错误

【镜像仿真篇】磁盘镜像仿真常见错误 记系统镜像仿真常见错误集—【蘇小沐】 1、实验环境 2023AFS39.E01&#xff08;Windows11系统镜像&#xff09;Arsenal Image Mounter&#xff0c;[v3.10.262]‍Vmware Workstation 17 Pro&#xff0c;[v17.5.1]Windows 11 专业工作站版…

Android ViewFlipper

Android ViewFlipper 在很多APP都有如下的头条/热榜效果&#xff08;上下自动翻滚&#xff09; 这种效果可以使用很多方式实现&#xff0c;有一个简便的方式可以使用ViewFlipper控件实现&#xff0c;ViewFlipper控件继承结果如下&#xff1a; 可以看出ViewFlipper 继承自ViewA…

在VMware上利用Samba实现资源共享

一、背景 FTP协议能让主机之间的文件传输变得简单方便&#xff0c;但是FTP协议的本质是传输文件。 举个栗子&#xff1a;当客户端想修改服务器上的test.txt&#xff0c;需要先get test.txt将文件下载下来&#xff0c;修改后再put test.txt 有没有一种方式能使客户端直接修改…

关于支付接口测试点的思考

支付接口测试是一项非常重要的测试工作&#xff0c;以下是一些测试支付接口的建议&#xff1a; 测试支付流程&#xff1a; 测试支付流程的正确性&#xff0c;包括支付前、支付中和支付后的各个环节&#xff0c;确保支付流程的顺畅和正确。 测试支付金额&#xff1a; 测试支…

Python 中的 Unit testing 文件写入

在 Python 中进行单元测试时&#xff0c;有时候需要测试文件写入操作。为了模拟文件写入并进行单元测试&#xff0c;你可以使用 Python 的 unittest 模块&#xff0c;并结合 io.StringIO 或 tempfile 模块来模拟文件操作。 1、问题背景 在 Python 中&#xff0c;为 ConfigPars…

Docker下Open WebUI,Ollama的安装实践

提示一下Open WebUI与ollama的关系。后端的同学可以理解为Open WebUI等于是个Navicat&#xff0c;Ollama就是具体的数据库实例。 官方安装文档&#xff1a; &#x1f3e1; Home | Open WebUI Open WebUI官网文档翻译&#xff1a; 注意&#xff1a; 使用Docker安装Open WebU…

58. 【Android教程】音频录制:MediaRecord

在第 57 节我们使用 MediaPlayer 实现了一个 mp3 播放器&#xff0c;除了播放 Android 还提供了 MediaRecorder 用于录音。Android 设备基本都会有一个麦克风&#xff0c;通过 MediaRecorder 可以打开麦克风进行语音采集&#xff0c;这一节我们就来学习如何在 Android 系统上实…

深入解析C++的auto自动类型推导(二)

目录 使用auto的好处 新标准新增功能 使用auto的限制 上一篇详细讲解了使用auto关键字进行自动类型推导时的推导规则&#xff0c;这一篇重点讲解auto的使用以及C14、C17、C20等新标准对auto的功能完善&#xff0c;最后再介绍auto的使用限制。上一篇请从这里阅读&#xff1a;…

线程池核心原理浅析

前言 由于系统资源是有限的&#xff0c;为了降低资源消耗&#xff0c;提高系统的性能和稳定性&#xff0c;引入了线程池对线程进行统一的管理和监控&#xff0c;本文将详细讲解线程池的使用、原理。 为什么使用线程池 池化思想 线程池主要用到了池化思想&#xff0c;池化思想…

【计算机科学速成课】笔记一

文章目录 写在前面1.计算机的早期历史2.电子计算机3.布尔运算和逻辑门4.二进制5.算术逻辑单元-ALU6.寄存器和内存 写在前面 所有的一切源于这样一个网站——CS自学指南。 这是新手小白入门计算机科学必要了解的知识——【计算机科学速成课】[40集全/精校] - Crash Course Comp…