当前位置: 首页 > news >正文

C语言进阶知识--自定义类型:结构体

目录

  1. 结构体类型的声明
  2. 结构体变量的创建和初始化
  3. 结构成员访问操作符(补充深化)
  4. 结构体内存对齐
  5. 结构体传参
  6. 结构体实现位段

正文开始

1. 结构体类型的声明

前面我们在学习操作符的时候,已经学习了结构体的知识,这里先系统回顾并深化核心概念——结构体本质是用户自定义的复合数据类型,它打破了基本数据类型(int、char等)只能存储单一类型数据的限制,通过“成员列表”将不同类型的数据封装成一个整体,更贴合现实场景中“对象”的描述需求(如学生包含姓名、年龄等多维度信息)。

1.1 结构体回顾

结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量(如int、char数组、指针,甚至其他结构体),且每个成员拥有独立的内存空间,但整体被视为一个“数据单元”。

1.1.1 结构的声明

结构体的声明语法包含三个核心部分:struct关键字、结构体标签(tag)、成员列表(member-list)和变量列表(variable-list),完整格式如下:

struct tag  // tag:结构体标签,用于标识结构体类型,如Stu、Node
{member-list;  // 成员列表:需指定每个成员的类型和名称,顺序可自定义
} variable-list;  // variable-list:声明结构体时直接定义的变量(可选)

注意点:声明末尾的分号不可省略,否则会导致编译错误(编译器无法识别声明结束)。

例如描述一个学生(包含姓名、年龄、性别、学号),标准声明如下:

// 全局声明:结构体类型struct Stu在整个工程中可见(需注意头文件重复包含问题)
struct Stu
{char name[20];  // 字符数组:存储姓名,长度20可容纳中文姓名(含结束符'\0')int age;        // 整型:存储年龄,范围符合人类年龄逻辑(0-150)char sex[5];    // 字符数组:存储性别(如"男"、"女"、"未知"),避免溢出char id[20];    // 字符数组:存储学号(含字母或特殊符号,如"2023-0818-001")
};  // 分号必须存在,此处未直接定义变量,仅声明类型

深化:结构体声明的作用域与typedef结合

  • 若结构体声明在函数内部(局部声明),则该结构体类型仅在当前函数内有效,外部函数无法使用(如在main函数内声明struct Stu,自定义函数printStu无法识别struct Stu类型),因此实际开发中通常采用全局声明(放在函数外或头文件中)。
  • 可通过typedef对结构体类型重命名,简化后续使用(避免重复写struct关键字),但需注意重命名的时机——必须在结构体类型完整声明后使用,例如:
    // 正确:先声明结构体类型,再通过typedef重命名
    typedef struct Stu
    {char name[20];int age;
    } Stu;  // 此后Stu等价于struct Stu,可直接用Stu定义变量// 错误:不能在匿名结构体内部提前使用重命名后的类型
    typedef struct
    {char name[20];Stu* next;  // 错误:此时Stu尚未定义,编译器无法识别
    } Stu;
    
1.1.2 结构体变量的创建和初始化

结构体变量的创建分为“声明类型时创建”和“单独创建”两种方式,初始化则需遵循“成员顺序匹配”或“指定成员名赋值”的规则,且支持部分初始化(未初始化的成员会被默认赋0值,如字符数组默认填充’\0’,整型默认0)。

方式1:声明结构体类型时直接创建变量

struct Stu
{char name[20];int age;
} s1, s2;  // s1、s2是struct Stu类型的变量,全局有效(若声明在函数外)

方式2:单独创建变量(需先声明结构体类型)

#include <stdio.h>// 全局声明结构体类型
struct Stu
{char name[20];int age;char sex[5];char id[20];
};int main()
{// 1. 按结构体成员顺序初始化(需与成员列表顺序一致)struct Stu s = { "张三", 20, "男", "20230818001" };// 访问成员并打印(后续章节详解访问操作符)printf("name: %s\n", s.name);  // 输出:name: 张三printf("age : %d\n", s.age);   // 输出:age : 20// 2. 按指定成员名初始化(顺序可自定义,未指定的成员默认赋0)struct Stu s2 = { .age = 18, .name = "李四", .id = "20230818002" };// s2.sex未初始化,默认是长度为5的空字符串(所有字符为'\0')printf("sex : %s\n", s2.sex);  // 输出:sex : (空)// 3. 部分初始化(仅初始化前2个成员,后2个成员默认赋0)struct Stu s3 = { "王五", 19 };printf("id : %s\n", s3.id);    // 输出:id : (空,因未初始化)return 0;
}

