在 C 语言中,结构体是一种自定义的数据类型,它允许将不同类型的数据项组合成一个单一实体。这在组织复杂数据时非常有用,因为它可以将有逻辑关系的数据组合在一起。结构体是一些值的集合,这些值是结构成员变量。
结构体定义由关键字?struct?和结构体名组成,结构体名可以根据需要自行定义。
struct tag {
member-list
member-list
member-list
...
} variable-list ;
struct 结构体标签
{
????变量定义;
????变量定义;
????变量定义;
????????...
} 结构变量;
tag?是结构体标签。
member-list?是标准的变量定义,比如?int i;?或者?float f;,或者其他有效的变量定义。
variable-list?结构变量,定义在结构的末尾,最后一个分号之前,您可以指定一个或多个结构变量。
在一般情况下,tag、member-list、variable-list?这 3 部分至少要出现 2 个。
//此声明声明了拥有3个成员的结构体,分别为int类型的a,char类型的的b和double类型的c
//同时又声明了结构体变量s1
//这个结构体并没有标明其标签
struct
{
int a;
char b;
double c;
} s1;
//此声明声明了拥有3个成员的结构体
//结构体的标签被命名为Student,没有声明变量
struct Student
{
int age;
char name[20];
int height;
};
//此声明声明了拥有3个成员的结构体
//结构体的标签被命名为Person,同时声明变量
struct Person
{
int age;
char name[20];
int height;
};
在上面的声明中,第二个和第三个声明被编译器当作两个完全不同的类型,即使他们的成员列表是一样的。因为它们的标签名不一样,即使两个结构体的成员列表完全相同,如果它们被声明为不同的类型(即使是匿名的),那么它们仍然是不同的类型。
在C语言中,当您定义一个结构体时,可以为其成员变量提供初始值。这种操作称为结构体的初始化。初始化时,您可以使用大括号{}
来包含一系列初始化器,这些初始化器的顺序应当与结构体定义中的成员声明顺序一致。每个初始化器由逗号分隔。
struct Person {
int age;
char name[20];
int height;
};
struct Person z = { 18, "zhangsan", 180 };
为了声明一个匿名结构体的实例,需要先给结构体定义一个别名,或者直接在声明时提供一个变量名。如果想要声明一个匿名结构体并立即创建一个该类型的实例,可以用下列方法:
(i)直接在声明时初始化结构体变量:
struct {
int age;
char name[20];
int height;
} z = { 18, 'zhangsan', 180 };
(ii)给匿名结构体定义一个别名:
typedef struct
{
int age;
char name[20];
int height;
} Person;
Person z = { 18, "zhangsan", 180 };
Person w = { 19, "wanger", 190 };
这里初始化变量时不能用:
struct Person z = { 18, "zhangsan", 180 };
这是错误的。
因为使用了typedef
,所以在定义变量时应该直接使用别名Person
,而不是struct Person
。正确的声明应该是:
Person z = { 18, "zhangsan", 180 };
如果使用struct Person
,编译器会认为你正在尝试声明一个新的、不同的结构体类型,它没有找到这个新类型的定义,因此会报告“不完整的类型”的错误。
这与有标签的结构体不同。
在C语言中,结构体的成员可以是几乎任何类型,包括:
基本数据类型:如 int
, float
, double
, char
等。
数组类型:可以是基本数据类型的数组,例如 int numbers[10];
,也可以是结构体类型的数组。
指针类型:包括指向基本数据类型、数组、其他结构体或者函数的指针。
结构体类型:
联合体(union)类型:可以包含联合体,它是一种特殊的数据结构,允许在相同的内存位置存储不同的数据类型。
枚举类型(enum):可以包含枚举类型成员,用于表示成员变量只能取有限个命名的整数值。
函数指针类型:可以包含指向函数的指针,这允许结构体“拥有”可以调用的函数。
下面是一个包含不同类型成员的结构体示例:
#include <stdio.h>
// 声明一个枚举类型
enum Color
{
RED,
GREEN,
BLUE
};
// 定义一个结构体类型
struct Example
{
int integer; // 基本数据类型
double floating_point; // 基本数据类型
char character; // 基本数据类型
char string[100]; // 数组类型
int *pointer; // 指针类型
struct Nested
{
float nested_floating_point;
} nested_struct; // 嵌套结构体类型
union
{
int union_int;
float union_float;
} union_member; // 联合体类型
enum Color color; // 枚举类型
void (*function_pointer)(void); // 函数指针类型
};
// 一个简单的函数,之后会被赋给结构体中的函数指针
void simpleFunction()
{
printf("Hello from simpleFunction!\n");
}
int main()
{
struct Example example;
example.integer = 10;
example.floating_point = 3.14;
example.character = 'A';
example.string[0] = '\0'; // 初始化字符串为空字符串
int value = 20;
example.pointer = &value; // 指向value的指针
example.nested_struct.nested_floating_point = 2.71;
example.union_member.union_float = 5.55f; // 使用联合体的float成员
example.color = RED; // 枚举类型成员赋值
example.function_pointer = simpleFunction; // 函数指针赋值
// 使用函数指针调用函数
example.function_pointer();
return 0;
}
结构体的成员可以包含其他结构体,也可以包含指向自己结构体类型的指针,而通常这种指针的应用是为了实现一些更高级的数据结构如链表和树等。
以下是一个使用自引用指针的结构体定义,这个结构体可以用来实现一个简单的单向链表节点:
#include <stdio.h>
struct ListNode
{
int value; // 这个节点存储的数据
struct ListNode* next; // 指向下一个节点的指针
};
int main()
{
// 创建链表节点
struct ListNode node1;
node1.value = 1;
node1.next = NULL; // 目前链表只有一个节点,所以next指向NULL
// 添加第二个节点
struct ListNode node2;
node2.value = 2;
node2.next = NULL;
// 将第一个节点的next指针指向第二个节点
node1.next = &node2;
// 现在,node1.next指向node2,形成了一个包含两个节点的链表
return 0;
}
如果两个结构体互相包含,则需要对其中一个结构体进行不完整声明,如下所示:
struct B;//对结构体B进行不完整声明
struct A//结构体A中包含指向结构体B的指针
{
struct B* partner;
//other members;
};
//结构体B中包含指向结构体A的指针,在A声明完后,B也随之进行声明
struct B
{
struct A* partner;
//other members;
};
格式:
结构体变量.成员
实例:
#include <stdio.h>
struct Student
{
char name[20];
double height;
int age;
};
int main()
{
struct Student a = {"xiaoa",1.80,19};
printf("%s %lf %d",a.name,a.height,a.age);
return 0;
}
运行结果:
也可以使用指针,但 . 操作符的优先级大于 * 操作符,要加()
#include <stdio.h>
struct Student
{
char name[20];
double height;
int age;
};
int main()
{
struct Student a = {"xiaoa",1.80,19};
struct Student* pa = &a;
printf("%s %lf %d",(*pa).name,(*pa).height,(*pa).age);
return 0;
}
运行结果:
格式:
结构体变量的指针->成员
实例:
#include <stdio.h>
struct Student
{
char name[20];
double height;
int age;
};
int main()
{
struct Student a = {"xiaoa",1.80,19};
struct Student* pa = &a;
printf("%s %lf %d\n",pa->name,pa->height,pa->age);
return 0;
}
运行结果:
这种方法占用内存和时间较多,因为要将实参拷贝成形参需要时间,形参存储需要内存空间。
#include <stdio.h>
struct Student//定义结构体
{
char name[20];
double height;
int age;
};
void print(struct Student student)//定义函数
{
printf("名字 %s\n",student.name);
printf("身高 %lf\n", student.height);
printf("年龄 %d\n", student.age);
}
int main()
{
struct Student a = {"xiaoa",1.80,19};//定义和初始化结构变量a
struct Student b = {"xiaob",1.85,18};//定义和初始化结构变量b
print(a);//利用print函数输出结构变量a
print(b);//利用print函数输出结构变量b
return 0;
}
运行结果:
这种方法更节省空间,不用创建形参。
#include <stdio.h>
struct Student//定义结构体
{
char name[20];
double height;
int age;
};
void print(struct Student *student)
{
printf("名字 %s\n", student->name);
printf("身高 %lf\n", student->height);
printf("年龄 %d\n", student->age);
}
int main()
{
struct Student a = { "xiaoa",1.80,19 };//定义和初始化结构变量a
struct Student b = { "xiaob",1.85,18 };//定义和初始化结构变量b
print(&a);//传入结构体变量a的地址
print(&b);//传入结构体变量b的地址
return 0;
}
运行结果: