结构体&共用体及其用法

5.1k 词

结构体

概念

结构体是一种构造类型,它是由若干不同类型的数据项组合而成的一个数据集合。

描述一组具有相同数据的有序集合,用于处理大量相同类型的数据运算,有时我们需要将不同类型的数据组合成一个有机的整体方便引用。

类似与面向对象编程中定义一个类以及其属性,不过C语言中不能在结构体中定义函数,不过可以使用函数指针实现类似于面向对象编程的方法来调用类的函数。

结构体类型和变量的定义

定义方法1:先定义结构体类型,再定义变量。

1
2
3
4
5
6
7
8
//定义类型
struct Student{
char name[20];
int age;
float score;
};
//定义变量
struct Student Bob,Lucy,...,Tom;

定义变量技巧:使用typedef在定义结构体类型的时候初始化一个新的类型名。

很多时候我们在创建结构体变量的时候常常需要使用定义方法1来创建变量,但是冗长的定义语句会降低代码的可读性,这时我们就可以用typedef来将一种结构体类型封装为一个变量类型,这样我们就可以像定义普通变量一样来定义结构体变量了。

1
2
3
4
5
6
7
8
typedef struct Student{
char name[20];
int age;
float score;
}Stu;

//使用Stu来定义结构体变量
Stu Bob,Lucy,...,Tom;

定义方法2:在定义结构体类型的时候,顺便定义一些结构体变量。

如果这种结构体类型是全局类型,那么使用这种方法定义结构体变量也是全局变量了。

1
2
3
4
5
6
//定义类型
struct Student{
char name[20];
int age;
float score;
}Bob,Lucy,...,Tom;//定义变量

另外,使用这种方法还可定义一些无名结构体变量,无名结构体变量使用这种方法在定义类型后定义变量。
无名结构体类型无法使用第一种方法定义变量。
无名结构体定义无名结构体时必须定义该结构体类型的至少一个变量
无名结构体常用于避免相同类型的结构体的重复定义。

1
2
3
4
5
6
//定义无名结构体类型
struct{
char name[20];
int age;
float score;
}Bob,Lucy,...,Tom;//定义变量

结构体变量的操作

初始化

结构体初始化可以一次性初始化多个属性,但是要按照结构体类型的定义顺序来初始化变量。

1
2
3
4
5
6
7
typedef struct Student{
char name[20];
int age;
float score;
}Stu;

Stu Bob = {"Bob",18,95.5};//定义结构体变量并且初始化

访问结构体变量

使用.运算符来访问结构体变量中的属性。
也可以使用结构体指针来访问结构体变量中的属性。

1
2
printf("%s",Bob.name);//使用结构体变量名来访问结构体变量中的属性
printf("%s",(*Bob).name);//使用结构体指针来访问结构体变量中的属性

结构体嵌套

结构体可以嵌套定义,也可以嵌套初始化。

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct Student{
char name[20];
int age;
float score;
}Stu;

typedef struct Teacher{
char name[20];
Stu stu;
}Tea;

Tea tea = {"Teacher",{"Bob",18,95.5}};//定义结构体变量并且初始化

结构体数组

结构体可以定义数组,结构体数组可以用于存储一组相同类型的数据。

1
2
3
4
5
6
7
typedef struct Student{
char name[20];
int age;
float score;
}Stu;

Stu stu[10];//定义结构体数组

结构体与指针

结构体指针

结构体指针可以指向结构体变量,也可以指向结构体数组。

1
2
3
4
5
6
7
8
typedef struct Student{
char name[20];
int age;
float score;
}Stu;

Stu stu[10];//定义结构体数组
Stu *p = stu;//定义结构体指针并指向结构体数组

结构体指针访问结构体变量

使用->运算符来访问结构体指针指向的结构体变量中的属性。

1
printf("%s",p->name);//使用结构体指针来访问结构体变量中的属性

结构体指针作为函数参数

结构体指针可以作为函数的参数,这样就可以在函数内部修改结构体变量的值了。

1
2
3
4
5
6
7
8
9
typedef struct Student{
char name[20];
int age;
float score;
}Stu;

void getStu(Stu *stu){
stu->age = 18;
}

结构体内指针成员

在定义结构体类型的时候,可以在结构体内部定义指针类型。方便引用。

1
2
3
4
5
6
typedef struct Student{
char name[20];
int age;
float score;
char *p;
}Stu;

使用结构体内成员的函数指针连接内部函数实现面向对象写法

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>
typedef struct Student{
char name[20];
int age;
int id;
void (*stuIDF)(struct Student s);
}Stu;

void getStuName(struct Student s){
printf("%s\n",s.name);
}

