自定义类型详解

目录

前言

本期内容介绍

一、结构体

1.1什么是结构体?

1.2结构体的声明

1.3结构的特殊声明

1.4结构体变量的定义和初始化

1.5结构体成员的访问

1.6结构体传参

1.7结构体的自引用

1.8结构体内存对齐

1.9为什么有内存对齐?

1.a修改默认对齐数

二、位段

2.1什么是位段?

2.2位段的内存分配

2.3位段的跨平台性

2.4位段的应用

三、枚举

3.1什么是枚举?

3.2枚举类型的定义

3.3枚举的优点

3.4枚举类型的使用

四、联合

 4.1什么是联合?

4.2联合类型的定义

4.3联合体的特点

4.4联合体计算大小


前言

我们以前介绍过数组,他是相同类型元素的集合。那不同类型的元素能不能也被集合在一起呢?其实是可以的!他就是结构体!本期小编将带你彻底搞明白结构体、枚举以及联合!

本期内容介绍

结构体

  结构体类型的声明

  结构体变量的定义和初始化

  结构体成员的访问

  结构体传参

  结构体类型的自引用

  结构体内存对齐

  结构体实现位段

枚举

  枚举类型的定义

  枚举的优点

  枚举的使用

联合

  联合类型的定义

  联合的特点

  联合的大小计算

一、结构体

1.1什么是结构体?

结构是一些值的集合,这些值被称为成员变量。结构体的成员变量可以是不同类型!也即是说结构体是不同类型元素的集合!

1.2结构体的声明

struct tag

{

        member-list;
}variable-list;

tag是自定义类型的标签名!struct是结构体的关键字!花括号里面是描述这个对象的属性,也就是成员属性!下面variable-list是变量列表,可以在这里直接创建结构体变量!要注意的是结构体后面要有一个封号;

ok ,举个栗子(描述一个学生):

我们知道要描述一个学生至少得知道他的姓名、年龄、和性别吧!我们先以这三个描述一个学生(还可以加很多)!

struct Student
{char name[20];char sex[5];int age;
};

这样就描述了一个学生的最基本的信息!注意C语言中没有字符串类型所以用字符数组来存储!还有就是在C语言中char占一个字节,而一个汉字是两个字节,所以性别还得使用一个字符数组存储!结构体的成员变量可以是一般的类型的的变量、数组、指针以及其他结构体!

OK我们来初始化一个实例出来看看:

struct Student
{char name[20];char sex[5];int age;
};int main()
{struct Student s1 = { "huahua","男",20 };printf("%s\t%s\t%d\n", s1.name, s1.sex, s1.age);return 0;
}

看结果:

当然也可以在上面变量列表那里创建变量:

struct Student
{char name[20];char sex[5];int age;
}s2 = { "hehe","男",30 };

1.3结构的特殊声明

先来看一段代码:

struct
{int a;float f;char c;
};

我们发现这个结构体没有标签名!其实他叫:匿名结构体!既然匿名他就不能直接在主函数中创建变量!因为他连名字都没有啊!

我们看一下:

那他如何创建变量呢?其实我们可以在变量列表那里创建:

struct
{int a;float f;char c;
}s = {2,3.1f,'h'};

这样编译就不会报错了:

注意:匿名结构体只能用一次!这个和java 中的那个匿名代码块很相似!我以前了解过匿名代码块是开机跑进程的!我们猜测匿名结构也应该是类似的作用!

下面我们再来看一个栗子:

struct
{int a;char c;
}x;struct
{int a;char c;
}*p;int main()
{if (p == &x){printf("true");}return 0;
}

会不会输出true?我们来看一下:

不但没有输出true还报了个警告!说是p与&x的类型不兼容!这是为什么呢?他们命名成员变量都一样啊!其实主要的原因是他们是匿名的,即使成员变量都一样! 由于没有标签名不知道是哪一个类型的!所以此时编辑器会认为上面的两个结构体的类型不同!所以他们的指针也不能匹配!

1.4结构体变量的定义和初始化

其实我们在上面就已经使用过初始化了!我们下面再来看看:

