C语言深入理解指针(二)

目录

  • 指针运算
    • 指针+-整数
    • 指针-指针
    • 指针的关系运算
  • 野指针
    • 野指针成因
      • 指针未初始化
      • 指针越界访问
      • 指针指向的空间释放
    • 如何规避野指针
      • 指针初始化
      • 注意指针越界
      • 指针不使用时就用NULL
      • 避免返回局部变量的地址
  • assert断言
  • 指针的使用和传址调用
    • 传址调用
    • 例子(strlen函数的实现)

指针运算

指针的基本运算有三种,分别是:
• 指针±整数
• 指针-指针
• 指针的关系运算

指针±整数

因为数组在内存中是连续存放的,比如int类型的数组,每个元素相差4个字节,因此我们只需要知道首元素的地址就可以通过加减的方式找到后面元素的地址

int arr[10] = {1,2,3,4,5,6,7,8,9,10}
#include <stdio.h>
//指针+- 整数
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]);
for(i=0; i<sz; i++)
{
printf("%d ", *(p+i));//p+i 这⾥就是指针+整数 i每增加1就往后移动4个字节
}
return 0;
}

指针-指针

/指针-指针
#include <stdio.h>
int my_strlen(char *s)
{
char *p = s;
while(*p != '\0' )//遇到\0就代表字符串结束
p++;
return p-s;//同过着两个指针相减我们可以得到这个指针的总长度
}
int main()
{
printf("%d\n", my_strlen("abc"));
return 0;
}

指针的关系运算

//指针的关系运算
#include <stdio.h>
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++;//p没加1就增加4个字节
}
return 0;
}

野指针

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

野指针成因

指针未初始化

#include <stdio.h>
int main()
{
int *p;//局部变量指针未初始化,默认为随机值
*p = 20;
return 0;
}

指针越界访问

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

指针指向的空间释放

#include <stdio.h>
int* test()
{
int n = 100;
return &n;//不是全局变量,在函数结束后地址就会消失
}
int main()
{
int*p = test();
printf("%d\n", *p);
return 0;
}

如何规避野指针

指针初始化

如果明确知道指针指向哪里就直接赋值地址,如果不知道指针应该指向哪里,可以给指针赋值NULL(空指针,也可以理解为0,但是不完全是0,因为0有整形和char类型,只是有那个意思)
NULL 是C语言中定义的⼀个标识符常量,值是0,0也是地址,这个地址是无法使用的,读写该地址会报错

#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endi

初始化如下

#include <stdio.h>
int main()
{
int num = 10;
int*p1 = &num;
int*p2 = NULL;
return 0;
}

注意指针越界

⼀个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是越界访问

指针不使用时就用NULL

当指针变量指向⼀块区域的时候,我们可以通过指针访问该区域,后期不再使用这个指针访问空间的时候,我们可以把该指针置为NULL。因为约定俗成的一个规则就是:只要是NULL指针就不去访问
因此使用指针之前可以判断指针是否为NULL。
这里就是用if语句判断

int main()
{
int arr[10] = {1,2,3,4,5,67,7,8,9,10};
int *p = &arr[0];
for(i=0; i<10; i++)
{
*(p++) = i;
}
//此时p已经越界了,可以把p置为NULL
p = NULL;
//下次使⽤的时候,判断p不为NULL的时候再使⽤
//...
p = &arr[0];//重新让p获得地址
if(p != NULL) //判断
{
//...
}
return 0;
}

避免返回局部变量的地址

我们就需要创建的变量不是局部变量,也就是说我们可以创建全局变量,当然你可以在mian函数里面创建变量,然后将变量的地址传入函数中,再通过函数进行一系列操作,结束时可以将变量的地址传出,这样就可以避免返回局部变量了。

assert断言

assert.h 头文件定义了宏 assert() ,用于在运行时确保程序符合指定条件,如果不符合,就报错终止运行。这个宏常常被称为“断言”
可以理解为进行了一次安检,在通过时会对这个变量进行检测,判断是否符合条件

 assert(p != NULL);

上面代码在程序运行到这⼀行语句时,验证变量 p 是否等于 NULL 。
如果确实不等于 NULL ,程序继续运行,否则就会终止运行,并且给出报错信息提示
assert() 宏接受⼀个表达式作为参数。如果该表达式为真(返回值非零), assert() 不会产生任何作用,程序继续运行。
如果该表达式为假(返回值为零),assert() 就会报错,在标准错误流 stderr 中写入⼀条错误信息,显示没有通过的表达式,以及包含这个表达式的文件名和行号。
assert() 的使用对程序员是非常友好的,使用 assert() 有几个好处:它不仅能自动标识文件和出问题的行号,还有⼀种无需更改代码就能开启或关闭 assert() 的机制。
如果已经确认程序没有问题,不需要再做断言,就在 #include <assert.h> 语句的前面,定义⼀个NDEBUG

#define NDEBUG
#include <assert.h>

