结构体与共用体

结构体基本使用

  结构体其实跟java类长得有点像,使用结构体也可以声明变量

1
2
3
4
5
6
7
8
struct 结构体名 {
数据类型 变量名;
...
数据类型 变量名;
};

// 使用结构体声明变量
struct 结构体名 变量名;

  如下代码声明了一个Person结构体,并且在main函数中使用该结构体声明了变量,用结构体声明变量与数组变量有相似之处,即声明结构体变量时如果不赋初始值,编译器会给结构体中的成员分配未初始化值(未初始化的值是不确定的),并分配内存地址,结构体变量的内存地址一旦被分配便不可更改

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
struct Person{
int age;
int height;
char* name;
char* hobby[];
};

int main() {
// 使用结构体定义变量
struct Person person;

// hobby数组已被声明,且声明时未赋予初始值
// 所以无法再对hobby数组赋值,此处语法错误
person.hobby = {1,2,3};

// 未经过初始化的结构体中的变量 age,目前属于未初始化值
printf("age = %d\n", person.age);

// 为 age 变量赋值,该变量此前未经过初始化
person.age = 22;

// 重新赋值过后的 age
printf("age = %d\n", person.age);

return 0;
}

  用结构体声明变量并对结构体变量所有成员进行初始化,也和数组初始化很像;需要注意的是,结构体中的成员不能包含可变长度的数组,即结构体中的数组必须固定长度;另外注意,指针变量的未初始化值是一个未知的内存地址(也可能是一个特定的内存地址),所以即使包含指针成员的结构体变量不需要初始化,也尽量将指针初始化为NULL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct Person{
int age;
int height;
char* name;

// 结构体中的数组必须固定长度
char* hobbies[2];
};

int main() {
// 使用结构体定义变量,并对全体成员进行初始化
struct Person person = {25, 180, "John", {"Reading", "Gardening"}};

// 访问结构体成员并输出
printf("Age: %d\n", person.age);
printf("Height: %d\n", person.height);
printf("Name: %s\n", person.name);
printf("Hobbies: %s, %s\n", person.hobbies[0], person.hobbies[1]);

return 0;
}

  用结构体声明变量并对结构体变量部分成员进行初始化,未被初始化的部分系统将会为其赋默认值,如整型将会被赋值0,指针将会被赋值NULL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct Person{
int age;
int height;
char* name;

// 结构体中的数组必须固定长度
char* hobbies[2];
};

int main() {
// 使用结构体定义变量,并对部分成员进行初始化
struct Person person = {25, 180, "John"};

// 访问结构体成员并输出
printf("Age: %d\n", person.age);
printf("Height: %d\n", person.height);
printf("Name: %s\n", person.name);

// 将会输出 null ,因为数组未被初始化
printf("Hobbies: %s, %s\n", person.hobbies[0], person.hobbies[1]);

return 0;
}

  也可以对指定的部分成员进行初始化,初始化先后顺序不受结构体中先后顺序的影响,未被初始化的部分系统将会为其赋默认值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct Person{
int age;
int height;
char* name;

// 结构体中的数组必须固定长度
char* hobbies[2];
};

int main() {
// 使用结构体定义变量,并对指定成员进行初始化
struct Person person = {.age = 25, .name = "John", .hobbies = {"Reading", "Gardening"}};

// 访问结构体成员并输出
printf("Age: %d\n", person.age);

// 将会输出 0 ,因为 height 未被初始化
printf("Height: %d\n", person.height);

printf("Name: %s\n", person.name);
printf("Hobbies: %s, %s\n", person.hobbies[0], person.hobbies[1]);

return 0;
}

  声明结构体的同时可以用结构体声明变量,在之后的使用过程中,再对结构体变量中的成员重新赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct Person{
int age;
int height;
char* name;
char* hobbies[2];
} person1, person2; // 声明结构体,同时用结构体声明变量

int main() {
// 给结构体变量中的成员重新赋值
person1.age = 20;
person2.age = 30;

return 0;
}

  声明结构体的同时用结构体声明变量,并且对结构体变量赋初始值

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
struct Person{
int age;
int height;
char* name;
char* hobbies[2];
}
// 声明的同时赋初始值
person1 = {20, 180, "John", {"Reading", "Gardening"}},
person2 = {30, 170, "Jack", {"Reading", "Gardening"}};

