【C语言】—— 指针二 : 初识指针(下)

【C语言】——函数栈帧

一、 c o n s t const const 修饰指针

1.1、 c o n s t const const 修饰变量

  
  当我们创建一个变量后,不想被修改,该怎么办呢?这时,我们就可以请出 c o n s t const const 来修饰变量
  

int main()
{int m = 0;m = 20;//m可以被修改const int n = 0;n = 20;//n不能被修改return 0;
}

  
运行结果:
在这里插入图片描述
如上图,若修改被 c o n s t const const 修饰的变量,则程序报错
  
  其实, n n n 本质上还是变量,只是被 c o n s t const const 修饰后,在语法上加了限制(此时的 n n n 称作常变量),只要我们在代码中对 n n n 进行修改,就不符合语法规则,程序就会报错。
  
  虽然通过正常手段无法修改 n n n 的值,但我们可以用间接手段:通过 n n n 的地址,来修改 n n n

#include<stdio.h>int main()
{const int n = 0;printf("n = %d\n", n);int* p = &n;*p = 20;printf("n = %d\n", n);return 0;
}

在这里插入图片描述

  但我们用 c o n s t const const 修饰变量,本质上是不希望变量被修改的,你现在通过指针把我给改了,是不是有点不讲武德。就好像我不希望你进我家,我把家门给锁,你现在翻窗进来,是不是有点不合适?
  
  所以我们应该让 p p p 拿到 n n n 的地址也修改不了 n n n ,那该怎么办呢?

  我们可以用 c o n s t const const 来修饰指针
  

1.2、 c o n s t const const 修饰指针

  

c o n s t const const 修饰指针变量的时候

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

注:当然, c o n s t const const 也可以在 ∗ * 两边都放的,但一般很少这么做。(你两边都不给我改,是不是太过分了)
  
通过代码来感受一下:
  
(1) c o n s t const const ∗ * 左边

#include<stdio.h>int main()
{int n = 0;int m = 10;const int* p = &n;p = &m;printf("%d\n", *p);return 0;
}

运行结果:
在这里插入图片描述

   c o n s t const const ∗ ∗ 左边 : 指针变量本身的内容可以改变

  

参考代码:

#include<stdio.h>int main()
{int n = 0;int m = 10;const int* p = &n;*p = 20;printf("%d\n", *p);return 0;
}

运行结果:

在这里插入图片描述
   c o n s t const const ∗ * 左边 : 指针所指向的内容不能通过指针来修改
  
(2)const 在 ∗ * 右边
  
参考代码:

#include<stdio.h>int main()
{int n = 0;int m = 10;int* const p = &n;*p = 20;printf("%d\n", *p);return 0;
}

运行结果:
在这里插入图片描述

   c o n s t const const ∗ * 右边 : 指针指向的内容可以通过指针来改变
  
参考代码:

#include<stdio.h>int main()
{int n = 0;int m = 10;int* const p = &n;p = &m;printf("%d\n", *p);return 0;
}

运行结果:
在这里插入图片描述

   c o n s t const const ∗ * 右边 : 指针变量本身不能被修改

  
  

二、野指针

  

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

  

2.1野指针的成因

(1)指针未初始化
int main()
{int* p;//局部变量指针未初始化,默认为随机值*p = 20;return 0;
}

注:局部变量指针不初始化,默认为随机值
  