struct Student
{char name[20];//姓名int age;//年龄int id;//学号
}s1 ,s2;//全局变量int main()
{struct Student s3, s4;//局部变量return 0;
}

在声明的时候给一些值就叫初始化!

struct Student
{char name[20];//姓名int age;//年龄int id;//学号
}s1 = {"李华",20,22111001}, s2 = { "小明",21,22111002 };//全局变量int main()
{struct Student s3 = { "张三",19,22111003 }, s4 = { "李四",18,22111004 };//局部变量return 0;
}

1.5结构体成员的访问

上面已经创建了结构体变量,该如何访问这些结构体变量的成员呢?我们就介绍一下结构体成员的访问:. 和  -> 我们在操作符详解里介绍过这两个操作符!我们当时只是说了他是用在结构体中的,但如何使用没有过多介绍下面我们开介绍一下:

. 操作符是结构体变量直接来访问结构体成员变量的!而->操作符是用结构体指针来访问结构体成员变量的!

OK!我们举个栗子:

typedef struct Student
{char name[20];int age;
}Stu;void print1(Stu s)
{printf("%s %d\n", s.name, s.age);
}void print2(Stu* ps)
{printf("%s %d\n", ps->name, ps->age);
}int main()
{Stu s1 = { "张三",20 };Stu s2 = { "李四",40 };print1(s1);print2(&s2);return 0;
}

这个栗子就很好的说明了.和 ->的各自作用!这里还有一个点就是tpyedef 这是个关键字!它的作用就是重命名,我把上面那个结构体类型重命名为Stu为了方便简洁!

1.6结构体传参

我们通过上面的栗子也看到了,结构体传参的时候既传结构体类型,也可以传结构体指针!那哪一种会更好呢?我们来分析一下:

struct S
{int arr[100];char c;
};void print1(struct S s)
{printf("%c\n", s.c);
}void print2(struct S* ps)
{printf("%c\n", ps->c);
}int main()
{struct S s = { {1,2,3},'h' };print1(s);print2(&s);return 0;
}

看结果:

效果一摸一样!那他两在传参是谁好一点呢? 其实地址更好一点!原因如下:

函数传参的时候,参数需要压栈,会有时间和空间上的系统开销!如果你把整个结构体对象传过去那他的空间开销很大,从而导致下效率下降!所以在传参的时候尽量传指针!关于这里如果还有不明白的,请点击函数栈帧的创建和销毁了解详情!

1.7结构体的自引用

结构体的自引用顾名思义就是结构体引用自己!我们看下面代码:

struct Node
{int data;struct Node next;
};

是否正确呢?我们分析一下,假设他是正确的!那他的大小是多少?我分画图分析一下:

这样的写法我们好像永远无法计算出结构体的大小!像套娃一样一直套下去了!那我们该如何做呢?其实名字是自引用,只要让他能找到他自己就行了!我们这里的处理方式是:用结构体指针来记录!到这里相信学过数据结构的朋友一下子就看出来了,这其实就是链表!我们来改一下:

struct Node
{int data;struct Node *next;
};

这样就ok了,我来画个图解释一下这种结构:

这里我们后面还会详解的!这里了解一下!

1.8结构体内存对齐

 我们上面已经大概介绍了结构体,那结构体的大小是多少呢?我们先来看一个例子:

struct s1
{char c1;int i;char c2;
};struct s2
{char c1;char c2;int i;
};int main()
{printf("%d\n", sizeof(struct s1));printf("%d\n", sizeof(struct s2));return 0;
}

我们来看结果之前来分析一下:

我们初步分析,应该是6个字节!看结果:

这好像和我们分析的不一样哎!它两的成员变量就换了一个位置怎么差别这么大呢?怎么回事呢?我们可以使用offsetof这个宏来看一看每个成员距开始位置的大小:

#include<stddef.h>
int main()
{printf("%d %d %d\n", offsetof(struct s1, c1), offsetof(struct s1, i), offsetof(struct s1, c2));printf("%d %d %d\n", offsetof(struct s2, c1), offsetof(struct s2, c2), offsetof(struct s2, i));return 0;
}

