《C和指针》读书笔记(第十章 结构和联合)

news2024/11/25 6:52:59

目录

  • 0 简介
  • 1 结构基础知识
    • 1.1 结构声明
    • 1.2 结构成员
    • 1.3 结构成员的直接访问
    • 1.4 结构成员的间接访问
    • 1.5 结构的自引用
    • 1.6 不完整的声明
    • 1.7 结构的初始化
  • 2 结构、指针和成员
    • 2.1 访问指针
    • 2.2 访问结构
    • 2.3 访问结构成员
    • 2.4 访问嵌套的结构
    • 2.5 访问指针成员
  • 3 结构的存储分配
  • 4 作为函数参数的结构
  • 5 位段
  • 6 联合
    • 6.1 变体记录
    • 6.2 联合的初始化
  • 7 总结

0 简介

在C语言中,相同类型的数据元素若想放在一起,可以采用数组,那不同类型的元素呢?

有的计算机语言是不会care这个问题的,比方说python,毕竟python本来就是所谓的弱数据类型的语言;C语言虽然出生较早,但历尽千帆,仍能常年在计算机语言排行榜中名列前茅,其优势之一便是对数据类型和内存的精准把控。而结构和联合,正是这种思想的完美诠释与实践。

在部分的教材和资料中,结构也称为结构体,联合也称为联合体共同体共用体

正所谓“一花独放不是春,百花齐放春满园”,因其可以容纳多种类型的数据,在实际开发中,结构和联合经常合作发力,为项目开发提供了很大的便利。

本篇内容概览

在这里插入图片描述

1 结构基础知识

聚合数据类型能够同时存储超过一个的单独数据。C提供了两种类型聚合数据类型,数组结构。结构是一些值的集合,这些值称为它的成员

数组可以元素可以通过下标访问,这只是因为数组的元素长度相同。但是在结构中情况并非如此。由于一个结构的成员可能长度不同,所以不能使用下标来访问他们。

1.1 结构声明

结构体的声明形式如下:

struct tag {member-list} variable-list;

除了结构标签的不完整声明,所有可选部分不能全部省略,至少要出现两个。这句话有点费解 ,只需要看几个例子便可以明白,在实际开发中,结构体常见的声明方式只有两种,将在下面进行说明。

struct {
	int a;
	char b;
	float c;
}x;

这里声明了一个名叫x的变量,包含三个成员,声明没有什么错误,但是因为拓展性不强,在实际开发中并不常用,较为常用的是以下两种。

struct SIMPLE{
	int a;
	char b;
	float c;
};

这种有了标签,就可以避免重复造轮子,有点像C++中创建的对象。对于具有相同属性的事物,只需要创建一次即可,在具体的使用中,可根据自己的需要进行变量的创建。
还有一种更加方便的方法,也就是采用typedef创建一种新的类型。

typedef struct {
	int a;
	char b;
	float c;
}Simple;

这种方法看起来在定义的时候稍微复杂了一点点,但是在使用的时候方便了一些,举个例子:

#include <stdio.h>
struct SIMPLE{
	int a;
	char b;
	float c;
};
typedef struct {
	int a;
	char b;
	float c;
}Simple;
int main()
{
    //定义结构体变量
	struct SIMPLE s1;
	Simple s2;
	//给结构体变量赋值
	s1.a = 10;
	s1.b = '?';
	s1.c = 3.1415;
	
	s2.a = 20;
	s2.b = '!';
	s2.c = 3.1415;	
	//打印输出
	printf("s1.a = %d\n",s1.a);
	printf("s1.b = %c\n",s1.b);
	printf("s1.c = %f\n",s1.c);
	printf("s2.a = %d\n",s2.a);
	printf("s2.b = %c\n",s2.b);
	printf("s2.c = %f\n",s2.c);
	
	return 0;
}

运行,打印输出:
在这里插入图片描述

可以看到如果采用了typedef关键字来声明结构体,则在定义结构体变量的时候就可以少写一个struct关键字,看似不会简洁太多,但是在大型项目开发中,会省去更多的时间。

1.2 结构成员

到目前为止的例子里,我只使用了简单类型的结构成员。但可以在一个结构外部声明的任何变看都可以作为结构的成员。尤其是,结构成员可以是标量数组指针甚至是其他结构标量一般指的是整型或者浮点型的数据)。
比如:

struct COMPLEX(
	float f;
	int   a[20];
	long  *lp;
	struct SIMPLE s;
	struct SIMPLE sa[10];
	struct SIMPLE *sp;
};

