【C语言】指针篇-初识指针(1/5)

news2024/12/23 11:59:05

Alt

🌈个人主页:是店小二呀
🌈C语言笔记专栏:C语言笔记
🌈C++笔记专栏: C++笔记

🌈喜欢的诗句:无人扶我青云志 我自踏雪至山巅
请添加图片描述

文章目录

    • **内存和地址(知识铺垫(了解即可))**
    • 如何理解编址
    • **指针变量**
    • **指针的初始化和赋值运算**
    • **指针变量的大小**
    • **指针变量类型的意义**
    • **指针+-整数**
    • const修饰指针
    • const修饰变量
    • const修饰指针变量
    • **指针运算**
    • **野指针**
    • **assert断言(assert.h头文件定义了宏assert())**
    • **传值调用和传址调用**


请添加图片描述

内存和地址(知识铺垫(了解即可))

内存(Memory)是计算机的重要部件,也称内存储器和[主存储器]它用于暂时存放CPU中的运算数据,以及与硬盘等[外部存储器]交换的数据。

当CPU(中央处理器)在处理数据的时,需要的数据是在内存中读取的,处理后的数据也会放回内存中。

问题:那么内存空间如何高效的管理的呢?

  • 是将内存划分为一个个的内存单元,每个内存单元的大小取一个字节(一个字节等于八个比特位,比特bit是二级制位(Binary digit)的简称,一个二进制包含的信息量成为一比特bit。)
  • 每个内存单元都有一个编号(相当于宿舍房间的门牌号),有这个内存单元的编号,CPU就可以通过这个编号快速找到一个内存空间
  • 在计算机中我们把内存单元的编号也称为地址。C语言给地址起了个新名字位指针,这里解释了计算机中内存是按照字节编址的,也是每个字节都有唯一的地址,而对于比特是没有地址
  • 可以理解为:内存单元的编号==地址 ==指针;

请添加图片描述

如何理解编址

CPU访问内存中的某个字节空间,必须知道这个字节空间在内存的什么位置,因为内存中有很多字节,所以需要给内存进行编制,计算机中的编址不是把每个字节的地址都记录下,而是通过硬件设计完成

钢琴、吉他上面没有写上“都瑞咪发嗦啦”这样 的信息,但演奏者照样能够准确找到每⼀个琴弦的每⼀个位置,这是为何?因为制造商已经在乐器硬件层面上设计好了,并且所有的演奏者都知道。本质是⼀种约定出来的共识!

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

请添加图片描述

这里只关注一组线:地址总线

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

地址信息被下达给内存,在内存上,就可以找到地址对应的数据,将数据在通过数据总线传入CPU内寄存器中


##正文

指针变量

了解内存和地址的关系,可知创建变量需要向内存申请一定大小的空间

指针变量时用于存放其他变量的地址(其他变量在内存中存储的位置),简称指针。指针本身是一种变量,需要占用一定大小的空间的,用来存放指针值(指针变量本身的地址)。

指针定义说明的一般形式:

类型说明符(void*指针变量名(name);
*表示pa是一个指针变量
类型说明符表示指针变量指向什么类型的对象

比如:

int p=1;char q='a';
int *pa=&p;说明指针变量pa是指向int类型变量的指针
char *qa=&q;说明指针变量qa是指向char类型变量的指针

注意:

  • 指针变量值表达的是某个数据对象的地址,只允许取正的整数值的
  • 但是他不等同于整形类型变量,如果指针变量取0值,即为NULL(空),则表示指针指向对象不存在,为空指针

指针的初始化和赋值运算

```类型说明符 指针变量名=初始化地址值``

注意:

  • 初始化中也是将初始地址赋值给指针变量
  • 在赋值语句中,变量的地址也只能赋值给指针变量,这种赋值运算操作限制在同类之间

## 指针运算符(& 、*)

取地址操作符 &:返回存放其他变量的内存地址(只限于一个具体的变量或数值元素,不可用于表达式)

int p=1;
int *pa=&p;//这里pa存放p的地址

*解引用操作符 :返回地址(指针标量指向的地址)中的变量值

int p=1;
int *pa=&p;//这里pa存放p的地址
int ret=*pa//ret==1

这里样对于变量的修改,多了一种途径,写代码就会更加灵活


指针变量的大小

通过前面“内存和地址”,32位机器有32根地址总线,每根地址线出来的电信号转换成数字信号后(1或0),将32根地址线产生的二级制序列当做一个地址,那么一个地址需要32个bit位,也是4个字节存储。那么在64位机器,有64根地址总线,一个的地址就是64个二级制位组成的二级制序列,那么一个需要地址需要64个bit位,也是8个字节存储。

小总结:

指针变量是用来存放地址的,那么在不同机器下,地址的大小也会影响指针变量的大小

  1. 32位平台下地址是32个bit位,指针变量大小是4个字节
  2. 64位平台下地址是64位bit位,指针变量大小是8个字节
  3. 指针变量的大小和类型是无关的,只要指针类型的变量,在相同的平台下,大小都是相同的

指针变量类型的意义

既然指针变量的大小与指向的类型无关,那么为什么要区分各种指针类型呢?

那当然是因为指针类型在其他方面有特殊的意义

#include <stdio.h>

int main()
{
    int n=0x11223344;
    int *p1=&n;
    *p=0;
    
    int m=0x11223344;
    char *p2=(char *)&m;//原本&m 应该由int *类型存储
    *pc=0;
    return 0;
}
这里涉及到了大小端的知识,理解指针类型对解引用的影响

如图可得的,n的四个字节全部改为0,而m只是第一个字节改为0

结论:指针的类型决定,对指针解引用能修改几个字节的权利

拿上面的例子:

  1. n是int 类型(占四个字节,其中两个数字算一个字节),int *指针类型指向n的地址,*当解引用的时候,int 指针只能访问四个字节

  2. m是char 类型(占一个字节,其中两个数字算一个字节),char *指针类型指向m的地址,*当解引用的时候,char 指针只能访问一个字节(修改44)


指针±整数

代码:

#include <stdio.h>
int main()
{
int n = 10;
char *pc = (char*)&n;
int *pi = &n;
printf("%p\n", &n);
    
printf("%p\n", pc);
printf("%p\n", pc+1);
    
printf("%p\n", pi);
printf("%p\n", pi+1);
return 0;
}

请添加图片描述

结论:指针类型决定了指针向前或者向后走一步有多大(距离)。char *类型的指针变量+1跳过1个字节,int *类型的指针变量+1跳过了4个


const修饰指针

const修饰变量

如果不希望这个变量被修改,可以使用const进行限制。

int main()
{
   const int m=0;
    m=1;//ok?
}

上边的代码:使用const修饰(语法上加了限制),只要对n变量进行修改,就不符合语法规则,就会报错,对于const修饰后的变量是不能进行直接修改的.

通过上面刚学的一种修改变量的方法,通过n的地址取修改n值(打破语法规则)

int main()
{
    const int m=0;
    int *p=&m;
    *p=20;
    return 0;
}

这样子n变量还能被修改,const使用的就没有多大意义,这样子不是自己打自己脸吗?那么有什么办法p拿到了n的地址也不能修改n值(对指针下手)。

const修饰指针变量

int main()
{
    int m=0;
    int n=1;
  第一种:
    const int *pa=&m;
    pa=&n;
   *pa=10;//右边为不可修改值
  第二种:  
    int * const pa=&m;
    pa=&n;//左边为不可修改值
   *pa=10;
}

总结:当const修饰指针变量的时候

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

指针运算

指针的基本运算有三种

1.指针±整数(在连续存放的数据,只要知道第一个元素的地址,就可以知道后面所有的地址),这里整数也称为偏移量

数组
  int main()
{
    int nums[]={1,2,3,4,5};
    int *p=nums;//首元素的地址
    printf("%d",*(p+2));//那么p的偏移量为+2,打印结果是3
    return 0;
}

2.指针-指针

int main() 
{
    char *p="abcd";//不要忘记还有'\0'
    char *s=p;
    while(*p!='\0')
    {
        p++;
    }
    int ret=p-s;//4
     printf("%d",ret);   
    return 0;
}

结论:指针减指针表示指向两个指针之间的元素个数,求首元素到某个元素之间相错个数(在连续存储的情况下)

3.指针的关系运算

int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = &arr[0];
int i = 0;
int sz = sizeof(arr)/sizeof(arr[0]);
while(p<arr+sz) //指针的⼤⼩⽐较
{
printf("%d ", *p);
p++;
}
return 0;

野指针

野指针;指针指向位置是不可知的(具有不确定因素)

下列有几种野指针的成因

1.指针未初始化int *p;

2.指针越界访问(数组越界访问)

int nums[10]={0};
int *p=nums;
for(int i=0;i<11;i<++)
{
    *(p++)=i;
}

3.指针指向的空间释放

int* test()
{
    int m=0;
    return &m
}
int mian()
{
    int *p=test();
    printf("%d\n",*p);
    return 0;
}
m是一个被销毁的局部变量,这里指针指向m所在的位置空间是不明确的,原本属于m的空间,可能被给了其他变量占用,这样子就导致程序可能不能达到预期效果

规避野指针

1.确定指针指向一片有效的空间,如果指针目前没有指向,可以为指针赋值为NULL,NULL是一个定义的标识符常量,值为0,0也是地址,这个地址是无法使用的

2.当指针变量不再使用,设置为NULL,指针使用之前检查有效性(判断语句或者断言)

3.规定:只要指针为NULL就不去访问,但是给野指针赋值为NULL,将野指针暂时管理起来,还是存在危险的


assert断言(assert.h头文件定义了宏assert())

作用:当符合程序符合指定条件,没有啥影响,如何不符合条件,就会报错终止运行。而这个宏常称为断言。

具体使用细节:

#inlclude <assert.h>
int p=0
assert(p!=0);
  1. assert()宏接受一个表达式作为参数。如果表达式为真(返回值为非零),没有啥影响,程序继续执行。
  2. 如果表达式为假(返回值为零),assert()就会报错,在标准报错流中stderr中写入一条错误信息(显示没有通过的表达式、表达式文件名和行号)

好处:

  • 自动标识文件和出问题的行号
  • 存在一种无需更改代码就能开启或关闭assert()的机制,就是在#include <assert.h>语句的前面,定义一个宏NDEBUG
#define NDEBUG
#include <stdio.h>

对于宏NEDBUG使用时,编译器就会禁用文件中assert()语句。如果程序出现问题,可以注释掉,就可以重新启动assert()语句

不足:assert()的引入了额外的检查,增加了程序的运行时间

  • debugrelease版本下,一般debug调试中使用,在release(发布版本)选择禁用assert,提高程序效率。
  • 在VS这样的集成开发环境中,release版本中,直接优化掉了。

总结:debug版本有利于程序员排查问题,release版本不行用户使用程序的效率


传值调用和传址调用

在函数章节,提到形参是实参的一份临时拷贝,形参的改变不会影响到实参

#include <stdio.h>
void Swap(int x,int y)
{
    int tmp=x;
    x=y;
    y=tmp;
}
int main()
{
    int a=10;int b=20;
	Swap(a,b);
}

请添加图片描述

这里x和y的值等于a和b的值,但是各属于独立的空间,那么x和y值交换,不会影响到实参a和b值的交换

对于这种将变量的数值传递给函数,调用方法:传值调用

对此可以使用指针

#include <stdio.h>
void Swap(int *x,int *y)
{
    int tmp=*x;
    *x=*y;
    *y=tmp;
}
int main()
{
    int a=10;int b=20;
	Swap(&a,&b);
}

请添加图片描述

这里在main函数中将a和b的地址传递给了Swap函数,Swap函数通过地址间接的操作main函数中的a和b

对于这种将变量的地址传递给函数,调用方法:传址调用


请添加图片描述

谢谢大家的观看,这里是个人笔记,希望对你学习C有帮助。

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

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

相关文章

故障诊断 | 基于LSTM的滚动轴承故障诊断

效果 概述 基于LSTM(长短期记忆网络)的滚动轴承故障诊断是一种利用深度学习技术来预测滚动轴承是否存在故障的方法。下面是一个基本的滚动轴承故障诊断的流程: 数据收集:首先,需要收集与滚动轴承相关的振动信号数据。这些数据可以通过传感器或振动监测系统获取。收集的数…

OSI七层网络模型 —— 筑梦之路

在信息技术领域&#xff0c;OSI七层模型是一个经典的网络通信框架&#xff0c;它将网络通信分为七个层次&#xff0c;每一层都有其独特的功能和作用。为了帮助记忆这七个层次&#xff0c;有一个巧妙的方法&#xff1a;将每个层次的英文单词首字母组合起来&#xff0c;形成了一句…

c# .net 香橙派 Orangepi GPIO高低电平、上升沿触发\下降沿触发 监听回调方法

c# .net 香橙派GPIO高低电平、上升沿触发\下降沿触发 监听回调方法 通过gpio readall 查看 gpio编码 这里用orangepi zero3 ,gpio= 70为例 当gpio 70 输入高电平时,触发回调 c# .net 代码 方法1: Nuget 包 System.Device.Gpio ,微软官方库对香橙派支持越来越好了,用得…

构建第一个ArkTS应用之@BuilderParam装饰器:引用@Builder函数

当开发者创建了自定义组件&#xff0c;并想对该组件添加特定功能时&#xff0c;例如在自定义组件中添加一个点击跳转操作。若直接在组件内嵌入事件方法&#xff0c;将会导致所有引入该自定义组件的地方均增加了该功能。为解决此问题&#xff0c;ArkUI引入了BuilderParam装饰器&…

2024年网络安全行业全景图 | 亚信安全实力占据62领域

近日&#xff0c;安全牛《中国网络安全行业全景图》&#xff08;第十一版&#xff09;正式发布。 本次发布的全景图&#xff0c;包含16个一级安全分类&#xff0c;108个二级细分领域&#xff0c;共收录454家国内安全厂商&#xff0c;细分领域共收录2413项。亚信安全凭借在云安全…

① 学习PID--先认识有什么电机和驱动

我们在学习和使用PID的时候&#xff0c;可能会有很多电机的选择。然而不同的电机使用的PID参数是不太一样的。所以我们需要认识电机和驱动器。 1 电机有什么类型 1.1 电机的简介 电机是一种可以在电能和机械能的之间相互转换的设备&#xff0c;其中发电机是将机械能转换为电能…

虚拟机下CentOS7开启SSH连接

虚拟机下CentOS7开启SSH连接 自己在VMware中装了CentOS 6.3&#xff0c;然后主机&#xff08;或者说xshell&#xff09;与里面的虚拟机连不通&#xff0c;刚学习&#xff0c;一头雾水&#xff0c;查了半天&#xff0c;也不知道怎么弄。 在虚拟机&#xff08;Vmware Workstatio…

优思学院|2024年如何成为一名六西格玛黑带?

如果你总是觉得无论多么努力&#xff0c;职场上似乎难以有所突破&#xff0c;那么你应该知道&#xff0c;你并不是孤独的。 实际上&#xff0c;大量研究表明&#xff0c;高达90%的人对自己的工作感到不满&#xff0c;这意味着在你认识的每10人中&#xff0c;可能只有1人对其工…

小程序视频如何下载到电脑上

小程序视频如何下载到电脑上&#xff0c;很简单 1.利用Fiddler和Charles这些专业的抓包工具 2.利用录屏 3.利用专门抓取资源的工具(集成了抓取下载&#xff0c;而且对资源下载很友好) 工具我已经打包好了 下载高手链接&#xff1a;https://pan.baidu.com/s/1qJ81sNBzzzU0w…

烧结钕铁硼永磁体是如何生产的?

烧结钕铁硼永磁体是采用粉末冶金法生产的&#xff0c;从备料到成品发货一般要经过十几个工艺环节&#xff0c;在不同阶段还包括若干次检测分析。 整个生产过程是一个系统工程&#xff0c;环环相扣。一般我们将生产磁体毛坯的过程称为前道生产环节&#xff0c;将毛坯加工成最终…

Thingsboard PE 白标的使用

只有专业版支持白标功能。 使用 ThingsBoard Cloud 或安装您自己的平台实例。 一、介绍 ThingsBoard Web 界面提供了简便的操作,让您能够轻松配置您的公司或产品标识和配色方案,无需进行编码工作或重新启动服务。 系统管理员、租户和客户管理员可以根据需要自定义配色方案、…

计算机网络2——物理层2

文章目录 一、信道复用技术1、介绍2、频分复用、时分复用和统计时分复用3、波分复用4、码分复用 二、数字传输系统三、宽带接入技术1、介绍2、ADSL 技术3、光纤同轴混合网(HFC网)4、FTTx技术 一、信道复用技术 1、介绍 复用(multiplexing)是通信技术中的基本概念。计算机网络…

抖音小店开店必做的几个基础搭建,新手注意,不做店铺没流量

大家好&#xff0c;我是电商笨笨熊 新手入手做抖音小店一定不要着急选品&#xff0c;先去做店铺基础搭建&#xff0c;吸引更多流量进入店铺&#xff0c;提升店铺曝光度。 店铺搭建都没做好&#xff0c;流量来了你也接不住&#xff0c;所以今天我们就来聊聊哪些必做的搭建内容…

常见 Java 代码缺陷及规避方式

阿里妹导读 在日常开发过程中&#xff0c;我们会碰到各种各样的代码缺陷或者 Bug&#xff0c;比如 NPE、 线程安全问题、异常处理等。这篇文章总结了一些常见的问题及应对方案&#xff0c;希望能帮助到大家。 问题列表 空指针异常 NPE 或许是编程语言中最常见的问题&#xff0…

rv1103/buildroot系统中添加包如v4l2

v4l2: rv1103给出的包中已经有v4l,只需要在menuconfig中打开编译选项&#xff0c;步骤如下&#xff1a; 在luckfox的github网站中下载的源代码在~/linux/luckfox/luckfox-pico-main中目录结构如下&#xff1a; 打开编译选项 cd ./sysdrv/source/buildroot/buildroot-2023.02.…

浅谈普通人成为程序员的几个原因

成为程序员的原因可以因人而异&#xff0c;以下是一些普遍的原因&#xff1a; 兴趣和热情&#xff1a;很多人对计算机科学和编程非常感兴趣。他们喜欢探索如何使用代码来解决问题&#xff0c;并且享受编程过程中的逻辑思考和创造性。 高薪和就业机会&#xff1a;现代社会对技术…

GEE错误——Landsat影像加载后显示白色或黑色如何解决?

错误展示 简介 在GEE中,如果加载的Landsat影像显示为白色或黑色,可能的原因: 1. 数据范围问题:Landsat影像通常以16位有符号整数的格式存储,但在加载到GEE时,默认使用了0到1的归一化数据范围。这可能导致影像显示不正确。解决方法是通过将图像转换为正确的数据范围来修…

ubuntu下的串口调试工具cutecom

系统&#xff1a;ubuntu20.04 &#xff08;1&#xff09;接线 使用 rs485&#xff1c;-----> rs232 转接口&#xff08; 设备直接出来的是rs485&#xff09;&#xff0c;电脑主机接入一根 rs232&#xff1c;-----> USB口 连接线&#xff0c;ubuntu系统下打开 termin…

2024年全球可穿戴腕带设备市场将增长 7%,蓝牙BLE助力其发展

根据市场调查机构 Canalys 今日发布的最新报告&#xff0c;2023 年&#xff0c;全球可穿戴腕带设备市场实现 1.4% 的温和增长&#xff0c;出货量达 1.85 亿台。该机构预测 2024 年&#xff0c;全球可穿戴腕带市场将增长 7%。 Canalys 对 2024 年可穿戴腕带市场持谨慎乐观的态…

身份证二要素API接口的作用

身份证二要素API接口又叫身份证实名认证、身份证二要素验证接口、姓名和身份证号核验接口&#xff0c;主要就是输入姓名和身份证号&#xff0c;通过官方权威核查&#xff0c;实时校验此二要素是否一致&#xff0c;同时返回生日、性别、籍贯等信息。那么这个接口有什么作用呢&am…