结构体(1)

目录

一.结构体类型的声明

1.结构体的概念

2.声明

3.结构体的特殊声明

4.结构体的自引用

二.结构体内存对齐

        1.对其规则

        2.练习

         3.为什么存在内存对齐


一.结构体类型的声明

1.结构体的概念

        结构体是一些值的集合,这些值成为结构体成员变量。这些变量的类型不必相同。

2.声明

        (1)语法形式:

struct tag
{
	member_list;
}variabal-list;

name是结构体标签;member_list是结构体成员;varialbal-list是结构体name创建的变量。

        (2)结构体创建变量

struct Student
{
	char name[20];//姓名
	char sex[5];//性别:男、女、保密
	int age;//年龄
	int score;//成绩
}p1;//p1是创建的结构体变量

        结构体成员的类型可以是常量、变量、指针、数组、以及其他结构体。

        (3)结构体变量的初始化

  •  按照顺序初始化
#include <stdio.h>

struct Student
{
	char name[20];//姓名
	char sex[5];//性别:男、女、保密
	int age;//年龄
	int score;//成绩
}p1;//p1是创建的结构体变量

int main()
{
	//按照顺序初始化
	struct Student p2 = { "张三","baomi",25,90 };
	return 0;
}
  • 按照指定顺序初始化
#include<stdio.h>

struct Student
{
	char name[20];//姓名
	char sex[5];//性别:男、女、保密
	int age;//年龄
	int score;//成绩
}p1;//p1是创建的结构体变量

int main()
{
	//按照指定顺序初始化
	struct Student p3 = { .age = 18, .sex = "man", .score = 97, .name = "wangwu" };

	return 0;
}

        当结构体按照指定顺序初始化的时候,我们必须指明结构体成员并用“=”来进行初始化。结构体成员名前面要加上"."。就像上面代码所示“ .age = 18 ”、“ .sex = "man" ”。

        以上是结构体比较常见的初始化方法,结构体还有特殊的初始化方法:结构体嵌套初始化

#include<stdio.h>

struct Student
{
	char name[20];//姓名
	char sex[5];//性别:男、女、保密
	int age;//年龄
	int score;//成绩
}p1;//p1是创建的结构体变量

struct school
{
	int szie;
	char name[20];
	struct Student p;
	char location[20];
};

//结构体的嵌套初始化
struct school s = { 200, "xiwangxiaoxue", {"lisi", "baomi", 6, 84 }, "beijing" };

int main()
{
	//结构体的嵌套初始化
	struct school s = { 200, "xiwangxiaoxue", {"lisi", "baomi", 6, 84 }, "beijing" };

	return 0;
}

        结构体的嵌套初始化本质就是,一个结构的初始化包含在另一个结构体的初始化中。

3.结构体的特殊声明

        在有些时候,声明结构是可以不完全声明的。我们将这种结构体称为匿名结构体

struct
{
	int a;
	char b;
	float c;
}x;

struct
{
	int a;
	char b;
	float c;
}a[20],*p;

        对于上述两种结构,我们在声明的时候省略掉了结构体的标签,此时以上两结构体是匿名结构体。

        (1)尽管上面两个结构体的成员相同,但是编译器会将他们当做两个完全不同的类型。因此,对于上面代码,我们不能写:p = &x。p和x是不同的类型.

        (2)匿名结构体如果没有对结构体进行重命名的话,结构体只能用一次,不能够多次使用.

4.结构体的自引用

       (1)概念

        结构体自引用,就是在结构体内部,包含指向自身类型结构体点的指针。

        (2)自引用方法:

  • 没有typedef时
struct Node
{
	int data;
	struct Node next;
};

        这种生命是错误的,因为这种声明是一个无限循环,成员next是一个结构体,next的内部还是一个结构体,就这样依次往下循环迭代.使得无法确定结构体的长度,而且内存空间会被无限制的往下调用.因此,这种方式是非法的。

正确方法:使用指针

struct Node
{
	int data;
	struct Node* next;
};

        由于指针的长度是确定的(32位机器上是4;64位机器上是8),因此,编译器能够正确的确定结构体的长度.

  • 有typedef时

        在夹杂typedef对匿名结构体类型命名时容易出现问题.

typedef struct
{
	int data;
	Node* next;
}Node;

        这种方法是错误的,Node我们是在结构体后面声明的,在匿名结构体内部提前使用Node来创建成员变量这是不行的,因为Node的作用于是从结构体的末尾开始的。

正确方法:不要使用匿名结构体了

typedef struct base
{
	int data;
	struct Node* next;
}Node;

二.结构体内存对齐

        1.对其规则

  •  结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处。
  • 其他成员变量对齐到对齐数整数倍的地址处。

        对齐数 = 编译器默认对齐数该成员变量大小的的较小值.(VS中的对齐数是8;Linux的gcc中没有对齐数,对齐数就是成员自身的大小)

  • 结构体的总大小为最大对齐数的整数倍.
  • 如果嵌套了结构体的情况,嵌套结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。

        2.练习

例1:

struct S1
{
    char c1;
    int i;
    char c2;
};
printf("%d\n", sizeof(struct S1));

 输出结果为:

struct S2
{
	char c1;
	char c2;
	int i;
};
printf("%d\n", sizeof(struct S2));

 输出结果为:

         3.为什么存在内存对齐

                (1)平台原因(移植原因)

                不是所有的平台都能访问任意地址上的任意数据的,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出异常.

                (2)性能原因

                 数据结构(尤其是栈)应该尽可能的在自然边界上对齐.原因在于,为了访问未对齐的数,处理器要做两次内存访问;而对齐的只要做一次访问.结构体内存对齐是拿空间换取时间的做法.

我们在创建结构体类型时,为了既节省空间又节省时间,可以将占用空间小的成员尽量聚到一起。

修改默认对齐数:

        当我们认为默认对齐数不合适的时候,我们可以用#pragma这个预处理指令来修改编译器的默认对齐数。

#include <stdio.h>
#pragma pack(1)//设置默认对⻬数为1
struct S
{
	char c1;
	int i;
	char c2;
};
#pragma pack()//取消设置的对⻬数,还原为默认
int main()
{
	printf("%zd\n", sizeof(struct S));
	return 0;
}

输出结果为: 

 

        上述代码将默认对齐数改为了1,所以int i的最小对齐数是1,对齐到偏移量是1的位置,占4个字节,char c2的对齐数是1,对齐到偏移量为为5的位置。在该结构体中所有的对齐数都是1,所以结构体的大小是6.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值