C语言入门系列:指针入门(超详细)

文章目录

  • 一,什么是指针
    • 1,内存
    • 2,指针是什么?
  • 二,指针的声明
    • 1,声明指针类型变量
    • 2,二级指针
  • 三,指针的计算
    • 1,两个指针运算符
      • 1.1 *运算符
      • 1.2 & 运算符
      • 1.3 &运算符与*运算符的关系
    • 2,指针变量的初始化
      • 2.1 指针变量的大坑
      • 2.2 指针变量初始化
      • 2.3 指针变量初始化最佳实践
  • 三,指针的运算
    • 1,指针与整数值的加减运算
    • 2,指针与指针的加法运算
    • 3,指针与指针的减法
    • 4,指针与指针的比较运算

指针是 C 语言的重点,也是难点,这篇文章主要讲解指针是什么以及如何使用。

一,什么是指针

1,内存

指针与内存是息息相关的,在学习指针之前,先回忆下内存相关的知识。

在这里插入图片描述
内存是程序运行期间存储数据的硬件设备,为了方便管理,计算机将内存划分为一个个小的单元,每个单元的大小是一个字节。

如果把内存比作一栋酒店大楼,内存单元就像是一个个小的房间,数据就住在小房间里。

在这里插入图片描述

我们知道,为了客人能准确找到属于自己的房间,酒店房间是有房号的。

在这里插入图片描述

同样,内存单元也是有编号的,这个编号在计算机中称之为“内存地址”。

在这里插入图片描述

2,指针是什么?

指针就是内存单元的编号,本质上是一个内存地址,相当于房卡上的房间号。

从形式上看,指针和整型数据并没有什么区别,都是数字。区别在于,指针是内存地址,不用于与其他数据进行加减乘除等运算,也不会展示给用户。

二,指针的声明

1,声明指针类型变量

在编写代码过程中,通常会声明一个变量,然后对变量进行赋值或者其他各种运算。

要使用指针,也需要声明一个指针类型的变量。

一定要牢记:指针变量就是一个普通变量,只不过它的值是内存地址而已。

指针类型由两部分构成

  • ①指针标识符,C语言用字符*表示指针
  • ②指针类型,指针不能单独存在,必须和数据类型一起出现,表明这个指针是某种数据类型的指针

比如,char*表示一个指向字符的指针,float*表示一个指向float类型的值的指针。

int* intPtr;

上面示例声明了一个变量intPtr,它是一个指针,指向的内存地址存放的是一个整数。

星号*可以放在变量名与类型关键字之间的任何地方,下面的写法都是正确的。

int   *intPtr;
int * intPtr;
int*  intPtr;

推荐使用星号紧跟在类型关键字后面的写法,即int* intPtr;

声明指针变量时需要注意,如果要在一行声明多个指针变量,每个变量前都要携带字符*

// 正确
int * foo, * bar;// 错误
int* foo, bar;

上面示例中,第二行实际上仅仅声明了一个指针变量,foo是整数指针变量,而bar是整数变量,即*只对第一个变量生效。

2,二级指针

一个指针指向的可能还是指针,这时就要用两个星号**表示,这种指针通常称为二级指针

int** foo;

上面示例表示变量foo是一个指针,即变量foo存储的还是一个内存地址,这个内存地址指向的内存中存储的则是一个整数。

int a = 10;
// &a表示a变量的内存地址
int* pa = &a;
// &a表示指针变量pa的内存地址
int** ppa = &pa;

在这里插入图片描述

三,指针的计算

1,两个指针运算符

1.1 *运算符

*这个字符除了声明变量时代表指针外,还可以作为运算符,用来获取指针指向的内存中的值。

void plus1(int* p) {*p = *p + 1;
}

上面代码中,函数plus1的参数是一个整数指针p。

函数体里面,*p就表示指针p所指向的那个整数值。

对*p赋值,就是改变指针p指向的内存中的值。

这有点绕,和普通变量对比更容易理解。

int a = 10;
int b = 100;
// 将b的地址赋于指针pb
int* pb = &b;*pb = *pb +1;
a = a + 1;

