结构体
基本用法
struct student {
char name[100];
int age;
};
struct student st; // 定义一个变量 st,其类型是 student
st.age = 20;
strcpy(st.name, "张三"); // 不能用 st.name = "张三"
// 如何使用:
printf("name= %s, age = %d\n", st.name, st.age);
// 定义2:定义+初始化
struct student st1 = {"李四", 25};
// 定义3:可以省略1个
struct student st2 = {"王二"};
// 定义4:所有字段都初始化为0
struct student st3 = {0};
// 定义5:指定字段,这样顺序可以打乱
struct student st4 = {.age=26, .name="麻子"};
用等号赋值就是内存拷贝
struct student st1;
st1 = st;
结构体的内存布局
1
// 占用2个字节,没问题
struct A {
char a1;
char a2;
};
// 占用8个字节,也没问题
struct A {
int a1;
int a2;
};
// 占用8个Byte
struct A {
char a1;
int a2;
};
// 这是因为结构体的内存总是对齐的
// 按照占用最大内存的元素为基本单位,做对齐
内存中是这样的
1Byte | 1Byte | 1Byte | 1Byte |
---|---|---|---|
char a1; | |||
int a2; |
占用 8 Byte
2
// 这个也占8个 Byte
struct A {
char a1;
char a2;
char a3;
char a4;
int i;
};
// 因为空闲的内存给补上了
内存中是这样的
1Byte | 1Byte | 1Byte | 1Byte |
---|---|---|---|
char a1; | char a2; | char a3; | char a4; |
int a2; |
占用 8 Byte
3
struct A {
char a1;
short a2;
int a3;
};
内存中是这样的
1Byte | 1Byte | 1Byte | 1Byte |
---|---|---|---|
char a1; | short a2; | ||
int a2; |
4
struct A {
char a1;
short a2;
char a3;
int i;
};
内存中是这样的
1Byte | 1Byte | 1Byte | 1Byte |
---|---|---|---|
char a1; | short a2; | ||
char a1; | |||
int a2; |
占用 12 个字节
所以,你可以通过调整顺序,减少内存消耗。但计算机 不会自动帮你优化
5:出现数组的情况
struct A {
char a1[5];
int i;
};
占用 12 个字节
1Byte | 1Byte | 1Byte | 1Byte |
---|---|---|---|
char a[0]; | char a[1]; | char a[2]; | char a[3]; |
char a[4]; | |||
int a2; |
按照每个元素对齐,而不是按照数组为单位
操作内存
struct A {
char a1[4];
int i;
};
struct A a;
char *s = (char *) &a;
s[0] = 'a';
s[1] = 'b';
s[5] = 'c'; //其实指向 i 的第1个Byte了
printf("%s\n", a.a1);
printf("%d\n", a.i);
return 0;
所以
- 可以像操作数组一样,用指针操作结构体
- 可以用指针,把浪费的“空隙”用起来
指定bit
// 总共只占用1个Byte
struct A {
char a1: 2; // 占用2个bit
char a2: 2; // 占用2个bit
char a3: 4; //占用4个bit
};
可以用这个技巧把内存应用到极致,尤其是在简陋的设备上
但是不要用不同的类型做定义
// 这是错的
struct A {
char a1:1;
int a2:1;
};
// 实测在不同的机器上占用内存不一样。在内存中的堆叠方式也不一样
结构体数组
struct student {
char name[20];
unsigned char age;
int sex;
};
// 初始化方法1
struct student st[3];
strcpy(st[0].name, "张三");
st[0].age = 15;
st[0].sex=1;
// 初始化方法2
struct student st2[3] = { {"张三", 16, 1},
{"李四", 18, 0} };
// 初始化方法3
struct student st3[] = { {"张三", 16, 1},
{"李四", 18, 0} };
基于结构体数组的特点,元素交换可以直接用内存交换来做
struct student {
char name[20];
unsigned char age;
int sex;
};
int swap_std(struct student *a, struct student *b) {
static struct student tmp;
memcpy(&tmp, a, sizeof(tmp));
memcpy(a, b, sizeof(tmp));
memcpy(b, &tmp, sizeof(tmp));
}
int main() {
struct student st[] = { {"张三", 16, 1},
{"李四", 18, 0},
{"王二", 14, 0},
{"白展堂", 20, 0},
{"吕秀才", 19, 0}
};
swap_std(&st[0],&st[1]);
//显示
for (int i = 0; i < sizeof(st) / sizeof(st[0]); i++) {
printf("%s,%d,%d\n", st[i].name, st[i].age, st[i].sex);
}
//其实结构体也可以直接赋值
int swap_std(struct student *a, struct student *b) {
static struct student tmp;
tmp = *a;
*a = *b;
*b = tmp;
return 0;
}
}
结构体嵌套
如何使用?
struct A {
char a1;
};
struct B{
struct A a;
int b1;
};
struct B b;
b.a.a1;
内存
内存 case 1
struct A {
char a1;
};
// B 占 2 个 Byte
struct B{
struct A a;
char b1;
};
内存 case 2
struct A {
char a1;
};
// B 占 8 个 Byte
struct B{
struct A a;
int b1;
};
似乎在内存中是紧凑的堆叠
struct A {
int a1;
char a2;
};
// 占用 16 个字节
struct B {
struct A a;
char b1;
int b2;
};
1Byte | 1Byte | 1Byte | 1Byte |
---|---|---|---|
int A.a1 | |||
char A.a2 | |||
char b1 | |||
int b2 |
b1 不会用来补其它结构体的内存空隙
自引用
// 这个是错的,因为会引起无限递归
// struct A {
// char a1;
// struct A a;
// };
// 这个是可以的:
struct A {
char a1;
struct A *a;
};
结构体与指针
指向结构体的指针
struct student st;
struct student *p = &st;
strcpy(p->name, "hello");
p->age = 20;
p->age
等价于 (*p).age
指向结构体数组的指针
struct student {
char name[100];
int age;
};
struct student st[3] = {
{"张三", 25},
{"李四", 26},
{"王二", 29}
};
struct student *p = st;
// 用指针赋值
p->age = 20;
p++;
p->age = 15;
// 用指针取值
p--;
for (int i = 0; i < 3; i++) {
printf("%s,%d\n", p[i].name, p[i].age);
}
结构体的成员是指针的情况
struct student {
char *name;
};
struct student st = {0};
st.name = calloc(20, sizeof(char)); // 因为是指针,因此需要分配内存
strcpy(st.name, "hello");
printf("%s", st.name);
free(st.name);
下面这个很好理解,浅拷贝
struct student {
char *name;
};
struct student st = {0};
st.name = calloc(20, sizeof(char)); // 因为是指针,因此需要分配内存
struct student st1 = st;
strcpy(st.name, "hello");
printf("%s", st1.name);
free(st.name);
额外
p->a
的优先级高于*p
,因此*p->a
代表取成员a
这个指针指向的内容- 因为结构体的成员也可以是指针,指向另一个结构体,因此可以写出类似
p1->a->a1
,p1->a->arr2[1]
这样的代码 - 结构体作为函数入参,虽然正确但应该避免,因为每次调用函数都会把整个结构体复制一遍,效率很低。最好传入指针
堆中的结构体
struct student p1={0}; // 在栈里面
struct student *p = malloc(sizeof(struct student)); // p->name 在堆里面
free(p);
错误的结构体
struct B
{
char *p = malloc(100); //不能这样写,这里只是定义,不能执行,编译器不认识。
int a;
};
联合体 union
union A {
int a1;
short a2;
char a3;
};
union A a;
// union A a = {1}; // 也可以这样,给第一个赋值
printf("%ld\n", sizeof(union A));
printf("%p== %p== %p\n", &a.a1, &a.a2, &a.a3);
联合体在形式上与结构体类似,但区别是联合体成员的内存是共用的,每个成员在内存中的开头都一样。
使用场景举例
// 需要一种变量,它可能是 int、float、string 类型
struct VARIABLE {
enum {
INT, FLOAT, STRING
} type;
union {
int i;
float f;
char *s;
} value;
};
struct VARIABLE v1;
v1.type = INT;
v1.value.f = 1;
枚举类型 enum
enum spectrum {
red, yellow, green, blue, white, black
}; // 其实是定义了很多int型的常量(0, 1, 2, ...),提高代码可读性
int main() {
enum spectrum flower_color = red;
}
还可以自定义值
enum spectrum {
red = 100,
yellow, //这里后面累加1
green, blue, white, black
};
typedef
用途
- 增加代码的可读性
- 减少代码量
- 提高可维护性
- 提高跨平台能力
typedef struct student stu; // 就是定义 stu = struct student
typedef unsigned char BYTE; // 就是定义了 BYTE = unsigned char
int main() {
stu st; // 等价于 struct student st
BYTE a; // 等价于 unsigned char a
sizeof(BYTE);
}
可维护性:例如,你代码里面有很多 short a1;
你可以这样:
typedef short SHORT;
SHORT a1;
SHORT a2;
如果很多版本后,你想把那些变量改成 long long 类型,只需要改1行 def long long SHORT
跨平台:
#ifdef UNICODE
typedef wchar_t TCHAR // 不同平台用的 char 类型不一样
#else
typedef char TCHAR
#endif
// 下面是主体代码:
#define UNICODE // 靠这个来定义不同平台上的变量定义
TCHAR a;