【C语言精髓之指针】结构体指针(->与.两个运算符的区别)
/*** @file * @author jUicE_g2R(qq:3406291309)————彬(bin-必应)* 通信与信息专业大二在读 * @copyright 2023.10* @COPYRIGHT 原创技术笔记:转载需获得博主本人同意,且需标明转载源* @language C/C++* @IDE Base on Microsoft Visual Studio 2022* @state Corrected*/
jUicE_g2R的个人主页
前期回顾,指针*、取地址&、解引用*、引用&
最近帮别人改C语言程序,才发现了自己对这部分还是理解不够深(平时写C++会忽略一些底层的东西,比如指针、地址一类的东西),记录一下。
1 错误示范
//错误示范
#include <stdio.h>
#include <malloc.h>
typedef struct {int id;char name[10];
} nodeN,*pNode;
int main(void) {pNode ptr = (pNode)malloc(sizeof(nodeN)); //node是一个结构体指针变量,malloc为该指针指向的对象开辟了空间scanf_s("%s", ptr->name); //这样写绝对会报:...处 这个文件 引发的异常: ... 写入位置 ... 时发生访问冲突。return 0;
}
2 先来了解一下:点运算符 、 箭头运算符 与 取地址运算符
-
首先, p t r ptr ptr 是一个 指针变量 指针变量 指针变量。
typedef struct* pNode等同于typedef int* pInt,是 对 指针类型名 指针类型名 指针类型名 的 重定义 重定义 重定义,而不是定义了一个 名为 p N o d e pNode pNode 的 指针变量 指针变量 指针变量pNode ptr = (pNode)malloc(sizeof(nodeN));的替换写法为:nodeN* ptr = (pNode)malloc(sizeof(nodeN));或nodeN* ptr = (*nodeN)malloc(sizeof(nodeN)); -
其次,要知道
->(箭头运算符) 和.(点运算符) 近似,都是获取 类(或结构体) 类(或结构体) 类(或结构体) 成员 的 运算符 运算符 运算符(+、*等等一类的),这种通过 运算符 运算符 运算符 获取成员的方式叫做: 访问 访问 访问 ,而 访问 访问 访问 的实质就是 获取到数据。 -
再者,需要知道这些 运算符 运算符 运算符 的 优先级 优先级 优先级: 括号运算符 括号运算符 括号运算符 > 点运算符 点运算符 点运算符 > 箭头运算符 箭头运算符 箭头运算符 > 取地址运算符 取地址运算符 取地址运算符
&node.name、&ptr->name2-1 然后,需要知道 点运算符 点运算符 点运算符 与 箭头运算符 箭头运算符 箭头运算符 的区别:
| 区别 | 点运算符 | 箭头运算符 |
|---|---|---|
| 含义 | 成员选择(对象) | 成员选择(指针) |
| 使用形式 | 对象.成员名 | 对象指针->成员名 |
| 返回值 | 直接返回访问得到的值 | 返回指向结构体成员的指针 |
| 调用 | 要求编译器知道结构体的定义 | 不需要知道结构体的定义(因为是指针,无需知道具体定义,只需要知道指着哪个就行【即知道被指对象的地址就可以了】) |
| 适用性 | 需要在使用 node.name 之前包含该结构体的头文件,这样不太适用于多文件开发【更偏向用于单文件】 | 无需【因为不需要知道定义】 |
简单说:
点运算符 点运算符 点运算符 是 结构体变量 结构体变量 结构体变量 访问 其 成员 的 操作符
箭头运算符 箭头运算符 箭头运算符 是 结构体指针 结构体指针 结构体指针 访问 其 指向的成员变量 的 操作符
3 有如下两种修改方法
2-1 不使用 scanf_s
#define _CRT_SECURE_NO_WARNINGS //必须加在所有.h文件的前面,使编译器不对 “没有使用安全函数scanf_s” 这种行为 进行报错
#include <stdio.h>
#include <malloc.h>
typedef struct {int id;char name[10];
} nodeN,*pNode;
int main(void) {pNode ptr = (pNode)malloc(sizeof(nodeN));scanf("%s", ptr->name); return 0;
}
_CRT_SECURE_NO_WARNINGS
我们鼠标箭头移至 _CRT_SECURE_NO_WARNINGS 这段,右键选择 速览定义 速览定义 速览定义,就可以看到(vcruntime.h 文件的)这一段:
// See note on use of "deprecate" at the top of this file
#define _CRT_DEPRECATE_TEXT(_Text) __declspec(deprecated(_Text))#if defined _CRT_SECURE_NO_DEPRECATE && !defined _CRT_SECURE_NO_WARNINGS#define _CRT_SECURE_NO_WARNINGS
#endif#ifndef _CRT_INSECURE_DEPRECATE#ifdef _CRT_SECURE_NO_WARNINGS#define _CRT_INSECURE_DEPRECATE(_Replacement)#else#define _CRT_INSECURE_DEPRECATE(_Replacement) _CRT_DEPRECATE_TEXT( \"This function or variable may be unsafe. Consider using " \#_Replacement \" instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. " \"See online help for details.")#endif
#endif
其实就相当于:
#ifdef _CRT_SECURE_NO_WARNINGS
//TODO:编译器不对安全函数进行检查
#else
//TODO:编译器必须对安全函数的使用进行检查
#elseif
2-2 使用 scanf_s
注意
安全函数的第三个参数不可少,传入的是变量的字节大小,可以用 s i z e o f ( ) 函数 sizeof()函数 sizeof()函数 获取,也可以手动计算
#include <stdio.h>
#include <malloc.h>
#define NAMESIZE 10
typedef struct {int id;char name[NAMESIZE];
} nodeN,*pNode;
int main(void) {pNode ptr = (pNode)malloc(sizeof(nodeN));scanf_s("%s", ptr->name, NAMESIZE); //这个 sizeof()函数 返回的值必须加上//scanf_s("%s", &(ptr->name), NAMESIZE); //这样也是可以的,&(ptr->name)是一种无效操作return 0;
}
4 进一步理解结构体指针
首先,不得不说: 地址即指针,指针即地址!!! 地址即指针,指针即地址!!! 地址即指针,指针即地址!!!
4-1 取地址运算符&
-
首先,取地址运算符
&返回的结果一般认为是 地址 地址 地址,实际上返回的就是一个指针(因为指针变量的值就是地址值) -
其次,
scanf_s()中 是否必要有 取值运算符&呢?并非如此, 因为scanf_s()第二个参数就是 传的 地址值。
一种是不知道地址,就要通过&去获取地址:即 变量 变量 变量 去向左结合&来获得 变量的地址 变量的地址 变量的地址。
另一种 ,如果知道 变量的地址 变量的地址 变量的地址,可以直接填入(如直接填入 n a m e name name 这个地址)。(这一点下面的 仔细揣摩以下两个概念 仔细揣摩以下两个概念 仔细揣摩以下两个概念 会用的)
4-2 数组名name
//C
#define NAMESIZE 10
char name[NAMESIZE];
-
1、 n a m e name name 这个数组名 是一个指针常量!!!
数组名 即为 数组首地址, n a m e name name 等价于
&name[0],数组名 是 数组 第一个元素 的 指针,即 n a m e name name 指向 n a m e 数组 name数组 name数组 的 首元素
下面的例子足以说明 n a m e name name 这个数组名 就是一个地址:
scanf_s("%s", name, NAMESIZE);//终端读入 NAMESIZE 字节大小的 char型 的 数据流,存入到 name数组 里
- 2、 n a m e + i name+i name+i 等价于
&a[i]
n a m e + i name+i name+i 指向 数组 第 i 个 元素
//C:终端读入,向 name数组 的第二个单元 写入 char型 数据
scanf_s("%c", &name[1], NAMESIZE);
scanf_s("%c", name+1, NAMESIZE);
-
3、数组名取地址
&name是一种 无效 操作n a m e name name 等于
&name,他们的值 都表示 数组首元素的地址
printf_s("%p\n", &name[0]); //首元素的地址值 的 指标:00D3F9B8
printf_s("%p\n", name); //输出数组首元素的地址:00D3F9B8
printf_s("%p\n", &name); //输出数组本身(即首元素地址)的地址:00D3F9B8
虽然两者值相同,但是他们的 类型 是不相同的:
| 区别 | name | &name |
|---|---|---|
| 定义 | 指针常量 | 一个指向指针常量的指针(指针的指针) |
| 类型 | 一级指针 一级指针 一级指针 | 函数指针类型( 二级指针 二级指针 二级指针) |
| 在这里的类型 | char* | char(*),一个指向返回值为 char型 的函数的指针 |
简单说:&name 指向 n a m e name name, n a m e name name 指向 n a m e 数组 name数组 name数组 的 首元素。
4-3 进一步,上升到 结构体指针 ptr
//C
#define NAMESIZE 10
typedef struct {int id;char name[NMAESIZE];
} nodeN,*pNode;
int main(void){pNode ptr = (pNode)malloc(sizeof(nodeN));//结构体指针nodeN node;//结构体变量
-
1、可以把 一个结构体变量 想象成一个 集成了一种或多种类型 的 一维数组
类推先前的结论: n a m e name name 这个数组名 是一个指针常量,则: p t r 结构体指针 ptr结构体指针 ptr结构体指针 = n o d e 结构体名 node结构体名 node结构体名
p t r 指针 ptr指针 ptr指针 的值为 其 指向对象 指向对象 指向对象(结构体)的 首地址值,或叫做: p t r 指针 ptr指针 ptr指针 指向 结构体的首地址
-
2、修改 和 只访问 结构体变量 结构体变量 结构体变量 中 非数组 成员
通过 scanf_s()终端从外部读入输入 值 算一种 修改(赋值) 参数的方式
通过 printf_s()终端写出到外部 值 算一种 只访问 参数的方式
//node.id实际上就是个变量 而不是 指针
scanf_s("%d", &node.id, sizeof(node.id));
printf_s("%d", node.id, sizeof(node.id));
- 3、修改 和 只访问 结构体指针 结构体指针 结构体指针 中 非数组 成员
//ptr->id,箭头运算符 直接返回 ptr->id指针所指的 成员id的值,而不会返回指针本身!!!
scanf_s("%d", &ptr->id, sizeof(ptr->id));
printf_s("%d", ptr->id, sizeof(ptr->id));
printf_s("%d", (*ptr).id, sizeof(ptr->id));
( ∗ p t r ) . i d (*ptr).id (∗ptr).id 等于 p t r − > i d ptr->id ptr−>id !!!【 ( ∗ p t r ) . i d (*ptr).id (∗ptr).id 中 ( ∗ p t r ) (*ptr) (∗ptr) ,由于有括号, p t r ptr ptr 先与 ∗ * ∗ 相与,这步叫 解引用 解引用 解引用,此时 ( ∗ p t r ) (*ptr) (∗ptr) 与 n o d e node node 等价】
4、修改 和 只访问 结构体指针 结构体指针 结构体指针 中 数组 成员
仔细揣摩以下两个概念
一个就是 在 2-1 提到了: 箭头运算符 箭头运算符 箭头运算符 返回的是 指向结构体成员的指针
另一个就是 对象指针 − > 成员 对象指针->成员 对象指针−>成员 是 值
如果 成员 是 非数组 非数组 非数组,这个 值 就是 对应成员的数据类型的 值
如果 成员 是 数组 数组 数组,这个 值 是个 指针!!!(这个值 为 数组的首地址),这样就好解释为什么是这种改法了:
scanf_s("%s", ptr->name, NAMESIZE);//ptr->name 就是 结构体中name数组成员 的首地址
//scanf_s("%s", ptr->name, sizeof((*ptr).name));
5、修改 和 只访问 结构体变量 结构体变量 结构体变量 中 数组 成员
同理, n o d e . n a m e node.name node.name 也可以获得 n a m e 指针 name指针 name指针 的值
scanf_s("%s", node.name, NAMESIZE);
&(ptr->name) 又是什么玩意?
&(ptr->name)是无效的操作,可以不看这部分内容,因为是个人观点(不代表正确)
ptr->name 是一个 地址值,可以看成一个指针。
&(ptr->name) 这就要涉及 指针的指针 了。(前面说了:取地址运算符 & 返回的结果一般认为是 地址 地址 地址,实际上返回的就是一个指针)
因为单纯的访问指针1 的话,就只需要使用一级指针1, 而操作(修改)指针的话就要使用到 二级指针2,即&(ptr->name) ,就是指针的指针(->(ptr->name))。
由于我们要通过 终端输入的方式 向 n a m e name name 这个结构体成员变量赋值,实质上是在修改指针1,就需要传入3 二级指针2来修改 一级指针1,进而,我们就可以通过这个指针2 来访问和修改 一级指针1 所指向的内存位置上的数据。
1 一级指针:ptr->name 这个指针
2 二级指针:1 的指针【或叫地址】
3 先以 一级指针 为例:
void update(*a){*a=2;}
int main(void) {int a=1;update(&a); //更新整形变量 a 的值,需要传入 a 的指针(用 & 获取)printf_s("%d", a);
}
进一步 二级指针:
void update(int** pp) {**pp = 2;}int main(void) {int a = 1;int* p = &a; //p指向aupdate(&p); //传入a的二级指针printf_s("%d", a);
}
5 警告 C6011 取消对 NULL 指针 “ptr” 的引用 的解决办法
这个是在给 指针指向的对象 开辟了空间时,即:
//C
pNode ptr = (pNode)malloc(sizeof(nodeN));
//C++
pNode p = new node;
的时候,没有对 " 申请空间的时候可能会存在失败的情况 " 的极端情况进行处理。
解决办法:
//C/C++
while(!ptr){ //直至申请成功为止//TODO:上述代码
}

jUicE_g2R的个人主页,确定不点开看看?
相关文章:
【C语言精髓之指针】结构体指针(->与.两个运算符的区别)
/*** file * author jUicE_g2R(qq:3406291309)————彬(bin-必应)* 通信与信息专业大二在读 * copyright 2023.10* COPYRIGHT 原创技术笔记:转载需获得博主本人同意,且需标明转载源* language C/C* IDE Base on Mic…...
SpringCloud 微服务全栈体系(一)
第一章 认识微服务 随着互联网行业的发展,对服务的要求也越来越高,服务架构也从单体架构逐渐演变为现在流行的微服务架构。这些架构之间有怎样的差别呢? 一、单体架构 单体架构:将业务的所有功能集中在一个项目中开发ÿ…...
Echarts自定义柱状图
目录 效果图 echarts官网找相似图 将柱状图引入html页面中 自定义柱状图 将不需要的属性删除 编辑 修改图形大小 grid 不显示x轴 编辑 不显示y轴线和相关刻度 编辑 y轴文字的颜色设置为自己想要的颜色 修改第一组柱子相关样式(条状) …...
LuatOS-SOC接口文档(air780E)-- ioqueue - io序列操作
ioqueue.init(hwtimer_id,cmd_cnt,repeat_cnt) 初始化一个io操作队列 参数 传入值类型 解释 int 硬件定时器id,默认用0,根据实际MCU确定,air105为0~5,与pwm共用,同一个通道号不能同时为pwm和ioqueue int 一个完…...
探讨Socks5代理技术的原理及其在不同领域的应用
Socks5代理:实现网络连接的智能之选 作为一种网络代理协议,Socks5代理技术通过转发网络数据包,实现了客户端和服务器之间的代理传输。其独特的特性在跨界电商、爬虫数据分析、企业出海和游戏体验等领域发挥着关键作用,为用户提供…...
sql注入的基本手法
目的 通过sqk注入获取数据内容 掌握sql注入基本手法 我们这里使用 1.联合注入 就是利用union select 语句 两条语句 同时执行 实现跨库跨表查询 条件 两条select语句查询结果具有相同列数 对应列数数据类型相同 简单的步骤 1.目标分析 ?id…...
8.1 C++ 标准输入输出流
C/C语言是一种通用的编程语言,具有高效、灵活和可移植等特点。C语言主要用于系统编程,如操作系统、编译器、数据库等;C语言是C语言的扩展,增加了面向对象编程的特性,适用于大型软件系统、图形用户界面、嵌入式系统等。…...
hive往es映射表写数据报错
hive是基于Hadoop的一个数据仓库工具,用来进行数据提取、转化、加载,这是一种可以存储、查询和分析存储在Hadoop中的大规模数据的机制。hive数据仓库工具能将结构化的数据文件映射为一张数据库表,并提供SQL查询功能,能将SQL语句转…...
2023年【广东省安全员A证第四批(主要负责人)】考试试卷及广东省安全员A证第四批(主要负责人)模拟考试
题库来源:安全生产模拟考试一点通公众号小程序 广东省安全员A证第四批(主要负责人)考试试卷根据新广东省安全员A证第四批(主要负责人)考试大纲要求,安全生产模拟考试一点通将广东省安全员A证第四批&#x…...
YOLOv5算法改进(15)— 如何去更换Neck网络(包括代码+添加步骤+网络结构图)
前言:Hello大家好,我是小哥谈。在学习完了如何去更换主干网络之后,接着就让我们通过案例的方式去学习下如何去更换Neck网络。本篇文章的特色就是比较浅显易懂,附加了很多的网络结构图,通过结构图的形式向大家娓娓道来,希望大家学习之后能够有所收获!🌈 前期回顾: YO…...
用Nginx搭建一个具备缓存功能的反向代理服务
在同一台服务器上,使用nginx提供服务,然后使用openresty提供反向代理服务。 参考《Ubuntu 20.04使用源码安装nginx 1.14.0》安装nginx。 参考《用Nginx搭建一个可用的静态资源Web服务器》搭建静态资源Web服务器,但是/nginx/conf/nginx.conf里…...
YOLOv5改进实战 | 更换主干网络Backbone(三)之轻量化模型Shufflenetv2
前言 轻量化网络设计是一种针对移动设备等资源受限环境的深度学习模型设计方法。下面是一些常见的轻量化网络设计方法: 网络剪枝:移除神经网络中冗余的连接和参数,以达到模型压缩和加速的目的。分组卷积:将卷积操作分解为若干个较小的卷积操作,并将它们分别作用于输入的不…...
【Markdown】 Markdown 操作备忘录
To Do List 显示目前todo list 的状态 getLogger() 单例类, 通过引入模块,获取单例日志对象 结果可视化调研 模型结果保存及测试 - [ ] getLogger() 单例类, 通过引入模块,获取单例日志对象 - [ ] 结果可视化调研 - [x] 模型结果…...
【自动化测试】基于Selenium + Python的web自动化框架
一、什么是Selenium? Selenium是一个基于浏览器的自动化工具,她提供了一种跨平台、跨浏览器的端到端的web自动化解决方案。Selenium主要包括三部分:Selenium IDE、Selenium WebDriver 和Selenium Grid: 1、Selenium IDE&…...
zookeeper连接客户端操作数据时报错Socket is not connected
文章目录 一、报错信息二、问题描述三、原因分析:四、解决方案: 一、报错信息 DEBUG org.apache.zookeeper.ClientCnxnSocketNIO - Ignoring exception during shutdown input java.net.SocketException: Socket is not connectedat sun.nio.ch.Net.tra…...
mysql select语句中from 之后跟查询语句
概念:将from后面的查询语句放在FROM的后面,则查询到的结果,就会被当成一个“表”; 这里有一个特别要注意的地方,放在FROM后面的子查询,必须要加别名。 select dui.id,dui.party_service_id mes_id, dui.party_id,dui.…...
Yolov8小目标检测(26):多尺度空洞注意力(MSDA) | 中科院一区顶刊 DilateFormer 2023.9
💡💡💡本文独家改进:多尺度空洞注意力(MSDA)采用多头的设计,在不同的头部使用不同的空洞率执行滑动窗口膨胀注意力(SWDA),全网独家首发,创新力度十足,适合科研 多尺度空洞注意力(MSDA) | 亲测在红外弱小目标检测涨点,map@0.5 从0.755提升至0.784 💡�…...
NLP:从头开始的文本矢量化方法
一、说明 NLP 项目使用文本,但机器学习算法不能使用文本,除非将其转换为数字表示。这种表示通常称为向量,它可以应用于文本的任何合理单位:单个标记、n-gram、句子、段落,甚至整个文档。 在整个语料库的统计 NLP 中&am…...
Kotlin 中 apply、let、also、run的区别
apply apply 函数接收一个目标并回来该目标自身。它答应您在目标上履行一些操作,同时仍然回来原始目标。 fun <T> T.apply(block: T.() -> Unit): TT 是目标的类型,block 是一个 lambda 表达式,能够在该目标上履行一些操作。在这个…...
Android JKS MD5 SHA1 公钥生成 私钥生成 APP备案 内容获取
1 查看 jks keytool -list -v -keystore /Users/lipengfei/Desktop/android/androidproject.jks密钥库类型: jks 密钥库提供方: SUN您的密钥库包含 1 个条目别名: ddgj 创建日期: 2018-11-16 条目类型: PrivateKeyEntry 证书链长度: 1 证书[1]: 所有者: CNcn, OUcn, Ocn, Lcn,…...
Zustand 状态管理库:极简而强大的解决方案
Zustand 是一个轻量级、快速和可扩展的状态管理库,特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...
高防服务器能够抵御哪些网络攻击呢?
高防服务器作为一种有着高度防御能力的服务器,可以帮助网站应对分布式拒绝服务攻击,有效识别和清理一些恶意的网络流量,为用户提供安全且稳定的网络环境,那么,高防服务器一般都可以抵御哪些网络攻击呢?下面…...
Spring AI与Spring Modulith核心技术解析
Spring AI核心架构解析 Spring AI(https://spring.io/projects/spring-ai)作为Spring生态中的AI集成框架,其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似,但特别为多语…...
如何理解 IP 数据报中的 TTL?
目录 前言理解 前言 面试灵魂一问:说说对 IP 数据报中 TTL 的理解?我们都知道,IP 数据报由首部和数据两部分组成,首部又分为两部分:固定部分和可变部分,共占 20 字节,而即将讨论的 TTL 就位于首…...
服务器--宝塔命令
一、宝塔面板安装命令 ⚠️ 必须使用 root 用户 或 sudo 权限执行! sudo su - 1. CentOS 系统: yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh2. Ubuntu / Debian 系统…...
MinIO Docker 部署:仅开放一个端口
MinIO Docker 部署:仅开放一个端口 在实际的服务器部署中,出于安全和管理的考虑,我们可能只能开放一个端口。MinIO 是一个高性能的对象存储服务,支持 Docker 部署,但默认情况下它需要两个端口:一个是 API 端口(用于存储和访问数据),另一个是控制台端口(用于管理界面…...
从面试角度回答Android中ContentProvider启动原理
Android中ContentProvider原理的面试角度解析,分为已启动和未启动两种场景: 一、ContentProvider已启动的情况 1. 核心流程 触发条件:当其他组件(如Activity、Service)通过ContentR…...
Ubuntu系统复制(U盘-电脑硬盘)
所需环境 电脑自带硬盘:1块 (1T) U盘1:Ubuntu系统引导盘(用于“U盘2”复制到“电脑自带硬盘”) U盘2:Ubuntu系统盘(1T,用于被复制) !!!建议“电脑…...
Docker拉取MySQL后数据库连接失败的解决方案
在使用Docker部署MySQL时,拉取并启动容器后,有时可能会遇到数据库连接失败的问题。这种问题可能由多种原因导致,包括配置错误、网络设置问题、权限问题等。本文将分析可能的原因,并提供解决方案。 一、确认MySQL容器的运行状态 …...
k8s从入门到放弃之HPA控制器
k8s从入门到放弃之HPA控制器 Kubernetes中的Horizontal Pod Autoscaler (HPA)控制器是一种用于自动扩展部署、副本集或复制控制器中Pod数量的机制。它可以根据观察到的CPU利用率(或其他自定义指标)来调整这些对象的规模,从而帮助应用程序在负…...