然后,重新编译程序,编译器就会禁用文件中所有的 assert() 语句。如果程序又出现问题,可以移除这条 #define NDBUG 指令(或者把它注释掉),再次编译,这样就重新启用了 assert() 语句。
assert() 的缺点是,因为引入了额外的检查,增加了程序的运行时间。
⼀般我们可以在debug中使用,在release版本中选择禁用assert就行,在VS这样的集成开发环境中,在release版本中,直接就是优化掉了。这样在debug版本写有利于程序员排查问题,在release版本不影响用户使用时程序的效率

指针的使用和传址调用

传址调用

学习指针的目的是使用指针解决问题,那什么问题,非指针不可呢?
例如:写⼀个函数,交换两个整型变量的值

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

代码运行入下:
在这里插入图片描述
在main函数内部,创建了a和b,a的地址是在调用Swap1函数时,将a和b传递给了Swap1函数,在Swap1函数内部创建了形参x和y接收a和b的值,但是
x和y确实接收到了a和b的值,不过x的地址和a的地址不一样,y的地址和b的地址不一样,相当于x和y是独立的空间,那么在Swap1函数内部交换x和y的值,自然不会影响a和b,当Swap1函数调用结束后回到main数,a和b的没法交换。Swap1函数在使用的时候,是把变量本⾝直接传递给了函数,这种调用函数的方式我们之前在函数的时候就知道了,这种叫传值调用
。因此当我们传入内存后,运行结果如下:
在这里插入图片描述
我们可以看到实现成Swap2的方式,顺利完成了任务,这里调用Swap2函数的时候是将变量的地址传
递给了函数,这种函数调用方式叫:传址调用

结论:实参传递给形参的时候,形参会单独创建⼀份临时空间来接收实参,对形参的修改不影响实参。所以Swap是失败的了

例子(strlen函数的实现)

//计数器⽅式
int my_strlen(const char * str)
{
int count = 0;
assert(str);
while(*str)
{
count++;
str++;
}
return count;
}
int main()
{
int len = my_strlen("abcdef");
printf("%d\n", len);
return 0;
}

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

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

相关文章

SSC338D/SSC338Q CA7*2+IPU5M/Multi-sensorISP: HDR/3DNR

SSC338D/SSC338Q系列产品是高度集成的多媒体片上系统&#xff08;SoC&#xff09;产品&#xff0c;适用于IP摄像机、车载摄像机和USB摄像机等高分辨率智能视频录制应用。该芯片包括32位双核RISC处理器、高级图像信号处理器&#xff08;ISP&#xff09;、高性能MJPEG/H.264/H.26…

idear导入他人项目如何快速运行

最近idear经常导入别人的项目&#xff0c;结果永远在加载依赖项。网上查了一堆资料&#xff0c;什么jdk问题&#xff0c;环境变量问题&#xff0c;maven仓库路径问题&#xff0c;总之就是没啥用。那有没有什么简单粗暴的办法&#xff0c;能够导入项目后快速运行呢。 解决方法&a…

MATLAB 2024a安装包下载及安装教程、超详细步骤

[安装环境]: Win 11/Win 10 MATLAB和Mathematica、Maple并称为三大数学软件。它在数学类科技应用软件中在数值计算方面首屈一指。行矩阵运算、绘制函数和数据、实现算法、创建用户界面、连接其他编程语言的程序等。MATLAB的基本数据单位是矩阵&#xff0c;它的指令表达式与数学…

Git常用指令大全详解

Git常用指令大全详解 Git&#xff0c;作为目前最流行的分布式版本控制系统&#xff0c;其强大的功能和灵活性为开发者提供了极大的便利。无论是个人项目还是团队协作&#xff0c;Git都扮演着不可或缺的角色。本文将详细总结Git的常用指令&#xff0c;帮助大家更好地掌握这一工…

九大B端开源组件库:总览篇,B端设计绕不开的话题。

如果你经常接触B端设计&#xff0c;开源组件库是各个绕不开的话题&#xff0c;市场上的组件库林林总总&#xff0c;本次做一个总体介绍&#xff0c;后面有机会再分开详细介绍。 一、最常用的组件库&#xff0c;推出的时间。 自Bootstrap诞生后&#xff0c;B端PC中后台组件库如…

爬虫代理API的全面解析:让数据抓取更高效

在大数据时代&#xff0c;网络爬虫已经成为收集和分析数据的重要工具。然而&#xff0c;频繁的请求会导致IP被封禁&#xff0c;这时候爬虫代理API就显得尤为重要。本文将详细介绍爬虫代理API的作用、优势及如何使用&#xff0c;帮助你更高效地进行数据抓取。 什么是爬虫代理AP…

C++刷怪笼(5)内存管理

一.前言 学习完类和对象之后&#xff0c;我们对C有了基本的了解&#xff0c;但是我们对于C的学习&#xff0c;除了广度&#xff0c;还需要深度&#xff0c;今天让我们走进底层&#xff0c;看看C对其内存是如何进行管理的。 二.正文 我们对这段代码进行详细的分析如下图&#…

技术美术一百问(02)

