C语言结构体详解
C语言中用数组表示一组相同类型元素的集合,要表示不同类型元素的集合就要用到结构体。
结构体的声明
结构体的标准格式如下:
//struct是关键字,tag是结构体标签,member-list是结构体成员列表,variable-list是结构体变量列表,就是在结构体声明的同时创建的变量(也可以没有)
struct tag
{
member-list;
}variable-list;//这里的分号不能省略
例如要描述一个学生类型:
struct student
{
char name[20];//姓名
int age;//年龄
char id[20];//学号
};//分号不能省略
结构体变量的定义和初始化
结构体变量的定义有以下两种方式:
struct point
{
int x;
int y;
}p1;//在声明结构体类型的同时创建变量
int main()
{
struct point p2;//定义结构体变量p2,此时的struct point相当于一个数据类型,和int等数据类型类似,注意struct不能省略
}
在定义变量的同时为变量的成员赋值就是初始化:
struct point
{
int x;
int y;
};
struct Point p3 = {x, y};//创建变量同时赋值
struct Stu //类型声明
{
char name[15];//名字
int age; //年龄
};
struct Stu s = {"zhangsan", 20};//初始化
struct Node
{
int data;
struct Point p;
struct Node* next;
}n1 = {10, {4,5}, NULL}; //结构体嵌套初始化
struct Node n2 = {20, {5, 6}, NULL};//结构体嵌套初始化
结构体成员访问
结构体变量访问成员使用.操作符,结构体指针访问成员使用->操作符:
struct Stu
{
char name[20];
int age;
};
void print(struct Stu* ps)
{
//使用结构体指针访问指向对象的成员
printf("name = %s age = %d\n", ps->name, ps->age);
}
int main()
{
struct Stu s = {"zhangsan", 20};
printf("name:%s age:%s\n",s.name,s.age);//结构体变量访问成员
print(&s);//结构体地址传参
return 0;
}
结构体传参
struct S
{
int data[1000];
int num;
};
struct S s = {{1,2,3,4}, 1000};
//结构体传参
void print1(struct S s)
{
printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{
printf("%d\n", ps->num);
}
int main()
{
print1(s); //传结构体
print2(&s); //传地址
return 0;
}
上面两个print函数中print2更好,因为值传递需要临时拷贝一份结构体变量,如果结构体很大会加大内存开销,降低性能,所以结构体传参最好用地址传递。
匿名结构体
结构体类型声明时可以没有标签tag,称为匿名结构体,但是这种结构体只能用一次,下面举个例子:
//匿名结构体类型
struct
{
int a;
char b;
float c;
}x;//只能在这里定义变量,因为没有标签,不能在其他地方定义变量
struct
{
int a;
char b;
float c;
}a[20], *p;
p = &x;//这样写是不可以的,p是匿名结构体指针,x是匿名结构体变量,虽然两个结构体的成员相同,但是也视为是两种不同的结构体。
结构体的自引用
是否可以在结构体中包含一个该结构体类型的成员呢:
//代码1
struct Node
{
int data;
struct Node next;//结构体成员的类型和结构体相同
};
//上面的写法是不可行的,如果可以,sizeof(struct Node)求不出来
//下面是正确的方法,链表的节点就是这样的结构体
//代码2
struct Node
{
int data;
struct Node* next;//这里存放指向下一个节点的地址
};
结构体内存对齐
结构体是不同类型的元素的集合,那么不同结构体的大小怎么计算呢,这就涉及到结构体的内存对齐,下面是结构体内存对齐的规则:
①第一个成员在与结构体变量偏移量为0的地址处,也就是结构体的起始位置。
②其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数=编译器默认的一个对齐数 与 该成员大小的较小值。
vs中默认对齐数为8,Linux中没有默认对齐数,对齐数就是成员自身的大小
③结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
④如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
下面举个例子说明一下:
c1的大小是1,默认对齐数是8,所以根据规则2,c1的对齐数是1,对齐到0的地址处是可以的。
i的大小是4,默认对齐数是8,所以根据规则2,i的对齐数是4,因为c1只占了1个字节,i又要对其到4的整数倍,所以需要浪费三个字节,i从偏移量4的位置开始存储。
c2同c1,对齐数是1,i占四个字节,c2从偏移量8的位置开始是可以的。
又因为规则3,总大小应该是最大对齐数的整数倍,c1和c2对齐数是1,i的对齐数是4,所以最大对齐数是4,目前的总大小是9,因此总大小应该增加到4的整数倍,也就是12,c2后面要再浪费三个字节的空间。
根据上面的例子可以得出下面的结论:
在设计结构体类型时,尽量让占用空间小的成员集中在一起。
默认对齐数可以自行修改:
#pragma pack(8)//设置默认对齐数为8
struct S1
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认