对结构体的初步了解

>结构体
结构 是一些值得集合,这些值称为成员变量,每个结构体的成员可以是不同类型的变量。
1.结构体的声明:

因为在变量列表里定义的都是全局变量,所以在代码里尽量少的使用全局变量。
举例
struct std
{
         char name[20];
         int age;
         int sex[5];
         int num[20]; //学号
}; //分号不能丢



结构 体的成员:可以是标量,数组,指针,甚至是其他结构体。
结构体的访问:结构体访问成员变量是通过(.)访问的。点操作符接受两个操作数。
举例:

struct std
{
         char name[20];
         int age;
};
int main( void )
{
         struct std s1;
        strcpy(s1.name, "zhangsan" );
               s.age=10;
        printf( "%s %d\n" , s.name, s.age);
        system( "pause" );
         return 0;
}  

>结构体类型创建
1。有名结构体的创建
(1)定义结构体类型时,创建变量。
struct student
{
         char name[20];
         int age;
         char sex;
         float score;
}std;
struct student 为类型和int, char类似,同时创建了std变量。而且std为全局变量。
(2)先创建类型,再声明(定义变量)
struct student
{
        char name[20];
        int age;
        char sex;
        float score;
};
int main(void)
{
        struct student std;//声明的变量
        system("pause");
        return 0;
}
优点:一次定义多次使用,会带来新的命名(提高复杂度)。
2.结构体的特殊声明:在生命结构体时可以不完全声明。
struct
{
        char name[20];
        int age;
        char sex;
        float score;
}stu;
优点:无名结构体不引入新的类型(降低复杂度)
举例:
(1)
struct
{
         double d;
         char c;
         int i;  
}a[20],*p;
struct
{
         double d;
         char c;
         int i;  
}x;
在这里我们可以省略标签tag。而这就是不完全声明。
注意这种做法是不可取的   p=*x;  因为编译器会把上面两个不完全声明当作两个不同的类型,所以是非法的
(2)
struct A
{
         int a;
         struct *B pb;
};
struct B
{
         int b;
         struct *A pa;
};
当结构体A嵌套结构体B时,且结构体B嵌套结构体A时,必须要在上边写一个不完整的声明。否则就会有错误。
3.typedef结构体
typedef struct student
{
        char name[20];
        int age;
        char sex;
        float score;
}stu;
int main(void)
{
          stu s;//创建了一个S变量
        system("pause");
        return 0;
}
typedef是对类型的重命名,在这里struct student 类型就是stu.也就是说stu是struct student 的新名字。
>结构体初始化(传参)
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct Stu {
         int id;
         int age;
         char arr[20];
} Stu ,* pStu ;
int main( void )
{
          //初始化方法一
         struct Stu s;
        s.id = 1;
        s.age = 20;
        strcpy(s.arr, "Hello" );
          //初始化方法二
         struct Stu s1 = { 2, 21, "World" };
         Stu s3;
          //初始化方法三(重命名后)
         Stu s4 = { 4, 24, "Java" };
         Stu * p = &s4;
                //三种不同的输出方式
        printf( "%d %d %s\n" ,s4.id,s4.age,s4.arr);
        printf( "%d %d %s\n" , (*p).id, (*p).age, (*p).arr);
        printf( "%d %d %s\n" , p->id, p->age, p->arr);
         Stu * p2 = &s1;
         pStu p3 = &s1;
        printf( "%d %d %s\n" , p3->id, p3->age, p3->arr);
         pStu p4 = NULL ;
        p4 = ( pStu )malloc( sizeof ( Stu ));//动态开辟内存
        p4->id = 05;
        p4->age = 26;
        
        strcpy(p4->arr, "zhangsan" );
        printf( "%d %d %s\n" , p4->id, p4->age, p4->arr);
        free(p4);
        system( "pause" );
         return 0;
}


