C语言之指针初阶

news2025/2/23 14:00:57


目录

前言

一、内存与地址的关系

二、指针变量

三、野指针

四、const

五、传值调用与传址调用

总结


前言

        本文主要介绍C语言指针的一些基础知识,为后面深入理解指针打下基础,因此本文内容主要包括内存与地址的关系,指针的基本语法,指针运算,野指针,还有const修饰指针和assert断言的使用,最后还会讲到指针的传址调用,希望对大家有所帮助。


一、内存与地址的关系

指针作为C语言的核心知识,那么指针究竟是什么呢?

  1. 首先指针其实就是地址,而地址是内存中一个个内存单元的编号
  2. 我们知道计算机上CPU(中央处理器)在处理数据的时候,需要的数据是在内存中读取的,处理后的数据也会放回内存中,那我们买电脑的时候,电脑上内存有8GB/16GB/32GB等,这些内存就是程序运行时需要用到的内存
  3. 为了更高效的管理与使用这些内存,于是就将这些内存分为一个个内存单元,每个内存单元的大小取一个字节,也就是8个比特位,⼀个比特位可以存储一个2进制的位1或者0
  4. 每个内存单元也都有一个编号,有了这个内存单元的编号,CPU就可以快速找到一个内存空间,在计算机中我们把内存单元的编号也称为地址。C语言中给地址起了新的名字:指针
  5. 所以我们可以这样理解:内存单元的编号 == 地址 == 指针

如图:


二、指针变量

指针变量就是储存地址的变量

1. 取地址操作符  &

&操作符是一个单目操作符,用来取出一个变量的地址

比如,创建 int a,观察其地址

图中画红线的部分就为a变量在内存中的地址以及储存的数据,即0x004FF82C为地址,0a 00 00 00为以16进制储存的数据,其中 0a 表示的就是以16进制保存的十进制数字10,因为一个16进制数需要4个比特位表示,0a就是两个16进制数,占了8个比特位刚好为一个字节,而0a 00 00 00 四个这样的就刚好表示4个字节,至于为什么0a在前面,这是编译器自己的规则


2.指针变量创建

对于一般的指针变量的创建

(类型) * 变量名;

这样就将变量 a ,b 的地址存储在对应类型的指针变量里面,其余的如float、double等可以以此类推

注意:这里的*号表示其变量为指针变量,它是与变量名结合,前面的类型是指针变量的类型,它与指针访问地址时的权限大小有关(后面有讲)


3.解引用操作符  *

* 解引用操作符为一个单目操作符,它可以通过地址找到其对应的数据

因为指针变量存储的就是地址,所以指针变量搭配*就可以找到其对应的数据进行操作

如:

我们可以通过解引用操作符修改指针对应的变量数据


4.指针变量类型的意义

指针变量的大小和类型无关,只要是指针变量,在同一个平台下,大小都是一样的,比如在32位平台上指针类型的变量大小都是4个字节,64位平台上为8个字节(以下在32位上演示)

既然指针变量的大小和类型无关,那么指针变量的类型有什么意义呢?

其实,这个意义非常重要:指针的类型决定了,对指针解引用的时候有多大的权限(一次能操作几个字节)。比如: char* 的指针解引用就只能访问⼀个字节,而 int* 的指针的解引用就能访问四个字节,这个权限的大小就是指针变量类型的大小

如:我们再次创建一个变量a。(注:程序每次运行时分配的地址不一样)

除了a变量的地址不一样,其他和上面一样为 0a 00 00 00,它表示的是10,并且每两位表示一个字节,而一个字节表示一个内存单元,因此如上的0x0099FC8C其实表示的是储存0a的地址,我们可以一列一列的观察其内存

因为a为整形变量,占4个字节,因此其在内存中为4个连续的内存单元,如上标记的区域,此时如果我们创建一个整形指针变量接收a的地址,那么我们解引用该指针就可以操作这四个字节

如:

注:90 01 00 00 在读取时是以 00000190,也就是190,为16进制

十进制刚好为400

此时变量a可以被正常修改,而如果我们以字符类型的指针接收a的地址后,我们一次只能修改一个字节

如:

16进制28等于十进制40,如上我们貌似也能正常修改整形变量a的数组,但实际上只要我们修改的数值大于两位16进制能表达的最大数字,就不能正常修改a的数值

如:

我们只能改变一个字节,也就是char类型的指针一次只能修改一个字节

