当前位置: 首页 > news >正文

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)。

相关文章:

  • Maven添加reactor依赖失败
  • DS:堆的应用——两种算法和TOP-K问题
  • 为什么print语句被Python3遗弃?
  • 玄机——第六章 流量特征分析-waf 上的截获的黑客攻击流量 wp
  • Java学习笔记(一)Java内容介绍、程序举例、DOS命令、Java跨平台特性的本质、课后练习
  • Lua 面向对象编程
  • 如何修改外接移动硬盘的区号
  • 【RabbitMQ】异步消息及Rabbitmq安装
  • ardupilot开发 --- Jetson Orin Nano 后篇
  • 阿里云 邮件系统DNS域名解析 搭配 postfix+dovecot 邮件服务器
  • 打印水仙花数
  • 数据挖掘与分析——数据预处理
  • IMU用于飞行坐姿校正
  • 【中文】PDF文档切分\切片\拆分最优方案-数据预处理阶段,为后续导入RAG向量数据库和ES数据库实现双路召回
  • 29.Netty进阶-黏包半包
  • Bytom交易说明(账户管理模式)
  • exports和module.exports
  • JavaScript 奇技淫巧
  • learning koa2.x
  • node-sass 安装卡在 node scripts/install.js 解决办法
  • Sass 快速入门教程
  • Storybook 5.0正式发布:有史以来变化最大的版本\n
  • vue-router的history模式发布配置
  • yii2权限控制rbac之rule详细讲解
  • 闭包,sync使用细节
  • 开源中国专访:Chameleon原理首发,其它跨多端统一框架都是假的?
  • 来,膜拜下android roadmap,强大的执行力
  • 排序算法之--选择排序
  • 前嗅ForeSpider中数据浏览界面介绍
  • 限制Java线程池运行线程以及等待线程数量的策略
  • 详解NodeJs流之一
  • Salesforce和SAP Netweaver里数据库表的元数据设计
  • ​HTTP与HTTPS:网络通信的安全卫士
  • ​Redis 实现计数器和限速器的
  • #if和#ifdef区别
  • #控制台大学课堂点名问题_课堂随机点名
  • #在 README.md 中生成项目目录结构
  • $$$$GB2312-80区位编码表$$$$
  • (vue)页面文件上传获取:action地址
  • (附源码)spring boot北京冬奥会志愿者报名系统 毕业设计 150947
  • (附源码)ssm经济信息门户网站 毕业设计 141634
  • (四)搭建容器云管理平台笔记—安装ETCD(不使用证书)
  • (原)记一次CentOS7 磁盘空间大小异常的解决过程
  • **《Linux/Unix系统编程手册》读书笔记24章**
  • . NET自动找可写目录
  • ./mysql.server: 没有那个文件或目录_Linux下安装MySQL出现“ls: /var/lib/mysql/*.pid: 没有那个文件或目录”...
  • .net core MVC 通过 Filters 过滤器拦截请求及响应内容
  • .NET Micro Framework 4.2 beta 源码探析
  • .Net Web项目创建比较不错的参考文章
  • .net6 core Worker Service项目,使用Exchange Web Services (EWS) 分页获取电子邮件收件箱列表,邮件信息字段
  • .NET微信公众号开发-2.0创建自定义菜单
  • .net专家(张羿专栏)
  • .set 数据导入matlab,设置变量导入选项 - MATLAB setvaropts - MathWorks 中国
  • @JsonFormat 和 @DateTimeFormat 的区别
  • [ Linux 长征路第二篇] 基本指令head,tail,date,cal,find,grep,zip,tar,bc,unname