[C语言] 12--结构体

结构体的声明:
struct 结构体标签
{
成员1;
成员2;
成员3;

}
注意:
①struct : 关键字 , 用来告诉编译器 我要声明的东西是一个自定义的类型*(结构体)
②结构体标签:用来区分不同的结构体类型
③成员:指结构体内部由哪些基础数据类型所构成, 可以是任何的数据类型

1.如何定义与初始化

定义与初始化实际为结构体分配内存空间 栈

//普通成员初始化
struct TieZhu  c = { "Guide of Programming in the Linux Envirnment" , 3.59 , "张三" } ;
// 指定成员初始化
struct TieZhu  c = { 
    .Price = 3.59 ,                     
    .Name = "张三",
    .Book = "Guide of Programming in the Linux Envirnment" , 
    .num = 1234654 
};              

注意:
①普通成员初始化: 写起来方便一点, 但是用起来麻烦一点,不利于代码的更新于迭代。
②指定成员初始化:写起来麻烦一点, 但是用起来比较轻松, 有利于后期代码的更新迭代, 不会照成成员与成员之间初始化错位的问题。

在这里插入图片描述

2.成员引用

概念:结构体相当于一个数据的集合,里面有多个不同的数据组合而成,每一个成员都可以引用符来单独引用
语法:
结构体变量名.成员 //普通的结构体变量
结构体指针变量->成员 //结构体指针的访问方法

// 修改结构体变量中某一个成员的值
c.Price = 5 ;

// 如何引用
 printf("Book:%s \nPrice:%f  \nName:%s\n" , c.Book,  c.Price , c.Name);

对结构体的堆内存进行赋值需要注意:
在这里插入图片描述
在这里插入图片描述

由上图可知如果Book指向的是另外一个堆空间,那么在释放结构体的内存时,需要先释放Book所指向的内存区。
总结:

// 结构体声明   -->   不占用内存空间
struct TieZhu

{
    int num ;
    char *Book;
    float Price;
    char Name[32]; 
};

void main()
{
    //结构体变量  定义与初始化
    struct TieZhu a = {123, "Book", 3.14, "Even"};
    //结构体指针
    struct TieZhu *p = calloc(1,sizeof(struct TieZhu));
    //结构体数组
    int arr[10];
    struct TieZhu arr[10];
    //成员引用
    a.num = 245;
    p->num = 250;
    arr[0].num = 366;
}

3.结构体声明的变异

结构体声明 --> 不占用内存空间

struct TieZhu

{
    int num ;
    char *Book;
    float Price;
    char Name[32]; 
};

变异1:在声明结构体类型时,顺便定义了变量

struct TieZhu

{
    int num ;
    char *Book;
    float Price;
    char Name[32]; 
}Even, *Jacy;   //在声明结构体类型时,顺便定义了两个变量

Even.num = 1024;
Jacy = &Even; //让指针Jacy指向Even的地址
Jacy->Book = "Hello";

变异2: 省略结构体的标签名,但是注意一般在省略标签的情况
下需要在声明语句后面顺便定义变量/指针, 否则后面无法在定义这种类型的结构体变量。
这种写法比较少出现, 如果要出现一般是作为某个结构体内部的成员使用。

struct
{
    int num ;
    char *Book;
    float Price;
    char Name[32]; 
}Even, *Jacy;   //在声明结构体类型时,顺便定义了两个变量

Even.num = 1024;
Jacy = &Even; //让指针Jacy指向Even的地址
Jacy->Book = "Hello";

示例:

struct node
{
    int i ;
    char c ;
    float f ;
    struct{      // 结构体内部的成员
        float ff ;
        double d ;
    } info ;
};

int main(int argc, char const *argv[])
{
      struct node data = {
            .c = 'A',
            .i = 1024 ,
            .f = 3.489 ,
            .info.d = 665.1234,
            .info.ff = 3.444
      };
      struct node * p = &data ;
      printf("d:%lf , ff:%lf \n" , data.info.d , data.info.ff  );
      printf("d:%lf , ff:%lf \n" , p->info.d , p->info.ff  );
      return 0;        
}

变异 3 :【推荐使用】
使用 typedef 来给结构体的类型取别名
Tz 相当于 struct TieZhu 可以直接定义普通结构体变量
P_Tz 相当于 struct TieZhu * 可以直接定义结构体指针变量

typedef struct TieZhu
{
    int num ;
    char *Book;
    float Price;
    char Name[32]; 
}Tz , *P_Tz ;

示例:

typedef struct TieZhu
{
    int num ;
    char *Book;
    float Price;
    char Name[32]; 
}Tz , *P_Tz ;
int main(int argc, char const *argv[])
{
      Tz b = {
          .Book = "Hello" ,
          .Name = "Even",
          .Price = 3.14           
      };
       P_Tz p = &b ;
       printf("Name : %s \n" , b.Name );
       printf("Name : %s \n" , p->Name );
       return 0;
}

