自定义类型(一)

简介: 自定义类型

1.结构体


1.1 结构体类型的声明


结构是某些值的集合,这些值被称为成员变量,结构的成员类型没有特殊的规定


struct tag//tag 是结构体标签,可以理解为所创建的结构体名称
{
  member - list;//成员列表,所有类型变量的总称
}variable-list;//有结构体名称所创造的变量,即结构体变量

描述一位大学生


struct student
{
  char name[20];//姓名
  int age;//年龄
  char sex[10];//性别
  char number[10];//学号
};


fbf51adb42ac4534da81c9f8bb802f61_b27302a538354be8942d954969f19d68.png


特殊声明–无结构体标签

匿名结构体类型


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


这种结构体的生命只能使用一次,即在创建完结构体变量a之后就无法再次使用。如果对此变量进行取地址操作同样是非法的。


1.2 结构体的自引用


对于结构体本身包含一个类型为该结构体本身的成员,这样的想法是否可行呢?

答案是:当然是可以的啦


观察下列代码,自引用是否正确


struct student
{
  char name[20];
  int age;
  char sex[10];
  struct student next;
};
int main()
{
  printf("%d", sizeof(struct student));
  return 0;
}

11ae1da0435986f7d1c0a8dd065a35d3_e6c5230e47444348ba65b82448a28d31.png

这里出现的问题就是结构体中的next没有明确的数目,不清楚到底有多少个学生,陷入无限的死循环中去。


修改之后


struct student
{
  char name[20];
  int age;
  char sex[10];
  struct student* next;
};

修改之后,只是将指向下一个学生的地址的指针放在结构体中,可以通过指针进而访问下一个学生的信息


结构体自引用还会出现另一个问题–先有鸡还是先有蛋


typedef struct student
{
  char name[20];
  int age;
  char sex[10];
  stu* next;
}sut;


这里说不清楚到底是先有 结构体变量stu 还是先有 成员stu

为了避免这种问题的出现,可以进行以下修改


typedef struct student
{
  char name[20];
  int age;
  char sex[10];
  struct stu* next;
}sut;


1.3 结构体变量的定义和初始化


结构体也是一种类型,是类型就需要定义变量,那么该如何去定义变量呢?


类型就像是图纸,通过类型去创建变量,就像是拿着图纸去造房子


struct student//结构体类型声明
{
  char name[20];
  int age;
  char sex[10];
}stu;//定义结构体变量stu


初始化就是在创建变量的同时赋以数值


stu = { "zhangsan",20,"nan" };


1.4 结构体内存对齐


创建变量之后就会遇到另一个问题:结构体变量的大小该如何计算?

这里就需要引入:结构体内存对齐的概念


首先介绍结构体的对齐规则


1.第一个成员在与结构体变量偏移量为0的地址处
2.其余成员变量要对齐到对齐数的整数倍的地址处
    对齐数==编译器默认的一个对齐数 与 该成员大小的较小值
    (vs默认对齐数的值是8)
3.结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍
4.如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,
结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。


内存对齐存在的原因

1.平台原因

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

2.性能问题:

数据结构应该尽可能地在自然边界上对齐

为了访问未对齐的内存,处理器需要做两次内存访问;对于对齐的内存仅需要一次访问。

总结

结构体的内存对齐是拿空间换取时间的做法

例如


struct student
{
  char c;
  int i;
}s;

6b97bc7bdab1107fc810025b17d86d1e_3f25d5c9bdfd49c18d498e3086d65033.png

在内存未对齐的情况下,结构体变量s访问整型变量i时需要两次;

而在内存对齐的情况下,只需要跳过字符变量c,直接一次访问变量i


尝试算一算结构体的大小

练习一


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


f0d7ce3d429fc49ffb23be2d85896edb_da0374fb717143ffa6848ea9186d6d1d.png

c93119b31243fbb4f002c0cb9c1f3732_0a010836a1c44c7ab33a69eca954d433.png


第一个成员char c1,在偏移量为0的地址处;
第二个成员 int i,整型自身是4个字节,vs默认对齐数是8个字节,
所以int i的对齐数是4个字节,对齐在偏移量为4的地址处;
第三个成员char c2,字符大小是4个字节,vs默认对齐数是8个字节,
所以char c2对齐数是1个字节,对齐到地址为8的地址处
此时结构体大小是9个字节
因为结构体总大小为最大最对齐数的大小,三个成员最大的对齐数是4,
所以结构体总大小是12


练习二–嵌套结构体类型


