C语言-指针及变量的概念与使用

1、指针的概念

计算机中所有的数据都必须放在内存中,不同类型的数据占用的字节数不一样,例如 int 占用 4 个字节,char 占 用 1 个字节。为了正确地访问这些数据,必须为每个字节都编上号码,就像门牌号、身份证号一样,每个字节的编号是唯一的,根据编号可以准确地找到某个字节。

下图是 4G 内存中每个字节的编号(以十六进制表示):

我们将内存中字节的编号称为地址(Address)或指针(Pointer)。地址从 0 开始依次增加,对于 32 位环境,程序能够使用的内存为 4GB,最小的地址为 0,最大的地址为 0XFFFFFFFF。

下面的代码演示了如何输出一个地址:

1. #include <stdio.h>
2.
3. int main(){
4. int a = 100;
5. char str[20] = "c.biancheng.net";
6. printf("%#X, %#X\\n", &a, str);
7. return 0;
8. }

运行结果: 0X28FF3C, 0X28FF10

%#X 表示以十六进制形式输出,并附带前缀 0X。a 是一个变量,用来存放整数,需要在前面加&来获得它的地址;str 本身就表示字符串的首地址,不需要加&。

C 语言中有一个控制符%p,专门用来以十六进制形式输出地址,不过 %p 的输出格式并不统一,有的编译器带 0x前缀,有的不带,所以此处我们并没有采用。

一切都是地址

C 语言用变量来存储数据,用函数来定义一段可以重复使用的代码,它们最终都要放到内存中才能供 CPU 使用。 数据和代码都以二进制的形式存储在内存中,计算机无法从格式上区分某块内存到底存储的是数据还是代码。当程序被加载到内存后,操作系统会给不同的内存块指定不同的权限,拥有读取和执行权限的内存块就是代码,而拥有读取和写入权限(也可能只有读取权限)的内存块就是数据。 CPU 只能通过地址来取得内存中的代码和数据,程序在执行过程中会告知 CPU 要执行的代码以及要读写的数据的地址。如果程序不小心出错,或者开发者有意为之,在 CPU 要写入数据时给它一个代码区域的地址,就会发生内存访问错误。这种内存访问错误会被硬件和操作系统拦截,强制程序崩溃,程序员没有挽救的机会。 CPU 访问内存时需要的是地址,而不是变量名和函数名!变量名和函数名只是地址的一种助记符,当源文件被编译和链接成可执行程序后,它们都会被替换成地址。编译和链接过程的一项重要任务就是找到这些名称所对应的地址。 假设变量 a、b、c 在内存中的地址分别是 0X1000、0X2000、0X3000,那么加法运算 c = a + b;将会被转换成类似下面的形式:

0X3000 = (0X1000) + (0X2000);

( )表示取值操作,整个表达式的意思是,取出地址 0X1000 和 0X2000 上的值,将它们相加,把相加的结果赋值给地址为 0X3000 的内存

变量名和函数名为我们提供了方便,让我们在编写代码的过程中可以使用易于阅读和理解的英文字符串,不用直接面对二进制地址,那场景简直让人崩溃。

需要注意的是,虽然变量名、函数名、字符串名和数组名在本质上是一样的,它们都是地址的助记符,但在编写代码的过程中,我们认为变量名表示的是数据本身,而函数名、字符串名和数组名表示的是代码块或数据块的首地址。

2、变量的定义

数据在内存中的地址也称为指针,如果一个变量存储了一份数据的指针,我们就称它为指针变量

在c语言中,允许用一个变量来存放指针,这种变量称为指针变量。指针变量的值就是某分数据的地址,这样的一份数据可以是数组,字符串,函数,也可以是另外的一个普通变量或者指针变量

现在假设有一个char类型的变量c,它存储了字符k,并占用了地址为0x11a的内存(地址通常用十六进制表示)另外有一个指针变量p,他的值为0x11a,正好等于变量c的地址,这种情况我们层p指向了c,或者说p是指向变量c的指针。

定义指针变量

定义指针变量与定义普通变量非常类似,不过要在变量名前加星号,格式为:

datatype *name;或者datatype *name = value;

*表示这是一个指针变量,datatype表示指针变量所指向的数据类型。例如:

int *p1;

P1是一个指向int类型数据的指针变量,至于P1酒精指向哪一份数据,应该由赋予它的值决定。再如:

int a = 100;
int *p_a = &a;

在定义指针变量P_a的同时对他进行初始化,并将变量a的地址赋予它,此时p_a就指向了a,值得注意的是,p_a需要的一个地址,a前面必须要加取地址符&,否则是不对的

和普通变量一样,指针变量也可以被多次写入,只要你想,随时都能改变指针变量的值,请看下面的代码