深化:嵌套结构体的初始化
若结构体成员是另一个结构体(嵌套结构体),初始化时需通过“成员名.嵌套成员名”或按顺序逐层赋值。例如:

// 声明嵌套结构体:地址(包含省、市)
struct Address
{char province[20];char city[20];
};// 声明学生结构体,包含Address类型成员
struct Stu
{char name[20];int age;struct Address addr;  // 嵌套结构体成员
};int main()
{// 按顺序初始化嵌套结构体struct Stu s1 = { "赵六", 21, "江苏省", "南京市" };// 按指定成员名初始化嵌套结构体struct Stu s2 = { .name = "孙七", .addr.province = "浙江省",  // 嵌套成员需用"."逐层指定.age = 22, .addr.city = "杭州市" };printf("s2 addr: %s-%s\n", s2.addr.province, s2.addr.city);  // 输出:s2 addr: 浙江省-杭州市return 0;
}

1.2 结构的特殊声明

结构的特殊声明即“匿名结构体”——声明时省略结构体标签(tag),仅包含struct关键字、成员列表和变量列表。匿名结构体的核心特点是类型不可复用,仅能在声明时创建变量(或通过typedef重命名后复用)。

// 匿名结构体声明:无tag,仅创建变量x(全局变量)
struct
{int a;char b;float c;
} x;  // x是该匿名结构体的唯一变量(若不重命名,无法再创建其他变量)// 另一个匿名结构体(与上面的匿名结构体类型不同)
struct
{int a;char b;float c;
} a[20], *p;  // a[20]:匿名结构体数组;p:指向该匿名结构体的指针

关键问题:匿名结构体的类型独立性
即使两个匿名结构体的成员列表完全相同(如上述两个匿名结构体均包含int a、char b、float c),编译器也会将它们视为完全不同的类型,因此以下代码非法:

p = &x;  // 错误:p指向的匿名结构体类型与x的匿名结构体类型不同

结论:匿名结构体若需复用类型,必须通过typedef重命名,例如:

// 匿名结构体+typedef:重命名为MyStruct,可复用类型
typedef struct
{int a;char b;float c;
} MyStruct;MyStruct x;        // 合法:用重命名后的类型创建变量
MyStruct a[20], *p;// 合法:创建数组和指针
p = &x;            // 合法:类型匹配

1.3 结构的自引用

结构的自引用指结构体成员中包含“指向当前结构体类型的指针”,常用于实现链表、树等数据结构(需存储“下一个节点”的地址)。注意:自引用不能用结构体变量作为成员,只能用指针,否则会导致结构体大小无限递归(结构体包含自身变量,变量又包含结构体,循环嵌套)。

错误的自引用方式
struct Node
{int data;          // 数据域:存储节点数据struct Node next;  // 错误:next是struct Node类型变量,导致无限递归
};
// 问题:计算sizeof(struct Node)时,next的大小包含struct Node,陷入无限循环
正确的自引用方式
struct Node
{int data;          // 数据域struct Node* next; // 正确:next是指向struct Node的指针(指针大小固定为4/8字节,与结构体大小无关)
};
// sizeof(struct Node) = 数据域大小 + 指针大小(如32位系统:4+4=8字节,64位系统:4+8=12字节)

深化:typedef与自引用的结合陷阱
若用typedef对匿名结构体重命名并自引用,需避免“提前使用重命名类型”的错误。例如:

// 错误:匿名结构体内部提前使用Node(此时Node尚未定义)
typedef struct
{int data;Node* next;  // 错误:编译器未识别Node类型
} Node;// 正确:先声明带tag的结构体,再重命名
typedef struct Node  // 先指定tag为Node
{int data;struct Node* next;  // 用struct Node*自引用(此时struct Node已声明)
} Node;  // 重命名为Node,后续可直接用Node*自引用

2. 结构体内存对齐

结构体的内存对齐是C语言的核心考点,本质是编译器为了平衡硬件兼容性和访问性能,对结构体成员的内存地址进行规则化排列的机制。掌握对齐规则是计算结构体大小、优化内存占用的关键。

2.1 对齐规则(深化细节与计算示例)

