C的自定义类型

news2024/11/26 8:25:59

目录

1. 结构体

1.1. 结构体类型的声明

1.1.1. 特殊声明

2. 结构的自引用

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

4. 结构体内存对齐

4.1. 结构体内存对齐

4.2. 修改默认对齐数

5. 结构体传参

6. 结构体实现位段(位段的填充&可移植性)

6.1. 什么是位段

6.2. 位段的内存分配 

2. 枚举

2.1. 枚举

2.2. 枚举常量的优点

3. 联合

3.1. 联合

3.2. 判断大小端

3.3. 联合大小的计算


1. 结构体

首先,为什么要求这些自定义类型呢?在C语言中,其标准已经为我们提供了许多的内置类型,int、char、double等等,但是,有些情况下,人们发现单单靠这些内置类型无法满足现实世界各种复杂的情况,例如,如果我们要描述一本书,我们是不是应该描述它的书名、作者、出版社等等各种信息,我们发现如果此时只有内置类型,是不可能达成类似这种复杂需求的,于是C赋予了程序员们一种权利,可以定义自定义类型。而结构体就是自定义类型中的一种。

1.1. 结构体类型的声明

首先,我们看看结构体的声明是怎样的呢?

// 假如我要描述一本书
struct book
{
	char book_name[20];
	char author_name[10];
	int book_pages;
	//... 各种信息
};   // 注意这里的 ; 不可漏掉

上面的struct book就是一个结构体类型的声明。

1.1.1. 特殊声明

在声明结构体类型时,C标准允许可以不完全声明:

struct
{
	int x;
	int y;
	int z;
}target;     //并且此时只能在这里定义这个类型的变量

struct
{
	int x;
	int y;
	int z;
}*p;   // 定义了一个这个结构体类型指针的变量

上面的类型就是一个匿名结构体类型,有人看到这两个匿名结构体的成员变量完全一样,那能不能这样做呢?

void Test1(void)
{
	p = &x;
}

首先,这样做是不好的。编译器对于匿名结构体的处理是:如果匿名结构体的成员变量一样,编译器也认为它们是不同的类型。

2. 结构的自引用

结构体的自引用简单理解就是:结构体类型中的成员变量包含一个结构体类型的指针变量。

struct Node
{
	int val;
	struct Node next;
};

上面的代码可行吗? 答案是,不可行。为什么呢?struct Node这个自定义类型中包含一个类型为struct Node的成员next,而这个成员也是一个自定义类型struct Node,那它里面也有一个struct Node类型的成员啊,这种无穷包含自己,在编译器看来是一种非法行为,因为此时这个类型的大小无法确定。

正确的自引用是这样的: 

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

而我们直到typedef可以对一种数据类型进行重命名,那么它可以对结构体类型重命名吗?当然可以。例如如下:

typedef struct Node
{
	int val;
	struct Node* next;
}Node;

上面的代码的意思是什么呢?就是定义了一个struct Node的结构体类型,我们对这个结构体类型重命名为 Node,即 Node 等价于 struct Node,它们代表着同一种类型。 

然而,有人看到这里就会突发奇想,他说既然Node和 struct Node代表着同一种类型,那么可不可以这样呢?

typedef struct Node
{
	int val;
	Node* next;
}Node;  // 走到这里才代表对struct Node进行重命名为 Node

首先,说答案,上面这种声明是非法的,因为此时的这个成员变量next的类型还没有完成重命名,也就是先有鸡还是先有蛋的问题,只有走完这个类型重命名语句,才会对struct Node进行重命名为 Node,不可以在重命名之前就使用重命名后的类型。因此正确的命名是如下这种形式:

typedef struct Node
{
	int val;
	struct Node* next;
}Node;

然而,有时候我们会在书中看到这样的声明风格:

typedef struct Node
{
	int val;
	struct Node* next;
}Node,*PNode;

其实很简单,

这里的Node等价于struct Node

PNode就相当于 struct Node*

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

struct Book
{
	char book_name[20];
	int book_pages;
	double price;
}b1 = {"XqianC语言",531,33.3};    // 声明 + 定义了一个struct Book类型的全局变量

