系列文章目录
提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加
例如:第一章 Python 机器学习入门之pandas的使用
提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
提示:这里可以添加本文要记录的大概内容:
例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。
提示:以下是本篇文章正文内容,下面案例可供参考
一、结构体
概述
结构体是由一批数据组合 而成的结构型数据。每个数据称为结构型数据的"成员" ,其描述了一块内存区间的大小及解释意义
结构体属于用户自定义的数据类型,允许用户存储不同的数据类型
如:学生信息

结构体用于描述某个个体的不同属性信息。
结构体类型声明
语法结构:
c
struct 结构体名{
结构体成员列表
};
//typedef重命名结构体类型
typedef struct 结构体名{
//成员列表
}类型别名字;
typedef struct Student Student_t; // 类型别名
<struct 结构体名> 就是构造数据类型,可以当作类型来使用。
声明只是告诉编译器该如何表示数据,但是它没有让计算机为其分配空间。
typedef冲命名的同时不能创建变量
c
typedef struct Student {
int id;
} Student_t, studentVar; // 这里studentVar也是一个类型别名,不是变量
typedef struct Student {
int id;
} Student_t, studentVar = {10}; //报错
//这时变量只能单独创建
struct Student stu1;
Student_t stu2;
结构体变量定义及初始化
定义
c
//定义结构体类型时顺便创建变量
struct 结构体名{
结构体成员列表
}变量名; //不环保
struct {
结构体成员列表
}变量名; //该类型只能使用这一次,后续不能再用该类型来定义另一个变量。除非再写一遍struct{...}变量名;
//之后创建变量
struct 结构体名 变量名; //推荐方式
结构体变量初始化
花括号括起,逗号隔开,对应类型匹配
c
struct 结构体名 变量名1 = {成员1值,成员2值..} //每个成员都赋值
struct 结构体名 变量名2 = {成员1值,成员2值} //部分成员赋值,按顺序赋予(跳越赋值会出错)
struct 结构体名 变量名3 = {.成员1 = 值1,.成员3 = 值3} //指定成员赋值
struct 结构体名{
结构体成员列表
}变量名1 = {....},变量名2 = {....}; //声明时创建变量并初始胡啊
定义变量之后再赋值,就只能引用单个成员赋值了
注意:以最后初始化的值为准
结构体变量访问和赋值
c
结构体变量名.结构体成员 = 值;
结构体指针名->结构体成员 = 值; // . 和 -> 的优先级与()同级,左到右结合性
strcpy(变量名.成员,"字符串");//(定义过后成员数组的赋值)
结构体变量名.成员.子成员.........最低一级子成员;
如果其成员本身又是一种结构体类型,那么可以通过若干个成员运算符,一级一级的找到最低一级成员再对其进行操作;
可以将一个结构体变量作为一个整体赋值给另一相同类型的结构体变量,可以到达整体赋值的效果;这个成员变量的值都将全部整体赋值给另外一个变量;(只有这一种情况可以对结构体变量整体引用)
不能将一个结构体变量作为一个整体进行输入和输出;
c
#include <stdio.h>
#include <string.h>
struct student{
char name[10];
int age;
int num;
float score;
}stu5 = {"XiaoHu",24,20210555,90.6},stu6 = {.age = 35,.num = 20210666};
//4.创建类型时定义变量并初始化
int main(){
//1.定义变量后访问
struct student stu1;
stu1.age = 20;
stu1.num = 20210111;
stu1.score = 63.0;
strcpy(stu1.name,"XiaoWan");
//stu1.name = "HHHH"; //数组定义结束后不能直接赋值
printf("%s,%d,%d,%f\n",stu1.name,stu1.age,stu1.num,stu1.score);
//2.定义变量时全部初始化
struct student stu2 = {"XiaoHong",23,20210222,85.2};
printf("%s,%d,%d,%f\n",stu2.name,stu2.age,stu2.num,stu2.score);
//3.部分初始化
struct student stu3 = {"XiaoWu",34};
printf("%s,%d,%d,%f\n",stu3.name,stu3.age,stu3.num,stu3.score);
//4.指定初始化
struct student stu4 = {.num = 20210444,.score = 68.2};
printf("%s,%d,%d,%f\n",stu4.name,stu4.age,stu4.num,stu4.score);
printf("%s,%d,%d,%f\n",stu5.name,stu5.age,stu5.num,stu5.score);
printf("%s,%d,%d,%f\n",stu6.name,stu6.age,stu6.num,stu6.score);
//再次访问
strcpy(stu6.name,"XiaoLiu");
stu6.score = 100;
printf("%s,%d,%d,%f\n",stu6.name,stu6.age,stu6.num,stu6.score);
return 0;
}
结构体数组
如果有多个对象,将结构体变量放入到数组中方便维护
语法结构:
c
struct 结构体名 数组名[元素个数] = {{},{},......{}};
c
#include <stdio.h>
#include <string.h>
struct student{
char name[10];
int age;
int num;
float score;
}stu5 = {"XiaoHu",24,20210555,90.6},stu6 = {.age = 35,.num = 20210666};
int main(){
struct student arr[3] = {{"XiaoWan",23,20210000,99.9},{"XiaoWu",24,20211111,88.8},{"XiaoBai",25,20212222,77}};
for(int i = 0; i < 3; i++){
printf("%s,%d,%d,%f\n",arr[i].name,arr[i].age,arr[i].num,arr[i].score);
}
strcpy(arr[0].name,"DaHuang");
arr[0].age = 100;
arr[0].num = 20215555;
arr[0].score = 66.6;
printf("%s,%d,%d,%f\n",arr[0].name,arr[0].age,arr[0].num,arr[0].score);
return 0;
}
结构体指针
作用:通过指针访问结构体中的成员
语法
c
struct 结构体名 * 指针名;
利用操作符 -> 可以通过结构体指针访问结构体属性
c
#include <stdio.h>
#include <string.h>
struct student{
char name[10];
int age;
int num;
float score;
}stu5 = {"XiaoHu",24,20210555,90.6},stu6 = {.age = 35,.num = 20210666},*p1;//定义指针变量方式1
int main(){
struct student arr[3] = {{"XiaoWan",23,20210000,99.9},{"XiaoWu",24,20211111,88.8},{"XiaoBai",25,20212222,77}};
for(int i = 0; i < 3; i++){
printf("%s,%d,%d,%f\n",arr[i].name,arr[i].age,arr[i].num,arr[i].score);
}
//通过结构体指针访问
struct student *p = arr; // //定义指针变量方式2
strcpy(p->name,"LaoWan");
p->age = 24;
(*p).num = 20216666; //先解引用再访问
printf("%s,%d,%d,%f\n",arr[0].name,arr[0].age,arr[0].num,arr[0].score);
return 0;
}
指针访问的三种形式
c
for(int i = 0; i < 3; i++){
printf("%s,%d,%d,%f\n",arr[i].name,(*(arr + i)).age,(arr + i)->num,arr[i].score);
}
//通过结构体指针访问
struct student *p = arr;
for(int i = 0; i < 3; i++){
printf("%s,%d,%d,%f\n",p[i].name,(*(p + i)).age,(p + i)->num,p[i].score);
}
结构体嵌套结构体
含义
结构体中的成员可以是另一个结构体
语法
c
struct 结构体名{
struc 结构体名 成员名;
};
自引用
c
#include <stdio.h>
struct Node{
int deta;
struct Node *next;
//struct Node next; //报错
};
//这就是链表的形成
int main(){
printf("%ld\n",sizeof(struct Node)); //16
return 0;
}
结构体的自引用用指针的形式,否则会导致无限循环,系统无法确定结构体长度,非法。
使用typedef,代码如下:
c
#include "stdio.h"
typedef struct student_t
{
struct student_t *a;
short b;
int value;
}student_t;
使用typedef时,如果自引用的话,需要带上标签 "struct student_t ",如果定义成员直接使用"student_t" 则会报错,因为在结构体内部定义结构体变量时,它会去找这个结构体,但该结构体还未声明完成,所以无法被引用和定义
互引用
c
#include <stdio.h>
struct student2; //
struct student1{
int deta;
struct student2 *stu2;
};
struct student2{
char c;
struct student1 *stu1;
};
int main(){
struct student1 a;
struct student2 b;
printf("%ld %ld\n",sizeof(a),sizeof(b));
return 0;
}
注意两点:
- 和自引用一样,必须用指针形式
- 注意该结构体已经先声明好,之后才可以定义。这里使用"不完全声明"来实现。有的平台不加也没问题,不过最好加上。
嵌套别的结构体
比如有两个对象a1、a2,a1包含的成员,a2也都包含,并且a2还有其他的成员。那么a1就可以直接放到a2中去
含义:结构体中的成员可以是另一个结构体
c
1 #include<stdio.h>
2 #include<string.h>
3
4 struct persen
5 {
6 char name[10];
7 int age;
8 char sex;
9 };
10 struct student
11 {
12 struct persen stu;
13 float score;
14 };
15 struct teacher
16 {
17 struct persen tea;
18 char phone[12];
19 };
20
21 int main()
22 {
23 struct student s;
24 struct teacher t;
25 struct teacher *p = &t;
26
27 strcpy(s.stu.name,"Xiaohua");//嵌套调用方式
28 s.stu.age = 18;
29 s.stu.sex = 'm';
30 s.score = 99.99;
31
32 strcpy(p->tea.name,"Huahua");//嵌套指针调用
33 p->tea.age = 30;
34 p->tea.sex = 'w';
35 strcpy(p->phone,"1991111111");
36
37 printf("student: name: %s, age: %d, sex: %c, score: %f\n",s.stu.name, s.stu.age,s.stu.sex,s.score);
38 printf("teacher: name: %s, age: %d, sex: %c, score: %s\n",t.tea.name, t.tea.age,t.tea.sex,t.phone);
39 return 0;
40 }
如果嵌套的结构体B是在A内部才声明的,并且没定义一个对应的对象实体b,这个结构体B的大小不算进结构体A中。编译会警告
c
#include <stdio.h>
struct student2{
char c;
struct student1{
int deta;
};
//}stu1; //定义实体变量后才算进struct student2的大小
};
int main(){
struct student1 a;
struct student2 b;
printf("%ld %ld\n",sizeof(a),sizeof(b)); //4 1
return 0;
}
嵌套初始化
里层的结构体也用花括号括起
c
struct point{
int i;
int j;
};
struct Node{
int data;
struct point p;
struct Node *next;
}n1 = {1,{2,3},NULL};
struct Node n2 = {20,{4,5},NULL};
结构体大小
字节对齐