结构体的对齐规则共4条,需结合编译器默认对齐数(不同编译器默认值不同)理解:

  1. 第一个成员的对齐:结构体的第一个成员必须对齐到“结构体变量起始地址偏移量为0”的位置(即第一个成员的地址 = 结构体变量的地址)。
  2. 其他成员的对齐:从第二个成员开始,每个成员需对齐到“对齐数”的整数倍地址处。
    • 对齐数 = min(编译器默认对齐数, 成员自身大小)
    • 常见编译器默认对齐数:VS默认8,Linux gcc默认无(对齐数=成员自身大小),Mac clang默认8。
  3. 结构体总大小的对齐:结构体的总大小必须是“所有成员对齐数中的最大值”的整数倍(若不足则填充空白字节)。
  4. 嵌套结构体的对齐:若结构体包含嵌套结构体成员,嵌套结构体成员需对齐到“自身成员对齐数最大值”的整数倍地址处;结构体总大小则是“外层成员对齐数最大值 + 嵌套结构体成员对齐数最大值”的整数倍。
结合示例理解对齐计算(以VS编译器为例,默认对齐数=8)

练习1:计算struct S1的大小

struct S1
{char c1;  // 成员1:char(大小1),对齐数=min(8,1)=1,偏移量0(占地址0)int i;    // 成员2:int(大小4),对齐数=min(8,4)=4,需对齐到4的整数倍地址(地址4),地址1-3填充空白(3字节)char c2;  // 成员3:char(大小1),对齐数=1,偏移量5(占地址5)
};
// 步骤1:计算成员占用的地址范围:0(c1)、4-7(i)、5(c2)→ 已用地址0-7(共8字节)
// 步骤2:找最大对齐数:max(1,4,1)=4
// 步骤3:总大小需是4的整数倍,8是4的整数倍 → sizeof(struct S1)=8? (原文档可能笔误,实际VS下S1大小为8)

练习2:计算struct S2的大小(成员顺序优化)

struct S2
{char c1;  // 偏移量0(地址0),对齐数1char c2;  // 偏移量1(地址1),对齐数1(无需填充)int i;    // 对齐数4,需对齐到4的整数倍地址(地址4),地址2-3填充空白(2字节)
};
// 最大对齐数=4,总大小=4(地址0-1:c1、c2;地址4-7:i)→ 总8字节(8是4的整数倍)
// 对比S1和S2:成员完全相同,但S2总大小=8,S1总大小=8(VS下),若成员顺序不同,空白字节数可能不同(如S1若成员为char、char、int,空白2字节;char、int、char,空白3字节)

练习3:计算struct S3的大小(含double类型)

struct S3
{double d;  // double(大小8),对齐数=min(8,8)=8,偏移量0(地址0-7)char c;    // char(大小1),对齐数1,偏移量8(地址8)int i;     // int(大小4),对齐数4,需对齐到4的整数倍地址(地址12),地址9-11填充空白(3字节)
};
// 最大对齐数=8,总大小需是8的整数倍:当前已用地址0-15(共16字节),16是8的整数倍 → sizeof(struct S3)=16

练习4:计算struct S4的大小(嵌套结构体)

struct S4
{char c1;    // 对齐数1,偏移量0(地址0)struct S3 s3;  // 嵌套结构体S3,其成员最大对齐数=8(double的对齐数),需对齐到8的整数倍地址(地址8),地址1-7填充空白(7字节)double d;   // double(大小8),对齐数8,偏移量8+16=24(地址24-31)
};
// 步骤1:S3的大小=16,占地址8-23
// 步骤2:d占地址24-31
// 步骤3:最大对齐数=max(1,8,8)=8,总大小需是8的整数倍:地址0-31共32字节,32是8的整数倍 → sizeof(struct S4)=32

2.2 为什么存在内存对齐?(深化硬件原理)

内存对齐的本质是“硬件限制”与“性能优化”的妥协,具体原因分两类:

  1. 平台兼容性(硬件限制)
    部分硬件平台(如早期ARM、嵌入式芯片)仅支持访问“特定地址”的数据(如只能访问4的整数倍地址的int数据),若数据地址未对齐,会触发硬件异常(如总线错误),导致程序崩溃。而内存对齐可确保结构体成员地址符合硬件要求,实现跨平台兼容。

  2. 访问性能优化
    处理器访问内存时,并非按字节读取,而是按“缓存行”(Cache Line,通常为4字节、8字节或16字节)批量读取。若数据未对齐,可能需要两次缓存读取才能获取完整数据;若对齐,则仅需一次读取。例如:

    • 未对齐场景:int数据存于地址1-4,处理器需先读地址0-3(获取前3字节),再读地址4-7(获取第4字节),两次读取后拼接数据。
    • 对齐场景:int数据存于地址4-7,处理器一次读取地址4-7即可获取完整数据,效率提升一倍。