struct Node
{
	struct Book b;
	struct Node* next;
}n1 = { {"wangwuLinux",843,55.5},NULL };    // 定义一个结构体嵌套类型的全局变量

void Test3(void)
{
	struct Book b2;  // 定义一个struct Book类型的局部变量
	// 给b2的成员变量赋值
	b2.book_name[20] = "lisiC++";
	b2.book_pages = 632;
	b2.price = 44.4; 
	
	// 定义 + 初始化
	struct Node n2 = { { "cuihuaMySql", 483, 66.6 }, NULL };   // 定义一个结构体嵌套类型的局部变量 
}

4. 结构体内存对齐

4.1. 结构体内存对齐

首先,我们看看下面的这两个类型:

struct type1
{
	char ch1;
	int i;
	char ch2;
};

struct type2
{
	char ch1;
	char ch2;
	int i;
};

现在要求我们计算着两个类型分别是多大,即:

void Test4(void)
{
	printf("struct type1 size = %d\n", sizeof(struct type1));
	printf("struct type2 size = %d\n", sizeof(struct type2));
}

有人一看这两个类型,诶,这两个类型的成员变量除了顺序不一样,其他都是一样的啊,让我算的话,它们的大小难道不应该是 两个 char + 一个 int类型的大小,即等于6吗?OK,让我们看看它的结果是多少呢?

出人意料,诶,怎么回事,不是6就是算了,怎么这两个类型的大小还不一样。为了解决这个问题,我们要引出一个东西,称之为结构体内存对齐规则。

那么结构体内存对齐规则是什么呢?如下:

1. 第一个成员在与结构体变量偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的对齐数 与 该成员变量类型大小 的较小值
vs下默认对齐数为8
3. 结构体总大小为最大对齐数的整数倍。
最大对齐数:所有成员变量类型大小中的最大值。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

有了结构体内存对齐规则,我们就可以分析上面这两个结构体类型的大小为何如此了,分析如下:

声明:为了更好理解下面的图,由于结构体对齐规则导致没有使用的空间用红色表示

对struct type1的分析如下: 

对struct type2的分析如下: 

有了对上面的理解,我们试着去计算下面这个结构体类型的大小:

struct type3
{
    char ch1
	struct type2 t2;
	char ch2;
	int i;
};

为了验证上面结构体成员的偏移量是否与预期正确,我们可以用offsetof,它是一个宏,可以帮助我们计算一个结构体中某个成员变量相对于起始位置的偏移量。

// 原型如下
// 其包含在 #include <stddef.h>
// structName --- 结构体名字
// memberName --- 成员变量名字
size_t offsetof( structName, memberName );
void Test5(void)
{
	printf("ch1 offset: %d\n", offsetof(struct type3, ch1));
	printf("t2 offset: %d\n", offsetof(struct type3, t2));
	printf("ch2 offset: %d\n", offsetof(struct type3, ch2));
	printf("i offset: %d\n", offsetof(struct type3, i));
}

结果如下: 

很显然,符合我们的预期。

那么为什么要存在内存对齐:

1. 平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2. 性能原因: 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
总体来说,结构体的内存对齐是拿空间来换取时间的做法。

那在设计结构体的时候,如果我们既要满足对齐,又要节省空间,该如何做呢?

答案是:让占用空间小的结构体成员尽量集中在一起,例如:

struct type1
{
	char ch1;
	int i;
	char ch2;
};

struct type2
{
	char ch1;
	char ch2;
	int i;
};

struct type1和struct type2的成员变量是一样的,但是它们的大小确是不一样的,前者占12个字节,后者占8个字节,原因就在于struct type2中的较小成员集中在了一起,节省了一定的空间。

4.2. 修改默认对齐数

我们知道,VS下的默认对齐数是8,但是我们却可以显式的修改其默认对齐数。

// 编译器的默认对齐数为8
struct type5
{                 
	char ch;   
	double d;  
};

#pragma pack(4)   // 将编译器的对齐数修改为4
struct type5
{
	char ch;
	double d;
};
#pragma pack()   //  取消设置的对齐数,还原为默认对齐数

