一文带你玩转自定义类型

简介: 一文带你玩转自定义类型

目录

结构体

结构体的声明

特殊的声明

结构体的自引用

结构体的定义和初始化

结构体内存对齐

练习

内存对齐的原因

修改默认对齐数

结构体传参

位段

位段的含义

位段的内存分配

位段的跨平台问题

位段的应用

枚举

枚举类型的定义

枚举的优点

枚举的使用

联合体

联合体的定义

联合的特点

联合体大小的计算


结构体

结构体是一些值的集合,这些值是它的成员。它们可以是不同类型的变量。

结构体的声明

注意:它的分号不能丢了

//栗子:这里定义一个学生
struct Stu
{
  int age;
  char name[20];
  char sex[5];
};

特殊的声明

在声明结构体的时候,还有一种特殊的声明,就是匿名结构体类型:

//栗子:
struct
{
  int a;
  char b;
  float c;
}s1;
struct
{
  int a;
  char b;
  float c;
}*p;

上面两个结构体省略了结构体的名字,这时有一个问题就是:

p = &s1在编译器上是编译不过去的。原因就是编译器把他们两个当成了不同的结构体类型。

结构体的自引用

在结构体中包含一个类型为该结构本身的成员是可以的:

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

结构体的定义和初始化

声明类型的时候可以定义变量,也可以在声明完后定义。初始化可以在定义完后初始化,也可以定义的同时初始化。

struct point
{
  int a;
  int b;
}s1;   //声明的同时定义
struct point s2; //先声明再定义结构体变量
//初始化:定义的同时初始化
struct point s3 = { 1,2 };
struct Stu
{
  int age;
  char name[20];
};
struct Stu s = { 20,"zhangsan" };
struct Node
{
  int date;
  struct Stu s1;
  struct Node* next;
}n1 = {1, {19, "lishi"}, NULL}; //结构体嵌套初始化
struct Node n2 = { 2, {25,"wangwu"}, NULL };//结构体嵌套初始化

结构体内存对齐

对于结构体内存对齐有以下几点规则:

第一个成员放在与结构体变量偏移量为0的位位置

其他变量要对齐到对齐数的整数倍处的地址处

对齐数是编译器默认的一个对齐数与该成员大小的较小值(vs默认值为8,gcc,Linux没有默认对齐数,对齐数是成员自身大小)

结构体总大小必须是最大对齐数的倍数

如果是嵌套了结构体的情况,嵌套的结构体对齐到自己最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数的整数倍(含嵌套结构体的对齐数)

练习

//练习一
struct s1
{
  char c1;
  int i;
  char c2;
};
int main()
{
  printf("%d\n", sizeof(struct s1));
}

c1为首成员,对齐偏移量为0的位置,占1个字节。i为4个字节小于8,对齐数为4,从&c1+3的位置开始存放i。c2为1个字节小于8,对齐数为1,接在i后面存放。又因为它们加起来为9不是最大对齐数的倍数,所以要提升为12.

//练习二
struct s2
{
  char c1;
  char c2;
  int i;
};
int main()
{
  printf("%d\n", sizeof(struct s2));
}

//练习3
struct S3
{
  double d;
  char c;
  int i;
};
int main()
{
  printf("%d\n", sizeof(struct S3));
}

//练习4
struct S4
{
  char c1;
  struct S3 s3;
  double d;
};
int main()
{
  printf("%d\n", sizeof(struct S4));
}

内存对齐的原因

平台原因:

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

性能原因:

数据结构应该尽量的在自然边界上对齐。原因就是为了访问未对齐的内存,处理器需要进行两次内存访问。而对齐的内存只需要访问一次。

总得来说就是内存对齐是用来拿空间换时间的做法。

所以在设计结构体的时候我们要尽量的满足对齐,又节约空间。做法就是让空间小的成员尽量在一起:

//错误的做法
struct S1
{
  char c1;
  int i;
  char c2;
};
//正确的做法
struct S2
{
  char c1;
  char c2;
  int i;
};

修改默认对齐数

修改对齐数我们需要用到一个预处理指令:#pragma,它可以改变我们的默认对齐数:

#pragma pack(8)
struct S1
{
  char c1;
  int i;
  char c2;
};
#pragma pack()
#pragma pack(1)
struct S2
{
  char c1;
  int i;
  char c2;
};
#pragma pack()
int main()
{
  printf("%d\n", sizeof(struct S1));
  printf("%d\n", sizeof(struct S2));
    return 0;
}

在我们遇到对齐方式不合适的时候,我们就可以用pragma来改变默认对齐数。

结构体传参

结构体传参有两种方法:一种是直接传参,一种是地址传参。一般来说是地址传参比较好。

因为:函数传参需要压栈,会有时间和空间的开销。如果传递的结构体对象过大,那么参数压栈的开销就会比较大,所以会导致性能的下降。