对于上述代码的最后两行:

  • *pb = *pb +1,这个表达式可以拆解为4步,计算机首先从指针变量pb中取出地址0xffeecc再去这个地址指向的内存单元中获取整数100然后执行运算100+1,执行完成后,0xffeecc这个内存单元的值就变成101。
  • a = a + 1,相当于上面的表达式,执行过程更简单。计算机从变量a对应的内存直接取出整数10然后执行运算10+1执行完成后,a变量对应的内存的数据更新为11
    在这里插入图片描述

1.2 & 运算符

&运算符用来取出一个变量所在的内存地址。

int x = 1;
printf("x's address is %p\n", &x);

上面示例中,x是一个整数变量,&x就是x的值所在的内存地址。printf()的%p是内存地址的占位符,可以打印出内存地址。

上一小节中,参数变量加1的函数,可以像下面这样使用。

void plus1(int* p) {*p = *p + 1;
}int x = 1;
plus1(&x);
printf("%d\n", x); // 2

注意,调用plus1()函数以后,打印变量x的值,发现结果是2,但是我们并没有对x进行显示的重新赋值,原因调用plus1函数时,将变量c的地址作为参数进行传递,plus1直接根据地址取出初始值,执行加1的运算,然后更新内存中的值为2,不必使用变量x就可以修改x变量的值。

1.3 &运算符与*运算符的关系

&运算符与*运算符互为逆运算,下面的表达式,是成立的。

int i = 5;if (i == *(&i)) // 正确

2,指针变量的初始化

2.1 指针变量的大坑

声明指针变量之后,编译器会为指针变量本身分配一个内存空间,这个内存空间可能还保存着历史数据。

也就是说,这个指针变量可能指向一个随机的地址。

如果此时就去读写这个地址对应的内存,可能出现非常严重的后果,必然这个地址指向的是账户余额,有可能导致账户虚增或者虚减。

int* p;
*p = 1; // 错误

上述代码是必须避免的,因为指针p指向的内存单元是随机的。

2.2 指针变量初始化

正确写法是声明指针变量声明,立即指向一个明确的地址,这就是指针变量的初始化,初始化之后再进行读写。

int* p;
int i;p = &i;
*p = 13;

上面示例中,p是指针变量,声明这个变量后,p会指向一个随机的内存地址。

这时要将它指向一个已经分配好的内存地址,上例就是再声明一个整数变量i,编译器会为i分配内存地址,然后让p指向i的内存地址(p = &i;)。

完成初始化之后,就可以对p指向的内存地址进行赋值了(*p = 13;)。

2.3 指针变量初始化最佳实践

强烈推荐,声明指针变量的同时,将指针变量的值设为NULL。

int* p = NULL;

NULL在 C 语言中是一个常量,表示地址为0的内存空间,这个地址是无法使用的,读写该地址会报错。

这样即使之后我们忘记了把指针变量p指向预期的内存地址,在程序运行过程中会报错,而不是以可怕的、随机的方式运行。

三,指针的运算

我们现在知道了,指针虽然代表的是内存地址,但其本质上是一个无符号整数。

C语言允许指针参与运算,但是指针的运算规则和整数的运算规则是相差很大的。

1,指针与整数值的加减运算

指针与整数值的运算,表示指针的移动。

short* j;
j = (short*)0x1234;
j = j + 1; // 0x1236

上面示例中,j是一个指针,指向内存地址0x1234

由于0x1234本身是整数类型(int),跟j的类型(short*)并不兼容,所以强制使用类型投射,将0x1234转成short*。

表明上看,j + 1应该等于0x1235,但正确答案是0x1236。

原因是j + 1表示指针向内存地址的高位移动一个单位,而一个单位的short类型占据两个字节的宽度,所以相当于向高位移动两个字节。同样的,j - 1得到的结果是0x1232。

指针移动的单位,与指针指向的数据类型有关。数据类型占据多少个字节,每单位就移动多少个字节。

2,指针与指针的加法运算

指针只能与整数值进行加减运算,两个指针进行加法是非法的。

unsigned short* j;
unsigned short* k;
x = j + k; // 非法

上面示例是两个指针相加,这是非法的。

3,指针与指针的减法

相同类型的指针允许进行减法运算,返回它们之间的距离,即相隔多少个数据单位。