这就是指针类型的意义,当然不止如此,指针变量的类型还决定了指针加减整数的时候一次跳过多少字节,下面就会讲到


5.指针 + - 整数

先说结论:指针加减整数,会使指针前进或后退n个字节,而指针的类型决定了指针向前或者向后走一步有多大(距离)

也就是说,指针类型决定了指针加减1时的步长,比如char*指针,它一次只能跳过一个字节,它加减n也就是向前或向后跳过n*sizeof(char)个字节

比如:

(注:此处int *parr = arr不能写成&arr,下篇指针进阶文章我会讲到) 

此处我们就利用了循环来使数组首元素地址依次跳过 i个int类型大小的字节,实现了循环打印数组元素

此处我们有几处需要注意的点:

  1. 数组的元素在内存中是连续存放的,并且地址由低到高,不了解的可以参考我主页的数组文章
  2. 此处我们发现,如果把 *(arr+i) 换成 arr[ i ] 也就是我们之前的写法达到的效果是一样的,这是因为 *(arr+i) 是完全等价于 arr[ i ] ,也就是说,当编译器遇到 arr[ i ] 时会把它解读为 *(arr+i),按这样理解,因为 arr+i 等于 i+arr ,也就是可以写成 *(i+arr),进而可以写成 i[arr] ,我们可以验证一下
  3. 答案是完全可以的,但是平时不建议这样写,因为 i[arr] 可读性不如 arr[ i ]。总结就是 [ ] 操作符其实也是解引用的效果,只不过多了加法的作用


6.指针 - 指针

对于指针 - 指针这个运算来说,只有两指针指向的是同一块连续的内存区域时才有意义

我们可以通过指针 - 指针来计算数组两元素地址之间有多少个元素

如:

那么为什么是9个而不是10个呢?

我们可以画图来理解:

画图我们就可以很直观的感受到,arr[9] 的元素没有被计算到,因为如果指针指向的数据大小大于一个字节,那么指针储存的该数据地址为该数据储存在内存中的首地址,参考上文中的 int a 变量的地址观察


三、野指针

1.概念

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

野指针成因:

  1. 指针未初始化:主要是创建在函数中的指针变量没有进行初始化,造成指针指向的地址是随机值,此时指针指向的地址随机,不能对其进行解引用。
  2. 指针越界访问:这种主要出现在数组中,指针指向的地址超出了数组所在的内存区域,指向了一个不确定的地址
  3. 指针指向的空间被释放:这种主要发生在指针变量指向的地址是已经被释放的内存空间地址,被释放的空间不属于该程序,虽然可能引用不会导致报错,但是不安全


2.如何规避野指针

野指针的危害有:访问违规、数据损坏、内存泄露、安全风险等。

野指针的危害众多、因此我们的代码中需要规避野指针,那么如何规避野指针呢?

  1. 指针变量初始化时如果没有需要赋值的地址就先赋值为NULL
  2. 指针变量不再使用时,及时置NULL,指针使用之前检查有效性:当指针变量指向一块区域的时候,我们可以通过指针访问该区域,后期不再使用这个指针访问空间的时候,我们可以把该指针置为NULL。因为约定俗成的⼀个规则就是:只要是NULL指针就不去访问, 同时使用指针之前可以判断指针是否为NULL。
  3. 避免返回局部变量的地址,比如避免函数返回局部变量地址

除了以上的方法,还有一个常用的方法:

assert 断言

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

比如:assert (p != NULL);

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

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

如:

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

如下assert()就会失去作用:

如果想再次启用assert()只需要注释掉第一行的宏就行

assert() 的缺点:因为引入了额外的检查,增加了程序的运行时间。 一般我们可以在 Debug 中使用,在 Release 版本中选择禁用 assert 就行,在 VS 这样的集成开发环境中,Release 版本中,assert()直接就是自动优化掉了。这样在debug版本写有利于程序员排查问题,在Release 版本不影响用户使用时程序的效率。


四、const

const的作用:被const修饰的变量不能被直接修改

如:

程序在还未运行时已经发出错误警告

虽然不能直接修改,但是还能通过指针变量间接修改:


但是如果const修饰的是指针变量,就分以下两种情况:

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

如:


五、传值调用与传址调用

传值调用与传址调用,这个主要针对的是自定义函数的参数,也就是说:

  1. 函数参数为非指针类型,调用时传入非指针类型参数,即为传值调用
  2. 函数参数为指针类型,调用时传给函数地址,即为传址调用

那么这两个有什么区别呢?