结论:内存对齐是“用空间换时间”的典型策略——通过填充少量空白字节,换取硬件兼容性和访问性能的提升。

2.3 内存对齐的优化策略(深化实践技巧)

设计结构体时,需在“满足对齐”和“节省空间”之间平衡,核心策略是**“将小尺寸成员集中存放”**,减少空白字节的填充。

以包含char、int、double的结构体为例:

  • 优化前(成员顺序:char、int、double):
    对齐数分别为1、4、8,总大小=1(char)+3(空白)+4(int)+0(空白)+8(double)=16字节(最大对齐数8,16是8的整数倍)。
  • 优化后(成员顺序:char、char、int、double):
    若增加一个char成员,集中存放两个char(共2字节),再放int(4字节)、double(8字节),总大小=2(char*2)+2(空白)+4(int)+8(double)=16字节(与优化前大小相同,但多存储一个成员)。

进阶技巧:修改默认对齐数
当默认对齐数导致内存浪费过多时(如结构体成员均为char,默认对齐数8会导致大量空白),可通过#pragma pack(n)修改默认对齐数(n需为2的幂,如1、2、4、8),修改后需用#pragma pack()还原默认值,避免影响后续代码。

示例:将默认对齐数设为1(取消对齐,按字节连续存储)

#include <stdio.h>
#pragma pack(1)  // 设置默认对齐数为1(所有成员对齐数=自身大小)
struct S
{char c1;  // 偏移0,占1字节int i;    // 偏移1,占4字节(无需填充)char c2;  // 偏移5,占1字节
};
#pragma pack()  // 还原默认对齐数int main()
{printf("%d\n", sizeof(struct S));  // 输出:1+4+1=6(无空白字节)return 0;
}

3. 结构成员访问操作符(补充深化)

结构体成员的访问需通过两种操作符实现,分别对应“结构体变量”和“结构体指针”,本质是“直接访问”与“间接访问”的区别。

3.1 点操作符(.):直接访问结构体变量的成员

语法:结构体变量名.成员名
适用场景:已知结构体变量(非指针)时,直接通过“.”获取成员。
示例:

struct Stu s = { "张三", 20 };
printf("name: %s\n", s.name);  // 正确:s是结构体变量,用.访问name

3.2 箭头操作符(->):间接访问结构体指针指向的成员

语法:结构体指针名->成员名
适用场景:已知结构体指针(如函数传参得到的指针)时,通过“->”间接获取成员(等价于(*指针名).成员名,但更简洁)。
示例:

struct Stu s = { "张三", 20 };
struct Stu* ps = &s;
printf("age: %d\n", ps->age);    // 正确:ps是指针,用->访问age
printf("age: %d\n", (*ps).age);  // 等价:先解引用指针得到变量,再用.访问

注意点:操作符优先级
“.”和“->”的优先级高于算术运算符和赋值运算符,因此在表达式中无需额外加括号。例如:

ps->age += 5;  // 正确:先访问ps->age,再执行+=5
// 等价于 (*ps).age +=5;

4. 结构体传参

结构体传参是函数调用中的常见场景,需在“传值”和“传地址”两种方式中选择,核心差异在于参数传递的开销和成员修改的权限

4.1 两种传参方式对比

传参方式语法示例核心原理优点缺点
传结构体(值传递)void print1(struct S s)将结构体变量的所有成员拷贝到函数栈帧中函数内修改成员不会影响原变量(安全)拷贝开销大(结构体大时性能差),栈空间占用多
传结构体地址(地址传递)void print2(struct S* ps)将结构体变量的地址(4/8字节)拷贝到栈中拷贝开销小(仅传指针),性能好函数内可通过指针修改原变量(需用const保护)

示例代码(原文档基础上补充const保护):

struct S
{int data[1000];  // 大数组:增加拷贝开销,凸显传地址优势int num;
};struct S s = {{1,2,3,4}, 1000};// 传值:拷贝整个结构体(data数组+num,共1000*4+4=4004字节)
void print1(struct S s)
{printf("%d\n", s.num);  // 输出1000,修改s.num不影响原变量
}// 传地址:仅拷贝8字节(64位系统指针大小),用const保护原变量不被修改
void print2(const struct S* ps)
{// ps->num = 2000;  // 错误:const修饰指针,禁止修改指向的内容printf("%d\n", ps->num);  // 输出1000,安全且高效
}int main()
{print1(s);  // 传值:栈帧需分配4004字节,开销大print2(&s); // 传地址:栈帧仅分配8字节,开销小return 0;
}