1.3 结构成员的直接访问

结构变量的成员是通过点操作符(.)访问的。点操作符接受两个操作数,左操作数就是结构变量的名字,右操作数就是需要访问的成员的名字。这个表达式的结果就是指定的成员。例如,考虑下面这个声明:

struct COMPLEX comp;

比方说,表达式

((comp.sa)[4]).c

下标引用和点操作符具有相同的优先级,他们的结合性都是从左向右,所以可以省略所有的括号。变成了下面的表达式

comp.sa[4].c

此二者等效。

所以在访问嵌套的数据类型的时候,与普通的数据元素访问没有什么区别。只需要一层一层访问即可。

1.4 结构成员的间接访问

当我们拥有一个指向结构的指针时,该如何访问这个结构的成员呢?首先就是对指针执行间接访问操作。然后使用点操作符来访问它的成员。但是点操作符的优先级高于间接访问操作符,所以在访问结构的成员的时候就出现了下面的情况。
先定义一个指向结构体的指针:

struct COMPLEX *CP;

然后用访问其元素:

(*cp).f

这样的操作符显然过于繁琐,于是在C语言中就出现了->操作符,结构体指针可用来访问结构体成员,如下:

cp->f
cp->a
cp->s

表达式1访问一个浮点数成员,表达式2访问一个数组名,表达式3访问一个结构。

1.5 结构的自引用

结构体中包含一个同类型的结构体是否合法呢?答案是否定的。因为这样可以无穷无尽地包含下去,导致程序无法执行。比如:

struct SELF_REF1 {
	int a;
	struct SELF_REF1 b;
	int c;
};

但是结构体中包含指向同类型结构体的指针是合法的,比如:

struct SELF_REF2 {
	int a;
	struct SELF_REF2 *b;
	int c;
};

因为我们仅仅是多了一个指向同类型结构体的指针,不会出现层层包含,无穷无尽的情况。在实际开发中,经常用来实现一些数据结构,比方说链表,每个节点都可能指向相同类型的下一个节点(有时不止一个)。

1.6 不完整的声明

通常情况下,我们的声明都是完整的,但若是有两个相互包含的结构体,究竟应该先定义哪一个呢?

这个时候,我们的不完整声明就派上用场了,如下所示:

struct B;
struct A {
    struct B *partner;
};
struct B {
    struct A *partner;
};

虽然声明了结构体B,但是并未完全声明,因为结构体成员未给出,主要是没法给出,若是给出A,但A也尚未定义。于是就有了这样的处理方法。

如此一来,就形成了“你中有我,我中有你”的两个结构体。如下图所示:

在这里插入图片描述

1.7 结构的初始化

结构体的初始化方式和数组的初始化很类似。在一对花括号内用逗号分隔,然后再分别对各个成员进行初始化(赋值)即可。如果初始列表的值不够,剩余的结构成员将使用缺省值进行初始化。举个例子:

struct INIT_EX {
	int a;
	short b[10];
	Simple c;
}x = { 10,{1,2,3,4,5},{10,'x', 3.14} };

可以看到成员中有个数组b,我们仅初始化了起始的5个元素,其余元素则会采用缺省值进行初始化,一般情况下初始化为0。如下图所示:

在这里插入图片描述

2 结构、指针和成员

直接或者通过指针访问结构体是相当简单的,因为这样的做法和数组非常类似,但是在稍微复杂一点的情形下,我们又该如何访问该结构体成员呢?
为了更好地阐述结构体,结构体指针,结构体成员之间的关系,先定义相关的结构体。

typedef struct {
	int a;
	short b[2];
} Ex2;
typedef struct {
	int a;
	char b[3];
	Ex2 c;
	struct EX *d;
}EX;

再定义相关的结构体变量。

	EX x = { 10, "Hi", {5, { -1, 25}}, 0};
	EX *px = &x;

2.1 访问指针

来看看px的含义。
表达式px的右值是整个结构体的内容。如下图所示:

在这里插入图片描述
左值很好理解,就是可以接受一个新的值。

2.2 访问结构

要想访问结构,也很简单,直接用间接访问操作符即可,所以表达式*px的右值就是px所指向的整个结构。

表达式的左值,同样也可以接受新值。

2.3 访问结构成员

访问结构成员也是一样,我们先来访问结构体变量x中的变量a和变量b
访问a很简单,可以直接使用表达式px->a
b是个数组,所以px->b表示的是b首元素的地址,表达式*(px->b)px->b[0]访问该数组第一个元素的值,访问后续元素和数组的访问方式类似。如下图所示:

在这里插入图片描述

2.4 访问嵌套的结构

C也是个结构体,要想访问C中的元素,要先通过px->c访问到c结构体,然后再访问其中的元素即可。
比如,px->c.a是访问结构体c的a元素,px->c.b与上述说法一样,是一个指针常量,指向结构体cb数组的首地址,访问b中的元素同样有两种方式,下标访问(px->c.b[0])和间接访问(*(px->c.b))。如下图所示:

在这里插入图片描述

2.5 访问指针成员

现在我们的结构体成员d尚未指向任何结构体,所以先建一个结构体,并把x.d指向它。

	EX y = { 20, "mm", {12, { 5, 7}}, 0 };
	x.d = &y;

现在y也指向了一个结构,整体变成了这样的结构:

在这里插入图片描述
那么要想访问结构体y中的元素,则先要通过px->y访问到y结构体。结构体y中一些元素的访问方法如下:

px->d->a;
px->d->b[0];
px->d->c.a;
px->d->c.b[0];

3 结构的存储分配

结构的存储分配也是一个非常有意思的话题。毕竟C语言偏向底层的语言,很多数据的定义直接关系到分配内存的大小。
例如:

#include<stdio.h>
struct ALIGN1 {
    char a;
    int b;
    char c;
};
struct ALIGN2 {
    int b;
    char a;
    char c;
};

int main()
{
	printf("struct ALIGN1占%d个字节内存\n",sizeof(struct ALIGN1));
	printf("struct ALIGN2占%d个字节内存\n",sizeof(struct ALIGN2));
	return 0;
}

打印输出:

在这里插入图片描述
可以看到,两个基本相同的结构体,仅仅因为数据存储顺序的不同,会导致其占不同的内存。具体的内存分配如下图所示:

在这里插入图片描述
绿色部分表示没有具体含义的空间。

在声明中对结构的成员列表重新排列,让那些对边界要求最严格的成员首先出现,对边界要求最弱的成员最后出现。这种做法可以最大限度地减少因边界对齐而带来的空间损失。

4 作为函数参数的结构

结构体也可以作为函数的参数进行传递。直接传递结构体是合法的,但这种操作并不是很“优雅”。同数组一样,往往采用指针的方式进行传递,不过数组是默认以指针的方式进行传递,而结构体却不是这样。如果直接将结构体变量名称当做实参传入,会直接将整个结构体传入该函数,比较浪费栈空间。

所以我们在定义自定义函数的时候,形参就定义为结构体指针,到时候传入结构体指针即可。比如:

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
typedef struct
{
	char name[10];
	short age;
}people_info;

void get_info(people_info *info)
{
	strcpy(info->name, "Mystic");
	info->age = 22;
}
int main()
{
	//定义结构体变量并初始化
	people_info p1 = {"No Name",25};
	//定义结构体指针
	people_info *p = &p1;
	//重新给结构体定义新值
	get_info(p);
	//打印输出
	printf("%s\n", p->name);
	printf("%d\n", p->age);
	system("pause");
	return 0;
}

我们定义了一个结构体,然后调用get_info函数来给结构体录入个人信息。再返回主函数,验证我们录入的信息是否正确。
打印输出:
在这里插入图片描述
可以看出,我们的函数运行是没有问题的。

5 位段

位段是一个神奇的存在,仅仅从这一点上,就可以看出C语言的设计师为了紧密联系内存,到底花了多少心思。这种设计,就相当于将一个完整的数据分成了若干个部分,每个单独的部分可以表示不同的含义。

有两点需要注意:首先,位段成员必须声明为int、signed int或unsigned int类型。其次,在成员名的后面是一个冒号和一个整数,这个整数指定该位段所占的数目。

举个例子:

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
typedef struct
{
	unsigned int class_1 : 5;
	unsigned int class_2 : 5;
	unsigned int class_3 : 5;
	unsigned int class_4 : 5;
	unsigned int class_5 : 5;
}class_name;

int main()
{
	class_name normal;

	normal.class_1 = 26;
	normal.class_2 = 23;
	normal.class_3 = 30;
	normal.class_4 = 31;
	normal.class_5 = 29;
	printf("%d\n", normal.class_1);
	printf("%d\n", normal.class_2);
	printf("%d\n", normal.class_3);
	printf("%d\n", normal.class_4);
	printf("%d\n", normal.class_5);
	printf("结构体占内存大小为%d个字节\n", sizeof(normal));
	system("pause");
	return 0;
}