#pragma pack(1)   // 将编译器的对齐数修改为1,就意味着不对齐.此时的大小就是9
struct type5
{
	char ch;
	double d;
};
#pragma pack()   //  取消设置的对齐数,还原为默认对齐数

结构体在对齐方式不合适的时候,我们可以自己更改默认对齐数。  

5. 结构体传参

struct Info
{
	int data[1000];
	char* name[1000];
};

// 结构体传参
void print1(struct Info tmp)
{
	//...
}

// 结构体的地址传参
void print2(struct Info* p_tmp)
{
	//...
}

void Test7(void)
{
	struct Info information = { { 1, 2, 3 }, {NULL} };
	print1(information);  // 不推荐,值传递传参的压栈系统开销过大
	print2(&information); // 推荐,址传递传参的压栈系统开销很小
}

对于结构体的传参,我们推荐采用以传结构体地址的方式;因为函数在传参的时候,会将其参数压栈,在时间上和空间上都会有消耗,如果我们传递一个结构体对象,当这个结构体很大的时候,参数压栈的系统开销就会很大,会导致性能的降低,而如果传递一个结构体指针,其大小是固定的(32位下4个字节、64位下8个字节),可以节省系统的开销,一定程度上提高性能。

结论:结构体传参尽量传结构体的地址。

6. 结构体实现位段(位段的填充&可移植性)

6.1. 什么是位段

1. 位段的成员必须是 int unsigned int signed int或者char(char也是属于整形家族)
2. 位段的成员名后边有一个冒号和一个数字。
struct bit
{
	int a : 2;
	int b : 5;
	int c : 10;
	int d : 30;
};

上面就是一个位段,我们可以看到,位段的成员后面有一个冒号和一个数字,这个数字代表着你这个成员占用了几个bit位,例如:a这个成员就会占用2个bit位。那么位段的大小如何计算呢?位段需要进行内存对齐吗?

首先,对于位段我们应该知道,其主要作用是:节省空间;而我们知道结构体的内存对齐是以空间换取时间的一种方式,很显然,这就与位段的初衷相矛盾了,故位段是不会有内存对齐的。

void Test8(void)
{
	printf("bit size = %d\n", sizeof(struct bit));
}

那么上面这个位段是多大呢?

可以看到这个位段共占用了8个字节,的确节省了空间。那么位段究竟是如何分配内存的呢?

6.2. 位段的内存分配 

1. 位段的成员可以是 int、unsigned int、 signed int、  或者是 char( 属于整形家族)类型
2. 位段的空间上是按照需要以 4 个字节( int )或者 1 个字节( char )的方式来开辟的。
3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段

之所以说位段是不跨平台的,是由于当出现上面这种情况时,我们不知道它的内存分配是怎样的,例如:上面剩余了15个bit位,d到底有没有使用它,是不确定的,标准并没有明确规定是否使用这个15个bit。因此,对于位段的使用要小心,如果要求程序具有移植性,那么尽量减少位段的使用。

位段的跨平台问题
1. int 位段被当成有符号数还是无符号数是不确定的。
2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。
3. 位段中的成员在内存中从左向右分配,还是从右向左分,配标准尚未定义(vs下是从右向左分配的)。
4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的(vs下没有利用剩余的空间)

总结:

位段较结构体相比,位段可以达到同样的效果,但是可以很好的节省空间,但由于标准对许多细节并没有明确规定,存在跨平台的问题,其可移植性是有待商榷。

2. 枚举

2.1. 枚举

枚举(enum)是一种用于定义一组相关常量的数据类型。它可以帮助开发人员更清晰地表示某个值的取值范围,并提供更好的可读性和可维护性。

在枚举类型中,我们可以定义一组具体的命名常量,也称为枚举成员。每个枚举成员都有一个与之关联的值,它可以是数字(如整数)或者是其他数据类型(如字符串)。枚举成员之间用逗号隔开。