(2)指针越界访问
#include<stdio.h>int mian()
{int arr[10] = { 0 };int* p = &arr[0];int i = 0;for (i = 0; i <= 11; i++){//当指针指向的范围超出数组arr的范围时,p就是野指针*p(p++) = i;}return 0;
}

注:当指针指向超出 a r r arr arr 数组的范围时, p p p 就是野指针
  

(3)指针指向的空间释放
#include<stdio.h>int* test()
{int n = 100;return &n;
}
int mian()
{int* p = test();printf("%d\n", *p);return 0;
}

注:在【C语言】——详解函数一文中,曾提到函数中形参只是实参的一份拷贝,变量 n n n 在出函数范围时已释放,这时的指针变量就是野指针。
  

2.2、如何规避野指针

(1)指针初始化
  • 明确知道指针指向哪里,就初始化一个明确的地址
  • 如果创建时,还不知道指针具体指向位置,则给指针赋值 N U L L NULL NULL

  
注: N U L L NULL NULL 是C语言中定义的一个标识符常量,本质是 00 也是地址,但该地址位于内核之中,我们用户是无法使用的,读写该地址会报错。
  

#ifndef NULL#ifdef __cplusplus#define NULL 0#else#define NULL ((void *)0)#endif
#endif

  

  我们可以把野指针看成野狗,很危险。给野指针赋值 NULL 相当于把野狗拴在树上,这时,你只要不靠近就不会有危险,相对安全 。

这里放张野狗图

  

(2)小心指针越界

  
  创建变量时,向内存中申请了多少空间,通过指针也只能访问这些空间。超出这些空间访问,就是越界访问,指针也就成了野指针。上述例子中,数组的越界访问就是如此。
  

(3)指针变量不再使用时,及时置 NULL,指针使用之前检查有效性

  当我们创建指针变量想访问某个区域,后期不再访问时,可以将指针变量置为 NULL
  
  使用指针变量前,我们先检查他的有效性,即检查它是否为空指针,如果为空指针,我们就不再访问
  
  还是上面野狗的例子,虽然野狗被拴起来,但是靠近他还是很危险,要和他保持一定距离才算安全。
  
  事实上,我们使用指针最合理的方式是:先判断,再使用
  

(4)避免返回局部变量的地址

  
  这一点,上述第三个例子中,不要返回函数中创建的局部变量的地址。
  
  

三、 a s s e r t assert assert 断言

  
  C语言 <assert.h> 头文件中,定义了宏 a s s e r t assert assert,那么 a s s e r t assert assert 的功能是什么呢?
  
   a s s e r t assert assert 用于在运行时程序符合规定条件,如不符合,则报错程序终止运行并给出报错信息提示
a s s e r t assert assert 常常被称作断言
  

assert(p != NULL)

  
  程序在运行到上述语句时,会先判断指针 p p p 是否为空指针,如果为空指针,则报错给出信息并终止程序运行。如果不是空指针,则程序正常运行。

   a s s e r t () assert() assert()宏接受一个表达式作为参数(该表达式不一定非要指针),该表达式为真 a s s e r t () assert() assert()宏不会产生任何作用,程序正常运行,当表达式为假, a s s e r t () assert() assert()宏就会报错,在标准错误流 s t d e r r stderr stderr 中写入一条错误信息,表示没有通过的表达式,以及包含这个表达式的文件名和行号。

那么 a s s e r t assert assert 有什么好处呢?

  • 他能自动表示文件和出问题的行号
  • 他有一种无需更改代码就开启和关闭的机制。如果想关闭 a s s e r t assert assert 断言,只需在 #include <assert.h> 前面,加上一个 N D E B U G NDEBUG NDEBUG
#define NDEBUG
#include <assert.h>

  
  而如果程序又出现问题,我们只需要将 N D E B U G NDEBUG NDEBUG 删除掉, a s s e r t () assert() assert()就能重新启动了。
  
  这时,可能还有小伙伴会问,我可以直接用 i f if if 语句来判断啊,为什么要用 a s s e r t assert assert 断言呢?
  

相比与if语句, a s s e r t assert assert 断言:

  • 出现错误,直接报错,并指明文件哪一行
  • i f if if 语句在不用时,要把他注释掉,因为让他放在那会占用内存空间

  
  当然 a s s e r t assert assert 断言也有缺点:因为引用了额外的检查,增加了程序运行时间。

  同时, a s s e r t assert assert 断言一般在 d e b u g debug debug 版本中使用,在 r e l e a s e release release 版本我们将它禁用就行,因为在 r e l e a s e release release 版本 a s s e r t assert assert 断言会直接被优化掉。
  
  

四、传值调用与传址调用

4.1、传值调用

  
  在之前的学习中,我们知道,函数传参中,形参仅仅是实参的一份临时拷贝,对形参的修改并不会改变实参的值(详情请看【C语言】——详解函数)。这种函数调用方式叫做传值调用。
  
  但如果我们想封装一个函数,让他交换两个变量的值,该怎么实现呢?这时我们就需要传址调用来实现,让我们一起来看看。
  

4.2、传址调用

  
  当我们函数传参时,将变量的地址传给函数,这种函数的调用方式叫做:传址调用。
  
我们来看下面代码:

#include<stdio.h>void Swap(int* px, int* py)
{int tmp = 0;tmp = *px;*px = *py;*py = tmp;
}int main()
{int a = 0;int b = 0;scanf("%d %d", &a, &b);printf("交换前: a=%d b=%d\n", a, b);Swap(&a, &b);printf("交换后: a=%d b=%d\n", a, b);return 0;
}

运行结果:
在这里插入图片描述

  我们可以看到,在 m a i n main main 函数中将 a a a b b b 的地址传给了 s w a p swap swap 函数, s w a p swap swap 函数通过地址,简介访问两个变量,实现了两个变量间的交换

  

结论:

  • 当我们调用函数,只需要主函数中变量的值时,可以使用传值调用
  • 当我们需要函数内部修改主函数中的值,就需要传址调用

  
  

五、二级指针

  
  在讲解二级指针之前,我们先来理清一级指针变量 ( p ) (p) p的三种关系:
  

  • p p p 中可以放 a a a 的地址
  • p p p 可以通过解引用找到 a a a
  • p p p 自己本身也有一个地址

  
如图:

在这里插入图片描述

  
  那什么又是二级指针呢?
  
  二级指针就是存放指针变量地址的变量
  

#include<stdio.h>int main()
{int a = 10;int* pa = &a;int** ppa = &pa;return 0;
}

  
图示:

在这里插入图片描述

  
  在上述代码中, p p a ppa ppa 就是二级指针变量,变量类型 i n t int int**,怎么来理解呢?

  • 前面 i n t int int* 表示他所指向的变量为指针变量,
  • 第二颗 ∗ * 表示他一个指针变量

  
对于二级指针的运算有:

  • * p p a ppa ppa 通过对 p p a ppa ppa 中的地址进行解引用,这样找的是 p a pa pa, * p p a ppa ppa 其实访问的就是 p a pa pa
int b = 20;
*ppa = &b;//等价于 pa = &b;
  • ** p p a ppa ppa 先通过 * p p a ppa ppa 找到 p a pa pa,然后对 p a pa pa 进行解引用从操作: * p a pa pa,找到的是 a a a
**ppa = 30;
//等价于 *pa = 30;
//等价于 a = 30;

  
  

六、指针数组

6.1、指针数组的概念

  
  首先,我先来问一个问题,指针数组是指针还是数组?
  
  我们学习新知识的时候,可以用类似的旧知识来类比:整形数组是整形变量还是数组?答案很明显嘛,就是数组,同理,指针数组也是数组
  
  就像整形数组存放的元素类型是整形,指针数组存放的每个元素为指针
  

在这里插入图片描述

指针数组的每个元素是地址,又可以指向一块区域。

  

6.2、指针数组模拟二维数组

  

#include<stdio.h>int main()
{int arr1[] = { 1,2,3,4,5 };int arr2[] = { 2,3,4,5,6 };int arr3[] = { 3,4,5,6,7 };int* parr[3] = { &arr1[0],&arr2[0],&arr3[0] };int i = 0;int j = 0;for (i = 0; i < 3; i++){for (j = 0; j < 5; j++){printf("%d ", parr[i][j]);}printf("\n");}
}

  
运行结果:
在这里插入图片描述
  
在这里插入图片描述
  
  
   p a r r parr parr [ [ [ i i i ] ] ] 是访问 p a r r parr parr 数组的元素, p a r r parr parr [ [ [ i i i ] ] ] 找到的数组元素指向了整型一维数组, p a r r parr parr [ [ [ i i i ] ] ] [ [ [ j j j ] ] ] 就是整形一维数组中的元素。
  
  上述代码模拟出二维数组的效果,实际上并非完全是二维数组,因为二维数组在内存中是连续存储的,而上述代码每一行的存储可能差了十万八千里。
  
  
  
  


  好啦,本期关于指针就介绍到这里啦,希望本期博客能对你有所帮助,同时,如果有错误的地方请多多指正,让我们在C语言的学习路上一起进步!

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

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

相关文章

HNU-计算机系统-实验1-原型机vspm1.0-(二周目玩家视角)

前言 二周目玩家&#xff0c;浅试一下这次的原型机实验。总体感觉跟上一年的很相似&#xff0c;但还是有所不同。 可以比较明显地感觉到&#xff0c;这个界面越来越好看了&#xff0c;可操作与可探索的功能也越来越多了。 我们HNU的SYSTEM真的越来越好了&#xff01;&#x…

5 个适用于 Windows 10 和 11 的最佳 PDF 转 Word 转换器

PDF 文件是共享文档的首选格式&#xff0c;但是此类文件存在一些限制&#xff0c;导致难以修改或编辑。因此&#xff0c;您可能会发现自己正在寻找一种将 PDF 文件转换为 Word 或其他可编辑格式的方法。 有许多不同的 PDF 转换器&#xff0c;每种转换器提供的功能略有不同。本…

个人简历主页搭建系列-03:Hexo+Github Pages 介绍,框架配置

今天的更新内容主要是了解为什么选择这个网站搭建方案&#xff0c;以及一些前置软件的安装。 Why Hexo? 首先我们了解一下几种简单的网站框架搭建方案&#xff0c;看看对于搭建简历网站的需求哪个更合适。 在 BuiltWith&#xff08;网站技术分析工具&#xff09;上我们可以…

微信小程序(一)

WebView app.是全局配置&#xff0c;app.json是全局配置文件&#xff0c;在页面的.json配置文件中的配置会覆盖我们全局的配置 快捷键&#xff1a; .box 敲回车 ----- <view class"box"></view> .row*8 敲回车&#xff1a; .row{$}*8 敲回车 案例1&…

信雅纳网络测试的二次开发集成:XOA(Xena Open-Source Automation)开源自动化测试

目录 XOA是什么 XOA CLI XOA Python API ​XOA Python Test Suite/测试套件 XOA Converter Source Code XOA是什么 XOA&#xff08;Xena Open-Source Automation&#xff09;是一个开源的测试自动化框架&#xff0c;追求“高效、易用、灵活”的跨操作系统的开发框架。能…

Android SystemServer进程解析

SystemServer进程在android系统中占了举足轻重的地位&#xff0c;系统的所有服务和SystemUI都是由它启动。 一、SystemServer进程主函数流程 1、主函数三部曲 //frameworks/base/services/java/com/android/server/SystemServer.java /** * The main entry point from zy…

Docker使用(四)Docker常见问题分析和解决收集整理

Docker使用(四)Docker常见问题分析和解决收集整理 五、常见问题 1、 启动异常 【描述】&#xff1a; 【分析】&#xff1a;[rootlocalhost ~]# systemctl status docker 【解决】&#xff1a; &#xff08;1&#xff09;卸载后重新安装&#xff0c;不能解决这个问题。 …

基于正点原子潘多拉STM32L496开发板的简易示波器

一、前言 由于需要对ADC采样性能的评估&#xff0c;重点在于对原波形的拟合性能。 考虑到数据的直观性&#xff0c;本来计划采集后使用串口导出&#xff0c;并用图形做数据拟合&#xff0c;但是这样做的效率低下&#xff0c;不符合实时观察的需要&#xff0c;于是将开发板的屏幕…

oracle基础-子查询 备份

一、什么是子查询 子查询是在SQL语句内的另外一条select语句&#xff0c;也被称为内查询活着内select语句。在select、insert、update、delete命令中允许是一个表达式的地方都可以包含子查询&#xff0c;子查询也可以包含在另一个子查询中。 【例1.1】在Scott模式下&#xff0…

AJAX学习(四)

版权声明 本文章来源于B站上的某马课程&#xff0c;由本人整理&#xff0c;仅供学习交流使用。如涉及侵权问题&#xff0c;请立即与本人联系&#xff0c;本人将积极配合删除相关内容。感谢理解和支持&#xff0c;本人致力于维护原创作品的权益&#xff0c;共同营造一个尊重知识…

github 中的java前后端项目整合到本地运行

前言: 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;不提供完整代码&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 本文章未…

百度paddleocr GPU版部署

显卡&#xff1a;NVIDIA GeForce RTX 4070&#xff0c;Nvidia驱动程序版本&#xff1a;537.13 Nvidia驱动程序能支持的最高cuda版本&#xff1a;12.2.138 Python&#xff1a;python3.10.11。试过python3.12&#xff0c;安装paddleocr失败&#xff0c;找不到相关模块。 飞桨版本…

使用 Postman 批量发送请求的最佳实践

背景 最近写了几个接口&#xff1a; 获取 books 的接口获取 likes 的接口获取 collections 的接口 但是我还是不放心&#xff0c;因为这些接口到底稳不稳定呢&#xff1f;上线后有没有隐患呢&#xff1f;所以我想做一个批量发送接口模拟~ 但是想要做到批量发送接口&#xf…

每日五道java面试题之springMVC篇(四)

目录&#xff1a; 第一题. Spring MVC怎么样设定重定向和转发的&#xff1f;第二题.Spring MVC怎么和AJAX相互调用的&#xff1f;第三题. 如何解决POST请求中文乱码问题&#xff0c;GET的又如何处理呢&#xff1f;第四题. Spring MVC的异常处理&#xff1f;第五题. 如果在拦截请…

hcia复习总结5

路由表 路由器的转发原理&#xff1a;当一个数据包进入路由器&#xff0c;路由器将基于数据包中的 目标IP地址查看本地的 路由表 。如果路由表中存在记录&#xff0c;则将 无条件 按照 路由表记录执行&#xff1b;如果没有记录&#xff0c;则将该数据包直接丢弃。 <aa…

SpringMVC 02

这里先附上前一篇的地址,以上系列均为博主的学习路线,仅供参考 初识Spring MVC-CSDN博客 下面我们从SpringMVC传递数组开始讲起 1.传递数组 传递数组的方式和传递普通变量的方式其实是相同的,下面我们附上传递的图片 RequestMapping("/r7")public String r1(String[…

springboot+poi-tl根据模板导出word(含动态表格和图片),并将导出的文档压缩zip导出

springbootpoi-tl根据模板导出word&#xff08;含动态表格和图片&#xff09; 官网&#xff1a;http://deepoove.com/poi-tl/ 参考网站&#xff1a;https://blog.csdn.net/M625387195/article/details/124855854 pom导入的maven依赖 <dependency><groupId>com.dee…

002——编译鸿蒙(Liteos -a)

目录 一、鸿蒙是什么 二、Kconfig 2.1 概述 2.2 编译器 2.3 make使用 本文章引用了很多韦东山老师的教程内容&#xff0c;算是我学习过程中的笔记吧。如果侵权请联系我。 一、鸿蒙是什么 这里我补充一下对鸿蒙的描述 这张图片是鸿蒙发布时使用的&#xff0c;鸿蒙是一个很…

excel导入功能(适用于vue和react都可)

如图所示&#xff08;需求&#xff09;&#xff1a;点击导入excel后&#xff0c;数据自动新增到列表数据内 这里以vue3 andt 为例 template 标签内代码 &#xff1a; <a-uploadname"file":multiple"true":show-upload-list"false":customR…

iOS 腾讯Pag动画框架-实现PagView的截图功能

背景 产品想要一个首页的截图功能&#xff0c;一听这个功能&#xff0c;心想那还不简单&#xff0c;将父视图控件转换成图片保存就行了。按照这个思路实现&#xff0c;很快就打脸啦&#xff0c;首页的这些动画一个都没有截出来&#xff0c;就像消失啦似的。然后蠢蠢的将动画暂…