结构体,枚举,联合体,内存对齐

简介:

@[TOC]

前言

本篇介绍自定义类型:结构体,枚举,联合体。

对于结构体:重点掌握结构体,联合体的内存对齐,和结构体的位段

自定义类型

int ,int * ,float,double等, 这些都是C语言中内嵌的标准类型--直接用的。但是对于C语言没有内嵌的类型,需要我们通过某些关键字主动的定义,因此出现了 结构体,枚举,联合体。

结构体

什么是结构体

我们知道一种类型只能对应一种数据,依int为例,其对应整形。那么对于像人这种数据集合的,有没有对应的数据类型呢?---结构体

C语言规定了结构体,通过关键字 struct 的自定义类型。

结构体的声明

  • 我们称结构体中的类型为成员
  • struct 只是声明了一种数据类型,对于数据我们仍是存放在结构体变量中。同时struct只是一个关键字,不是真的的结构体。
  • 无论何时定义结构体变量或者指针 struct +tag都要有
  • 定义时结尾的不能丢。
struct tag
{
member-list;
}variable-list;
//tag--结构体类型标签
//member-list 结构体中的成员
// variable-list 结构体变量名

正常声明

//形式一
//声明结构体类型struct Stu,含有char,int类型的成员
struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
};//分号不能丢!!!!!!!!!!!!!
  
 //形式二
 //声明结构体变量S.
 struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
}S;//分号不能丢 
     
  //形式三
      struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
};//分号不能丢 
 int main()
      {
         struct Stu S;//声明一个结构体变量。
         struct Stu S[3];//声明一个结构体数组,数组中每个元素都是结构体类型
      }

特殊声明

在声明结构时,可以不完全声明

//无tag的匿名结构体,可以这样定义,非常不好,不建议怎么用
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}a[20], *p;



弊端:

  •  p=&x;
    *在编译器看来,上面无tag的2种声明是2种类型。因此p=&x;是非法的。*
  • 另外非常不利于再次定义结构体变量,可以看成是一次性用品

结构体的自引用

//错误案例
struct Node
{
int data;
struct Node next;
}
//正确案例
struct Node
{
int data;
struct Node *next;
}
  • 这种在声明过程中加入一个本类型的成员,编译器会认为其是非法的,但是如果加入本类型的指针是没有问题的。
  • 博主在这里认为是因为:因为程序是一行一行读的,Node还在定义中(成员信息不全),自引用是非法的。但是对于结构体指针,只是一个指向结构体的指针,编译器不会报错的。

image-20211029181119852

结构体变量的成员初始化

结构体通过 {}来依次对结构体成员赋值。

非嵌套结构体成员的初始化

//方式一:全局结构体变量的初始化
struct Point
{
    int x;
    int y;
}p1; //声明类型的同时定义变量p1
struct Point p2; //定义结构体变量p2
struct Point p3 = { 1, 2 };//初始化



//局部变量的初始化
struct Stu //类型声明
{
    char name[15];//名字
    //注意字符数组初始化时要用“”
    int age; //年龄
};
int main()
{

    struct  Stu  S = { "zhangsan", 20 };//定义变量并初始化。
    return 0;
}

嵌套结构体成员的初始化

struct Point
{
    int x;
    int y;
};
struct Node
{
    int data;
    struct Point p;
    //注意这里可以加结构体变量的原因是:前面已经完整声明结构体类型
    struct Node* next;
};

struct Node n2 = { 20, {5, 6}, NULL };
//通过{} 来 初始化嵌套的结构体

结构体数组的定义与初始化

  • 数组可以存放很多种数据类型,自定义的结构体也是一种数据类型,因此有了结构体数组,数组中的每个元素都是结构体。
  • 数组所有知识,结构体数组全部适用
struct Stu //类型声明
{
   char name[15];//名字
   //注意字符数组初始化格式;""
   int age; //年龄
};
int main()
{
   struct Stu S[3] = { {"李四",23},{"张三",24},{"王二",25} };//定义结构体数组S,并初始化
   return 0;
}

结构体成员访问

  • 对于结构体变量,访问成员使用C语言关键字:“.”.

image-20211029184815735

  • 对于结构体指针,访问指向结构体的成员时有2种形式---更推荐第一种,方便,已理解。
    image-20211029184940974

