【C语言】结构体structure:
- C语言可以自定义数据类型。结构体是其中一个自定义的数据类型。
- 结构体类型是复杂的数据类型,将多个不同数据按一定功能进行整体封装,用一个名称来给结构体命名。可用typedef为结构体提供别名。
- 关键字struct。结构体包括结构体名称、结构体成员(由成员类型和成员变量名组成)。
- 结构体中各成员的类型可以不同,可以是基本数据类型,也可以其它结构体或指针等。
- 需要定义结构体类型。结构体可以作为变量,需要定义该类型的变量。结构体也可以作为函数参数传递,也可以作为函数的返回类型。结构体还可以定义更复杂的抽象数据类型(链表、树等)。
1、定义结构体类型
结构体类型:
关键字struct。结构体包括结构体名称、结构体的成员(由成员类型和成员变量名称组成)。
结构体中各成员的类型可以不同,可以是基本数据类型,也可以其它结构体或指针等。
注意:
① 最后的分号";"不能省略。
② 一般在所有函数之前定义结构体类型。
cs
struct 结构体名称 //结构体名称:即结构体标签
{
结构体的成员1; // 结构体的成员:由成员类型和成员变量名称组成
结构体的成员2;
...
};
cs
// 例如:名为person的结构体,成员变量有字符串name,整数age,字符串job
struct person
{
char name[32];
int age;
char job[16];
};
2、结构体作为变量
(2-1)定义结构体变量
定义结构体类型后,定义结构体变量。可以定义一个或多个结构体变量。
注意:
① 定义结构体类型,不会分配内存。定义结构体变量后,才会为变量分配内存。
② 一般在所有函数之前定义结构体类型。在具体函数中定义局部的结构体变量。
③ 结构体变量名可以和结构体名称相同。
cs
struct person
{
char name[32];
int age;
char job[16];
};
struct person p; // 定义一个结构体变量
struct person p1, p2, p3; // 定义多个结构体变量
struct person p1, p2[60], *p3; // 定义多个结构体变量
(2-2)访问结构体成员
使用成员访问运算符(". ")来访问结构体成员,即:结构体变量名**.**结构体成员变量名。
cs
#include <stdio.h>
#include <string.h>
struct person
{
char name[32];
int age;
char job[16];
};
int main(void)
{
struct person p;
strcpy(p.name, "Mark");
p.age = 25;
strcpy(p.job, "teacher");
printf("p.name = %s, p.age = %d, p.job = %s\n", p.name, p.age, p.job);
return 0;
}
// 结果:
p.name = Mark, p.age = 25, p.job = teacher
补充:
也可以在定义结构体类型的时候定义结构体变量,在结构体的末尾大括号{ }外 最后一个分号**;**前。可以定义一个或多个结构体变量。
定义结构体变量时可以给变量设置初始值,可以使用成员访问运算符(".")访问结构体成员。
注意:若在所有函数之前定义结构体类型,则同时定义的结构体变量就是全局变量。
cs
#include <stdio.h>
struct person
{
char name[32];
int age;
char job[16];
} p = {"Jack",18,"programmer"}; // 定义结构体类型时,定义一个结构体变量
int main(void)
{
printf("p.name = %s, p.age = %d, p.job = %s\n", p.name, p.age, p.job);
return 0;
}
// 结果:
p.name = Jack, p.age = 18, p.job = programmer
cs
#include <stdio.h>
struct person
{
char name[32];
int age;
char job[16];
} p[] = { // 定义结构体类型时,定义一个结构体数组变量
{"Jack",18,"programmer"},
{"Mark",25,"teacher"}
};
int main(void)
{
int n = sizeof(p) / sizeof(struct person); // 获取数组元素个数
for(int i = 0;i < n;i++)
{
printf("p[%d].name = %s, p[%d].age = %d, p[%d].job = %s\n", i, p[i].name, i, p[i].age, i, p[i].job);
}
return 0;
}
// 结果:
p[0].name = Jack, p[0].age = 18, p[0].job = programmer
p[1].name = Mark, p[1].age = 25, p[1].job = teacher
3、结构体作为函数参数
结构体作为参数传给函数,传入方式与其它类型变量或指针相同。
注意:结构体作为函数参数,向函数传递的是结构体副本,不会修改原结构体内容。若要修改原结构体内容,则使用结构体指针作为函数参数。
cs
#include <stdio.h>
struct person
{
char name[32];
int age;
char job[16];
};
void changestruct(struct person person);
int main(void)
{
struct person p;
strcpy(p.name, "John");
p.age = 18;
strcpy(p.job, "programmer");
printf("programmer: %s, age %d\n", p.name, p.age);
changestruct(p); // 结构体作为参数传入函数
printf("End: programmer: %s, age %d\n", p.name, p.age);
return 0;
}
void changestruct(struct person person) // 参数类型是结构体类型
{
person.age = 22; // 不修改原结构体
printf("After: programmer: %s, age %d\n", person.name, person.age);
}
// 结果:
programmer: John, age 18
After: programmer: John, age 22
End: programmer: John, age 18
4、结构体作为函数返回值
函数若返回结构体,则函数的返回类型是结构体类型,需有结构体变量接收函数返回的结构体。
cs
#include <stdio.h>
#include <string.h>
struct person
{
char name[32];
int age;
char job[16];
};
struct person getstruct(void); // 函数返回类型是结构体类型
int main(void)
{
struct person p; // 定义结构体变量
p = getstruct(); // 接收函数返回的结构体
if(strcmp(p.job, "programmer") == 0)
printf("programmer: %s, age %d\n", p.name, p.age);
return 0;
}
struct person getstruct(void) // 函数返回类型是结构体类型
{
struct person person;
strcpy(person.name, "Willion");
person.age = 30;
strcpy(person.job, "programmer");
return person; // 函数返回结构体
}
// 结果:
programmer: Willion, age 30
5、结构体可以作为其它结构体的成员
注意:若结构体的成员是其它结构体(子结构体),则子结构体必须在之前定义,才能作为结构体的成员。否则,报错(error: field 'birth' has incomplete type。 注:此处birth为成员变量名)。
cs
#include <stdio.h>
#include <string.h>
struct birthday
{
int year;
int month;
};
struct person
{
char name[32];
int age;
char job[16];
struct birthday birth; // 结构体成员为其它结构体
};
int main(void)
{
struct person p;
strcpy(p.name, "Jack");
p.age = 18;
strcpy(p.job, "programmer");
p.birth.year = 2006; // 使用成员访问运算符"."一级一级访问成员
p.birth.month = 2;
printf("%s, %d, %s, birthday %d-%d", p.name, p.age, p.job, p.birth.year, p.birth.month);
return 0;
}
// 结果:
Jack, 18, programmer, birthday 2006-2
6、结构体大小
- 使用sizeof获取结构体大小。
- sizeof(结构体变量),返回结构体所有成员的内存的大小以及可能的填充字节。
- 因结构体的内存布局和对齐方式,可能会占用字节填充。
cs
#include <stdio.h>
struct person
{
char name[32]; // 字符串name:32字节
int age; // 整数age:4字节
char *job; // 字符串指针job:8字节(64位的计算机。若是32位的计算机,则4字节)
};
int main(void)
{
struct person p;
printf("p memory size is %d\n", sizeof(p));
return 0;
}
// 结果:
p memory size is 48 // 优化对齐,有填充
可使用__attribute__设置内存布局,告诉编译器在编译过程中进行优化对齐。此功能和编译器有关。gcc编译器是非紧凑模式的。
attribute((packed)):使用紧凑内存布局,即取消优化对齐,按实际占用字节数对齐。
注意:attribute两边都是两个下划线"_"。
cs
#include <stdio.h>
struct person
{
char name[32]; // 字符串name:32字节
int age; // 整数age:4字节
char *job; // 字符串指针job:8字节(64位的计算机)
};
struct person_packed
{
char name[32];
int age;
char *job;
}__attribute__((packed)); // 取消优化对齐
int main(void)
{
struct person p;
struct person_packed p_packed;
printf("p memory size is %d\n", sizeof(p));
printf("p_packed memory size is %d\n", sizeof(p_packed));
return 0;
}
// 结果:
p memory size is 48 // 优化对齐,有填充
p_packed memory size is 44 // 取消优化对齐
可使用标准库stddef**.**h中的宏offsetof查看结构体中各成员相对于结构体开头偏移多少字节。
宏 offsetof 的声明: offsetof(type, member-designtor)
参数:type是class类型(例如:结构体),member-designtor是结构体的成员变量名。
返回:结构体的成员变量名相对于结构体开头的偏移量(单位:字节)。
cs
#include <stdio.h>
#include <stddef.h>
struct person
{
char name[32]; // 字符串name:32字节
int age; // 整数age:4字节
char *job; // 字符串指针job:8字节(64位的计算机)
};
struct person_packed
{
char name[32];
int age;
char *job;
}__attribute__((packed)); // 取消优化对齐
int main(void)
{
printf("structure person: name offset %d bytes\n",offsetof(struct person, name));
printf("structure person: age offset %d bytes\n",offsetof(struct person, age));
printf("structure person: job offset %d bytes\n",offsetof(struct person, job));
printf("structure person_packed: name offset %d bytes\n",offsetof(struct person_packed, name));
printf("structure person_packed: age offset %d bytes\n",offsetof(struct person_packed, age));
printf("structure person_packed: job offset %d bytes\n",offsetof(struct person_packed, job));
return 0;
}
// 结果:
structure person: name offset 0 bytes
structure person: age offset 32 bytes
structure person: job offset 40 bytes // 优化对齐,有填充
structure person_packed: name offset 0 bytes
structure person_packed: age offset 32 bytes
structure person_packed: job offset 36 bytes
7、结构体指针
指针指向结构体时,该指针称为结构体指针。指针变量存储的是结构体变量的内存地址。
- 需要结构体变量,并获取结构体变量的内存地址(&结构体变量)。
- 需要结构体指针(*结构体指针变量)。
- 指针指向结构体(结构体指针变量=&结构体变量)。
- 通过"->"访问指针指向的结构体的成员(结构体指针变量->结构体成员)。也可以使用". "访问结构体成员( (*结构体指针变量). 结构体成员)。
cs
#include <stdio.h>
struct person
{
char name[32];
int age;
char job[16];
};
int main(void)
{
struct person person = {"John", 18, "programmer"}; // 声明结构体变量,并初始化
struct person *p; // 声明结构体指针变量
p = &person; // 指针指向结构体
printf("%s, %d, %s\n", p->name, p->age, p->job); // 通过"->"访问结构体成员
printf("%s, %d, %s\n", (*p).name, (*p).age, (*p).job); // 通过"."访问结构体成员
return 0;
}
// 结果:
John, 18, programmer
John, 18, programmer
可以根据用户输入,通过指针将内容写入结构体中。
cs
#include <stdio.h>
struct person
{
char name[32];
int age;
char job[16];
};
int main(void)
{
struct person *p, person; // 声明结构体指针和结构体变量
p = &person; // 指针指向结构体
printf("Input name: ");
scanf("%s", &p->name); // 获取用户输入,存储到结构体指针指向的结构体成员变量
printf("Input age: ");
scanf("%d", &p->age);
printf("Input job: ");
scanf("%s", &p->job);
printf("%s, %d, %s\n", p->name, p->age, p->job); // 通过"->"访问结构体成员
printf("%s, %d, %s\n", (*p).name, (*p).age, (*p).job); // 通过"."访问结构体成员
return 0;
}
// 结果:
Input name: John 【输入:John】
Input age: 18 【输入:18】
Input job: programmer 【输入:programmer】
John, 18, programmer
John, 18, programmer
可以将结构体指针作为函数参数,结构体指针指向结构体,可通过指针修改原结构体内容。
cs
#include <stdio.h>
#include <string.h>
struct person
{
char name[32];
int age;
char job[16];
};
int changestruct(struct person *p);
int main(void)
{
struct person *p, p1 = {"John", 18, "programmer"};
p = &p1; // 结构体指针指向结构体(即结构体指针变量中存储结构体的内存地址)
printf("Before: %s, %d, %s\n", p->name, p->age, p->job);
changestruct(p); // 结构体指针作为参数传入函数
printf("After: %s, %d, %s\n", p->name, p->age, p->job);
return 0;
}
int changestruct(struct person *p) // 函数参数是结构体指针
{
strcpy(p->name, "Mark");
p->age = 25;
strcpy(p->job, "teacher");
return 0;
}
// 结果:
Before: John, 18, programmer
After: Mark, 25, teacher
8、typedef 数据类型重命名
每次定义结构体变量时,都要使用"struct 结构体名称 结构体变量名",使得代码繁琐。
可以使用typedef给整个结构体定义提供别名,每次定义结构体变量时,只需"别名 结构体变量名"即可,即用 "别名" 代替 "struct 结构体名称" 。
typedef:数据类型重命名(提供别名)
- 可以给基本数据类型重命名,也可以给指针、数组、结构体等数据类型重命名。
- 结构体重命名后,原结构体定义仍保留,不会丢失或改变。
- 数据类型重命名,可以减少因误解或混淆导致的错误,使得代码更简洁易读。
cs
typedef struct person
{
char name[32];
int age;
char job[16];
} Person; //名为person的结构体,别名为Person
Person person; // 定义结构体变量person
Person *p // 定义结构体指针p
int fun1(Person person) // 结构体person作为函数参数
int fun1(Person *p) // 结构体指针p作为函数参数
...