比方说我们想存储5个班级的人数,每个班级最多为31人,我们就可以定义个结构体来实现位段,如果不使用位段,即使每个班级的人数定义为char类型的变量,总共也需要5个字节,当我们定义了位段,就可以省去一个字节。

打印输出:在这里插入图片描述
当然,不仅仅是节省存储空间这么简单,在实际中还有其他的妙用。比方说我们需要两个设备之间的通信,发送PWM波,那么我们就需要变量来保存该PWM波的基本属性,包括每组的脉冲数num、占空比duty和总共发送组的数量group。然后需要先将该信息发送给另一个设备,方便该设备做好接收准备,每个数据帧有两个字节,构成如下:
在这里插入图片描述
如果没有位段我们就需要一系列的移位运算(和其他运算),才能得到最终想要发送的数据,但是有了位段,就简单多了,而且不易出错,程序如下所示:

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
union 
{
	struct signal_info
	{
		unsigned int num : 4;     //每组发送的PWM波数量
		unsigned int duty : 7;    //占空比(%)
		unsigned int group : 5;   //总共发送几组
	};
	unsigned int signal_information;
}signal_info_union;


int main()
{
	signal_info_union.num = 1;
	signal_info_union.duty = 50;
	signal_info_union.group = 10;


	printf("%d\n", signal_info_union.signal_information);
	system("pause");
	return 0;
}

注:上述例子用到了联合体,如果对联合体不熟悉,可以先看本文后面的章节。

打印输出:
在这里插入图片描述
我们手动的计算结果也是21281,二者相吻合。

6 联合

在C语言中,变量的定义是分配存储空间的过程。一般每个变量都具有其独有的存储空间,那么可不可以在同一个内存空间中存储不同的数据类型呢?

答案是可以的,使用联合体就可以达到这样的目的。较之于结构体,联合体在实际的开发中出现的频率并不是很高,但这并不是说联合体不重要。

可见,在内存方面,C语言的设计者可谓是下足了功夫,这也是C语言虽饱经风霜,却从未在计算机语言的发展长河中销声匿迹的原因之一。

6.1 变体记录

变体记录可以看作联合的升级版, 变体记录中联合成员是比intfloat更为复杂的结构。

那么,究竟什么是变体记录呢?在网上我找到了这样的一番描述。

若记录是由一部分固定不变和另一部分变化部分是随固定部分中的某个数据项的具体取值而定的数据项所组成的称为记录变体。

大概就可以知道是什么意思了,所以这个所谓的变体记录并不是联合体独有的概念。只是一种数据的记录形式

考虑下面的情况:
仓库储存两种货物, 一种是零件(part), 一种是装配件(subassembly), 装配件由一些零件组成。一个零件信息包括零件成本,零件供应商编号;一个装配件信息包括组成装配件的零件数, 以及零件信息。显然, 仓库的一条存货记录(inventory)可能是零件, 也可能是装配件, 并且包含入库日期和操作员编号, 可以用变体记录实现。

// 零件
struct PARTINFO {
    int cost;      // 零件成本
    int supplier;  // 供应商编号
};
// 装配件
struct SUBASSYINFO {
    int n_parts;   // 零件数
    PARTINFO parts[MAXPARTS]; // 每个零件信息
};
// 存货记录
struct INVREC {
    char date[9]; // 入库日期
    int oper;     // 操作员编号
    enum (PART, SUBASSY} type;
    union {
        struct PARTINFO part;
        struct SUBASSYINFO subassy;
    } info;
} record;

我们可以通过以下方式访问存货记录。

record.oper 获取存货操作员编号
if(record.type == PART)
{
	record.info.part.cost获取存货零件成本 
	record.info.part.supplier 获取存货零件供应商编号 
}
else if(record.type == SUBASSY)
{
	record.info.subassy.n_parts 获取存货装配件包含零件数 
	record.info.subassy.parts[0].cost 获取存货装配件第一个零件的成本 
}

6.2 联合的初始化

联合变量可以被初始化,但这个初始值必须是联合第一个成员的类型,而且它必须位于一对花括号里面。例如:

union {
	int a;
	float b;
	char c[4];
}x = { 5 };

我们不能将其初始化为一个浮点值或者字符值。如果给出的初始值是任何其他类型,它就会转换(如果可能的话)为一个整数并赋值给x.a

7 总结

结构的成员可以是标量数组指针。结构也可以包含本身也是结构的成员(除了自己,但它的成员可以是指向这个结构的指针)。