使用枚举,我们可以通过给定的枚举成员来表示某个特定的取值。这有助于编写更可读的代码,并减少硬编码所带来的错误。此外,枚举还可以作为函数参数、变量和属性的类型,增加代码的类型安全性。

enum color
{
	RED,
	YELLOW,
	BILU
};

上面的enum color 就是一种枚举类型,{}中的内容是枚举类型的可能取值,也称之为枚举常量,这些枚举常量都是有值的,默认从0开始,依次递增1,当然在定义的时候也可以赋初值,例如:

enum color
{
	RED = 5,
	YELLOW = 3,
    BLACK,   // 这里的BLACK没有赋初值,那么就是YELLOW + 1,即等于4
	BILU = 7   // 注意,最后一项的枚举常量不加,
};

2.2. 枚举常量的优点

为什么要使用枚举常量呢?它与#define定义的符号常量有什么区别呢?

枚举常量的优点:

1. 增加代码的可读性和可维护性
2. #define 定义的标识符比较枚举有类型检查,更加严谨。
枚举常量是属于一种枚举类型的,它是具有类型检查的,与符号常量相比更为严谨
3. 防止了命名污染(封装)
4. 便于调试,宏定义的符号常量在预编译阶段就被替换了。
5. 使用方便,一次可以定义多个常量

因此,我们推荐使用枚举常量,以减少宏定义的标识符的使用

3. 联合

3.1. 联合

联合(union)是一种特殊的 自定义类型 ,它可用于在同一内存空间中存储不同的数据类型。它允许使用同一块内存来存储多种类型的数据,但同一时间只能使用其中的一种类型数据。

联合与结构体非常相似,都可以定义多个成员,但联合只分配给它成员中最大的元素所需要的内存空间(需要考虑内存对齐,当最大成员的大小不是最大对齐数的整数倍时,就需要内存对齐)。因此,联合可以通过不同的成员来表示同一块内存中的数据,这使得它在一定程度上可以节省内存空间。

联合体的特征就是:联合的成员共用同一块空间(所以联合也叫共用体),示例如下:

union Un
{
	char ch;
	int i;
};

那么上面这个联合体的大小是多少呢?

void Test10(void)
{
	printf("Un size: %d\n", sizeof(union Un));
}

联合的大小是由其最大的成员决定的,例如上面的这个联合,其最大成员是一个int类型,又因为此时这个联合体的最大对齐数就是4,因此上面这个联合的大小就是4。 

void Test11(void)
{
	union Un u;
	printf("u address: %p\n", &u);
	printf("ch address: %p\n", &(u.ch));
	printf("i address: %p\n", &(u.i));
}

可以发现,联合对象和它的成员的地址是一样的,也就是说,其内存分配如图所示:

联合的特定是成员共享同一块空间,但这也限制了在同一时刻只可以使用一种成员。例如:

union Un
{
	char ch;
	int i;
};
void Test12(void)
{
	union Un u;
	u.i = 0x11223344;
	u.ch = 0x66;
}

在使用联合时,改变其中一个成员变量,另一个成员变量也会被修改,因此一般情况下,联合在某一时刻下是单独使用一个成员变量的 。

3.2. 判断大小端

什么叫大端,什么叫小端呢?

大端(Big Endian)和小端(Little Endian)是用于描述存储和处理多字节数据的方式。

大端字节序(Big Endian)是指将最高有效字节(Most Significant Byte,MSB)存储在最低地址处,最低有效字节(Least Significant Byte,LSB)存储在最高地址处。也就是说,数据的高位字节存储在低位地址,低位字节存储在高位地址。

小端字节序(Little Endian)则相反,它是指将最低有效字节(LSB)存储在最低地址处,最高有效字节(MSB)存储在最高地址处。也就是说,数据的低位字节存储在低位地址,高位字节存储在高位地址。

void Test13(void)
{
	int i = 0x12345678;
	// 对于0x12345678的大端字节序和小端字节序
	// 低地址 <---------------> 高地址

	// 大端字节序:
	// 0x12 0x34 0x56 0x78

	// 小端字节序:
	// 0x78 0x56 0x34 0x12
}