和普通变量一样,指针变量也可以被多次写入,只要你想,随时都能改变指针变量的值,请看下面的代码:

1. //定义普通变量
2. float a = 99.5, b = 10.6;
3. char c = '@', d = '#';
4. //定义指针变量
5. float *p1 = &a;
6. char *p2 = &c;
7. //修改指针变量的值
8. p1 = &b;
9. p2 = &d;

是一个特殊符号,表明一个变量是指针变量,定义P1,P2时必须待.而给P1,P2赋值时,因为已经知道了它是一个指针变量,就没必要多此一举带上*,后便可以像使用普通变量一样来使用指针变量,也就是说定义指针时必须带上*,给指针变量赋值时不能带*。

假设变量 a、b、c、d 的地址分别为 0X1000、0X1004、0X2000、0X2004,下面的示意图很好地反映了 p1、p2 指向的变化:

需要强调的是,P1,P2的类型分别是float和char,而不是float和char,他们是完全不同的数据类型,读者要引起注意。

指针变量也可以连续定义,例如:

int *a,*b,*c;

注意每个变量前面都要带*。如果写成下面的形式,那么只有 a 是指针变量,b、c 都是类型为 int 的普通变量:

int *a, b, c;

通过指针变量取得数据

指针变量存储了数据的地址,通过指针变量能够获得该地址上的数据,格式为:

*pointer;

这里的*称为指针运算符,用来取得某个地址上的数据,请看下面的例子:

1. #include <stdio.h>
2.
3. int main(){
4. int a = 15;
5. int *p = &a;
6. printf("%d, %d\\n", a, *p); //两种方式都可以输出a的值
7. return 0;
8. }

运行结果:

15.15

假设 a 的地址是 0X1000,p 指向 a 后,p 本身的值也会变为 0X1000,*p 表示获取地址 0X1000 上的数据,也即变量 a 的值。从运行结果看,*p 和 a 是等价的。

上节我们说过,CPU 读写数据必须要知道数据在内存中的地址,普通变量和指针变量都是地址的助记符,虽然通过*p 和 a 获取到的数据一样,但它们的运行过程稍有不同:a 只需要一次运算就能够取得数据,而 *p 要经过两次运算,多了一层“间接”。

假设变量 a、p 的地址分别为 0X1000、0XF0A0,它们的指向关系如下图所示:

程序被编译和链接后,a、p 被替换成相应的地址。使用 *p 的话,要先通过地址 0XF0A0 取得变量 p 本身的值,这个值是变量 a 的地址,然后再通过这个值取得变量 a 的数据,前后共有两次运算;而使用 a 的话,可以通过地址 0X1000 直接取得它的数据,只需要一步运算。

也就是说,使用指针是间接获取数据,使用变量名是直接获取数据,前者比后者的代价要高。

指针除了可以获取内存上的数据,也可以修改内存上的数据,例如:

1. #include <stdio.h>
2.
3. int main(){
4. int a = 15, b = 99, c = 222;
5. int *p = &a; //定义指针变量
6. *p = b; //通过指针变量修改内存上的数据
7. c = *p; //通过指针变量获取内存上的数据
8. printf("%d, %d, %d, %d\\n", a, b, c, *p);
9. return 0;
10. }

运行结果:

99;99;99;99

p 代表的是 a 中的数据,它等价于 a,可以将另外的一份数据赋值给它,也可以将它赋值给另外的一个变量。

**在不同的场景下有不同的作用:*可以用在指针变量的定义中,表明这是一个指针变量,以和普通变量区分开;使用指针变量时在前面加表示获取指针指向的数据,或者说表示的是指针指向的数据本身。

也就是说,定义指针变量时的和使用指针变量时的意义完全不同。以下面的语句为例:

1. int *p = &a;
2. *p = 100;

第 1 行代码中用来指明p是一个指针变量,第二行代码中用来获取指针指向的数据。

需要注意的是,给指针变量本身赋值时不能加*,修改上面的语句:

1. int *p;
2. p = &a;
3. *p = 100;

第二行代码中的p前面就不能加*

指针变量也可以出现在普通变量能出现的任何表达式中,例如:

1. int x, y, *px = &x, *py = &y;
2. y = *px + 5; //表示把x的内容加5并赋给y,*px+5相当于(*px)+5
3. y = ++*px; //px的内容加上1之后赋给y,++*px相当于++(*px)
4. y = *px++; //相当于y=(*px)++
5. py = px; //把一个指针的值赋给另一个指针

示例:通过指针交换两个变量的值