其实主要是传值调用时,在函数内部修改形参不会影响实参,而在传址调用时,修改形参也同样会会修改实参

比如这个例题:编写一个函数,交换两个整形变量的内容

此前我们在主函数中只需要再创建一个变量,通过三者交换即可达成题目这样的效果,但如果我们在自定义函数里面,函数参数为两个整形变量,分别接收需要交换内容的两个实参,使用一样的方法是达不到一样的效果的,这时候我们只需要使用传址调用即可

如:

通过传给函数实参的地址,在函数中用指针变量的形参接收,就可以在函数中解引用该指针变量来修改对应的实参变量的内容

这就是传值调用与传址调用的不同

另外,如果函数的参数为数组类型,其实也是指针变量,给函数传参时,一般传入的就是数组名,因为数组名就是数组首元素的地址,至于详细原因我会在指针进阶中讲到


总结

        以上就是本文的全部内容,希望对大家有所帮助,下一篇我会继续写指针的进阶篇,感谢大家的支持

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

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

相关文章

Pycharm使用Anaconda虚拟环境

一、前置 安装 Pychram安装 Anaconda&#xff0c;并配置虚拟环境 参考&#xff1a; Anaconda虚拟环境 anaconda虚拟环境pytorch安装 二、在Pycharm中使用Anaconda的虚拟环境 打开 Pycharm的命令行可以看到 Anaconda 的虚拟环境已经启动。 三、问题集合 &#xff08;1&…

都是免费的SSL证书,有什么区别

国内做SSL证书的服务商还是比较多&#xff0c;但也不是所有服务商都提供免费的SSL证书&#xff0c;一般只有少数几家提供免费SSL证书。那么&#xff0c;同样都是免费的SSL证书&#xff0c;有哪些不一样的地方呢&#xff1f; 1、验证类型&#xff1a;免费SSL证书通常只提供域名…

什么是透明加密?如何用透明加密保护数据安全?

透明加密技术是近年来针对企业文件保密需求应运而生的一种文件加密技术。它的特点是对于使用者来说是“未知”或“透明”的&#xff0c;即在打开或编辑指定文件时&#xff0c;系统会自动对未加密的文件进行加密&#xff0c;对已加密的文件自动解密。文件在硬盘上以密文形式存在…

力扣HOT100 - 45. 跳跃游戏 II

解题思路&#xff1a; 贪心 class Solution {public int jump(int[] nums) {int end 0;int maxPosition 0;int steps 0;for (int i 0; i < nums.length - 1; i) {maxPosition Math.max(maxPosition, i nums[i]);if (i end) {end maxPosition;steps;}}return steps;…

跨镜动线分析全新升级,用AI发掘店内客流密码,助力精准营销

作为零售门店的管理者&#xff0c;你对消费者的喜好真的了解吗&#xff1f;你是否想过&#xff0c;有些你认为的冷门商品可能并非真的冷门&#xff0c;只是摆放的区域不合适。 在实际运营中&#xff0c;只有对门店商品、顾客喜好足够了解才能不断优化运营&#xff0c;提升业绩。…

六西格玛管理培训公司:事业进阶的充电站,助你冲破职场天花板!

六西格玛&#xff0c;源于制造业&#xff0c;却不仅仅局限于制造业。它是一种以数据为基础、以顾客为中心、以流程优化为手段的全面质量管理方法。通过六西格玛管理&#xff0c;企业可以系统性地识别并解决运营过程中的问题&#xff0c;提高产品和服务的质量&#xff0c;降低成…

Linux服务升级:OpenResty 升级1.25.3.1版本

目录 一、实验 1.环境 2.Windows 安装 Termius 3.Linux 部署 OpenResty 4.Linux 使用 OpenResty 实现内容展示&#xff08;content_by_lua&#xff09; 5.Linux 使用 OpenResty 实现重定向 &#xff08;rewrite_by_lua&#xff09; 6.Linux 使用 OpenResty 实现请求体&…

利用matplotlib和KNeighborsClassifier,进行DBSACN聚类算法

代码&#xff1a; # -*- coding: utf-8 -*- """ Created on Sat May 11 10:23:50 2024author: admin """ # 调用库 import numpy as np import matplotlib.pyplot as plt # 调用人工智能模型库 from sklearn.neighbors import KNeighborsClassi…

JVM 类的加载过程详解

