结构体、共用体、枚举

结构体类型的概念及定义

构造类型:不是基本类型的数据结构也不是指针,它是若干个相同或不同类型的数据构成的集合,常用的构造类型有数组、结构体、共用体。

数组用于保存多个相同类型的数据。

结构体用于保存多个不同类型的数据。

结构体的概念

结构体是一种构造类型的数据结构。

是一种或多种基本类型或构造类型的数据的集合。

结构体类型的定义

先定义结构体类型,再去定义结构体变量

1
2
3
4
5
struct 结构体名 {
成员;
} 结构体变量1, 结构体变量2;

struct 结构体名 变量3, 变量4;

如:

1
2
3
4
5
6
7
struct stu {
int num;
char name[20];
char sex;
} lucy, bob, lilei;

struct stu xiaohong, xiaoming;

注意:一般在全局定义结构体。

无名结构体的定义

在定义结构体类型的时候,没有结构体类型名,顺便定义结构体变量,因为没有类型名,所以以后不能再定义相关类型的数据。

1
2
3
struct {
成员列表;
} 变量1, 变量2;

例如:

1
2
3
4
5
struct {
int num;
char name[20];
char sex;
} lucy, bob;

给结构体类型取别名

这是经常做的事情。

1
2
3
typedef struct 结构体名 {
成员列表;
} 别名;

注意:typedef 给结构体起了一个别名,在使用别名定义结构体变量变量时不要加上 strcut.

例如:

1
2
3
4
5
typedef struct stu {
int num;
char name[20];
char sex;
} STU;

STU lucy;struct stu; 是等价的。

结构体变量的定义初始化及使用

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

结构体变量,是个变量,这个变量是若干个数据的集合。

  1. 在定义结构体变量之前首先得有结构体类型。
  2. 在定义结构体变量的时候,可以顺便给结构体变量赋初值。
  3. 结构体变量初始化的时候,各个成员顺序初始化。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdio.h>

// 定义结构体类型
struct stu {
int id;
char name[32];
char sex;
int age;
// 定义结构体变量之定义结构体类型的同时定义结构体变量
} zhangsan, lisi = {1002, "李四", 'B', 25};

// 使用typedef对结构体类型取别名
typedef struct {
int a;
int b;
char c;
} MSG;

int main(int argc, char *argv[]) {
// 定义结构体变量之类型定义完毕之后定义变量
struct stu wangwu;
// 结构体变量的初始化
struct stu zhaoliu = {1001, "赵六", 'B', 20};
// 如果使用typedef对结构体类型取别名
// 就无法在定义类型的同时定义结构体变量
// 在定义结构体变量的时候不用加struct
MSG msg1, msg2 = {100, 200, 'w'};
return 0;
}

结构体变量的使用

结构体变量对成员调用的方式:

1
结构体变量.结构体成员

结构体变量的简单使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <stdio.h>
#include <string.h>

struct stu {
int id;
char name[32];
char sex;
int age;
} zhangsan, lisi = {1002, "李四", 'B', 25};

typedef struct {
int a;
int b;
char c;
} MSG;

int main(int argc, char *argv[]) {
struct stu wangwu;
struct stu zhaoliu = {1001, "赵六", 'B', 20};
MSG msg1, msg2 = {100, 200, 'w'};
// 结构体变量的使用
zhangsan.id = 1001;
strcpy(zhangsan.name, "张三");
zhangsan.sex = 'B';
zhangsan.age = 18;
printf("%d - %s - %c - %d\n", zhangsan.id, zhangsan.name, zhangsan.sex,
zhangsan.age);
printf("%d - %s - %c - %d\n", lisi.id, lisi.name, lisi.sex, lisi.age);
printf("%d - %s - %c - %d\n", zhaoliu.id, zhaoliu.name, zhaoliu.sex,
zhaoliu.age);
printf("%d - %d - %c\n", msg2.a, msg2.b, msg2.c);

return 0;
}

输出结果:

image

在结构体中嵌套结构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <stdio.h>
#include <string.h>

// 在结构体中嵌套结构体
typedef struct {
int year;
int month;
int day;
} BD;

typedef struct {
int id;
char name[32];
BD birthday;
} STU;

int main() {
STU xiaoming;
xiaoming.id = 1001;
strcpy(xiaoming.name, "小明");
// 如果结构体中嵌套结构体,赋值时找到最内层的成员再进行赋值
xiaoming.birthday.year = 2002;
xiaoming.birthday.month = 12;
xiaoming.birthday.day = 20;
printf("%d - %s - %d-%d-%d\n", xiaoming.id, xiaoming.name,
xiaoming.birthday.year, xiaoming.birthday.month,
xiaoming.birthday.day);
// 嵌套的形式定义并初始化
STU xiaoli = {1002, "小丽", 2000, 1, 1};
printf("%d - %s - %d-%d-%d\n", xiaoli.id, xiaoli.name, xiaoli.birthday.year,
xiaoli.birthday.month, xiaoli.birthday.day);
return 0;
}

输出结果:

image

相同类型的结构体变量可以相互赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <string.h>

struct stu {
int id;
char name[32];
char sex;
int age;
};

int main(int argc, char *argv[]) {
struct stu zhangsan;
zhangsan.id = 1001;
strcpy(zhangsan.name, "张三");
zhangsan.sex = 'B';
zhangsan.age = 18;
printf("%d - %s - %c - %d\n", zhangsan.id, zhangsan.name, zhangsan.sex,
zhangsan.age);
// 相同类型的结构体变量之间可以直接赋值
struct stu lisi;
lisi = zhangsan;
printf("%d - %s - %c - %d\n", lisi.id, lisi.name, lisi.sex, lisi.age);
return 0;
}

输出结果:

image

结构体数组

结构体数组是个数组,由若干个相同类型的结构体变量构成的集合。

结构体数组的定义方法:

1
struct 结构体类型名 数组名[元素个数];

例如:

1
2
3
4
5
6
7
struct stu {
int num;
char name[20];
char sex;
};

struct stu edu[3];

结构体数组元素的引用:

1
数组名[索引];

结构体数组元素对成员的使用:

1
数组名[索引].成员

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>

typedef struct {
int num;
char name[20];
float score;
} STU;

int main(int argc, char *argv[]) {
// 定义一个结构体数组
STU edu[3] = {{101, "Lucy", 78}, {102, "Bob", 59.5}, {103, "Tom", 85}};
// 输出结构体数组中的元素
for (int j = 0; j < 3; j++) {
printf("%d - %s - %.2f\n", edu[j].num, edu[j].name, edu[j].score);
}
float sum = 0;
for (int i = 0; i < 3; i++) {
sum += edu[i].score;
}
printf("平均成绩为%.2f\n", sum / 3);

return 0;
}

输出结果:

image

结构体指针

结构体的地址,结构体变量存放内存中,也有起始地址。

结构体指针变量的定义方法:

1
struct 结构体名 *指针名;

结构体指针变量对成员的引用:

1
2
(*结构体指针变量名).成员
结构体指针变量名‐>成员
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct stu {
int id;
char name[32];
char sex;
int age;
};

int main(int argc, char *argv[]) {
// 定义一个结构体指针变量
struct stu *s;
// 在堆区开辟结构体空间并将其地址保存在结构体指针变量中
s = (struct stu *)malloc(sizeof(struct stu));
s->id = 1001;
strcpy(s->name, "张三");
s->sex = 'B';
s->age = 20;
printf("%d - %s - %c - %d\n", s->id, s->name, s->sex, s->age);

return 0;
}

输出结果:

image

结构体内存分配问题

规则 1:以多少个字节为单位开辟内存

为结构体变量分配内存时,先去找结构体的基本数据类型,找到占用字节数最大的那个类型,以它为基准开辟空间。

  1. 成员中只有 char 类型,以 1 字节为单位开辟空间。
  2. 成员中出现了 short 类型,没有更大的基本数据类型,以 2 个字节为单位开辟内存。
  3. 出现了 intfloat,没有更大字节的基本数据类型,以 4 个字节为单位开辟内存。
  4. 出现了 double
    1. msvc 中,以 8 个字节为单位开辟空间。
    2. gcc 中,以 4 字节为得开辟空间。

规则 2:字节对齐

  1. char:1 字节对齐,即存放 char 类型的变量的地址是 1 的倍数。
  2. short:2 字节对齐,即存放 short 类型的变量的地址是 2 的倍数。
  3. int:4 字节对齐,即存放 int 类型的变量的地址是 4 的倍数。
  4. long:在 32 位的操作系统中,4 字节对齐,即存放 long 类型的变量的地址是 4 的倍数。
  5. float:4 字节对齐,即存放 float 类型的变量的地址是 4 的倍数。
  6. double
    1. msvc 中,8 字节对齐,即存放 double 类型的变量的地址是 8 的倍数。
    2. gcc 中,4 字节对齐,即存放 double 类型的变量的地址是 4 的倍数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
struct stu {
int num;
int age;
} lucy; // 8字节

struct stu {
char sex;
int age;
} lucy; // 8字节

struct stu {
char a;
short int b;
int c;
} temp; // 8字节

struct stu {
char a;
int c;
short int b;
} temp; // 12字节

struct stu {
char buf[10];
int a;
} temp; // 16字节

struct stu {
char a;
double b;
}; // 12字节

字节对齐是一种空间换时间的操作。

位段

在结构体中,以位为单位的成员,称之为位段(位域)。

1
2
3
4
5
6
7
struct packed_data {
unsigned int a : 2;
unsigned int b : 6;
unsigned int c : 4;
unsigned int d : 4;
unsigned int i;
} data;
image