struct Stu
{
  char name[20];
  char sex[4];
  int age;
};
struct Stu s = { "zhangsan","nan", 20 };
//结构体传参
void print_f(struct Stu s)
{
  printf("%s\n", s.name);
}
//结构体地址传参
void print_t(struct Stu* s)
{
  printf("%d\n", s->age);
}
int main()
{
  print_f(s);//传结构体
  print_t(&s);//传地址
  return 0;
}

位段

位段的含义

段位和结构体是类似的,但是有两点不同:

段位的成员必须是int,unsigned int, signed int

段位的成员名后面有一个冒号和一个数字

举个栗子:

struct S
{
  int a : 2;
  int b : 5;
  int c : 10;
  int d : 30;
};

位段的内存分配

段位的成员是int unsigned int signed int 或者是char类型

段位的空间是按照需要以4个字节或者1个字节的方式开辟的。

段位涉及很多不确定的因素,段位是不跨平台的,注重可移植的程序需要避免使用段位。

栗子:

struct S
{
  char a : 3;
  char b : 4;
  char c : 5;
  char d : 4;
};
int main()
{
  struct S s = { 0 };
  s.a = 10;
  s.b = 12;
  s.c = 3;
  s.d = 4;
  return 0;
}

位段的跨平台问题

int 位段被当成有符号数还是无符号数是不确定的。

位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机

器会出问题。

位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。

当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是

舍弃剩余的位还是利用,这是不确定的

位段的应用

它可以极大的减少数据的空间,是数据可以高速传递

枚举

枚举就是可能的情况一一列举。比如:生活中的星期,月份,性别都可以一一列举

枚举类型的定义

enum day
{
  mon,
  tues,
  wed,
  thur,
  fri,
  sat,
  sun,
};
enum color
{
  red,
  green,
  blue
};

以上定义的day和color都是枚举类型,{}中的内容是枚举类型的可能取值,叫枚举常量。这些值都是有默认值的,默认从0开始,每次增加1,在定义的时候也是可以赋值的:

enum day
{
  mon = 1,
  sun = 7,
  sat = 6
};

枚举的优点

增加代码的可读性和可维护性

和#define的标识符比较枚举有类型检查,更加严谨

防止了命名污染(封装)

便于调试

使用方便,一次可定义多个常量

枚举的使用

int main()
{
  enum color str = red; //只有拿枚举类型给枚举常量赋值,才不会出现类型的差异
  str = 6;
  return 0;
}

联合体

联合体的定义

联合体是一种特殊的自定义类型。这种类型的变量包括了许多的成员,他的特点是这些成员共同使用一块空间。

举个栗子:

union un
{
  char c;
  int i;
};
//联合变量的定义
union un u;

联合的特点

联合体的成员公用一块空间,这样的联合体大小至少是最大成员的大小。

通过下面代码可以发现,它们公用一块空间,且还会改变对方的值

union un
{
  int i;
  char c;
};
int main()
{
  union un u;
  printf("%p\n", &(u.i));
  printf("%p\n", &(u.c));
  u.i = 0x11223344;
  u.c = 0x55;
  printf("%x\n", u.i);
  return 0;
}

联合体大小的计算

联合的大小至少是最大成员的大小

当最大成员的大小不是最大对齐数的倍数的时候,就要对齐到最大对齐数的整数倍

栗子:

union un1
{
  char c[5];
  int i;
};
union un2
{
  short c[7];
  int i;
};
int main()
{
  printf("%d\n", sizeof(union un1));
  printf("%d\n", sizeof(union un2));
  return 0;
}

目录
相关文章
|
6月前
|
存储 Linux C++
自定义类型讲解
自定义类型讲解
81 0
|
5月前
|
存储 编译器 Linux
自定义类型详解(1)
自定义类型详解(1)
41 5
|
5月前
自定义类型详解(2)
自定义类型详解(2)
42 1
|
6月前
|
存储 移动开发 API
其他内置类型
本文介绍了 .NET 中的 Console 类和 Environment 类。Console 类提供了控制台输入输出的功能,如设置背景色和前景色、打印文本、读取行和发出蜂鸣声。而 Environment 类则包含有关全局环境的信息和方法,如当前目录、进程路径、处理器数量、操作系统信息等。另外,文章还提及了 .NET Framework 的 AppDomain(用于表示应用程序域,但在 .NET Core 中功能减弱)和 .NET Core 中新引入的 AppContext 类,用于存储全局数据和开关。
|
6月前
|
编译器 Linux C++
自定义类型详解
自定义类型详解
|
6月前
|
编译器 C++
自定义类型
自定义类型
|
6月前
|
C++
c++基本内置类型
c++基本内置类型
51 0
|
11月前
|
存储 算法 程序员
自定义类型总结
自定义类型总结
70 0
|
存储
自定义类型超详细解答!!!!!(下)
自定义类型超详细解答!!!!!
|
编译器 C++
自定义类型超详细解答!!!!!(上)
自定义类型超详细解答!!!!!