int main() {
printf("person1 Age: %d\n", person1.age);
printf("person1 Height: %d\n", person1.height);
printf("person1 Name: %s\n", person1.name);
printf("person1 Hobbies: %s, %s\n", person1.hobbies[0], person1.hobbies[1]);

printf("\n");

printf("person2 Age: %d\n", person2.age);
printf("person2 Height: %d\n", person2.height);
printf("person2 Name: %s\n", person2.name);
printf("person2 Hobbies: %s, %s\n", person2.hobbies[0], person2.hobbies[1]);

return 0;
}

  使用typedef给结构体起别名,用以简化声明结构体变量的写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 给结构体起别名 P,在下文中直接使用 P 定义结构体变量
typedef struct Person{
int age;
int height;
char* name;
char* hobbies[2];
} P;

int main() {
// 用结构体的别名定义结构体变量
P person = {20, 180, "John", {"Reading", "Gardening"}};

printf("person Age: %d\n", person.age);
printf("person Height: %d\n", person.height);
printf("person Name: %s\n", person.name);
printf("person Hobbies: %s, %s\n", person.hobbies[0], person.hobbies[1]);

return 0;
}

  没有结构体名的结构体即为匿名结构体,声明匿名结构体时如果不声明结构体变量,那后面就没机会了,因为匿名结构体没有结构体名

1
2
3
4
5
6
struct {
int age;
int height;
char* name;
char* hobbies[2];
} person;

  当然了除非用typedef给匿名结构体起别名,感觉这种格式挺好的,使用别名代替结构体名,而且在代码中声明结构体变量时也简洁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 给匿名结构体起别名
typedef struct {
int age;
int height;
char* name;
char* hobbies[2];
} P;

int main() {
// 使用匿名结构体的别名声明结构体变量
P person = {20, 180, "John", {"Reading", "Gardening"}};

printf("person Age: %d\n", person.age);
printf("person Height: %d\n", person.height);
printf("person Name: %s\n", person.name);
printf("person Hobbies: %s, %s\n", person.hobbies[0], person.hobbies[1]);

return 0;
}

结构体值传递

  结构体变量与数组的不同之处是,数组的传递方式是地址传递,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int main() {
int arr[] = { 1,2,3,4,5 };

// 将数组传递给 test 方法
// 实际上传递的是数组的地址值
test(arr);

// 此处输出 10,因为在 test 方法中对数组第一个元素进行了修改
printf("arr[0] = %d\n", arr[0]);

return 0;
}

void test(int arr[]){
arr[0] = 10;
}

  而结构体的传递方式是值传递,即仅把构造体变量的值传入,并不会影响原构造体变量中的数值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int main(){
Person person = { .age = 18, .height = 180, .name = "haha", .hobbies = {"reading"} };

// 将构造体变量传递给 test2
// 实际上是值传递,将构造体变量相同的值传入
test2(person);

// 此处输出 18,因为 test 方法中的修改并不会影响到构造体变量的值
printf("person.age = %d\n", person.age);

return 0;
}

void test2(Person person) {
person.age = 20;
}

结构体数组

  如下代码,声明了一个Person结构体,然后使用该结构体声明了一个Person数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct Person {
int age;
int height;
char* name;
char* hobbies[2];
};

int main(){
struct Person people[] = {
{20, 180, "Jack", {"Sports", "Reading"}},
{22, 170, "John", {"Running"}} };

printf("person Age: %d\n", people[0].age);
printf("person Height: %d\n", people[0].height);
printf("person Name: %s\n", people[0].name);
printf("person Hobbies: %s, %s\n", people[0].hobbies[0], people[0].hobbies[1]);

return 0;
}

  也可以给结构体起别名,用别名声明结构体数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
typedef struct Person {
int age;
int height;
char* name;
char* hobbies[2];
} Person;

int main() {
Person people[] = {
{20, 180, "Jack", {"Sports", "Reading"}},
{22, 170, "John", {"Running"}} };

printf("person Age: %d\n", people[0].age);
printf("person Height: %d\n", people[0].height);
printf("person Name: %s\n", people[0].name);
printf("person Hobbies: %s, %s\n", people[0].hobbies[0], people[0].hobbies[1]);

return 0;
}

  声明结构体的同时声明数组,在之后的使用过程中再对结构体数组重新赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct Person {
int age;
int height;
char* name;
char* hobbies[2];
} people[2];