1.指向结构体变量的指针形式:结构体名 *指针名
如   struct student  * std;
struct student
{
         char name[20];
         int id;
         int age;
};
struct student *pstruct;
方法:
1)首先在程序中声明结构类型XXX,并同时定义变量,为结构进行初始化操作。
2)定义结构体变量指针 pstruct,。然后pstruct=&student,使得指针指向student.
3)输出消息提示,然后在printf()中使用指向结构体变量的指针进行引用结构体成员变量,将消息输出。
4)以下的访问方式时等价的
a.student.成员名 
b.(*pstruct).成员名
c.pstruct->成员名
注意: 在使用->时注意一下情况
1.pstruct->i     表示结构体变量i的值
2.pstruct->i++     表示 结构体变量i的值,使用后该值加一
3.++pstruct->i     表示结构体变量i的值加一,计算后在进行使用。

总结 结构体传参时,要传结构体的地址。因为函数传参时参数需要压栈。如果对象是结构体的时候,结构体过大,参数压栈时系统的开销比较大,会导致性能下降。
>结构体内存对齐
1.在这里我们先说结构体内存对齐的四条规则
(1).第一个成员在结构体变量偏移量为0的地址处。
(2).从第二个成员开始,每个成员都要对齐到【对齐数】的整倍数处。 【对齐数】:编译器默认的一个对其书与该成员大小的较小值。VS—8.     LINUX—4.
(3).结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整倍数处。
(4).如果存在嵌套的情况,嵌套的结构体对齐到结构体自己的最大对齐的整数倍处。结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整倍数。
举例:
(1)
struct S1
{
        char c1;
        int i;
        char c2;
};
printf("%d\n", sizeof(struct(S1));

结果为:12
原因:c1只有一个字节,i占四个字节,为了对齐i从4开始到8结束,而前面的1,2 ,3也会被浪费。C2占一个字节在9处。因为结构体的对齐数为最大对齐数的整倍数处,而在此结构体里最大对齐数为4,所以最后的对齐数应该是4的倍数,向后数12为4的倍数。因此9,10,11都会被浪费。所以该结构体的大小为12
(2)
struct S2
{
        char c1;
        char c2;
        int i;  
};
printf("%d\n", sizeof(struct(S2));
结果为:8
原因:c1和c2都只占一个字节分别是0和1,i占4个字节,为了对齐i从4开始到8结束。而结构体的对齐数为结构体成员的最大对齐数,此结构里最大对齐数为4.而8是4的倍数,所以结构体大小为8.
(3)
struct S3
{
        double d;
        char c;
        int i;  
};
printf("%d\n", sizeof(struct(S3));
结果为:16
原因:d从0到7占8个字节,c占一个字节8,i为了对齐从9到12,而结构体的对齐数为结构体成员的最大对齐数,此结构里最大对齐数为8,而16是8的倍数,所以结构体大小为16.
(4)
struct S4
{
          char c;
          struct S3 s3;
         double d;
};
printf("%、d\n", sizeof(struct(S4));

结果为:32
原因:c占一个字节,而S3占16个字节,因为存在嵌套,所以要对齐到改嵌套的结构体的最大对齐数8处,所以S3占的字节数从8到23,d占8个字节从24到31.而所有结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整倍数。所以该结构体的大小为32
2.为什么存在内存对齐??
(1)平台原因(移植原因)
         不是所有的硬件平台都能访问任意地址的任意数据。某些硬件平台只能在某些地址处取得某些特定的数据。否则抛出硬件异常。
(2)性能原因
         数据结构(尤其是栈)应该尽可能的在自然边界上对齐。因为,为了访问为对其的内存,处理器需要两次内存访问,二对其的内存只要一次。

总结:结构体就是那空间换时间。所以在用结构体时,我们既要满足对齐还要节省空间。那怎么做到呢?那就是让占空间小得尽量集中在一起。

3预处理指令: #pragma pack (n)。
#pragma pack (n)是来设定变量以n字节对齐。n就是 在这里n的可取值为1,2,4,8,16.
n字节对齐就是表示变量存放的起始地址的偏移量有两种方式:
1.n大于该变量所占有的字节数,那么偏移量必须满足默认的对齐方式。
2.n小于该变量的类型所占用的字节数,那么偏移量为n的倍数。
结构的总大小也有约束:
如果n大于所有成员变量类型所占用的字节数,那么结构体的总结构大小必须为占用空间最大的变量占用的空间数的倍数。
在用完 #pragma pack 后我们要恢复默认对齐状态 #pragma pack ()
#pragma pack(push)//保持对齐状态
#pragma pack(4)//设定为4字节对齐
struct A
{
        char a ;
        double b ;
        int c ; 
};
#pragma pack(pop)//恢复默认对齐状态
这个结构体的大小为16.


                                                                      
>位段,位段计算机大小
格式:<成员名>:<二进制数>
struct A
{
        int a : 2;
        int b : 5;
        int c : 10;
        int d : 30;
};
printf("%d\n",sizeof(struct A));
解释:该 的大小为8。二进制数表示bite位。一个字节8个bite 位。
不存在内存对齐。 的成员必须是int ,  unsigned int 或 signed int。
的内存分配:
1. 的成员可以是 int ,  unsigned int ,signed int或char(整形家族)类型。
2.段的空间上是按照4字节(int)或1字节(char)的方式开辟的。
3。位段涉及很多不确定的因素,位段不跨平台。(注意可移植性的程序应避免使用位段)
位段跨平台问题:
1.int 位段被当作有符号数还是无符号数是不确定的。
2.位段中的最大位的数目不确定(16位机器最大为16,32位机器最大位数为32,写成25,在16位机器会出现问题)
3.位段中的成员在内存中从左向右分配,还是从左向右分配的标准还没定义。
4.如果一个结构包含两个段位,第二个位段成员较大,无法容纳于第一个段位剩余的位时,是舍弃剩余的位段还是利用,这是不确定的。


>枚举
枚举就是把需要的情况一一列举出来
例如
enum std
{
        male,
        female,
        secret,
};
解释:{}里的内容是枚举类型的可能取值,也叫枚举常量,这些可能的取值都是有值的,默认从0开始,依次向后递增1。也可以在定义的时候赋值。
enum std
{
        male = 1,
        female = 2,
        secret = 3,
};

枚举的优点:
1.增加代码的可读性和可维护性。
2.和#define定义的标识符相比,枚举类型有类型检查,更加严谨。(#define是无法调试)
3.防止了命名污染(减少了命名冲突)。
4.便于调试。
5.使用方便,一次可定义多个常量。


>联合
联合的形式:
union  共用体名
{
    成员列表;
}变量列表;
联合也叫共用体。联合变量的所有成员共享同片存储区,因此联合变量每个时刻里只能保存它某一个成员的值。
联合变量也可以在定义时初始化,但只能对第一个成员进行 初始化
union A
{
         int n;
         char b;
};
union A s = { 5 }; //只有s.n被初始化

共用体的引用:  共用体变量。成员名;
联合的特征:
1.这些类型的成员公用同一块大小的内存,一次只能使用其中的一个成员。也就是说在共用体中,只有一个成员起作用。(共用同一段内存目的是节约内存(节省空间的类型:位域))
2.union可以定义多个成员, union的大小由最大的成员的大小决定
3.union存放的顺序是所有的成员都从低地址开始存放。(因此可以用联合判断CPU的大小端)
int check()
{
         union C
        {
                int a;
                char b;
        }c;
        c.a = 1;
         return (c.b == 1);//如果返回1说明为小端
}
4.对union某一个成员赋值,会覆盖其他成员。也就是说共用体中起作用的是最后一次放进去的成员,在存入一个新成员后原有的成员会失去作用。(但提前是成员所占的字节数相同,当成员所占的字节数不相同时只会覆盖相应字节的上的值。比如对char成员赋值不会吧int成员赋盖掉,因为char只占用一个字节,而int占四个字节)。
5.不能对共用体变量名赋值,也不能企图引用变量名得到一个值。
6.不能用共用体作为函数的参数或返回值,当需要在函数间传递共用体时,可以使用指向共用体的变量指针实现。













3.

猜你喜欢

转载自blog.csdn.net/qq_40955824/article/details/80627062