void getStuID(struct Student s){
printf("%d\n",s.id);
}

int main(){
Stu stu = {"Bob",18,333111000,getStuName};
stu.stuIDF(stu);

stu.stuIDF = getStuID;
stu.stuIDF(stu);
return 0;
}

结构体与函数

结构体可以作为函数的参数,也可以作为函数的返回值。

1
2
3
4
5
6
7
8
9
typedef struct Student{
char name[20];
int age;
float score;
}Stu;

Stu getStu(Stu stu){
return stu;
}

结构体与文件

结构体可以存储到文件中,也可以从文件中读取结构体数据。

1
2
3
4
5
6
7
8
9
10
typedef struct Student{
char name[20];
int age;
float score;
}Stu;

Stu stu;
FILE *fp = fopen("data.txt","wb");
fwrite(&stu,sizeof(Stu),1,fp);
fclose(fp);

结构体的内存空间

结构体变量大小是所有成员之和吗?

实际上,结构体变量大小是所有成员之和,但是还要考虑对齐问题。

结构体成员的地址必须相对于结构体首地址的偏移量是成员大小的整数倍。

确定内存的颗粒度

以多少个字节为单位(颗粒大小)开辟内存给结构体变量分配内存的时候,会去结构体变量中找基本类型的成员中基本类型的成员占字节数多,就以它大大小为颗粒单位开辟内存。而实际上不同的编译器关于结构体的内存分配有不同的规则,但大多数遵循以下的规律。

  1. 成员中只有char型数据,以1字节为单位开辟内存
  2. 成员中出现了short 类型数据,没有更大字节数的基本类型数据,以2字节为单位开辟内存
  3. 出现了int、float没有更大字节的基本类型数据的时候以4字节为单位开辟内存。
  4. 出现了double类型的数据,要根据编译器确定,VC编译器是8字节,gcc编译器是4字节。
  5. 出现了long long类型的数据,以8字节为单位开辟内存。
  6. 如果成员中出现了数组,分配空间时将数组视作多个成员的集合

结构体成员的内存对齐

既然已经确定了颗粒度,那么让结构体成员的内存在分配时对齐颗粒度(这下真是“对齐颗粒度”了)就可以提高访问速度,但是会浪费一些内存空间。

结构体成员对齐的规则是:结构体成员的地址相对于结构体首地址的偏移量是颗粒度大小的整数倍。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<stdio.h>
#include<stdlib.h>
struct temp{
char a;
int b;
short c;
}TempS;

int main(){

printf("Size of char:%d\n",sizeof(char));
printf("Size of int:%d\n",sizeof(int));
printf("Size of short:%d\n",sizeof(short));

printf("Size of Struct:%d\n",sizeof(TempS));
return 0;
}

这个结构体类型成员有charintshort三种类型,根据上述规则可以确定,这个结构体类型以最大的int类型的4字节为单位开辟内存,因此虽然结构体中有小于4字节的charshort类型,但是依然给他们分配了4字节的空间,所以这个结构体的大小是12字节。


共用体

概念

在进行某些算法的时候,需要使几种不同类型的变量存到同一段内存单元中,几个变量所使用空间相互重叠这种几个不同的变量共同占用一段内存的结构,在C语言中,被称作“共用体”类型,共用体内多个成员共享一片储存空间,共用体的大小由最大的成员决定

共用体和结构体用法相似,也是一种构造类型。

共用体的特点

  1. 同一内存段可以用来存放几种不同类型的成员,但每一瞬时只有一种起作用
  2. 共用体变量中起作用的成员是最后一次存放的成员,在赋值任意一个成员后所有成员的值会被覆盖
  3. 共用体变量的地址和它的各成员的地址都是同一地址

测试代码: [点击运行]

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

union data {
int i;
float f;
char str[20];
};

int main() {
union data data;
data.i = 10;
printf("data.i: %d\n", data.i);
printf("data.f: %f\n", data.f);
printf("data.str: %s\n", data.str);

data.f = 220.5;
printf("data.i: %d\n", data.i);
printf("data.f: %f\n", data.f);
printf("data.str: %s\n", data.str);
return 0;
}

输出结果:

output
1
2
3
4
5
6
7
data.i: 10
data.f: 0.000000
data.str:

data.i: 1130135552
data.f: 220.500000
data.str:

共用体的大小

共用体的大小由最大的成员决定,如果共用体中包含多个结构体,共用体的大小由结构体中最大的成员决定。

测试代码: [点击运行]

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

union data {
int i;
float f;
char str[20];
struct {
int a;
char b;
} s;
};

int main() {
printf("sizeof(union data): %lu\n", sizeof(union data));
return 0;
}

输出结果:

output
1
sizeof(union data): 20