1. #include <stdio.h>
2.
3. int main(){
4. int a = 100, b = 999, temp;
5. int *pa = &a, *pb = &b;
6. printf("a=%d, b=%d\\n", a, b);
7. /*****开始交换*****/
8. temp = *pa; //将a的值先保存起来
9. *pa = *pb; //将b的值交给a
10. *pb = temp; //再将保存起来的a的值交给b
11. /*****结束交换*****/
12. printf("a=%d, b=%d\\n", a, b);
13. return 0;
14. }

运行结果: a=100, b=999 a=999, b=100

从运行结果可以看出。a,b的值已经发生了交换,需要注意的是临时变量temp,它的作用特别重要,因为执行*pa=*pb语句后,a的值会被b得知覆盖,如果不先将a的值保存起来以后就找不到了

关于*与&的谜题

假设有一个int类型的变量a,pa是指向它的指针,那么*&a和&*pa分别是什么意思?

&a可以理解为(&a),&a表示取变量a的地址(等价于pa),(&a)便是取这个地址上数据等价于pa),绕来绕去,又回到了原点,*&a仍然等价于a。

&*pa可以理解为&(*pa)*pa表示取得pa指向的数据(等价于a),&(*pa)表示数据的地址(等价于&a),所以&*pa等价于pa。

对于星号*的总结

在我们目前所学到的语法中,星号*主要有三种用途:

表示乘法:例如int a=3,b=5,c;c=a*b;这是最容易理解的

表示定义一个指针变量,以和普通变量区分开,例如int a=100;int *p=&a;

表示获取指针指向的数据,是一种简介操作,例如int a,b, **p=&a; *p=100; b=**p;

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

tauri开发中,使用node将png图片转成苹果的icns图标格式,解决tauri icon生成的mac图标过大问题

在tauri开发中&#xff0c;我们使用tauri icon生成的图标在windows上是正常的&#xff0c;但是在mac上就显示过大&#xff0c;也可以看tauri的issue&#xff1a;[v2]When using the Tauri Icon to generate icons, it is always larger than other icons in Mac tauri-apps/ta…

【Docker】Mac安装Docker Desktop导致磁盘剩余空间较少问题如何解决?

目录 一、背景描述 二、解决办法 三、清理效果 四、理论参考 解决方法 1. 清理未使用的 Docker 镜像、容器和卷 2. 查看 Docker 使用的磁盘空间 3. 调整 Docker 的存储位置 4. 增加磁盘空间 5. 调整 Docker Desktop 配置 6. 使用 Docker 清理工具&#xff08;例如 D…

微信小程序navigateTo:fail webview count limit exceed

theme: nico 你们好&#xff0c;我是金金金。 场景 uniapp编写微信小程序&#xff0c;使用uni.navigateTo跳转的过程中报错如下&#xff1a; 报错意思也非常明显了&#xff1a;errMsg":"navigateTo:fail webview 数量超出限制 排查 排查之前我先贴一下代码 代码非…

类与对象(2)---类的6个默认成员函数

1.类的6个默认成员函数 任何类在什么都不写时&#xff0c;编译器会自动生成以下6个默认成员函数。 默认成员函数&#xff1a;用户没有显式实现&#xff0c;编译器会生成的成员函数称为默认成员函数。 2.构造函数 2.1构造函数特性 构造函数的主要任务是初始化对象。 它有如下特…

[OpenGL]使用OpenGL实现透明效果

一、简介 本文介绍了如何使用OpenGL实现透明效果&#xff08;transparent&#xff09;&#xff0c;并在最后给出了全部的代码。 在实现透明效果时&#xff0c;使用OpenGL中的混合&#xff08;Blend&#xff09;功能&#xff0c;根据纹理贴图的 alpha 分量将各像素&#xff08;…

【实用技能】ASP.NET Core:在同一个 Razor 视图中使用文档编辑器和查看器

Essential Studio for ASP.NET Core UI控件库是构建应用程序所需的卓越套件&#xff0c;提供支持的 ASP.NET Core 工具包拥有超过 85 个组件&#xff0c;包含构建业务线应用程序所需的一切&#xff0c;包括数据网格、图表、甘特图、图表、电子表格、时间表、数据透视网格等流行…

Android从Drawable资源Id直接生成Bitmap,Kotlin

Android从Drawable资源Id直接生成Bitmap,Kotlin val t1 System.currentTimeMillis()val bmp getBmpFromDrawId(this, R.mipmap.ic_launcher_round)Log.d("fly", "1 ${bmp?.byteCount} h${bmp?.height} w${bmp?.width} cost time${System.currentTimeMillis…

【JavaScript】LeetCode:96-100

文章目录 96 单词拆分97 最长递增子序列98 乘积最大子数组99 分割等和子集100 最长有效括号 96 单词拆分 动态规划完全背包&#xff1a;背包-字符串s&#xff0c;物品-wordDict中的单词&#xff0c;可使用多次。问题转换&#xff1a;s能否被wordDict中的单词组成。dp[i]&#x…

【扩散——BFS】

题目 代码 #include <bits/stdc.h> using namespace std; const int t 2020, off 2020; #define x first #define y second typedef pair<int, int> PII; int dx[] {0, 0, 1, -1}, dy[] {-1, 1, 0, 0}; int dist[6080][6080]; // 0映射到2020&#xff0c;2020…

柯桥生活英语口语学习“面坨了”英语怎么表达?

“面坨了”英语怎么表达&#xff1f; 要想搞清楚这个表达&#xff0c;首先&#xff0c;我们要搞明白“坨”是啥意思&#xff1f; 所谓“坨”就是指&#xff0c;面条在汤里泡太久&#xff0c;从而变涨&#xff0c;黏糊凝固在一起的状态。 有一个词汇&#xff0c;很适合用来表达这…

IOT物联网低代码可视化大屏解决方案汇总

目录 参考来源云服务商阿里云物联网平台产品主页产品文档 开源项目DGIOT | 轻量级工业物联网开源平台项目特点项目地址开源许可 IoTGateway | 基于.NET6的跨平台工业物联网网关项目特点项目地址开源许可 IoTSharp | 基于.Net Core开源的物联网基础平台项目特点项目地址开源许可…

CSP-X2024山东小学组T2:消灭怪兽

题目链接 题目名称 题目描述 怪兽入侵了地球&#xff01; 为了抵抗入侵&#xff0c;人类设计出了按顺序排列好的 n n n 件武器&#xff0c;其中第 i i i 件武器的攻击力为 a i a_i ai​&#xff0c;可以造成 a i a_i ai​ 的伤害。 武器已经排列好了&#xff0c;因此不…

信息收集—JS框架识别泄露提取API接口泄露FUZZ爬虫插件项目

前言 免杀结束了&#xff0c;我们开个新的篇章——信息收集。为什么我一开始先写信息收集的文章呢&#xff0c;是因为现在我才发现我的信息收集能力其实有点弱的&#xff0c;所以呢开始知不足&#xff0c;而后进。 什么是JS JS就是JavaScript的简称&#xff0c;它和Java是没…

性能调优专题(9)之从JDK源码级别解析JVM类加载机制

一、类加载运行全过程 当我们用java命令运行某个类的main函数启动程序时&#xff0c;首先需要通过类加载器把主类加载到JVM。 package com.tuling.jvm;public class Math {public static final int initData 666;public static User user new User();public int compute() {…

Gin 框架入门(GO)-1

解决安装包失败问题&#xff08;*&#xff09; go env -w GO111MODULEon go env -w GOPROXYhttps://goproxy.cn,direct 1 介绍 Gin 是一个 Go (Golang) 编写的轻量级 http web 框架&#xff0c;运行速度非常快&#xff0c;Gin 最擅长的就是 Api 接口的高并发。 2 Gin 环境搭建…

Python如何从HTML提取img标签下的src属性

目录 前提准备步骤1. 解析HTML内容2. 查找所有的img标签3. 提取src属性 完整代码 前提准备 在处理网页数据时&#xff0c;我们经常需要从HTML中提取特定的信息&#xff0c;比如图片的URL。 这通常通过获取img标签的src属性来实现。 在开始之前&#xff0c;你需要确保已经安装…

web——upload-labs——第五关——大小写绕过绕过

先上传一个 先尝试直接上传一个普通的一句话木马 不行 可以看到&#xff0c;.htaccess文件也被过滤了&#xff0c;我们来查看一下源码 第五关的源码没有把字符强制转换为小写的语句&#xff1a; $file_ext strtolower($file_ext); //转换为小写 直接通过Burpsuite抓包修改文…

C#/WinForm拖拽文件上传

一、首先创建一个上传文件的类&#xff0c;继承Control类&#xff0c;如下&#xff1a; public class UploadControl : Control{private Image _image;public UploadControl(){this.SetStyle(ControlStyles.UserPaint | //控件自行绘制&#xff0c;而不使用操作系统的绘制Cont…

oracle查询字段类型长度等字段信息

1.查询oracle数据库的字符集 SELECT * FROM NLS_DATABASE_PARAMETERS WHERE PARAMETER NLS_CHARACTERSET; 2.查询字段长度类型 SELECT * FROM user_tab_columns WHERE table_name user AND COLUMN_NAME SNAME 请确保将user替换为您想要查询的表名。sname为字段名 这里的字…

大模型基础BERT——Transformers的双向编码器表示

大模型基础BERT——Transformers的双向编码器表示 整体概况 BERT&#xff1a;用于语言理解的深度双向Transform的预训练 论文题目&#xff1a;BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding Bidirectional Encoder Representations from…