利用联合的特性(其成员共有同一块空间),我们就可以判断某个机器下是大段还是小端存储,那么如何判断呢?

union Un
{
	char ch;
	int i;
};

void Test14(void)
{
	union Un u;
	u.i = 1;
	// 此时如果u.ch == 1,那么就是小端;如果u.ch == 0,那么就是大端
	if (u.ch == 1)
		printf("小端存储\n");
	if (u.ch == 0)
		printf("大端存储\n");
}

 

3.3. 联合大小的计算

union Un1
{
	char ch[5];
	int i;
};

上面的联合体是多大呢?注意,联合体的的大小首先是要保证能够存放最大成员,其次如果最大成员所占空间大小不是其最大对齐数的整数倍,那么需要内存对齐。例如上面的:

至此,C语言的自定义类型到此结束。 

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

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

相关文章

集丰照明|博物馆照明设计安全保护四大注意事项

博物馆展品是博物馆一切行为的中心&#xff0c;策划、装修、布展、照明、开展、撤展所有过程都围绕珍贵的展品而设置。因此&#xff0c;在整个行为过程中如何安全保护独一无二的珍稀展品成为博物馆展览的首要课题&#xff0c; 博物馆照明设计 必须优先处理好安全保护展品和更…

问题 X: 阿牛的EOF牛肉串(分类讨论)

算法如下&#xff1a; eg.f(n,o)表示的是以N为长度&#xff0c;以O为结尾的合法字符串个数 代码实现&#xff1a;

拿捏面试官,高频接口自动化测试面试题总结(附答案)狂收offer...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 面试题&#xff1…

基于水循环算法的无人机航迹规划-附代码

基于水循环算法的无人机航迹规划 文章目录 基于水循环算法的无人机航迹规划1.水循环搜索算法2.无人机飞行环境建模3.无人机航迹规划建模4.实验结果4.1地图创建4.2 航迹规划 5.参考文献6.Matlab代码 摘要&#xff1a;本文主要介绍利用水循环算法来优化无人机航迹规划。 1.水循环…

Windows防火墙导致端口访问不通

计算机A的IIS中部署的服务&#xff0c;在另外的计算机B中始终无法访问&#xff0c;ping和telnet都不通。   最初认为是硬件的问题&#xff0c;但是检查了网络后并未发现问题&#xff0c;同时计算机B与其它联网计算机之间也是可以相互访问。   接着测试从计算机A中ping和tel…

【网络安全 --- 文件上传靶场练习】文件上传靶场安装以及1-5关闯关思路及技巧,源码分析

一&#xff0c;前期准备环境和工具 1&#xff0c;vmware 16.0安装 若已安装&#xff0c;请忽略 【网络安全 --- 工具安装】VMware 16.0 详细安装过程&#xff08;提供资源&#xff09;-CSDN博客文章浏览阅读186次&#xff0c;点赞9次&#xff0c;收藏2次。【网络安全 --- 工…

idea 提升效率的常用快捷键 汇总

点击File --> Settings --> keymap便可进入看到 IDEA 提供的快捷键。我们也可以搜索和自定义所有快捷键 下面13个事我常用的快捷键&#xff0c;后面还有全部&#xff0c;可以当做字典来查 1.当前文件下查找&#xff1a;CtrlF 当前文件下替换&#xff1a;CtrlR 2.当前…

FPGA时序分析与约束(8)——时序引擎

一、概述 要想进行时序分析和约束&#xff0c;我们需要理解时序引擎究竟是如何进行时序分析的&#xff0c;包括时序引擎如何进行建立分析&#xff08;setup&#xff09;&#xff0c;保持分析(hold)&#xff0c;恢复时间分析(recovery)和移除时间分析(removal)。 二、时序引擎进…

2024王道考研计算机组成原理——输入输出系统

7.1.1 输入输出系统和几种IO控制方式 输入设备&#xff1a;把数据从主机外部输入主机内部 输出设备&#xff1a;把数据从主机内部输出到主机外部 现在的IO接口(芯片)通常被集成在南桥芯片的内部 DMA接口其实也是IO接口(芯片)的一种&#xff0c;磁盘准备的数据先一个字一个字…