结构体数组:
struct TieZhu arr[5] ; // 拥有5个结构体的元素的数组

结构体数组指针:
struct TieZhu(*p)[5] ; // 指向的是一个 拥有5个元素的结构体数组

结构体指针数组:
struct TieZhu * arr[5]; // 存放的是5个结构体类型的指针的数组

4.结构体尺寸

4.1 CPU字长

概念:字长就是指CPU(处理器)在执行一条指令时,最大一个运算能力,这个运算能力由两部分决定一个就是处理本身的运算能力,另一个就是系统的字长,比如常见的32位/64位系统,如果使用32位系统那么在处理数据的时候每一次最多可以处理32位的数据(4字节)
在这里插入图片描述

4.2 地址对齐

概念:每一款不同的处理器,存取内存数据都会有不同的策略,如果是32位的CPU,一般来讲他在存取内存数据的时候,每次至少存取4个字节(即32位),也就是按4字节对齐来存取的。换个角度来讲:CPU有这个能力,他能一次存取4个字节。
接下来我们可以想到,为了更高效地读取数据,编译器会尽可能地将变量塞进一个4字节单元里面,因为这样最省时间。如果变量比较大,4 个字节放不下,则编译器会尽可能地将变量塞进两个 4 字节单元里面,反正一句话:两个坑能装得下的就绝不用三个坑。这就是为什么变量的地址要对齐的最根本原因。
在这里插入图片描述
在这里插入图片描述

所谓的地址对齐主要思想:
尽可能提高CPU的运行效率(仅可能的少读取、写入内存)
一个数据如能使用一个单元来存放就绝对不使用两个,两个能存放绝对不用三个

4.3 普通变量的M值

概念:一个数据它的大小是固定的(比如整型),如果这个数据他所存放的地址能够被某一个数所整除(4)那么这个数就成为当前数据的M值。
可以根据具体的系统字长以及数据的大小可以计算得出M值。
示例:

int i ;  // i 占用 4 字节, 如果i 存放在能被4整除的地址下则是地址对齐, 因此他的M值为4 
char c ;// c占用 1 字节, 如果c 存放在能被1整除的地址下则是地址对齐, 因此他的M值为1 
short s ;// s 占用 2 字节, 如果s 存放在能被2整除的地址下则是地址对齐, 因此他的M值为2 
double d ; // d 占用 8 字节, 如果d 存放在能被4整除的地址下则是地址对齐, 因此他的M值为4 
float f ;// f 占用 4 字节, 如果f 存放在能被4整除的地址下则是地址对齐, 因此他的M值为4 

注意:
如果一个变量的大小超过4 (8/16/32) M值则按4计算即可

4.4 手残干预M值

char c attribute((aligned(16))) ;
注意:
attribute 机制是GNU特定语法,属于C语言标准的拓展。
attribute 前后都有个两个下划线_
attribute 右边由两对小括号 (( ))
attribute 还支持其它的设置…
一个变量他的M值只能提升不允许降低 , 只能是2的N次幂

4.5 结构体的M值

结构体中有多个成员,取决于成员中M值最大的成员。
结构体的地址,必须能被结构体的M值整除
结构体的尺寸,等于成员中宽度最宽成员的倍数

typedef struct node
{
    int i ;     // 4
    char c  ;   // 1
    short s ;   // 2
    double d ;  // 8
    long double ld ;  // 16    由于16是最宽的,因此结构体的大小不许能被16整除
    float f ;  // 4 
}Node;

以上结构体在64位系统中:
大小 为 48 --> 能被 long double整除
地址值能被M值4整除

4.6 可移植性

对于可移植的结构体来说一定要解决好该结构体在不同的操作系统(位数)如何统一 该结构体的大小。
方法有两个:
方法1 :
直接使用attribute 进行压实, 每一个成员之间没有留任何的空隙。

typedef struct node
{
    int i ;     
    char c  ;  
    short s ;   
    double d ; 
    long double ld ; 
    float f ;  
}__attribute__(( packed ));

方法2 :
对每一个成员进行压实

typedef struct node

{
    int i __attribute__((aligned(4))) ;     
    char c  __attribute__((aligned(1))) ;  
    short s __attribute__((aligned(2)));   
    double d  __attribute__((aligned(4)));  
    long double ld __attribute__((aligned(4))) ;  
    float f  __attribute__((aligned(4))); 
};

注意:
结构体的大小取决于多个因素。
地址对齐,M值的问题, 默认情况下结构体的大小为成员中最大的倍数。
结构体内部的每一个成员的大小都与系统的位数有关。
如果需要实现可移植性,结构体的每一个成员需要使用可移植类型+attribute机制对结构体进行压实。

  • 30
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值