【C语言精髓之指针】结构体指针(->与.两个运算符的区别)

news2024/11/19 15:16:21
/**
 * @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->name

    2-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()第二个参数就是 传的 地址值,这样的话,如果知道 变 量 的 地 址 变量的地址 ,可以直接填入。(这一点下面的 仔 细 揣 摩 以 下 两 个 概 念 仔细揣摩以下两个概念 模块 会用的)

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指向a
	update(&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的个人主页,确定不点开看看?

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

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

相关文章

下载安装Microsoft ODBC Driver for SQL Server和配置SQL Server ODBC数据源

1. 下载SQL Server ODBC驱动&#xff1a; Microsoft ODBC Driver for SQL Server - ODBC Driver for SQL Server | Microsoft Learn 2. 安装SQL Server ODBC驱动&#xff1a; 运行安装程序&#xff0c;出现如下图所示页面&#xff1b; 选择下一步&#xff1b;选择我同意许可协…

git学习——第2节 时光机穿梭

我们已经成功地添加并提交了一个readme.txt文件&#xff0c;现在&#xff0c;是时候继续工作了&#xff0c;于是&#xff0c;我们继续修改readme.txt文件&#xff0c;改成如下内容&#xff1a; Git is a distributed version control system. Git is free software. 现在&…

uni——底部弹框显示,底部导航隐藏

案例 在uni-app中&#xff0c;如果你在tabbar页面显示一个底部弹框&#xff0c;底部导航默认是会依旧显示的。如果你想在弹框显示时隐藏底部导航&#xff0c;你可以使用uni.hideTabBar和uni.showTabBar方法来控制底部导航的显示和隐藏。 export default {methods: {openPopup(…

汽车空调工作总结

工作总结 2022年3月加入公司&#xff0c;公司在河南&#xff0c;从事车载空调等相关项目&#xff0c;我的岗位是嵌入式软件工程师&#xff0c;在工作中也遇到了很多机遇和挑战&#xff0c;也学到了非常多的东西&#xff0c;在这里给大家分享下总结经验。 关于工作、公司 毕业…

线上答题活动小程序结合线下大屏复盘总结

线上答题活动小程序结合线下大屏复盘总结 ~ 说来话长&#xff0c;这个活动也接近尾声了&#xff0c;从刚开始着手开发&#xff0c;到现在已过去半年&#xff0c;好不夸张的&#xff0c;当时从4月份开始接触&#xff0c;现在已经十月份了 该小程序我发下主界面截图&#xff0…

ant提供对所有系统属性的访问

ant提供对所有系统属性的访问&#xff0c;就好像这些系统属性已经用 <property>任务定义过一样。 例如&#xff0c;下面的build文件中通过${os.name}获取操作系统名称&#xff0c;通过${java.home}获取Java的安装路径&#xff1a; <project name"demo_project&…

2023年最新版CorelDraw(cdr)软件下载安装教程

CorelDRAW 2023是Corel公司推出的最新版本的图形设计软件。CorelDRAW是一款功能强大的矢量图形编辑工具&#xff0c;被广泛用于图形设计、插图、页面布局、照片编辑和网页设计等领域。 1. 新增的设计工具&#xff1a;CorelDRAW 2023引入了一些全新的设计工具&#xff0c;使用户…

Adobe产品2024

一、软件下载&#xff1a; 二、软件介绍&#xff1a; Adobe公司旗下的产品在影视后期、平面设计等领域有着无可取代的地位。在创意和设计领域中&#xff0c;产品有多达 21 个&#xff0c;包括 Photoshop、Illustrator、InDesign、Premiere Pro、After Effects 和 Acrobat Pro …

LED显示屏系统组成及工作过程

LED显示屏是一种平板显示器&#xff0c;由一个个小的LED模块面板组成&#xff0c;用来显示文字、图像、视频等各种信息的设备&#xff0c;广泛应用于商业传媒、文化演出市场、体育场馆、信息传播、新闻发布、证券交易等不同环境和场景的需要。 LED显示屏系统是基于LED显示屏设备…

【算法训练-回溯算法 零】回溯算法解题框架

抽象地说&#xff0c;解决一个回溯问题&#xff0c;实际上就是遍历一棵决策树的过程&#xff0c;树的每个叶子节点存放着一个合法答案。你把整棵树遍历一遍&#xff0c;把叶子节点上的答案都收集起来&#xff0c;就能得到所有的合法答案。站在回溯树的一个节点上&#xff0c;你…

二维码智慧门牌管理系统升级解决方案

文章目录 前言一、返工返修区域的重要性二、作业流程简化与提高效率三、数据准确性的提升四、易维护性与可扩展性 前言 随着城市的发展和人们生活水平的提高&#xff0c;门牌管理系统也在不断升级。最近&#xff0c;二维码智慧门牌管理系统也迎来了升级解决方案。其中&#xf…

【算法|动态规划No.26】leetcode1745. 分割回文串 IV

个人主页&#xff1a;兜里有颗棉花糖 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【手撕算法系列专栏】【LeetCode】 &#x1f354;本专栏旨在提高自己算法能力的同时&#xff0c;记录一下自己的学习过程&#xff0c;希望…

idea git只查看某个人提交的代码记录

git插件只查看某个人提交的代码记录 右键显示弹框&#xff0c;选择Select in Git Log 展示的页面如下&#xff1a; 按住ctrlenter完成查询

操作系统-进程同步、进程互斥(王道视频p26、课本ch6)

这一节&#xff0c;总的来说&#xff0c;就是引出了 “进程同步”的内在含义 &#xff0c;“进程互斥”&#xff08;有限资源访问&#xff09;的解决方案和原则

顺序表第三节(通讯录基础版)

目录 可以先看一遍第二节在看这个 顺序表&#xff08;第二节&#xff09;实现和解析-CSDN博客 1.顺序表的头文件 2.初始化通讯录 3.添加通讯录 特殊&#xff1a;查找对应姓名的通讯录的序号 4.删除通讯录 5.展示通讯录 6.查找通讯录 7.修改通讯录 8.销毁通讯…

公司注册类型分类标准是怎样的

公司法上的分支机构、分公司、子公司是什么 - 公司法 (一)以公司股东的责任范围为标准分类 以公司股东的责任范围为标准&#xff0c;亦即以公司股东是否对公司债务承担责任为标准&#xff0c;可将公司分为无限责任公司、两合公司、股份两合公司、股份有限公司和有限责任公司。…

调用导致堆栈不对称。原因可能是托管的 PInvoke 签名与非托管的目标签名不匹配。请检查 PInvoke 签名的调用约定和参数与非托管的目标签名是否匹配。

调用方出错提示如下&#xff1a; 调用导致堆栈不对称。原因可能是托管的 PInvoke 签名与非托管的目标签名不匹配。请检查 PInvoke 签名的调用约定和参数与非托管的目标签名是否匹配。 可能原因&#xff1a; 修改之前的C定义&#xff1a; extern "C" __declspec(d…

系统设计 - 我们如何通俗的理解那些技术的运行原理 - 第二部分:CI CD、设计模式、数据库

本心、输入输出、结果 文章目录 系统设计 - 我们如何通俗的理解那些技术的运行原理 - 第二部分&#xff1a;CI CD、设计模式、数据库前言CI/CD第 1 部分 - 带有 CI/CD 的 SDLC第 2 部分 - CI 和 CD 之间的区别第 3 部分 - CI/CD 管道 Netflix Tech Stack &#xff08;CI/CD Pip…

DNSPod十问李攀:程序员如何卖出全国爆火降温杯?

本期嘉宾 李攀 1030Design创始人 李攀&#xff0c;1030Design创始人。带领团队打造了55度杯、讯飞翻译机、“一个天台”等现象级爆款&#xff0c;荣获Reddot、金点设计等多项国内外知名设计奖项&#xff0c;在公司战略、品牌定位、产品创新及新零售等领域有着丰富的经验。北京…

10G SFP+线缆选购指南

凭借低成本和易安装的优势&#xff0c;在10G速率短距离传输中SFP线缆比SFP光模块更受欢迎。本文将从类型、优势、应用和选购指导等方面为您介绍10G SFP线缆&#xff0c;旨在帮助您更快做出购买决策。 10G SFP线缆&#xff1a;定义和类型 SFP线缆是一种高速线缆&#xff0c;两…