结构体
基础知识
结构是一些值的集合,这些值被称为成员变量;
结构体可以存储不同类型的数据项,而数组中是存储相同类型数据项
声明
struct tag {//struct是关键字,tag是结构体标签名
member-list//成员变量的列
member-list
...
} variable-list ;
//variable-list 结构 变量 ,定义在结构的末尾,最后一个分号之前,可以指定一个或多个结构变量
//结构末尾是要带分号的
#include<stdio.h>
struct book{
int price;
char name[12];
char id[20];
}b4,b5,b6;//b4,b5,b6是全局变量
int main()
{
struct book b1;
struct book b2;
struct book b3;
return 0;
}
特殊的声明
匿名结构体类型
- 声明时省略了结构体标签
- 这种匿名结构体类型只能用上一次
struct
{
int a;
char b;
float c;
double d;
} s;
struct
{
int a;
char b;
float c;
double d;
} *ps;
int main()
{
ps = &s; //错误
return 0;
}
即使成员一样,但编译器照样会把上面两个声明看作是不同的类型,下面的结构体指针就不能存上面的地址
结构体的自引用
- 结构体中不是包含同类型的结构体变量,而是包含同类型结构体的指针
- 一般情况下,tag、member-list、variable-list 这3部分至少要出现2部分
- 结构体的成员可以包含其他结构体,也可以包含指向自己结构体类型的指针;但不能自己包含自己,也不能包含同类型的结构体变量
错误:
struct Node{
int date;
struct Node next;
};
正确:
struct Node{
int date;
struct Node* next;
};
struct N
{
int price;
struct N n;
};
注意:结构体自引用,通常这种指针的应用是为了实现一些更高级的数据结构如链表和树等。
结构体变量的定义(创建)和初始化
//定义(创建)
struct point
{
int x;
int price;
}s1 ; // 声明类型的 同时 创建 变量s1
struct point p1;//创建结构体变量p1
int main()
{
struct point s2;//创建局部变量s2
return 0;
}
//初始化:定义变量的同时赋值
//1
struct point
{
int x;
char c;
}s1 ;
int main()
{
struct point s2 = {32,‘d’};//初始化
return 0;
}
//2
#include<stdio.h>
struct S
{
int x;
char c;
}s1 ;
struct point
{
int x;
struct S s;
char c;
}s1 ;
int main()
{
struct point sa = { 20, {30,'e'} , 'x' };
printf("%d %d %c %c\n",sa.x ,sa.s.x ,sa.s.c ,sa.c);
return 0;
}
注意:访问结构体成员时 用 . 和 ->
. 是针对结构体变量的
-> 是针对结构体指针的
结构体内存对齐
结构体对齐规则:
- 第一个成员放在结构体变量在内存中存储位置的0偏移处开始。
- 其他成员都要对齐放到各自对齐数(成员的大小 和 默认对齐数的 较小值)的整数倍的地址处。(VS中默认对齐数为 8)
- 结构体的总大小是结构体的所有成员的对齐数中最大的那个对齐数的整数倍。(当不满为整数倍时直接提升到整数倍,这可能会浪费一些空间)
- 嵌套的结构体先对齐到 自己的最大对齐数 的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
注意:VS中默认对其数为8,Linux中没有默认对齐数的概念
例子1:计算结构体的大小
#include<stdio.h>
struct S{
char c1;
int i;
double d;
};
int main()
{
struct S s1 = {0};
printf("%d\n",sizeof(s1));
return 0;
}
成员对齐数依次为1,4,8; 最大对齐数为8
例子2:
#include<stdio.h>
struct S{
char c1;
char c2;
int b;
};
int main()
{
struct S s1 = {0};
printf("%d\n",sizeof(s1));
return 0;
}
//占8,且为4的倍数
//结果:8
例3:
struct S{
double d;
char c;
int i;
};
//16
当结构体进行嵌套结构体时:
- 嵌套的结构体先对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
#include<stdio.h>
struct B{
char c1;
int b;
};
struct S{
char c1;
struct B b1;
int b;
};
int main()
{
struct B b1 = {0};
struct S s1 = {0};
printf("%d\n",sizeof(s1));
return 0;
}
//32
//嵌套的结构体先对齐到8,整体大小是16,也是8的整倍数
修改默认对齐数
- 可以用 #pragma pack() 进行默认对齐数的修改
#include<stdio.h>
#pragma pack(2)//默认对齐数改为2
struct S{
char c1;
int b;
char c2;
};
#pragma pack() //2在中间起效,后面表示默认对齐数修改结束,改回8了
int main()
{
struct S s1 = {0};
printf("%d\n",sizeof(s1));
return 0;
}
//结果:8
结论:当你觉得对齐方式不合适的时候,可以自己更改
结构体传参
#include <stdio.h>
struct S
{
int data[1000];
int num;
};
struct S s = {{1, 2, 3, 4}, 1000};
//结构体传参
void print1(struct S s)
{
printf("%d\n", s.num);//用 .
}
//结构体地址传参
void print2(struct S* ps)
{
printf("%d\n", ps->num);//用 ->
}
int main()
{
print1(s); //传结构体
print2(&s); //传地址
return 0;
}
在print1和print2中 首选print2函数,因为:
函数传参的时候,参数是需要压栈的,会产生时间和空间上的系统开销。如果传递一个结构体对象时,结构体过大,参数压栈的系统开销就会很大,从而导致性能的下降。
结论:结构体传参的时候,传结构体的地址
位段
位段的声明和结构体是类似的,但有两个不同:
- 位段的成员必须是char, int , unsigned int 或 signed int
- 位段的成员名后面有 一个冒号和一个数
struct A{
int a:1;//a 成员占1个bit位
int _b:2;//_b 成员占2个bit位
int c:7;//c 成员占7个bit位
};
位段的内存分配
- 位段的空间上是按照需要 以4个字节 或者1个字节(char)的方式来开辟的
#include<stdio.h>
struct A{
//int型是4个字节大小,即32bit
int a:1;//a 成员占1个bit位
int _b:28;//_b 成员占28个bit位
//前面加起来为29,未达32,但后一个加上后大于了32;所以得重新再开启一个int型空间
//4个字节----32bit为
int c:7;//c 成员占7个bit位
};
int main()
{
printf("%d\n",sizeof(A));
return 0;
}
//结果:8
总结:与结构体相比,位段在可以达到同样的效果上又可以更好地节省空间,但存在跨平台上的问题。
枚举
枚举就是进行有限次地一一列举,将可能的取值一一列举
定义格式:
enum 枚举名
{
枚举元素1,
枚举元素2,//这里是用逗号
……
};
//{}中的是枚举常量
这些可能取值都是有值的,未赋值时默认从0开始,依次递增1
举例:
#include<stdio.h>
enum Day //星期
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
int main()
{
printf("%d\n",Wed);
return 0;
}
//结果:2
枚举优点
- 增加代码的可读性和可维护性
- 与 #define 定义的标识符相比,枚举有类型检查,更加严谨
- 有效防止命名污染(封装)
- 便于调试
- 使用方便,一次可以定义多个常量
联合(共用)体
联合类型的定义
这种类型定义的变量也包含一系列的成员,特点是这些成员共用同一块空间
格式:
union [union tag]
{
member definition;
member definition;
...
member definition;
} [one or more union variables];
联合体特点
- 联合体的成员是共用同一块内存空间的。
- 一个联合体变量的大小 至少 是最大成员的大小。
#include <stdio.h>
union Un
{
char c;//1
int i;//4
};
int main()
{
union Un u;
printf("%p\n", &u);
printf("%p\n", &(u.c));
printf("%p\n", &(u.i));
printf("%d\n", sizeof(u));//计算联合体变量的大小
return 0;
}
//3个地址是相同的
//4
联合体初始化
- 在同一时间,只能使用一个成员:因为共用一块空间,改动一个也会将其他值损坏
#include <stdio.h>
union Un
{
char c;//1
int i;//4
};
int main()
{
union Un u;
//union Un u = {10};//这样会给所有成员初始化相同的值10
u.i = 1000;
u.c = 100;
printf("%d\n",u.i);
printf("%d\n",u.c);
return 0;
}
这次我们在同一时间只使用一个变量
联合体大小的计算
- 联合体的大小 至少是 最大成员的大小
- 当最大成员的大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍
//1
#include<stdio.h>
union Un
{
char c[5];
char c1;
};
int main()
{
union Un u;
printf("%d\n",sizeof(u));
return 0;
}
//结果:5
//2
#include<stdio.h>
union Un
{
short a[3];//2 6
int b;//4 4
};
int main()
{
union Un u;
printf("%d\n",sizeof(u));
return 0;
}
//结果:8