文章目录 1. 哪些类需要加载2. 类加载步骤2.1 装载2.1.1 这个过程都做了什么事2.1.2 类的模板对象2.1.3 二进制流获取方式2.1.4 Class 实例的位置2.1.5 数组类的加载有什么不同 2.2 链接2.2.1 验证2.2.2 准备2.2.3 解析 2.3 初始化 1. 哪些类需要加载 在 Java 中数据类型分为 …

初始化linux数据盘(3TB)分区-格式化-挂载目录

场景说明&#xff1a;某云给我们服务器加载了一块3TB的硬盘扩容&#xff08;没有直接扩&#xff0c;原因是原来的盘做的是mbr&#xff08;什么年代了&#xff0c;谁干的&#xff09;的分区&#xff0c;最大识别2TB&#xff09; 确认磁盘 输入命令lsblk 查看数据盘信息 &#…

CEETRON SDK 可为您的CAE应用程序提供5大优势!

开发CAE应用程序是一项资源密集型、复杂且耗时的工作。成功的开发人员会尽其所能&#xff0c;确保他们专注于让他们的产品、他们的新想法独一无二的东西。凭借CEETRON系列产品及其集成的工具&#xff0c;Tech Soft 3D提供了唯一支持预处理、求解和后处理工作流程的完整CAE组件技…

PCIE协议-2-事务层规范-Completion Rules

2.2.9 完成规则 所有Read、Non-Posted Write和AtomicOp请求都需要完成&#xff08;Completion&#xff09;。完成包含一个完成头标&#xff0c;对于某些类型的完成&#xff0c;完成头标之后会跟随一定数量的DWs数据。完成头标的每个字段的规则在以下各节中定义。 完成通过ID路…

_remote.repositories作用

问题描述 明明我本地有某个依赖但是却还是报错&#xff0c;原因就是存在_remote.repositories且你的远程仓库中找不到该依赖&#xff0c;可能发生在你修改了远程仓库或镜像时。 例子 本地有这个依赖&#xff0c;但是报错。 解决 删除_remote.repositories文件&#xff0…

【多电压流程 Multivoltage Flow】- 5.特定工具使用建议(6.Formality)

使用Formality进行形式验证 Formality支持具有低功耗特性的功能等效性检查,如时钟门控、多阈值电压(multiple-Vt)、多电压供电、电源门控以及动态电压和频率缩放。Formality能够识别低功耗单元,例如隔离单元、电平转换器、始终开启单元、保持寄存器和电源门。 Formality支持…

微信小程序网格布局

效果图 实现 wxml <!-- 订单内容 --><view class"father"><!-- 订单item --><view class"childs" wx:for"{{List}}" wx:key"{{ index }}"></view></view> wxss .father{display: grid;grid-tem…

【二叉树】Leetcode 在每个树行中找最大值

题目讲解 515. 在每个树行中找最大值 算法讲解 这道题的重点在于怎么能够保存这一层的节点&#xff0c;并求出层节点的最大值&#xff1a;我们需要使用一个queue&#xff0c;每一次出队列的时候将当前队头结点的左右子树入队列&#xff08;这里就是将下一层的结点入队&#…

计算机Java项目|基于springboot的社区团购系统设计

作者主页&#xff1a;编程指南针 作者简介&#xff1a;Java领域优质创作者、CSDN博客专家 、CSDN内容合伙人、掘金特邀作者、阿里云博客专家、51CTO特邀作者、多年架构师设计经验、腾讯课堂常驻讲师 主要内容&#xff1a;Java项目、Python项目、前端项目、人工智能与大数据、简…

vscode无法连接 , .vscode-server版本问题

vscode无法连接 &#xff0c; .vscode-server版本问题 解决办法 &#xff1a; 查看自己的版本号 2. 两边vscode版本号需要一致 找一台vscode可以远程连接的&#xff0c; 将它的.vscode-server/bin/b06ae3b2d2dbfe28bca3134cc6be65935cdfea6a 传到 远程服务器上 或者 本地的…

vue3组件插槽

Index.vue: <script setup> import { ref, onMounted } from vue import Child from ./Child.vue import ./index.cssonMounted(() > {}) </script><template><div class"m-home-wrap"><Child>插槽</Child><div class&qu…

1063 计算谱半径

solution 找出虚部和实部平方和的最大值&#xff0c;输出该最大值的开方&#xff0c;保留两位小数 #include<iostream> #include<cmath> using namespace std; int main(){int n, a, b, ans 0;scanf("%d", &n);while(n--){scanf("%d%d"…