高位地址减去低位地址,返回的是正值;低位地址减去高位地址,返回的是负值。

这时,减法返回的值属于ptrdiff_t类型,这是一个带符号的整数类型别名,具体类型根据系统不同而不同。这个类型的原型定义在头文件stddef.h里面。

short* j1;
short* j2;j1 = (short*)0x1234;
j2 = (short*)0x1236;ptrdiff_t dist = j2 - j1;
printf("%td\n", dist); // 1

上面示例中,j1和j2是两个指向 short 类型的指针,变量dist是它们之间的距离,类型为ptrdiff_t,值为1,因为相差2个字节正好存放一个 short 类型的值。

4,指针与指针的比较运算

指针之间的比较运算,比较的是各自的内存地址哪一个更大,返回值是整数1(true)或0(false)。

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

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

相关文章

【Arthas案例】应用包含两个相同全限定类名StaticLoggerBinder,引起log4j.Level类找不到异常

3分钟内解决问题 两个不同的GAV依赖冲突,包含相同全限定类名,引起ClassNotFoundException Maven依赖的三坐标体系GAV(G-groupId,A-artifactId,V-version) 【案例1】某应用依赖两个GAV不同的jar,但包含两个相同全限定类…

OpenCv形态学(一)

目录 形态学转换 结构元素 腐蚀 膨胀 开运算 闭运算 形态学梯度 顶帽 黑帽 图像轮廓 查找轮廓 绘制轮廓 形态学转换 形态变换是一些基于图像形状的简单操作。通常在二值图像上执行。它需要两个输入,一个是我们的原始图像,第二个是决定操作性…

6月21日(周五)AH股总结:沪指失守3000点,恒生科技指数跌近2%,多只沪深300ETF午后量能显著放大

内容提要 沪指全天围绕3000点关口来回拉锯,收盘跌破3000点。白酒及光刻机概念集体走低,中芯国际港股跌超2%。CRO医药概念及水利股逆势走强。 A股低开低走 沪指全天围绕3000点关口来回拉锯,收盘跌破3000点,跌0.24%。深成指跌0.04…

ubuntu 20.04 访问csdn报错 Secure connection failed 解决

问题原因: 我一边更新源 sudo apt update & apt upgrade一边在看csdn,估计是这个导致的. 所以我直接把华为源换成了阿里源。 sudo apt update & apt upgrade再更新一次,解决。

探索语言模型的智能飞跃:预训练损失与突现能力的新视角

在人工智能的辉煌编年史中,语言模型(LMs)的崛起标志着自然语言处理领域的一个巨大飞跃。随着技术的进步,这些模型不仅在规模上日益庞大,更在性能上不断刷新着人们的认知边界。它们在问答、翻译、文本摘要等任务上展现出…

SQlyog连接到主机时报错:错误号码2058Plugin sha256 password could not be loaded

1.问题重述 MySQL版本:8.4.0 SQlyog连接到主机时报错:错误号码2058Plugin sha256 password could not be loaded,如下图 经过查阅资料得知出现这个问题是因为 mysl8之前的加密规则是 mysql_native_password ,而在mysql8之后&…

Vue DevTools

介绍 什么是 Vue DevTools? Vue DevTools 是一款旨在增强 Vue 开发者体验的工具,它是一款功能强大且用途广泛的工具,可以在使用 Vue 应用程序时显着提高您的生产力和调试能力。它的实时编辑、时间旅行调试和全面检查功能使其成为任何Vue.js开…

帕金森患者饮食指南:科学调养,呵护健康

🌼在医学的广阔领域中,帕金森病作为一种慢性神经系统疾病,除了需要专业的医疗治疗外,日常饮食的调养也显得尤为重要。 今天,就为大家带来一份专为帕金森患者打造的饮食建议,希望能为大家的健康调养提供一些…

数据库讲解---(数据库保护)【上】

目录 一.事务 1.1事务的概念【重要】 1.2事务的特性【重要】 1.2.1原子性(Atomicity) 1.2.2一致性(Consistency) 1.2.3隔离性(Isolation) 1.2.4持久性(Durability) 二.数据库恢复 2.1数据库系统的故障 2.1.1事务内部故障 2.1.2系统故障 2.1.3介质故障 2.1.4计算机…