一个联合的所有成员都存储于同一个内存位置。通过访问不同类型的联合成员,内存中相同的位组合可以被解释位不同的东西。联合变量也可以进行初始化,但初始化值必须与联合第1个变量的类型匹配。

在大型项目的开发中,往往会将结构体,联合体,数组等数据类型联合起来使用,还有各种各样让人眼花缭乱的指针,这对我们C语言的技术功底提出了较高的要求。

—END—

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

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

相关文章

TiDB亿级数据亚秒响应查询集群部署

目录 1 集群部署1.1 环境要求1.1.1 操作系统建议配置1.1.2 服务器建议配置 1.2 环境准备1.3 安装TiUP1.3.1 什么是TiUP1.3.2 安装TiUP组件1.3.3 配置TiUP环境1.3.4 检查TiUP 工具是否安装1.3.5 安装 cluster 组件1.3.6 升级cluster组件 1.4 编辑部署文件1.4.1 常见的部署场景1.…

Go语言并发微服务分布式高可用

Go语言并发微服务分布式高可用 Go语言基础 环境安装 命令行输入go&#xff0c;当前操作系统Os环境中依赖于PATH指定的日录们去找命令(可执行文件)windows会优先搜索当前日录&#xff0c;当前日录没有才依赖PATH中指定的日录 环境变量: 操作系统运行环境中提前定义好的变量P…

FreeRTOS简单任务创建和任务删除(基于stm32F407)

1. 实验目的 使用动态方法 xTaskCreate()创建任务&#xff0c;使用vTaskDelete()函数删除任务&#xff1b;创建开始任务start_task&#xff0c;在开始任务中创建其他三个任务&#xff0c;创建task1任务实现LED0每500ms闪烁一次&#xff0c;创建task2任务实现LED1每500ms闪烁一…

Linux C简易聊天室

对于初学者而已&#xff0c;我们学习的网络编程&#xff08;如TCP,UDP编程&#xff09;&#xff0c;我们通常都是在局域网内进行通信测试&#xff0c;有时候我们或者会想&#xff0c;我们现在写的内网网络数据和外网的网络数据有什么不同&#xff0c;我们内网的数据是如何走出外…

notepadd++快捷键记录

记录下 notepadd 常用快捷键 1.搜索 普通搜索&#xff1a;Ctrl F 正则表达式搜索&#xff1a; 查找模式用 正则表达式 。如 A|B|C &#xff0c;搜索多个关键字&#xff0c; 更多正则表达式探索中。 还可以选中 选取范围内 &#xff0c;就会只在鼠标选中区域内查找。 2.区…

Visual Studio Code 1.79 发布

发布模式 - 将工作区中的特定文件和文件夹标记为只读。 在某些开发场景中&#xff0c;将工作区的某些文件夹或文件显式标记为只读会很有帮助。例如&#xff0c;如果文件夹或文件内容由不同的进程管理(例如 node_modules 由 Node.js 包管理器管理的文件夹)&#xff0c;则将它们…

E往无前 | get正确使用姿势!腾讯云大数据ES日志场景优化案例回顾

导语&#xff1a; 随着ELK方案在开源日志分析领域越来越流行&#xff0c;各种业务场景也给ELK方案带来了越来越多的挑战。本文将回顾一次真实客户案例&#xff0c;从使用姿势上&#xff0c;提供一些大集群、多日志主题场景下的集群优化思路。 一、ELK不香了&#xff1f; 我们…

DevOps系列文章之 Docker-compose

一&#xff0c;Docker-compose全集 1&#xff0c;Docker-compose简介 Docker-Compose项目是Docker官方的开源项目&#xff0c;负责实现对Docker容器集群的快速编排。 Docker-Compose将所管理的容器分为三层&#xff0c;分别是工程&#xff08;project&#xff09;&#xff0c…

ChatGLM简介和SSE聊天接口测试效果

开发公司 智谱AI是由清华大学计算机系技术成果转化而来的公司&#xff0c;致力于打造新一代认知智能通用模型。公司合作研发了双语千亿级超大规模预训练模型GLM-130B&#xff0c;并构建了高精度通用知识图谱&#xff0c;形成数据与知识双轮驱动的认知引擎&#xff0c;基于此模型…

java异常 | 处理规范、全局异常、Error处理

文章目录 &#x1f683;异常类型&#x1f3a0;显示声明异常&#xff1a;①&#xff1a;try-catch②方法签名 &#x1f683;异常处理规范⚓️异常包装⚓️异常传递⚓️异常日志记录⚓️异常处理的最佳实践 &#x1f683;全局异常处理⛵️优点&#xff1a;⛵️代码示例&#xff1…