注意

  • 结构体访问中:对于数组的访问得到的是数组首元素地址,但是对于数组中其它元素位置的访问,可以通过像下面的方式进行访问

image-20211029185638394

  • 对于指针成员的访问:得到的指针的值。

image-20211029190151931

结构体内存对齐!!!!!

内存对齐的规则

  • C语言内嵌的标准类型,在内存中存储时都有其字节大小,如整形-4字节,double-8字节,但是对于结构体这种自定义类型,其占据内存大小不是简单的内部成员大小相加的和
  • 对于数组成员,对齐数看的是一个元素的字节大小,不是整体
  • 对于结构体,C语言有规定:结构体的大小是内存对齐后的内存大小。

结构体内存对齐的规则:

image-20211029211745846

用例

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9DPweTKN-1635521268675)(自定义类型.assets/image-20211029204836061-163551171727410.png)]

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

image-20211029204935016

struct S3
{
    int i;
    char c;
    double d;
};

int main()
{
    printf("%d\n", sizeof(struct S3));
    return 0;
}

image-20211029205911151

struct S3
{
    int i;
    char c;
    double d;
};
struct S4
{
    char c1;
    struct S3 s3;
    double d;
};

int main()
{
    printf("%d\n", sizeof(struct S4));
    return 0;
}

image-20211029212329910

为什么存在内存对齐

  • 平台原因(移植原因):
    不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常.
  • 性能原因:
    数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
    原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问**

    image-20211029213515543

  • 总体来说:

    内存对齐是拿空间换时间。----注意现在的内存非常大,这些浪费的可以不考虑。

怎么尽量避免因内存对齐,导致的空间浪费

  • 在设计结构体成员时:

    让占用空间小的成员尽量集中在一起 。

struct S1
{
char c1;
int i;
char c2;
};
struct S2
{
char c1;
char c2;
int i;
};
  • S1和S2类型的成员一模一样,但是S1和S2所占空间的大小有了一些区别

修改编译器的默认对齐数

  • 如果觉得默认对齐数不合适时,可以更改默认对数

#pragma 这个预处理指令,可以改变我们的默认对齐数

#pragma pack(4)//设置默认对齐数为4
struct S3
{

   int i;
   double d;

};
#pragma pack()//取消设置的对齐数,还原为默认对齐数8

struct S4
{
   int i;
   double d;
};

int main()
{
   printf("%d\n", sizeof(struct S3));
   printf("%d\n", sizeof(struct S4));


   return 0;
}

image-20211029215334475

结构体传参

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;
}
  • 上面的 print1 和 print2 函数哪个好些?
    答案是:首选print2函数。
  • 原因:
    函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。
  • 结论:
    结构体传参的时候,要传结构体的地址

位段

什么是位段

  • 不会解释,上代码。哈哈~ ~ ~
struct A
{
int a:2;
int b:5;
int c:10;
int d:30;
};//声明A是一个位段类型
  • a本来是4字节存入内存,经过位段后,a存入内存是2bite-----注意是bite,不是字节哦!。
  • 因此,位段的本质就是数据存储的大小进行截断改变。

    这可以看出,位段可能会导致数据的流失,

  • 位段的声明和结构是类似的,有两个不同

    • 位段的成员必须是 整形家族(char。int);
    • 位段的成员名后边有一个冒号和一个数字

位段的内存分配

  • 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟存入数据的。
  • 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
struct S
{
char a:3;
char b:4;
char c:5;
char d:4;
};
struct S s = {0};
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
//空间是如何开辟的?

image-20211029222954867

位段的跨平台问题

  • int 位段被当成有符号数还是无符号数是不确定的。
  • 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。
  • 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
    当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
  • 总结:跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在

位段的应用

  • 我们知道在网络上传输数据时,如果传输结构类型的数据,因为空间的浪费,会非常大,影响传输速度,但是如果使用位段就可以极大提高传输速度.

枚举

什么是枚举

  • 枚举顾名思义就是一一列举。把可能的取值一一列举。
    比如我们现实生活中:

    • 一周的星期一到星期日是有限的7天,可以一一列举。
      性别有:男、女、保密,也可以一一列举。
      月份有12个月,也可以一一列举
    • 这里就可以使用枚举了

枚举的定义

