/**
* @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:上述代码
}