看结果:

 两个成员变量就换了一个位置,其实位置居然差别这么大,为什么呢?这里我们就得介绍一下结构体的内存对齐了!

结构体内存对齐规则:

1.结构的第一个成员变量永远放在相较于结构体起始位置偏移量为0处!

2.从第二个开始,后面的每个元素都要对齐到某个对齐数的整数倍处!对齐数:结构体成员自身大小和默认对齐数的较小值!

在VS上默认对齐数是8,而在gcc是没有默认对齐数的(对齐数就是成员的自身大小)!

3.结构体的总大小,必须是最大对齐数的整数倍。最大对齐数:所有成员的对齐数中的最大值!

4.如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数的整数倍(包含嵌套结构体的对齐数)!

什么意思呢?下面我来一一解释:

struct s1
{char c;int i;double d;
};

看结果:

 OK,我们多来看几个:

struct s1
{int arr[3];char c;float f;
};

 

看结果:

再来看一个:

struct s1
{char c;int arr[2];
};struct s2
{char c1;struct s1;int a;
};

看结果:

现在我们再来分析一下上面的那个题:

OK,介绍到这里你可还在疑惑为什么有内存对齐?下面我们就来介绍一下为社么有内存对齐:

1.9为什么有内存对齐?

关于内存对齐主要有两个原因:

1.平台原因(移植原因)

不是所有硬件平台都能访问任意地址上的任意数据的,某些硬件只能在某些地址处取某些特定类型的数据,否则会抛出硬件异常!

2.性能原因(效率原因)

数据结构(尤其是栈)应尽可能的在自然边界上对齐。

原因在于,为了访问未对齐的内存,处理器需要做两次内存访问,而对齐的内存的仅仅需要一次!

(这种思路就是典型的空间换时间)!

什么意思呢?我来画个图解释一下:

 

我们思考能不能设计出一种又省空间又满足内存对齐的结构体呢?其实自习思考一下还是能的:

struct s1
{char c1;int i;char c2;
};struct s2
{char c1;char c2;int i;
};

这是一开始我们举的例子,两个结构体虽然成员变量就差了一个顺序但结构体的大小差的很多!显然我们发现第二个结构体更节省内存空间!观察发现它是将占空间小的char集中放在了一起!其实这就是节省空间的办法:让占内存小的成员尽量集中在一起!

1.a修改默认对齐数

我们上面已经了解了默认对齐数!VS是8,gcc上没有对齐数(默认对齐数就是成员变量自身大小)。默认对齐数能不能被修改呢?答案是 :可以的!如何修改呢?我们下面一起来看看:

这里要介绍一个预处理指令:#pragma 他可以帮我们修改我们的默认对齐数!

OK上个栗子演示一下:

struct s1
{char c1;int i;char c2;
};struct s2
{char c1;char c2;int i;
};

我们一开始默认对齐数是8,他两的结果我们刚刚看完是:12和8!

下面我们把第二个默认对齐书数为1:(改为 1应该就连着放了就应该是6):

#pragma pack(8)
struct s1
{char c1;int i;char c2;
};
#pragma pack(1)
struct s2
{char c1;char c2;int i;
};

 看结果:

再来把两个的默认对齐数全部改成 1:

这与我们一开始分析的一样的,我们一开始分析是默认为1的,而实际上他是有默认对齐数的!

注意:有了#pragma这个预处理指令,我们可以在结构体内存对齐不合适的时候可以自己按需要修改!但不要太随意的修改,要按需求适当的修改!!! 

二、位段

2.1什么是位段?

位段又称位域。是一种以结构体来实现的一种以位(bit)为单位的数据存储结构!它可以把数据存储的紧凑!其实看到这里就明白了,位段时为了节约内存空间而设计的!位段的声明和结构体是很类似的,有两个不同点:

(1)位段的成员必须是 int 、 unsigned int char(后面加的)。

(2)位段的成员名后边有一个冒号和一个数字!

OK,举个栗子:

struct A
{int a : 2;int b : 5;int c : 10;int d : 30;
};