Linux操作系统篇:多线程

一. Linux中线程是怎么理解的 1.1 线程概念 在Linux中,线程是在进程“内部”执行的,线程是处于进程的进程地址空间中运行,线程用到的资源都是进程的资源,线程是执行进程的一部分代码,线程是最小的执行流&am…

Android记录3--ExpandableListView使用+获取SIM卡状态信息

布局文件&#xff1a; /SIM_Card_Demo/res/layout/inbox.xml <LinearLayout xmlns:android“http://schemas.android.com/apk/res/android” xmlns:tools“http://schemas.android.com/tools” android:layout_width“match_parent” android:layout_height“match_par…

【代码】python实现一个BP神经网络-原理讲解与代码展示

​ 本文来自《老饼讲解-BP神经网络》https://www.bbbdata.com/ 目录 一、BP神经网络原理回顾1.1 BP神经网络的结构简单回顾1.2.BP神经网络的训练算法流程 二、python实现BP神经网络代码2.1.数据介绍2.2.pytorch实现BP神经网络代码 在python中要如何使用代码实现一个BP神经网络呢…

使用Rsbuild构建基于Vue3+Vant4开发h5应用

目录 一、介绍 1.1 Vant介绍 1.2 Rsbuild介绍 1.3 Vue介绍 二、构建应用 1.第一步 2.第二步 3.第三步 4.第四步 5.第五步 6.在项目中使用 Vant4 组件 7.移动端适配Rem 8. 执行 cnpm run dev 启动项目 一、介绍 1.1 Vant介绍 Vant 是一个轻量、可定制的移动端组…

单机小游戏好上架的应用市场有哪些?

&#x1f3c6;本文收录于「Bug调优」专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收藏&&…

Vue3中的常见组件通信(超详细版)

Vue3中的常见组件通信 概述 ​ 在vue3中常见的组件通信有props、mitt、v-model、 r e f s 、 refs、 refs、parent、provide、inject、pinia、slot等。不同的组件关系用不同的传递方式。常见的撘配形式如下表所示。 组件关系传递方式父传子1. props2. v-model3. $refs4. 默认…

Mac电脑FTP客户端推荐:Transmit 5 for Mac 中文版

Transmit 5是一款专为macOS平台设计的功能强大的FTP&#xff08;文件传输协议&#xff09;客户端软件。Transmit 5凭借其强大的功能、直观易用的界面和高效的性能&#xff0c;成为需要频繁进行文件传输和管理的个人用户和专业用户的理想选择。无论是对于新手还是经验丰富的用户…

Starlink全系卫星详细介绍,波段频谱、激光星间链路技术、数据传输速率等等

Starlink全系卫星详细介绍&#xff0c;波段频谱、激光星间链路技术、数据传输速率等等。 Starlink是SpaceX公司开发的一个低轨道&#xff08;LEO&#xff09;卫星网络系统&#xff0c;旨在为全球用户提供高速宽带互联网服务。截至2024年6月&#xff0c;Starlink已经发射并运行…

终于找到了免费的云服务器

今天朋友推荐了一个免费的云服务器&#xff1a;“阿贝云” 我最喜欢的是它的"免费虚拟主机"“免费云服务器”&#xff0c;省了我好多钱&#xff0c;我的使用感受是用起来经济实惠省心&#xff0c;不要钱的东西谁不喜欢呢&#xff0c;对于普通开发者来说&#xff0c;…

长尾式差分放大电路调零

长尾式放大电路用了两个参数相同的三极管&#xff0c;但实际上并没有完全相同的三极管&#xff0c;所以为了提高差分放大电路的对称性(一边电流增加多少&#xff0c;另一边电流减小多少&#xff0c;即能在电阻Re上产生的压降不变(后面做虚地处理))&#xff0c;在下图中加入可调…

【Linux 杂记】TOP命令

top命令用于动态显示系统中正在运行的进程的详细信息&#xff0c;以及系统的整体资源使用情况。以下是其主要输出解释&#xff1a; Header 表头信息&#xff1a; top&#xff1a;当前时间和运行时间。Tasks&#xff1a;进程统计信息&#xff0c;如总进程数、运行中、睡眠中等。…