VS2022将编码方式设置为UTF-8的一种方法

一、首先在VS2022的菜单栏中找到Tools/Customize。 二、在弹出的对话框中选择Commands标签&#xff0c;Menu bar中选择File&#xff0c;点击Add Command按钮。 三、在弹出的Add Command对话框中选择左侧Categories栏中选择File&#xff0c;在Commands栏中选择Advanced Save Opt…

吴恩达《机器学习》2-5->2-7:梯度下降算法与理解

一、梯度下降算法 梯度下降算法的目标是通过反复迭代来更新模型参数&#xff0c;以便最小化代价函数。代价函数通常用于衡量模型的性能&#xff0c;我们希望找到使代价函数最小的参数值。这个过程通常分为以下几个步骤&#xff1a; 初始化参数&#xff1a; 随机或设定初始参数…

帆软report JS实现填报控件只能填写一次

效果 方法&#xff1a; 代码&#xff1a; if(this.getValue()!"")//判断这个控件框是否有值&#xff0c;这里是不为空{this.setEnable(false)}//不为空&#xff0c;则不能再修改else{this.setEnable(true)}//为空&#xff0c;可以编辑

vue2【Options 选项API、mixin混入】,vue3【Composition 合成API、hooks】

目录 逻辑组合/复用机制 mixin混入状态复用【官方不推荐使用】 生命周期合并 同名覆盖 难溯源 hooks钩子【只能在setup生命周期中用】 ref ()、reactive()useState computed()useMemo 自定义&#xff1a; useXxx 示例 Vue2 &#xff1a;Options API选项类型&#x…

代码审计-锐捷NBR路由器 EWEB网管系统 远程命令执行

那天下着很大的雨&#xff0c;母亲从城里走回来的时候&#xff0c;浑身就是一个泥人&#xff0c;那一刻我就知道我没有别的选择了 出现漏洞的文件在 /guest_auth/guestIsUp.php 审查源码我们发现通过命令拼接的方式构造命令执行 构造payload&#xff1a; /guest_auth/guestI…

基于闪电搜索算法的无人机航迹规划-附代码

基于闪电搜索算法的无人机航迹规划 文章目录 基于闪电搜索算法的无人机航迹规划1.闪电搜索搜索算法2.无人机飞行环境建模3.无人机航迹规划建模4.实验结果4.1地图创建4.2 航迹规划 5.参考文献6.Matlab代码 摘要&#xff1a;本文主要介绍利用闪电搜索算法来优化无人机航迹规划。 …

基于入侵杂草算法的无人机航迹规划-附代码

基于入侵杂草算法的无人机航迹规划 文章目录 基于入侵杂草算法的无人机航迹规划1.入侵杂草搜索算法2.无人机飞行环境建模3.无人机航迹规划建模4.实验结果4.1地图创建4.2 航迹规划 5.参考文献6.Matlab代码 摘要&#xff1a;本文主要介绍利用入侵杂草算法来优化无人机航迹规划。 …

windows8080端口占用

查看端口占用 netstat -ano | findstr “8080”查看占用进程 tasklist | findstr “4664”关闭占用进程 taskkill /f /t /im httpd.exe

关于git推送代码到github远程仓库中文乱码问题,visual studio保存文件默认编码格式问题

中文乱码问题本质上的原因是&#xff1a;二者的编码格式不同。当你用GB2313格式保存一个文件&#xff0c;用utf-8的格式打开&#xff0c;它必然就显示乱码。 据我所知&#xff0c;github上面是utf-8&#xff0c;而visual studio默认保存为GB2312&#xff0c;把代码推送到gith…

ChineseChess.2023.10.29.02

中国象棋残局模拟器 中国象棋残局模拟器ChineseChess.2023.10.29.02_桌游棋牌热门视频

Mac docker+vscode

mac 使用docker vs code 通过vscode 可以使用docker容器的环境。 可以在容器安装gdb, 直接调试代码。 创建容易时候可以指定目录和容易目录可以共享文件。