这就是结构体实现的一个位段!这里你肯定会有疑问,结构体有大小,位段是用结构体实现的他也应该有大小!那他的大小是多少呢?我们一起来看看:

struct A
{int a : 2;int b : 5;int c : 10;int d : 30;
};int main()
{printf("%d\n", sizeof(struct A));return 0;
}

8个字节哎!为什么是8个字节呢?我们分析会不会和后面的数字有关呢?答案是肯定的!

位段的位实际上是二进制位!也就是后面的那些数字,他们表示在内存中所占二进制位的个数!

我们分析上面的二进制位加起来也才47个比特位也不够64位(8字节),我们猜测会不会有浪费呢?我们一起来看看位段是如何分配内存的!

2.2位段的内存分配

1、一般情况下,位段的成员是同一类型,不会夹杂不同的类型。

(原因是位锻本身是很不稳定的东西,如果成员的类型不同的话,就会使得变得复杂和充满      不确定!)

2、位段的空间上是按需以4字节(int)或1字节(char)的方式来开辟的。

3、位段涉及很多的不确定因素,位段时不跨平台的,注重可移植性的程序应该避免使用位段。

什么意思呢?我还是来画图解释一下:

那当前32位平台的VS是如何实现位段的内存分配的?我们举个例子来研究一下:

struct S
{char a : 3;char b : 4;char c : 5;char d : 4;
};

 这个位的大小是多少?我们来分析一下:

是不是2个字节就够了呢?看结果:

 

怎么是3个字节呢?是不是也存在浪费空间的的情况呢?我们再来换个思路分析:当某个字节放不下该成员的位时,重新开空间:

 到底是不是这样呢?我们来赋值验证一下:

struct S
{char a : 3;char b : 4;char c : 5;char d : 4;
};int main()
{struct S s = { 0 };s.a = 10;s.b = 12;s.c = 3;s.d = 4;return 0;
}

我们通过当前的分析把每个位都放进去,然后看内存即可:

果然是620304!~这也就证明了在当前VS上位段的内存分配时上述的那样!!!但由于标准并未规定位段的实现所以每个编译器可能不一样!这个只能说明VS是这样!!

2.3位段的跨平台性

我们前面就说过位段时不跨平台的,如果要程序要注重可移植性可跨平台行的话应该避免使用位段!位段为什么不夸平台呢?我们下面一起来看看:

1、int 位段被当成是有符号还是无符号的,这个是不确定的!(这关乎这最高一位是符号位还是数值位).

2、位段中最大的位数是不确定的!(例如早期16位机器int位2个字节(16bit),你如果把在这种平台上的位段放到32位平台上,他就会出错,根本有时候没有那么多位)

3、位段中的成员在内存中是如何分配的(从左到右,还是从右到左)这是标准未定义的!

4、当一个结构体包含有位段 ,第二个位段成员比较大,无法在第一位段剩余空间存储下的时候,是应该舍弃还是继续利用,标准未定义!!!

总结:和结构相比位段可以达到同样的效果,并且位段更加节省空间,但位段的跨平台存在问题!

2.4位段的应用

上面说的位段有很多缺点但为什么还要介绍它呢?他有什么具体应用呢?其实他的作用很大,平时和你对象聊天是就会有位段的运用(IP数据包):这是网络部分的知识,这里画个图了解一下:

三、枚举

3.1什么是枚举?

枚举故名思意就是一一列举,把可能的取值一一列举出来!例如:一周7天是不是可以列举出来,一个人的性别无非三种情况:男、女、保密!是不是也能一一列举出来!

3.2枚举类型的定义

 enum tag

{   

        member 1,

        member 2,

        member 3,

        member 4     

}variable_list;

枚举类型的关键字: enum 和struct 一样都是关键字!而且注意的是枚举除最后一个成员外,其他成员的最后都是逗号,不是封号,最后一个成员的后面没有符号!和结合体一样的是花括号后面有一个封号!!!{}中的member也叫做枚举常量他这里就是所有的取值可能,默认第一个是从0开始,然后依次逐增1,当然也可以在声明的时候赋值!

