网站模版 源码之家山东建站
/*** @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()
第二个参数就是 传的 地址值。
一种是不知道地址,就要通过&
去获取地址:即 变量 变量 变量 去向左结合&
来获得 变量的地址 变量的地址 变量的地址。
另一种 ,如果知道 变量的地址 变量的地址 变量的地址,可以直接填入(如直接填入 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:上述代码
}