结构体详解:
一、结构体的定义:
我们定义结构体,需要用到关键字struct,就和int类型、double类型一样,也需要用到int和double这两个关键字,用struct定义的变量称为结构体变量。
struct A
{
int a;
char c;
}s;
上面的代码就是简单定义了一个结构体 ,在结构体A的{}中的a和c我们称作结构体成员,也是变量,可以具有不同的类型,在外面的s我们称作为结构体变量,为struct所定义的。
二、结构体变量的初始化:
上面我们创建了一个结构体变量s,对s进行初始化为:
struct A s{3,'a'}
如何进行初始化要看结构体的成员变量。
三、结构体的大小:
typedef struct{
int a;
char b;
short c;
short d;
}AA_t;
int main()
{
printf("%zd\n",sizeof(AA_t));
return 0;
}
一开始我们肯定会想到结构体所占的字节数与成员变量有关,上面的代码中有一个int类型、一个char类型、两个short类型,4+1+2+2=9,那么这个结构体的大小应该为9个字节,可是真的是这样吗?
通过运行程序我们可以发现,结构体所占的字节数为12个字节,这是为什么呢?
下面来给大家讲解一下。
出现这种情况是因为结构体在内存中存放时会进行内存对齐,变量进行存储时会存放在他们所占字节的倍数的位置,但所占字节数不变。
上面结构体的数据存储便是这样存储的,从0开始存储,int类型占4个字节,占到0-3,后面要按照内存对齐的方式进行存储,char只占一个字节,而所有数字都是1的倍数,所以char可以存放到4,而short占两个字节,5不是2的倍数,所以char只能存放到6,存放两个字节占用6和7,还有一个short类型,8是2的倍数,所以可以存放到8,两个字节占用8和9,那这样一共也只占了10个字节,为什么程序输出的结构体大小占12个字节呢?
这时我们就要将另外一个概念,对齐数。
结构体的大小只能是结构体成员变量中的最大对齐数的整数倍,而结构体成员变量的对齐数是将结构体成员变量所占字节数和编译器默认的对齐数取小值作为对齐数。
typedef struct{
int a;//4个字节 编译器对齐数为8 取小值对齐数为4
char b;//1个字节 编译器对齐数为8 取小值对齐数为1
short c;//2个字节 编译器对齐数为8 取小值对齐数为2
short d;//2个字节 编译器对齐数为8 取小值对齐数为2
}AA_t;
由此可见,成员变量中的最大对齐数为4,所以结构体所占字节数必须为最大对齐数的整数倍,因为至少需要10个字节才能存储,所以结构体所占字节数不能为8,只能为12,这也是为什么输出来结构体的大小为12个字节。
看到这很多人就会疑惑,这样子存放数据不是会造成内存空间的浪费吗?为什么还要这样子存放数据呢?
有两点原因:
1.平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2.性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
虽然内存对齐固然会使的内存空间的浪费,但能加快处理器的访问,是以空间来换取时间。
四、结构体成员的访问:
1. .操作符
struct student
{
char name[20];
int age;
char sex[20];
};
int main()
{
struct student s = {"zhangsan",18,"nan" };
return 0;
}
我们已经初始化了结构体成员,那么我们要如何输出里面的数据呢,这时就要用到.操作符。
struct student
{
char name[20];
int age;
char sex[20];
};
int main()
{
struct student s = {"zhangsan",18,"nan" };
printf("%s %d %s", s.name, s.age, s.sex);
return 0;
}
用了.操作符后,便可以输出结构体内的数据了。
结果:
2.->操作符:
如果我们用指针来访问结构体,那么就不能使用.操作符了,而是应该使用->操作符来访问。
具体代码如下:
struct student
{
char name[20];
int age;
char sex[20];
};
int main()
{
struct student s = {"zhangsan",18,"nan" };
struct student* p = &s;
printf("%s %d %s", s.name, s.age, s.sex);
printf("\n");
printf("%s %d %s", p->name, p->age, p->sex);
return 0;
}
结果:
两次输出的结果是一样的,可见.操作符和->操作符都可以访问结构体,只不过.操作符是由结构体变量进行直接访问,而->操作符是由指针进行简洁访问。
五、结构体的传参:
第一种传参方式:
void print_struct(struct student s)
直接建立一个结构体变量进行传参。
第二种传参方式:
void print_struct1(struct student* p)
以结构体指针的方式进行传参。
代码:
struct student
{
char name[20];
int age;
char sex[20];
};
void print_struct(struct student s)
{
printf("%s %d %s\n", s.name, s.age, s.sex);
}
void print_struct1(struct student* p)
{
printf("%s %d %s", p->name, p->age, p->sex);
}
int main()
{
struct student s = {"zhangsan",18,"nan" };
struct student* p = &s;
print_struct(s);
print_struct1(p);
return 0;
}
结果:
和上面的一样,当传参以结构体变量的方式进行传参,是用.操作符进行访问,以结构体指针的方式进行传参 ,用->操作符进行访问。
联合体详解:
一、联合体的定义:
和结构体一样,联合体的定义也有自己的关键字:union
union Un
{
int a;
char b;
}u;
这是一个简单的联合体定义 ,和结构体一样,在{}里面的a和b称作为联合体的成员变量,在外面的u称作为联合体变量。
二、联合体变量的初始化:
union Un s={3,'a'}
也是根据联合体的成员变量来进行初始化。
三、联合体的大小:
union Un
{
int a;
char b;
double c;
}u;
int main()
{
printf("%zd\n", sizeof(u));
return 0;
}
上述代码中的联合体的大小为多少呢?是和结构体一样的内存对齐吗?其实不是的,在联合体中所有的元素都共用同一个内存,所以联合体也有另外一个名字叫做共用体,也就是所有元素都共用一个内存。
因为是共用一个内存,所以共用体所占字节数在大部分情况下等于成员变量所占用的最大字节数,但共用体所占字节数也必须为最大对齐数的整数倍。
union Un
{
int a;//对齐数为4 占4字节
char b;//对齐数为1 占1字节
double c;//对齐数为8 占8字节
}u;
int main()
{
printf("%zd\n", sizeof(u));
return 0;
}
上述代码的最大对齐数为8,成员变量所占的最大字节数也为8,为8的倍数,所以该共用体的大小为8个字节。
结果:
我们再来看一种情况
union Un
{
char a[7];
int b[3];
double c;
}u;
int main()
{
printf("%zd\n", sizeof(u));
return 0;
}
这个共用体的大小是多少呢,也是成员变量所占最大字节12个字节吗?
union Un
{
char a[7];//对齐数为1 占7个字节
int b[3];//对齐数为4 占12个字节
double c;//对齐数为8 占8个字节
}u;
int main()
{
printf("%zd\n", sizeof(u));
return 0;
}
成员变量所占的最大字节数为12个字节,最大对齐数为8,所以共用体的大小为8的整数倍,至少要12个字节,所以共用体的大小为16个字节。
结果:
四、联合体的成员访问:
和结构体一样,都可以通过.操作符和->操作符来进行访问,.操作符用于共用体变量进行直接访问,->操作符用于共用体指针进行间接访问。
五、联合体传参:
和结构体传参一样,可以以共用体变量进行传参,也可以通过共用体指针进行传参。
六、通过共用体来判断大小端存储:
代码:
union Un
{
char a;
int n;
}u;
int func()
{
u.n = 1;
return u.a;
}
int main()
{
int ret = func();
if (ret == 1)
{
printf("是小端存储\n");
}
else
{
printf("是大端存储\n");
}
return 0;
}
大小端存储在我之前的博客中有讲过,这里便不再赘述。
结果:
本次到这里就结束了,希望能够对大家有帮助。