注意:不能对位段成员取地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>

struct packed_data {
unsigned int a : 2;
unsigned int b : 6;
unsigned int c : 4;
unsigned int d : 4;
unsigned int i;
} data;

int main() {
printf("%d\n", sizeof(data));
printf("%p\n", &data);
printf("%p\n", &(data.i));
return 0;
}

输出结果:

image

注意:

  1. 对于位段成员赋值时不要超出位段定义的范围,例如成员 a 定义为 2 为,则去哦最大值为 3,如果给 a 赋值为 5,会取最低 2 位进行赋值。
  2. 位段成员必须为整型或字符型。
  3. 一个位段成员必须存放在一个存储单元中,不能跨两个单元,第一个单元不能容纳下一个位段,则该空间不用,从下一个单元其存放该位段。

位段的存储单元:

  1. char 型的位段,存储单元是 1 个字节;
  2. short int 型的位段,存储单元是 2 个字节;
  3. int 型的位段,存储单元是 4 字节;
  4. long int 型的位段,存储单元是 4 字节。
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>

struct stu {
char a : 7;
char b : 7;
char c : 2;
} temp;

int main() {
printf("%d\n", sizeof(temp));
return 0;
}

输出结果:

image

位段的长度不能大于存储单元的长度:

  1. char 型位段不能大于 8 位;
  2. short 型位段不能大于 16 位;
  3. int 型位段不能大于 32 位;
  4. long 型位段不能大于 32 位。
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>

struct stu {
char a : 9;
char b : 7;
char c : 2;
} temp;

int main() {
printf("%d\n", sizeof(temp));
return 0;
}
分析:编译出错,位段a不能大于其存储单元的大小

如一个段要从另一个存储单元开始,可以定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>

struct m_type {
unsigned char a : 1;
unsigned char b : 2;
unsigned char : 0;
unsigned char c : 3;
};

int main() {
struct m_type temp;
printf("%d\n", sizeof(temp));
return 0;
}

输出结果:

image

长度为 0 的位段作用是使下一个位段从下一个存储单元开始存放,将 a、b 存储在一个存储单元中,c 存储在下一个单元。

可以定义无意义位段,如:

1
2
3
unsigned a: 1;
unsigned : 2;
unsigned b: 3;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct data {
char a : 1;
char b : 1;
char c : 1;
char d : 1;
char e : 1;
char f : 1;
char g : 1;
char h : 1;
} temp;

int main() {
char p0;
// p0=0x01;// 0000 0001
temp.a = 1;
// p0 = temp; // 错的,类型不匹配
// p0=(char) temp;//错的,编译器不允许将结构体变量,强制转成基本类型的。
p0 = *((char *)(&temp));
return 0;
}

共用体

共用体和结构体类似,也是一种构造类型的数据结构。

定义共用体类型的方法和结构体非常相似,把 struct 改成 union 就可以了。

在进行某些算法的时候,需要使几种不同类型的变量存到同一段内存单元中,几个变量所使用空间相互重叠,共用体就是这样一种结构。

共用体所有成员占有同一段地址空间,共用体的大小是其占内存长度最大的成员的大小

共用体的特点:

  1. 同一内存段可以用来存放几种不同类型的成员,但每一瞬时只有一种起作用。
  2. 共用体变量中起作用的成员是最后一次存放的成员,在存入一个新的成员后原有的成员的值会被覆盖。
  3. 共用体变量的地址和它的各成员的地址都是同一地址。
  4. 共用体变量的初始化 union data a={123};,初始化共用体为第一个成员。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>

// 定义一个共用体
union un {
int a;
int b;
int c;
};

int main(int argc, char *argv[]) {
// 定义共用体变量
union un myun;
myun.a = 100;
myun.b = 200;
myun.c = 300;

printf("a = %d, b = %d, c = %d\n", myun.a, myun.b, myun.c);

return 0;
}

输出结果:

image

枚举

将变量的值一一列举出来,变量的值只限于列举出来的值的范围内。

枚举类型也是个构造类型的

枚举类型的定义:

1
2
3
enum 枚举类型名{
枚举值列表;
};

在枚举值表中应列出所有可用值,也称为枚举元素。

枚举变量仅能取枚举值所列元素。

枚举变量的定义方法:

1
enum 枚举类型名 枚举变量名;
  1. 枚举值是常量;
  2. 枚举元素本身由系统定义了一个表示序号的数值,默认从 0 开始;
  3. 可以改变枚举值的默认值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>

enum week { mon = 3, tue, wed, thu, fri = 4, sat, sun };

int main(int argc, char *argv[]) {

printf("%d\n", mon);
printf("%d\n", tue);
printf("%d\n", wed);
printf("%d\n", thu);
printf("%d\n", fri);
printf("%d\n", sat);
printf("%d\n", sun);

return 0;
}

输出结果:

image