int main() {
people[0].age = 20;
people[0].height = 180;
people[0].name = "John";
people[0].hobbies[0] = "Reading";
people[0].hobbies[1] = "Sports";

printf("person Age: %d\n", people[0].age);
printf("person Height: %d\n", people[0].height);
printf("person Name: %s\n", people[0].name);
printf("person Hobbies: %s, %s\n", people[0].hobbies[0], people[0].hobbies[1]);

return 0;
}

  声明结构体的同时声明数组,并且对数组进行初始化;其它的一些操作不再列举,都符合结构体变量的声明以及数组的声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct Person {
int age;
int height;
char* name;
char* hobbies[2];
} people[2] = {
{20, 180, "Jack", {"Sports", "Reading"}},
{22, 170, "John", {"Running"}} };

int main(){
printf("person Age: %d\n", people[0].age);
printf("person Height: %d\n", people[0].height);
printf("person Name: %s\n", people[0].name);
printf("person Hobbies: %s, %s\n", people[0].hobbies[0], people[0].hobbies[1]);

return 0;
}

结构体指针

  使用结构体声明结构体指针变量,并取结构体变量的地址值赋值给该指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct Person {
int age;
int height;
char* name;
char* hobbies[2];
};

int main(){
// 声明一个结构体变量并初始化
struct Person person = {20, 180, "Jack", {"Sports", "Reading"}};

// 声明结构体变量的指针
struct Person *pointer = &person;

// 通过指针修改 age 的值
(*pointer).age = 30;

// 将会输出 30,因为此前通过结构体变量的指针修改了 age 的值
printf("age = %d\n", person.age);

return 0;
}

  结构体的传递方式是值传递,通过结构体指针可以实现地址传递,如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int main(){
Person person = { .age = 18, .height = 180, .name = "haha", .hobbies = {"reading"} };

// 将构造体变量的地址传递给 test2
test2(&person);

// 此处输出 20,因为 test 方法中修改了原结构体变量中的 age 值
printf("person.age = %d\n", person.age);

return 0;
}

void test2(Person* pointer) {
// 通过结构体变量的地址值修改原结构体变量中的age
(*pointer).age = 20;
}

  使用结构体指针调用结构体变量中的成员语法比较麻烦,因此可以使用箭头操作符,如下

1
2
3
4
// 调用结构体变量中的 age
(*pointer).age
// 等同于
pointer -> age

1
2
3
4
5
6
7
8
void test2(Person* pointer) {
(*pointer).age = 20;
}

// 等同于
void test2(Person* pointer) {
pointer -> age = 20;
}

共用体的基本使用

  共用体内部可以和结构体一样有多个成员,但是共用体变量同一时间只能有一个赋值成员;共用体的所有成员都在同一个内存地址,所以共用体占用内存的大小就是共用体中占用内存最大的成员的内存大小

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
35
36
// 声明了一个包含四个成员的共用体
union Data{
int int_data1;
int int_data2;
short short_data;
float float_data;
};

int main(){

// 只给一个成员初始化
union Data data1 = {.int_data1 = 10};
// 将输出 10
printf("data = %d\n", data1.int_data1);

// 只给一个成员初始化
union Data data2;
data2.short_data = 20;
// 将输出 20
printf("data = %d\n", data2.short_data);

// 如果对两个成员初始化,输出将会出问题
// 因为共用体的内存地址中同一时刻只能有一个成员
union Data data3 = { .int_data1 = 10,.int_data2 = 30 };
// 这里竟然他妈的输出了 30,我猜应该是第二个初始化的成员挤走了第一个初始化的成员
printf("data = %d\n", data3.int_data1);

// 给一个成员初始化
union Data data4 = { .short_data = 20 };
// 给另一个成员重新赋值
data4.float_data = 55.5f;
// 输出也会发生异常,输出了 0,真他妈的捉摸不定
printf("data = %d\n", data4.short_data);

return 0;
}

  共用体的变量声明方式即成员调用方式基本和结构体一致

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
// 声明了一个包含四个成员的共用体
union Data{
int int_data1;
int int_data2;
short short_data;
float float_data;
}
data2,
data3 = {.short_data = 10};

int main(){

// 声明共用体变量,并在声明之后给一个成员赋值
union Data data1;
data1.float_data = 12.3f;

// 声明共用体同时声明了该共用体变量,此时仅需对它的成员赋值
data2.int_data2 = 6;

// 声明共用体同时声明并初始化了该共用体变量,此处对它的成员重新赋值
data3.short_data = 11;

// 声明共用体指针变量
union Data *pointer = &data1;
// 共用体同样支持箭头操作符
pointer -> float_data = 23.4f;

return 0;
}