4.2 传地址的深层优势(深化性能分析)

函数传参的本质是“参数压栈”——将参数值写入函数栈帧(栈空间由高地址向低地址生长)。当传递大型结构体时(如包含1000个int的结构体,大小4004字节):

  • 传值方式:需将4004字节的数据逐字节压栈,耗时较长,且栈空间有限(默认栈大小通常为1-8MB),若结构体过大可能导致栈溢出。
  • 传地址方式:仅需压栈8字节(64位指针),压栈操作瞬间完成,且不会占用过多栈空间,避免栈溢出风险。

结论:无论结构体大小,结构体传参均首选“传地址”方式,若无需修改原变量,需用const修饰指针参数(既保证安全,又不影响性能)。

5. 结构体实现位段

位段(Bit-Field)是结构体的特殊形式,通过“成员名:位数”的语法,将成员的内存占用精确到“比特(bit)”级别,核心作用是节省内存空间(适用于存储仅需少量bit的数据,如状态标志、协议字段)。

5.1 位段的声明规则(深化细节)

位段的声明与结构体类似,但需满足两个特殊规则:

  1. 成员类型限制:位段的成员必须是“整型相关类型”,包括intunsigned intsigned int(C99标准扩展支持charshort等),不能是float、double或指针类型。
  2. 位数指定:成员名后需加“:`位数”,表示该成员占用的bit数(位数不能超过成员类型的总bit数,如char成员最多占8位,int成员最多占32位)。

示例:声明一个存储“用户状态”的位段(共占用1+2+1=4位,即1字节)

struct UserStatus
{unsigned int isOnline : 1;  // 1位:0=离线,1=在线(仅需2种状态,1位足够)unsigned int role : 2;      // 2位:0=普通用户,1=管理员,2=VIP(需3种状态,2位足够)unsigned int isVip : 1;     // 1位:0=非VIP,1=VIP
};
// sizeof(struct UserStatus) = 4字节(因成员是unsigned int,按4字节开辟空间,实际仅用4位)

5.2 位段的内存分配(深化计算与案例)

位段的内存分配遵循“按需开辟”原则,具体规则如下:

  1. 开辟单位:根据成员类型确定开辟单位——int成员按4字节(32位)开辟,char成员按1字节(8位)开辟。
  2. 位分配顺序:同一开辟单位内,成员按“从右向左”(VS编译器)或“从左向右”(gcc编译器)分配bit位,剩余bit位不足时,需开辟新的单位。
  3. 跨平台不确定性:位段的内存分配无统一标准,导致跨平台兼容性差(如位数限制、分配顺序、符号位处理均不确定)。
示例:计算struct A的内存大小(VS编译器,int成员按4字节开辟)
struct A
{int _a : 2;  // 2位:第1个4字节(32位)的bit0-bit1int _b : 5;  // 5位:第1个4字节的bit2-bit6(与_a共用同一4字节)int _c : 10; // 10位:第1个4字节的bit7-bit16(剩余bit足够)int _d : 30; // 30位:第1个4字节剩余32-17=15位,不足30位,需开辟第2个4字节(bit0-bit29)
};
// 开辟2个4字节(共8字节),sizeof(struct A)=8
示例:char类型位段的内存分配(VS编译器,从右向左分配)
struct S
{char a : 3;  // 3位:第1字节的bit0-bit2char b : 4;  // 4位:第1字节的bit3-bit6(剩余1位,不足分配c)char c : 5;  // 5位:开辟第2字节,bit0-bit4char d : 4;  // 4位:第2字节剩余3位,不足分配d,开辟第3字节,bit0-bit3
};
// 开辟3个1字节(共3字节),sizeof(struct S)=3

5.3 位段的跨平台问题(深化实际影响)

位段的跨平台问题主要体现在4个方面,直接影响程序的可移植性:

  1. 符号位不确定:int位段默认是有符号还是无符号,编译器未统一(VS默认有符号,gcc默认无符号)。例如:int _a:1,VS中1表示-1(有符号位),gcc中1表示1(无符号位),导致同一赋值在不同平台结果不同。
  2. 最大位数限制:不同平台的整型大小不同(如16位机器int为16位,32位机器为32位),若位段成员位数超过平台整型大小(如16位机器定义int _a:20),会导致编译错误或数据溢出。
  3. 分配顺序相反:VS按“从右向左”分配bit位,gcc按“从左向右”分配,导致同一赋值的内存数据不同。例如:struct S {char a:3; char b:4;},s.a=1,s.b=2:
    • VS中:a占bit0-bit2(值1→001),b占bit3-bit6(值2→0010),字节数据为00100001(0x21)。
    • gcc中:a占bit5-bit7(值1→001),b占bit1-bit4(值2→0010),字节数据为00100100(0x24)。
  4. 剩余位处理差异:当同一开辟单位的剩余bit位不足时,部分编译器舍弃剩余位(VS),部分编译器利用剩余位(某些嵌入式编译器),导致结构体大小不同。

结论:位段仅适用于“平台固定、对内存占用敏感”的场景(如嵌入式设备、网络协议解析),注重可移植性的程序应避免使用位段(可用枚举或掩码替代)。

5.4 位段的使用注意事项(深化错误案例)

  1. 不能对位段成员取地址:位段成员的起始位置可能不是字节的整数倍(如占bit1-bit3),而内存地址是按字节分配的(每个字节一个地址),因此位段成员无独立地址,无法用&取地址,也不能用scanf直接输入。
    错误示例:

    struct A { int _a:2; };
    struct A sa;
    scanf("%d", &sa._a);  // 错误:cannot take address of bit-field '_a'
    

    正确示例(先存变量再赋值):

    int a = 0;
    scanf("%d", &a);
    sa._a = a;  // 需确保a的值不超过位段位数的范围(如2位最大为3),否则溢出
    
  2. 位段成员的赋值范围限制:位段成员的最大值为“2^位数 - 1”(无符号)或“2^(位数-1) - 1”(有符号),若赋值超过范围,会自动截断高位(仅保留低位)。例如:int _a:2(无符号),赋值5(101),截断后为1(01),导致数据错误。

总结

结构体是C语言中实现“数据封装”的核心工具,从基础的类型声明、变量初始化,到进阶的内存对齐、传参优化,再到特殊的位段应用,每个知识点均需结合“语法规则”和“底层原理”理解:

  1. 声明与初始化:掌握匿名结构体、自引用、嵌套结构体的正确写法,避免typedef陷阱。
  2. 内存对齐:通过对齐规则计算结构体大小,优化成员顺序减少内存浪费,必要时修改默认对齐数。
  3. 结构体传参:首选传地址方式,用const保护数据安全,降低栈开销。
  4. 位段:利用bit级内存分配节省空间,但需注意跨平台问题和使用限制。

掌握这些知识点,不仅能应对考试中的结构体大小计算、传参方式选择等问题,更能在实际开发中设计高效、紧凑的数据流结构(如链表节点、协议帧格式)。

http://www.dtcms.com/a/528649.html

相关文章:

  • OptionMaster Pro:期权数据智能处理系统的设计与实现
  • C. Maximum GCD on Whiteboard
  • 【AI论文】DITING:网络小说翻译评估的多智能体基准测试框架
  • 吉林省软环境建设网站免费开网站系统
  • vulnerable_docker_containement 靶机
  • Docker方式安装Nginx
  • 标签噪声学习:理论与方法详解
  • Docker 部署 Debian 全流程教程
  • 上海做网站公司wordpress 活动网站
  • Bee:从 0 到 1 打造一套现代化的全栈后台管理系统(React + Spring Boot)
  • 计算机操作系统:“抖动”与工作集
  • 数据结构(长期更新)第4讲:单链表
  • C#测试调用OpenXml填充word文档的表格
  • 基于python的网站开发项目做外汇网站代理商
  • 对TCP/IP协议的理解
  • 如何判断“IP+端口“通不通
  • tensorrt c++部署
  • TypeScript 基础类型与接口详解
  • MySQL————mysql connect
  • 能打开各种网站的搜索引擎原神网页设计作业
  • 【SpringCloud】Ribbon(LoadBalancer ) 和 Feign
  • Dockerfile 中 ENTRYPOINT 和 CMD 有什么区别 ?
  • 网站数据库模版深圳网站建设黄浦网络 骗钱
  • vs code 下docker使用方法,以php 项目为示例
  • 番禺网站建设哪里好深圳十大传媒公司
  • 前端常见的设计模式
  • 亚马逊云渠道商:如何通过配置自动替换构建故障自愈的云架构?
  • 豆包 Python 和 Java 的 AI 集成及模型转换
  • 深入解析C++命令模式:设计原理与实际应用
  • 商城网站建设目标上海前十名文化传媒公司