enum Day//星期
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
enum Sex//性别
{
MALE,
FEMALE,
SECRET
};
enum Color//颜色
{
RED,
GREEN,
BLUE
};
  • 以上定义的 enum Day , enum Sex , enum Color 都是枚举类型。
  • 枚举{}里面的,称谓枚举整形常量---常量,一旦定义了就不可修改了,枚举常量具有全局性。
  • 注意最后一个枚举常量,后面没有“,”;
  • 这些可能取值都是有值的,默认从0开始,一次递增1,当然在定义的时候也可以赋初值 ,不过也仍保留依次递增的特点

    image-20211029224601659

枚举的优点

  • 枚举常量具有全局性且不可修改,因此可以代替#define声明的宏,原因:

    • 增加代码的可读性和可维护性
    • #define定义的标识符比较枚举有类型检查,更加严谨。

      image-20211029225314970

    • 防止了命名污染(封装)
    • 便于调试

      image-20211029225456903

    • 使用方便,一次可以定义多个常 量
  • 鉴于怎么多优点,多多使用哈哈~~

联合体

联合类型的定义

  • 联合体也是一种特殊的自定义类型
    这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)
//联合类型的声明
union Un
{
char c;
int i;
};
//联合变量的定义
union Un un;
  • 联合体变量的定义位置和结构体一样,全局或者局部都行,尽量别无名定义

联合的特点

  • *联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为
    联合至少得有能力保存最大的那个成员)*

    union Un
    {
    int i;
    char c;
    };
    union Un un;
    // 下面输出的结果是一样的吗?
    printf("%d\n", &(un.i));
    printf("%d\n", &(un.c));
    //下面输出的结果是什么?
    un.i = 0x11223344;
    un.c = 0x55;
    printf("%x\n", un.i);

    image-20211029230300214

  • 由此可见不能随便使用联合体

联合体大小

  • 联合的大小至少是最大成员的大小。
  • 看对齐数,数组仍是数组元素,但是对于成员大小看的的数组整体大小。
  • 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍 .

    union Un1
    {
    char c[5];
    int i;
    };
    //5不是最大
    union Un2
    {
    short c[7];
    int i;
    };
    int main(){
    printf("%d\n", sizeof(union Un1));
    printf("%d\n", sizeof(union Un2));
    }

    image-20211029231504936

小结

  • 结构体的内存对齐是常考点,必须要掌握。
  • 如果我们定义全局变量时,可以多多使用枚举,优点多多。
相关文章
|
9月前
|
编译器 C语言 C++
C/C++内存对齐规则(结构体、联合体、类)
C/C++内存对齐规则(结构体、联合体、类)
|
10月前
|
安全 C++
【自定义类型:结构体,枚举,联合】内存对齐的原理和原因
【自定义类型:结构体,枚举,联合】内存对齐的原理和原因
65 0
|
3月前
|
存储 编译器 Linux
匿名结构体类型、结构体的自引用、结构体的内存对齐以及结构体传参
匿名结构体类型、结构体的自引用、结构体的内存对齐以及结构体传参
|
12天前
|
存储 Go
Go 内存分配:结构体中的优化技巧
Go 内存分配:结构体中的优化技巧
|
2月前
|
编译器 测试技术 C语言
【C语言】:自定义类型:结构体的使用及其内存对齐
【C语言】:自定义类型:结构体的使用及其内存对齐
41 7
|
2月前
|
存储 编译器 C语言
C语言学习记录——结构体(声明、初始化、自引用、内存对齐、结构体设计、修改默认对齐数、结构体传参)一
C语言学习记录——结构体(声明、初始化、自引用、内存对齐、结构体设计、修改默认对齐数、结构体传参)一
35 2
|
2月前
|
编译器 Linux C语言
C语言学习记录——结构体(声明、初始化、自引用、内存对齐、结构体设计、修改默认对齐数、结构体传参)二
C语言学习记录——结构体(声明、初始化、自引用、内存对齐、结构体设计、修改默认对齐数、结构体传参)二
30 1
|
2月前
|
存储 编译器 C语言
C语言的联合体:一种节省内存的数据结构
C语言的联合体:一种节省内存的数据结构
25 0
|
3月前
|
编译器 C++
内存对齐与内存开辟。结构体(struct),位段,枚举类型(enum),联合体(union)。
内存对齐与内存开辟。结构体(struct),位段,枚举类型(enum),联合体(union)
26 1
|
3月前
|
编译器 C语言 C++
c语言结构体的内存对齐
c语言结构体的内存对齐