骨传导蓝牙耳机哪个牌子好,列举几款知名的骨传导耳机

​骨传导耳机&#xff0c;顾名思义就是通过骨头传播声音的耳机&#xff0c;由于它在声音传播方式上与传统耳机不同&#xff0c;不需要借助外耳、耳道&#xff0c;也能让耳朵更好地感受到外界的声音&#xff0c;在一些特殊场合下可以让使用者听到环境音。骨传导耳机虽然很小巧轻…

AI实战营:目标检测与MMDetection

目标检测的基本范式 什么是目标检测 目标检测 vs 图像分类 目标检测 in 人脸识别 目标检测 in 智慧城市 目标检测 in 自动驾驶 目标检测 in 下游视觉任务 目标检测技术的演进 基础知识 框、边界框&#xff08;Bounding Box&#xff09; 交并比 Intersection Over Union 目标检…

奇舞周刊第495期:软件高可用实践那些事

记得点击文章末尾的“ 阅读原文 ”查看哟~ 下面先一起看下本期周刊 摘要 吧~ 奇舞推荐 ■ ■ ■ 软件高可用实践那些事儿 本文从高可用落地实践的角度&#xff0c;通过协作效率&#xff0c;技术落地和运营规范等几个层面&#xff0c;阐述了高可用具体实施步骤和落地细节。 浏览…

如何使用微软官方工具制作win11启动盘

windows11 启动盘制作和使用 一、启动盘制作准备二、制作步骤三、对其他电脑安装windows11系统 一、启动盘制作准备 1.至少存储空间为8GB的空白U盘 2.一台电脑 二、制作步骤 1.在官方网站中选择下载工具&#xff0c;选择创建windows11安装&#xff0c;下载完毕之后&#xff…

混淆矩阵、准确率、召回率、漏报率、误报率、F1分数

1、混淆矩阵 在二分类问题中&#xff0c;混淆矩阵被用来度量模型的准确率。因为在二分类问题中单一样本的预测结果只有Yes or No&#xff0c;即&#xff1a;真或者假两种结果&#xff0c;所以全体样本的经二分类模型处理后&#xff0c;处理结果不外乎四种情况&#xff0c;每种情…

ASP.NET Core MVC 从入门到精通之Html辅助标签(一)

随着技术的发展&#xff0c;ASP.NET Core MVC也推出了好长时间&#xff0c;经过不断的版本更新迭代&#xff0c;已经越来越完善&#xff0c;本系列文章主要讲解ASP.NET Core MVC开发B/S系统过程中所涉及到的相关内容&#xff0c;适用于初学者&#xff0c;在校毕业生&#xff0c…

微信小程序快速入门【一】

微信小程序快速入门【一】 文章目录 微信小程序快速入门【一】&#x1f468;‍&#x1f3eb;内容1&#xff1a;背景&#x1f468;‍⚖️内容2&#xff1a;准备工作&#x1f468;‍&#x1f4bb;内容3&#xff1a;新建一个小程序&#x1f349;文末推荐 &#x1f468;‍&#x1f…

有关 python 切片的趣事

哈喽大家好&#xff0c;我是咸鱼 今天来讲一个我在实现 python 列表切片时遇到的趣事 在正式开始之前&#xff0c;我们先来了解一下切片&#xff08;slice&#xff09; 切片操作是访问序列&#xff08;列表、字符串…&#xff09;中元素的另一种方法&#xff0c;它可以访问一…

莱特兄弟的家庭教育

莱特兄弟的三个设计经验 你可以从他们如何使得一辆自行车飞行中学到很多东西 克莱夫汤普森 大家都知道莱特兄弟是第一个实现动力飞行的人——他们的飞机于1903年12月17日在北卡罗来纳州的基蒂霍克起飞。 但是在实现这一突破之前的过程? 这真是令人感兴趣&#xff0c;并且充满了…

【PWN · ret2text | ‘/bin/sh‘写在bss段】[HNCTF 2022 Week1]ezr0p32

目录 前言 一、题目 二、解题过程 payload的构造 三、exp 总结 前言 一直在做libc的中规中矩的题目&#xff0c;遇到一题有点老的类型的题目有些陌生。但其实其中原理比较简单&#xff0c;但是涉及到/bin/sh获取的常规操作&#xff0c;而自己也没整理过&#xff0c;于…