不是简单的成员大小相加,编译器会为了内存对齐 在成员之间或末尾插入填充字节
c
#include <stdio.h>;
struct A
{
char a;
int b;
char c;
char d;
};
struct B
{
char a;
short b;
char c;
char d;
};
struct C
{
char a;
double b;
char c;
char d;
};
int main(int argc, const char *argv[])
{
printf("int = %d, %d\n",sizeof(int), sizeof(struct A)); //4 12
printf("short = %d, %d\n",sizeof(short), sizeof(struct B)); //2 6
printf("double = %d, %d\n",sizeof(double), sizeof(struct C)); //8 16
return 0;
}
字节对齐
正确理解结构体大小,需要掌握对齐规则。
-
含义:字节对齐主要是针对结构体而言的,通常编译器会自动对其成员变量进行对齐,以提高数据存取的效率;
-
为什么需要内存对齐?
- 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
- 性能原因: 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
- 总的来说,就是拿空间换取时间的做法
普通类型结构体大小的计算
假设某个结构体中元素从上到下排列的类型是:char、int、char、char、double、int *、char [2]、int[3]
| 元素类型 | 自身对齐(元素类型的大小) | 默认对齐(系统默认,通常32位-4,64位-8) | 有效对齐(自身和默认的较小值) | 存放地址 |
|---|---|---|---|---|
| char | 1 | 8 | 1 | 0 |
| int | 4 | 8 | 4 | 4~7 |
| char | 1 | 8 | 1 | 8 |
| char | 1 | 8 | 1 | 9 |
| double | 8 | 8 | 8 | 16~23 |
| int* | 4 | 8 | 4 | 24~27 |
| char[2] | 1 | 8 | 1 | 28~29 |
| int[3] | 4 | 8 | 4 | 32~43 |
| 0~43共44字节,最大有效对齐数是8,8的倍数48;最终结构体大小:48字节 |
存放地址偏移量:首元素偏移值为0。当前偏移量 = 上一个成员的偏移量 + 上一个成员的大小。如果当前偏移量不是当前有效对齐值的倍数,要填充到下一个倍数。
最终结构体大小是最大有效对齐的倍数。
静态数组:成员数组的大小就是 元素大小 × 元素个数,自身对齐值等于元素的对齐值。
c
#include <stdio.h>
#include <string.h>
struct student{
char a; //偏移0,占1字节
int b; //偏移1,数值4的倍数,填充到4,占4~7共4字节,
char c; //偏移8,占1字节
char d; //偏移9,占1字节
double e; //偏移10,不是8的倍数,填充到16,占16~23共8字节
int *p; //偏移量16+8=24,占24~27共4字节
char s[2]; //偏移量24+4=28,占28~29共2字节
int arr[3]; //偏移量28+2=30,不是4的倍数,填充到32,占32~43共12字节
}; //8的倍数48,
int main(){
printf("%ld\n",sizeof(struct student)); //48
return 0;
}
嵌套结构体大小的计算
遵循与普通结构体相同的对齐规则,但需要将内嵌结构体视为一个整体 类型,该类型的自身对齐数等于其内部所有成员的最大对齐值 ,大小就是该内嵌结构体的实际大小(含内部填充)。
c
struct Inner {
char c; // 1字节,偏移0
int i; // 4字节,偏移1→填充到4,占4字节(4~7)
double d; // 8字节,偏移8,占8字节(8~15)
};
// sizeof(Inner) = 16 (偏移0~15)
// 对齐值 = max(1,4,8)=8
struct Outer {
char ch; // 1字节,偏移0
struct Inner in; // 对齐值8,起始偏移必须为8的倍数 → 偏移8
short s; // 2字节,偏移24 (16+8=24)
};
printf("%ld\n",sizeof(struct Outer)); //32
printf("%d\n",offsetof(struct Outer,ch)); //0
printf("%d\n",offsetof(struct Outer,in)); //8
offsetof(type,member)函数可以返回成员的偏移量
结构体空间优化
将小类型放在一起并并降序排列通常可以最小化填充。
影响结构体大小的其它因素
- 编译器对齐指令
- #pragma pack(n):预处理指令,强制以 n 字节对齐(n = 1,2,4,8,...)。
- attribute((aligned(m)))(GCC):指定结构体或成员的对齐值。
- 例:#pragma pack(1) ,结构体大小变为成员大小之和(但可能降低访问效率)。#pragma pack(0)还原到系统默认对齐数
- 不同平台/编译器差异
- Windows 64 位:long 为 4 字节(LLP64),对齐值不同。
- Linux 64 位:long 为 8 字节(LP64)。
- 嵌入式环境可能默认 1 字节对齐(需确认)。
特殊结构体的大小
c
#include <stdio.h>
struct std{
}; //C 标准要求任何结构体(包括空)大小至少为 1(除 C++ 外),但 GNU C 允许空结构体大小为 0。
struct S {
int n;
char data[]; //柔性数组,sizeof(S)不包含deta占用的空间,只计算其前面成员大小。注意:柔性数组只能在结构体结尾,否则编译报错
//int m;
};
int main(){
printf("%ld\n",sizeof(struct std)); //0
printf("%ld\n",sizeof(struct S)); //4
return 0;
}
结构体传参
首选结构体指针传递
原因:
函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降
结构体在函数中的应用
函数需要多个参数时,可以将所有参数组成一个结构体,定义结构体变量,然后传递结构体变量的指针。
c
#include <stdio.h>
struct student{
char a;
int b;
long c;
int *d;
double e;
};
void handle_video(struct student *);
int main(){
struct student vinfo = {0};
handle_video(&vinfo);
return 0;
}
void handle_video(struct student *p){
printf("%d\n",p->a);
printf("%d\n",p->b);
printf("%ld\n",p->c);
printf("%p\n",p->d);
printf("%f\n",p->e);
}
二、位域
以比特位为单位指定成员占用的存储宽度,从而实现对硬件寄存器、网络协议包或紧凑数据结构的精确控制。
位域的定义
所谓的"位域"是把一个字节中的二进制位分为几个不同的区域,并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。这样就可以把几个不同的对象用一个字节的二进制位域来表示。
位域的一般形式:
c
struct
位域结构体名{
类型 成员名 : 位宽;
};
类型:必须是 int、unsigned int、signed int(C99 起允许 _Bool,C11 起允许 char 等其他整数类型)。
c
struct Flag {
unsigned int a : 1; // 占 1 位,取值范围 0~1
unsigned int b : 2; // 占 2 位,取值范围 0~3
unsigned int c : 3; // 占 3 位,取值范围 0~7
// unsigned int d : 33; //报错,位宽不能超过类型宽度
};
不能对位域成员使用 & 运算符(因为没有字节地址)。
内存布局与对齐规则
-
成员是同一类型:按照类型大小开辟存储单元,一次放置位域成员。相邻的同类型成员总宽度不超过开辟的存储单元,它们会被紧凑地放在同一个存储单元内。
-
若下一个位域的位宽无法放入当前存储单元的剩余空间,则填充当前单元剩余位(通常为零),并从下一个存储单元开始放置。
-
可强制填充
c
struct {
unsigned int a : 4; //开辟一个类型大小存储单元:4字节
unsigned int b : 6;
unsigned int c : 7; //三个同一类型,总位数小于4字节,全部放入一个单元
} s;
c
struct {
unsigned int a : 28; // 开辟一个类型大小存储单元:4字节
unsigned int b : 8; // 当前单元剩余 4 位,不够放 8 位,另开一个存储单元,上个单元剩余的位填充0
} s; //s的大小8字节
c
struct {
unsigned int a : 4;
unsigned int : 0; // 强制对齐到下一个存储单元的开始,将当前存储单元剩余的位全部填充,并跳到下一个存储单元。
unsigned int b : 8; //b 从新的 4 字节单元开始。
} s;
c
struct {
unsigned short a : 4; // 可能使用 unsigned short 作为单元(2 字节)
unsigned int b : 8; // 可能重新从 int 单元开始
} s;
缺点 :不同编译器的处理可能不同:有的会将 a 和 b 分开放入不同大小的单元,导致填充;有的会升级为统一类型。避免混合类型以保证可移植性。
移植性差,跨平台的程序最好不要使用位域
定义变量及初始化
c
#include <stdio.h>
struct student{
char a : 2;
char b : 3;
int c : 27;
}D = {1,2,9};//4
int main(){
printf("%ld\n",sizeof(struct student));
//1
struct student A = {1,2,98};
printf("%d,%d,%d\n",A.a,A.b,A.c);
//2
struct student B = {.a = 2,.c = 88,.b = 4};
printf("%d,%d,%d\n",B.a,B.b,B.c); //-2 -4 88 2超出有符号两位的表示范围(-2~1),4超出了3位的表示范围(-4~3),按照 补码 规则截断存储
//3
struct student C;
C.a = 1;
C.b = 3;
C.c = 77;
printf("%d,%d,%d\n",C.a,C.b,C.c);
printf("%d,%d,%d\n",D.a,D.b,D.c);
return 0;
}
三、共用体
在C语言中,不同数据类型的数据可以使用共同的存储区域,这种数据构造类型称为共用体 ,简称共用,又称联合体 。
与结构体两者在本质上的不同仅在于使用你内存的方式上
语法结构
c
union 共用体名{
类型 成员变量;
};
共用体定义及初始化
c
#include <string.h>
struct person{
char name[20];
int age;
char sex;
char phone[12];
};
union un{
int num;
char ch;
struct person student;
struct person teacher;
}C = {
.student.name = "XiaoBai",
.student.age = 21,
.student.sex = 'g',
.student.phone = "11111111111"
}; //方式1
int main(){
union un A;
A.num = 97; //方式2
printf("%d %c\n",A.num,A.num); //97 a
//注意:避免同时使用不同成员,赋值哪个用哪个
union un B = { //方式3
.student.name = "XiaoWang",
.student.age = 25,
.student.sex = 'm',
.student.phone = "12345678901"
};
printf("%s %d %c %s\n",B.student.name,B.student.age,B.student.sex,B.student.phone);
printf("%s %d %c %s\n",C.student.name,C.student.age,C.student.sex,C.student.phone);
return 0;
}
特点 :
一个被使用时,其它几个不使用
共用体大小
- 至少是最大成员的大小
- 并且要对齐到最大对齐数的整数倍
c
char name[20];
int age;
char sex;
char phone[12];
};
union un{
int num;
char ch;
struct person student;
struct person teacher;
}C = {
.student.name = "XiaoBai",
.student.age = 21,
.student.sex = 'g',
.student.phone = "11111111111"
};
int main(){
printf("%ld\n",sizeof(struct person));
printf("%ld\n",sizeof(union un));
return 0;
}
共用体判断电脑的大小端存储方式
c
int check_sys(){
union{
char c;
int i;
}u;
u.i = 1;
return u.c; // 返回1是小端,返回0是大端
}
共用体数组
c
#include <stdio.h>
#include <string.h>
struct person{
char name[20];
int age;
char sex;
char phone[12];
};
union un{
int num;
char ch;
struct person student;
struct person teacher;
}C = {
.student.name = "XiaoBai",
.student.age = 21,
.student.sex = 'g',
.student.phone = "11111111111"
},
D = {
.num = 20
};
int main(){
union un arr[10] = {C,D};
printf("%s\n",arr[0].student.name);
printf("%d\n",(arr + 1)->num);
printf("%d\n",(*(arr + 1)).num);
return 0;
}
共用体指针
c
#include <stdio.h>
#include <string.h>
struct person{
char name[20];
int age;
char sex;
char phone[12];
};
union un{
int num;
char ch;
struct person student;
struct person teacher;
}C = {
.student.name = "XiaoBai",
.student.age = 21,
.student.sex = 'g',
.student.phone = "11111111111"
},
D = {
.num = 20
};
int main(){
union un arr[10] = {C,D};
printf("%s\n",arr[0].student.name);
printf("%d\n",(arr + 1)->num);
printf("%d\n",(*(arr + 1)).num);
union un *p = &C;
p->student.age = 23;
p->student.sex = 'm';
printf("%d %c\n",C.student.age,p->student.sex);
return 0;
}
四、枚举
有些变量只有几种可能的取值(固定),比如:一周有七天、一年有四季、在枚举的定义中,会将变量一一列出来。
语法结构:
c
enum 枚举名{
枚举成员列表(注意:逗号隔开),也叫枚举常量
};
枚举类型定义及初始化
每个 enum 类型的成员都有一个整数值,如果不指定,则从 0 开始递增。
c
#include <stdio.h>
enum e{
one, //默认值为0,成员值默认从0开始依次向后加1
two,
three = 34, //可以指定值(整形常量表达式),下一个成员值是上一个加1,不能指定浮点数
four,
five
};
enum color{
red,
green,
blue
}co_r = red,co_g = green;
int main(){
enum e a = one; //枚举变量取值只能是枚举常量
enum e b =two;
enum e c = three;
printf("%d %d\n",a,b);
printf("%d %d\n",co_r,co_g);
printf("%d\n",c);
return 0;
}
枚举特点
- 默认第一个成员从0开始,后面成员依次+1
- 有特定值的成员后面,从特定值开始依次+1
- 同一个枚举类型中,成员值可以相同
- 不同的枚举类型中,成员名不可以一样
我们可以使用#define定义常量,为什么非要使用枚举?
枚举的优点:
- 代码的可读性和可维护性
- 和#define定义的标识符比较枚举有类型检查,更加严谨。
- 防止了命名污染(封装)
- 便于调试
- 使用方便,一次可以定义多个常量