问题 前向渲染和延迟渲染的流程 前向渲染和延迟渲染的区别 G-Buffer是什么 前向渲染和延迟渲染各自擅长的方向总结 GPU pipeline是怎么样的 Tessellation的三个阶段 什么是图形渲染API? 常见的图形渲染API有哪些&#xff1f; 答案 1.前向渲染和延迟渲染的流程 【例图…

使用Rust直接编译单个的Solidity合约

这里写自定义目录标题 使用Rust直接编译单个的Solidity合约前言预备知识准备工作示例 使用Rust直接编译单个的Solidity合约 前言 我们知道&#xff0c;我们平常开发Solidity智能合约时一般使用Hardhat框架&#xff0c;但是如果你是一个Rustacean (这是由 “Rust” 和 “crust…

2024年【中级消防设施操作员(考前冲刺)】及中级消防设施操作员(考前冲刺)模拟考试题库

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 中级消防设施操作员&#xff08;考前冲刺&#xff09;根据新中级消防设施操作员&#xff08;考前冲刺&#xff09;考试大纲要求&#xff0c;安全生产模拟考试一点通将中级消防设施操作员&#xff08;考前冲刺&#xf…

设计模式之建造者模式(通俗易懂--代码辅助理解【Java版】)

文章目录 设计模式概述1、建造者模式2、建造者模式使用场景3、优点4、缺点5、主要角色6、代码示例&#xff1a;1&#xff09;实现要求2&#xff09;UML图3)实现步骤&#xff1a;1&#xff09;创建一个表示食物条目和食物包装的接口2&#xff09;创建实现Packing接口的实体类3&a…

golang 字符串浅析

go的字符串是只读的 测试源代码 package mainimport ("fmt""unsafe" )func swap(x, y string) (string, string) {return y, x }func print_string(obj *string, msg string) {string_ptr : (*[2]uintptr)(unsafe.Pointer(obj))first_obj_addr : string_…

MySQL系列—13.事务

1、事务 事务是逻辑上的一组操作&#xff0c;要么都执行&#xff0c;要么都不执行。 事务控制语法 - 事务开始 begin; - 事务提交&#xff0c;提交后就会写入物理磁盘中去 commit; - 事务回滚&#xff0c;事务提交后&#xff0c;无法回滚 rollback;事务的四大特性&#xff0…

基于51单片机的温度电流电压检测系统(压力、电压、温度、电流、LCD1602)

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 基于51单片机&#xff0c;通过DS18B20检测温度&#xff0c;滑动变阻器连接数模转换器模拟电流、电压&#xff0c;通过LCD1602显示&#xff0c;程序里设置温度阈值为40&#xff0c;电流阈值为60&am…

六西格玛咨询:石油机械制造企业的成本控制与优化专家

一、石油机械制造行业现状及主要困扰 随着全球能源需求的日益增长&#xff0c;石油开采和生产设备需求不断增加&#xff0c;石油机械制造行业在过去数十年里得到了迅猛发展。然而&#xff0c;石油机械制造作为一个高度复杂且技术密集的行业&#xff0c;也面临着多重挑战。首先…

【Vue】- 路由及传参

文章目录 知识回顾前言源码分析1. 声明式导航2. 路由传参3. 可选符4. 重定向5. 4046. 跳转及传参7. 路由懒加载拓展知识总结router-link静态传参和动态路由的对比知识回顾 前言 什么是单页面应用程序? ● 所有功能在一个html页面上实现 单页面应用优缺点? ● 优点:按需更新…

大模型时代的 AI 产品经理何去何从?

每天 LLM 相关的资讯都让人过载。作为 AI 产品经理&#xff0c;你是更兴奋呢&#xff0c;还是更担忧呢&#xff1f;整理了一下个人近期的思考&#xff0c;欢迎感兴趣的朋友一起交流。不管答案如何&#xff0c;相信我们都不应置身事外。 1 关于大模型‍‍ 大模型的各种原理讲…

OpenAI O1热度己过-如此快速的冷场带来的是人们更多对现今“AI”的思考

引入o1的瞬间辉煌 o1如耀眼的新星&#xff0c;闪耀于科技天空。发布时&#xff0c;因其思考能力而备受瞩目。它能在回答问题之前进行更多的思考&#xff0c;尤其擅长复杂推理。这种能力引发了众多用户的好奇与热情。诸如“9.8和9.11哪个大&#xff1f;”的简单问题被广泛地验证…

上海人工智能实验室开源视频生成模型Vchitect 2.0 可生成20秒高清视频

上海人工智能实验室日前推出的Vchitect2.0视频生成模型正在悄然改变视频创作的游戏规则。这款尖端AI工具不仅简化了视频制作流程&#xff0c;还为创作者提供了前所未有的灵活性和高质量输出。 Vchitect2.0的核心优势在于其强大的生成能力和高度的可定制性。用户只需输入文字描…

Java获取随机数

在Java中获取随机数通常会使用java.util.Random类或者Math.random()方法 1.java.util.Random java.util.Random类用于生成伪随机数。 // 使用无参构造方法创建Random对象Random rand new Random();// 生成一个[0, 100)范围内的随机整数int randomInt rand.nextInt(100);Sys…