struct s1
{
  char c1;
  int i;
  char c2;
};
struct s2
{
  char c3;
  struct s1 s1;
}s2;
int main()
{
  printf("%d", sizeof(s2));
  return 0;
}


58d51ade67bf789bf355a07ceebf34ff_15caed658d9e4b04b9f722b8757d4e6c.png

17a3da2e2747d9ebf64da18e243bd12a_9202185975f84b4189a863810f0d86d6.png


第一个成员char c3在偏移量为0的地址处
第二个成员是结构体struct s1 s1,
嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体struct s1 s1
对齐在偏移量在4的地址处,
此时结构体大小是15
结构体总大小是最大对齐数的整数倍,最大对齐数是4,所以大小为16


修改默认对齐数

通过 #pragma预处理指令,改变默认对齐数


#include<stdio.h>
#pragma pack(1)//将默认对齐数修改为1
struct s1
{
  char c1;
  int i;
  char c2;
};
#pragma pack()//恢复默认对齐数
//修改默认对齐数只限定于预指令之间的范围
int main()
{
  printf("%d", sizeof(struct s1));
  return 0;
}


2b0d576ba69e67764d12992a6e7e9eb4_eedbce3d676d4d7290878e0bf511551e.png


将默认对齐数改为1,可以理解为此时没有结构体内存对齐,所以

结构体大小就是各成员大小总和


总结
结构在对齐方式不合适时,自己可以修改默认对齐数


1.5 结构体传参


观察下面的代码


#include<stdio.h>
struct student
{
  char c;
  int i;
};
struct student s = { 'm',20 };
void print1(struct student s)
{
  printf("%d\n", s.i);
}
void print2(struct student* ps)
{
  printf("%d\n", ps->i);
}
int main()
{
  print1(s);//传结构体
  print2(&s);//传地址
  return 0;
}


对于函数 print1和 print2哪个更好些?

答案是:首选 print2函数


函数传参时,参数是需要压栈,有时间和空间上的系统开销
如果传递结构体时,结构体过大,参数压栈的系统开销比较大,会导致性能的下降


所以结构体传参时,首选传结构体的地址


1.6 结构体实现位段


位段与结构体都可以减少内存空间的浪费


位段的声明和结构体相似,但也存在两个不同点


1.位段的成员必须是int,unsigned int ,signed int或者char
2.位段的成员名后边有一个冒号和一个数字


例如


struct A
{
  int a : 1;
  int b : 2;
  int c : 3;
  int d : 4;
};
int main()
{
  printf("%d", sizeof(struct A));
  return 0;
}


A是一个位段类型,既然是个数据类型,那么大小是非常重要的,所以位段A的大小是多少呢?


2e7da5b20f376792610a2de82a5bc426_3cc72876943c40de9b002580f1276cdb.png


运行之后是4,对于这个结果大家可能会很困惑,为什么呢?

这里就需要先介绍位段的内存分配,才能解决大家的疑问


位段的内存分配


1.位段的成员可以是`int,unsigned int,signed int`或者是`char`(整形家族)类型
2.位段的空间是按照需要以4个字节(`int`)或者1个字节(`char`)的方式来开辟
3.位段涉及很多不确定因素,位段是不跨平台的。


struct M
{
  char a : 3;
  char b : 4;
  char c : 5;
  char d : 4;
};
int main()
{
  struct M m = { 0 };
  m.a = 5;
  m.b = 10;
  m.c = 15;
  m.d = 4;
  printf("%d\n", sizeof(m));
  return 0;
}


b76bedbbd6799a00ea095e945e6d06a0_60fa211a68564a42a9cbbbba930dc4eb.png


若是一次所开辟的空间没有使用完,且不能够存下另一个变量

则只能按照类型重新开辟空间使用

之所以位段大小是3个字节,是因为首先是以1个字节`char`开8个byte,
 存下`char a`和`char b`,然后有开辟1个字节存放`char c`,
 最后开辟1个字节用于存放`char d`.
 1个char=8个byte
  char a : 3;
char b : 4;
1个char=8个byte
char c : 5;
1个char=8个byte
char d : 4;



597ce72d429c9c680373c466f180519e_dbf29827cc964058ac2e629f50e891e0.png

4fd7ca438c085cbb8c28f96e5b260afb_4c5f0237d3a34cc090d8b9059482aea4.png


位段存在跨平台问题


1. `int`类型的位段不清楚是被当成 `signed`类型还是 `unsigned`类型
2. 位段中最大位的数目不能确定
3. 位段中的成员在内存中没有定义从左向右还是从右向左分配
4.

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