OK,了解了枚举类型的定义我们来试一下:

enum Clolor
{RED,GREEN,BLUE
};int main()
{printf("%d\n", RED);printf("%d\n", GREEN);printf("%d\n", BLUE);return 0;
}

看结果:

enum Clolor
{RED = 1,GREEN = 5,BLUE
};

看结果:

3.3枚举的优点

这里肯定会想,为社么有枚举?不是#define 定义常量不也可以解决吗?你可能会想会不会有类型检查?的确是,那加上const 不也可以吗!其实不然,枚举存在不光是这个原因!枚举由他独特的优势:

1、增加代码的可读性和可维护性

2、和#define 定义的常量相比,枚举有类型检查,更加严谨!

3、便于调试(#define是直接替换,而枚举会有类型检查。并且#define是看到的代码和调试的代码不一样的,这样会导致调试效率大大降低或调试不出来)

4、使用方便,一次性可以定义多个常量(#define 一次只能定义一个)

3.4枚举类型的使用

这里在使用的时候,顺便验证一下枚举类型的检查类型:

enum Color
{RED = 1,GREEN,BULE
};int main()
{enum Color cc = 8;return 0;
}

在cpp的环境下验证的:

正常使用:

enum Color
{RED = 1,GREEN,BULE
};int main()
{enum Color cc = GREEN;return 0;
}

 这样就不会报错了!!!当然你也可以像结构体一样在变量列表那里创建!

四、联合

 4.1什么是联合?

联合体又称共用体,也是一种特殊的自定义类型。这中类型定义的变量也包含了很多成员,这些成员的总特征是共用一块内存空间,所以联合体也叫共用体!

OK,举个例子:

union Un
{int i;char c;
};

这就是一个联合体,他也是有关键字的:union

4.2联合类型的定义

枚举的定义和上面的两种都差不多:也是有关键字和标签名以及成员的!看看他的语法:

union tag

{

        member_list;

}variable_list;

这里要注意的是成员列表的后面是封号!他和上两个一样花括号后面有一个封号;

ok ,举个例子:


union Un
{int i;char c;
};int main()
{union Un u = { 0 };u.i = 1;u.c = 0;return 0;
}

这就是联合定义和初始化!

4.3联合体的特点

前面也介绍过了!联合的成员是共用一块内存空间的!一个联合体的大小至少为成员中最大的那一个!(共用一块空间得至少放的下最大的哪一个吧)

OK,举个例子验证一下:

union Un
{int i;char c;
};int main()
{union Un u = { 0 };u.i = 0x11223344;u.c = 0x55;return 0;
}

我们调试看一下内存:

介绍到这里我们就可以说一下在数据在内存中的存储那一期中留下的一个问题了:当时验证当前平台是大小端的时候提了一下用联合解决的方法,下面我们就来实现以下:首先回顾一下我们当时的解决方式:

int check_sys()
{int i = 1;return *(char*)&i;
}int main()
{if (check_sys() == 1)printf("小端\n");elseprintf("大端\n");
}

看结果:

下面我们来用联合体实现以一下:

int check_sys()
{union{int i;char c;}u;u.i = 1;return u.c;
}int main()
{if (check_sys() == 1)printf("小端\n");elseprintf("大端\n");
}

 看结果:

这里我设计成了匿名联合体,有朋友会好奇,不是返回时是u.c增么返回值是int 呢? 其实这里返回的是u.c那块空间存储的ASCII值!!!

4.4联合体计算大小

联合体也是有大小的,他的计算规则如下:

1、联合的大小至少是最大成员的大小。

2、当最大的成员大小不是最大对齐数的整数倍的时候,要对齐到最大对齐数的整数倍处!

OK,举个例子:

union u
{char c[5];int i;
};int main()
{printf("%d\n", sizeof(union u));return 0;
}

这个联合体的大小是多少?我们分析一下:

看结果:

 再来看一个:

 

union u
{short s[7];int i;
};int main()
{printf("%d\n", sizeof(union u));return 0;
}

OK,看结果:

 

OK,本期分享就到这里!好兄弟,我们下期再见! 

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

网工内推 | 自动化企业招网工,包吃,最高15K,厂商认证优先

01 影儿集团 招聘岗位&#xff1a;网络工程师 职责描述&#xff1a; 1、负责公司及分支站点网络架构设计规划、组建、优化及日常运维管理工作&#xff1b; 2、负责公司网络安全、网络质量及网络和安全设备等检查与监控&#xff1b; 3、负责网络设备安全策略的配置及优化&#…

Jmeter(四) - 从入门到精通 - 创建网络测试计划(详解教程)

1.简介 在本节中&#xff0c;您将学习如何创建基本的 测试计划来测试网站。您将创建五个用户&#xff0c;这些用户将请求发送到JMeter网站上的两个页面。另外&#xff0c;您将告诉用户两次运行测试。因此&#xff0c;请求总数为&#xff08;5个用户&#xff09;x&#xff08;2…

react搭建在线编辑html的站点——引入grapes实现在线拖拉拽编辑html

文章目录 ⭐前言⭐搭建react ts项目⭐引入grapes 插件⭐结束 ⭐前言 大家好&#xff0c;我是yma16&#xff0c;本文分享关于react搭建在线编辑html的站点。 react 发展历史 React是由Facebook开发的一种JavaScript库&#xff0c;用于构建用户界面。React最初发布于2013年&…

C++/Qt读写ini文件

今天介绍C/Qt读写ini文件&#xff0c;ini文件一般是作为配置文件来使用&#xff0c;比如一些程序的一些默认参数会写在一个ini文件中&#xff0c;程序运行时会进行对应的参数读取&#xff0c;详细可以查看百度ini文件的介绍。https://baike.baidu.com/item/ini%E6%96%87%E4%BB%…

Redis的简介,安装(Linux、Windows),配置文件的修改---详细介绍

Redis基础 Redis是一个基于内存的key-value结构数据库。 基于内存存储&#xff0c;读写性能高适合存储热点数据&#xff08;热点商品、资讯、新闻)企业应用广泛 1、Redis入门 1.1、Redis简介 The open source, in-memory data store used by millions of developers as a …

福布斯发布2023云计算100强榜单,全球流程挖掘领导者Celonis排名17

近日&#xff0c;全球流程挖掘领导者Celonis入选福布斯2023 年云计算 100 强榜单&#xff0c;估值130亿美元&#xff0c;排名第17&#xff0c;Celonis已经是连续三年跻身榜单前20名。 本次榜单由福布斯与Bessemer Venture Partners和Salesforce Ventures联合发布&#xff0c;旨…

SpringBoot整合WebSocket实现定时任务消息推送

在平时项目开发中&#xff0c;肯定有很多小伙伴会需要实现定时向某个页面推送消息的功能&#xff0c;为了解决大家无从下手的问题&#xff0c;加哥今天展示一套简单的代码解决方案。 1.创建WebSocketConfig配置类 在这个类中注入ServerEndpointExporter&#xff0c;这个bean会…

putty使用记录

在官网下载并安装putty 一、SSH 二、FTP open 192.168.1.118 put -r C:\Users\Administrator\Desktop\test /opt/lanren312/test # 上传&#xff08;文件夹&#xff09; get -r /opt/lanren312/test C:\Users\Administrator\Desktop\test2 # 下载&#xff08;文件夹&#xff…

教你如何实现接口防刷

教你如何实现接口防刷 前言 我们在浏览网站后台的时候&#xff0c;假如我们频繁请求&#xff0c;那么网站会提示 “请勿重复提交” 的字样&#xff0c;那么这个功能究竟有什么用呢&#xff0c;又是如何实现的呢&#xff1f; 其实这就是接口防刷的一种处理方式&#xff0c;通…

【linux--->高级IO】

文章目录 [TOC](文章目录) 一、五种IO模型概念1.阻塞IO2.非阻塞IO3.信号驱动IO4.多路复用/多路转接IO5.异步IO 二、非阻塞IO之fcntl应用1.fcntl系统调用接口介绍2.用fcntl实现非阻塞IO 三、多路转接IO之select应用1.select接口介绍2.使用select实现多路转接IOselect的优缺点 四…

算法练习--链表相关

文章目录 合并两个有序链表删除排序链表中的重复元素 1删除排序链表中的重复元素 2环形链表1环形链表2相交链表反转链表 合并两个有序链表 将两个升序链表合并为一个新的 升序 链表并返回。 新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1&#xff1a; 输入&…

JVM面试题

JVM理论 #JVM内存模型# Java内存模型&#xff08;JMM&#xff09;&#xff1f; Java的内存模型决定了线程间的通信方式&#xff0c;JMM的模型是由主存和工作内存构成&#xff0c;两个线程想要正常通信需要将工作内存中的变量刷到主存中&#xff0c;另一个线程才能正确读取得…

SpringBoot 升级内嵌Tomcat

SpringBoot 更新 Tomcat 最近公司的一个老项目需要升级下Tomcat&#xff0c;由于这个项目我完全没有参与&#xff0c;所以一开始我以为是一个老的Tomcat项目&#xff0c;升级它的Tomcat依赖或者是Tomcat容器镜像&#xff0c;后面发现是一个SpringBoot项目&#xff0c;升级的是…

基于SpringBoot+LayUI的宿舍管理系统 001

项目简介 源码来源于网络&#xff0c;项目文档仅用于参考&#xff0c;请自行二次完善哦。 系统以MySQL 8.0.23为数据库&#xff0c;在Spring Boot SpringMVC MyBatis Layui框架下基于B/S架构设计开发而成。 系统中的用户分为三类&#xff0c;分别为学生、宿管、后勤。这三…

SQL ASNI where from group order 顺序 where和having,SQL底层执行原理

SQL语句执行顺序&#xff1a; from–>where–>group by -->having — >select --> order 第一步&#xff1a;from语句&#xff0c;选择要操作的表。 第二步&#xff1a;where语句&#xff0c;在from后的表中设置筛选条件&#xff0c;筛选出符合条件的记录。 …

STM32--EXTI外部中断

前文回顾---STM32--GPIO 相关回顾--有关中断系统简介 目录 STM32中断 NVIC EXTI外部中断 AFIO EXTI框图 旋转编码器简介 对射式红外传感器工程 代码&#xff1a; 旋转编码器工程 代码&#xff1a; STM32中断 先说一下基本原理&#xff1a; 1.中断请求发生&#xff1a…

Profibus-DP转modbus RTU网关modbus rtu和tcp的区别

捷米JM-DPM-RTU网关在Profibus总线侧实现主站功能&#xff0c;在Modbus串口侧实现从站功能。可将ProfibusDP协议的设备&#xff08;如&#xff1a;EH流量计、倍福编码器等&#xff09;接入到Modbus网络中&#xff1b;通过增加DP/PA耦合器&#xff0c;也可将Profibus PA从站接入…

MyBatis操作数据库常见用法总结2

文章目录 1.动态SQL使用什么是动态sql为什么用动态sql标签拼接标签拼接标签拼接标签拼接标签拼接 补充1&#xff1a;resultType和resultMap补充2&#xff1a;后端开发中单元测试工具使用&#xff08;Junit框架&#xff09; 1.动态SQL使用 以insert标签为例 什么是动态sql 是…

golang协程池(goroutine池)ants库实践

golang中goroutine由运行时管理&#xff0c;使用go关键字就可以方便快捷的创建一个goroutine,受限于服务器硬件内存大小&#xff0c;如果不对goroutine数量进行限制&#xff0c;会出现Out of Memory错误。但是goroutine泄漏引发的血案&#xff0c;想必各位gopher都经历过&#…

易服客工作室:初学者的终极WordPress SEO教程(入门指导)

改善WordPress SEO对于获得更多网站流量至关重要。可悲的是&#xff0c;大多数WordPress SEO指南对于新用户来说太技术性了。 如果您认真考虑增加网站流量&#xff0c;则需要注意WordPress SEO最佳做法。 在本WordPress SEO教程中&#xff0c;我们将分享WordPress SEO的主要技…