C深剖关键字(5)

简介: 目录一:union关键字二:enum关键字三:typedef关键字

一:union关键字

联合关键字:联合体内部共享空间,整个union的大小由内部最大的元素决定。

#include<stdio.h>
union un
{
  int a;
  char b;
  // a和b共用一个空间,不过空间总大小由a决定
};
int main()
{
  // a和b共用一个空间,不过空间总大小由a决定
  printf("%d\n", sizeof(union un)); // 4
  return 0;
}

联合体的访问和结构体类似都可以采用 . 操作符或 -> 指向操作符。

union un x;
x.a = 10;
union un* p = &x;
p->a;

union 的空间布局问题:

联合体的空间开辟由元素最大的为准,在这里由a决定。四字节,那b占用的是a的低地址处还是高地址处呢?image.png任何变量在开辟空间的时候,这个变量开辟后都有地址,这个地址一定是众多字节中最小的


我们将联合体变量的地址和联合体内最大元素的地址打印出来看看:

image.png

不难看出,在数值大小上是一样的


对于b来说,在申请空间的时候,所有的空间申请都是由较低地址处向上开始分配的,所以b是在最低地址开始的,换言之,内部成员b开辟的空间和联合体本身和a变量的地址值是一样的。

image.png

结论:


联合体内所有成员的起始地址都是一样的,每一个都是第一个元素。


b永远在a的低地址处!!!


利用联合体的空间分布可以巧妙判断出大小端:

image.png这里要把一组二进制序列 0x 00 00 00 01保存在a对应的空间里,此时四个字节每个都有地址,而地址具有高低之分,而我们的数据按字节对应的1bit位进行划分的时候,数据就有高低权值位之别,所以存储方案有两种,一种高权值位放在高地址处,低权值低地址处,如我们上图的左边存法,01放在低地址处,第二种方案相反,如上图右边将01放在高地址处,因为b永远在a的低地址处,b占一个字节,如上图x.b=1红色记号笔划分出,如果存储方案是第一种,那么b=1,如果是第二种,那么b=0,而第一种存储方案正式小端的存储法则,第二种正是大端的存储法则。


如代码展示:

#include<stdio.h>
union un
{
  int a;
  char b;
};
int main()
{
  union un x;
  x.a = 1;
  if (x.b == 1)
  {
    printf("小端\n");
  }
  else
  {
    printf("大端\n");
  }
  return 0;
}

image.png 

注意:联合体的整大小必须能整除联合体内任何一个元素的大小

#include<stdio.h>
union un
{
  int a;
  char b[5];
};
int main()
{
  printf("%d\n", sizeof(union un)); // 8
  return 0;
}

根据我们之前的认知,联合体空间由最大元素决定,b的大小是5,按理来讲联合体大小应该是5,但运行起来大小却是8,联合体的整大小必须能整除联合体内任何一个元素的大小

再看一段代码:

体现联合体和大小端的对应关系:

#include<stdio.h>
union un
{
  int i;
  char a[4];
}*p, u;
int main()
{
  p = &u;
  p->a[0] = 0x39;
  p->a[1] = 0x38;
  p->a[2] = 0x37;
  p->a[3] = 0x36;
  printf("0x%x", p->i); //0x36373839
  return 0;
}

image.png

二:enum关键字

enum枚举关键字作用:枚举一堆的常量,内部的常量直接可以被当作数据使用,枚举本身也是新增或设计了一种类型,换言之,我们可以使用枚举类型直接定义变量。如下:

#include<stdio.h>
enum color
{
  RED,
  YELLOW,
  BLACK,
  GREEN,
  BLUE
};
int main()
{
  enum color c = RED;
  printf("%d\n", RED); // 0
  printf("%d\n", BLACK); // 2
  printf("%d\n", BLUE); // 4
  return 0;
}

枚举出来的本质就是整数,对应的就是某种字面值,不可被修改的,所以RED,BLACK,BLUE就是真正意义上的常量


为什么要存在枚举?


1:现实世界中,有一大堆具有相关性的常量需要被在代码中体现出来。


2:一旦我们枚举常量之后,所有常量的常量名不是数字而是直接用英文单词去代表,这样写出来的代码具有自描述性。    


枚举常量的设定:


如果将第一个枚举常量的内容赋予一个特定的数字,那么后续的枚举常量会呈现加1式的递增,也可以分段式递增:

#include<stdio.h>
enum color
{
  RED=10,
  YELLOW,
  BLACK=-9,
  GREEN,
  BLUE
};
int main()
{
  enum color c = RED;
  printf("%d\n", RED);   // 10
  printf("%d\n", YELLOW);  // 11
  printf("%d\n", BLACK);  // -9
  printf("%d\n", GREEN);  // -8
  printf("%d\n", BLUE);   // -7
  return 0;
}

三:typedef关键字

本质:类型重命名。

#include<stdio.h>
//(1)
// 对结构体类型进行重命名
typedef struct stu {
  char name[16];
  int age;
  char sex;
}stu_t;
//(2)
//对unsigned int 重命名 u_int 简化
typedef unsigned int u_int;
//(3)
//对指针int*重命名
typedef int* int_p;
//(4)
typedef int a[10];  // 此刻a相当于一种数组类型
int main()
{
  u_int x = 0;
  int_p p = NULL;
  stu_t s;
  a b;
  return 0;
}

类型重命名,可以对一些不太好理解的数据类型进行简化。


但是也不是说typedef可以随便的重命名,如果对指针或者数组进行重命名的时候,那么使用的时候,就会忽略一些细节。比如说数组的元素有几个,类型是什么,指针是几维指针,什么类型的指针。过度的typedef本质其实变相就是一种让人困扰的东西,但是比较推荐大家在结构体的时候运用typedef关键字


typedef和#define的区别


先看代码:


此段代码用的是typedef

#include<stdio.h>
typedef int* int_p;
int main()
{
  //int* a, b;
  // 此时a为指针,b为int 整型类型
  //int* a = NULL, b = 0; //可读性太差
  int_p a, b;
  // 此时a 和 b均为指针,等价于int* a, * b;
  return 0;
}

千万不要把typedef类型重命名看作某种替换,不能直接把int_t理解为int*,二者不能直接替换。而应将int_t理解成一种全新的类型,所以就不存在*会和a先结合还是和b先结合的问题。这个*的类型会对其后的所有定义的变量全部起效,int_p就作为一种独立类型去使用。


再来用#define试试

#include<stdio.h>
#define ptr_t int*
int main()
{
  ptr_t a, b, c;
  //等价于
  int* a, b, c;
  //a为int*, b和c均为int;
  return 0;
}

结论:

typedef类型重命名,并不是本质的文本替换,形成了一个新的独立的类型

宏define做的是纯纯文本替换

案例:

#include<stdio.h>
#define INT32 int
typedef int int32;
int main()
{
  unsigned INT32 a; 
  //因为宏define是文本替换,所以INT32就相当于int,所以就是ungsigned int a;
  //unsigned int32 b; 代码报错
  return 0;
}

问题:typedef static int int32_t 行不行?image.png从图中很容易看出是不行的,此时编译器出错。

在32个关键字中,有五个存储类型关键字:

image.png

而存储类型关键字有个特点:

存储关键字,不可以同时出现,也就是说,在一个变量定义的时候,只能有一个。

所以typedef和static这两个关键字不能同时出现,所以上述代码会报错。

相关文章
|
5天前
|
存储 安全 编译器
【C++航海王:追寻罗杰的编程之路】引用、内联、auto关键字、基于范围的for、指针空值nullptr
【C++航海王:追寻罗杰的编程之路】引用、内联、auto关键字、基于范围的for、指针空值nullptr
32 5
|
2月前
|
编译器 C语言 C++
C语言第三十二弹---自定义类型:联合和枚举
C语言第三十二弹---自定义类型:联合和枚举
|
8月前
|
存储 编译器 C语言
【C++初阶(四)aoto关键字与基于范围的for循环】
【C++初阶(四)aoto关键字与基于范围的for循环】
51 0
|
11月前
|
存储 C语言
C语言学习第四课——一些关键字,操作符以及数组与指针的介绍
C语言学习第四课——一些关键字,操作符以及数组与指针的介绍
55 0
|
存储 编译器 Linux
C语言深度剖析:关键字
本篇内容既是对之前C语言学习的重点复习内容,也是对之前内容的补充,大体内容逻辑参考了《C语言深度解剖》这本书,所以这节内容更像是个对C语言学习的一个重点内容复盘,也就是将C语言重点知识重新串一遍,同时也对之前一些掌握不太好的知识的补充。所以下面几乎都是一个一个的知识点,友情提示:不是很适合零基础的同学。基础不太好的请将C语言的基础内容部分系统进行学习。
C语言深度剖析:关键字
|
C语言
【c语言进阶】三分钟教会你终极套娃:指向函数指针数组的指针,让你真正理解如何解读一个变量的类型
【c语言进阶】三分钟教会你终极套娃:指向函数指针数组的指针,让你真正理解如何解读一个变量的类型
71 0
|
Java
关于关键字volatile的一二
关于关键字volatile的一二
56 0
|
安全 编译器 C语言
【C语言】刨根问底 - 深剖const关键字
【C语言】刨根问底 - 深剖const关键字
74 0
【C语言】刨根问底 - 深剖const关键字
|
存储 编译器 C语言
初学C语言的福音-初识关键字和指针(一)
初学C语言的福音-初识关键字和指针
74 0
初学C语言的福音-初识关键字和指针(一)
|
C语言
初学C语言的福音-初识关键字和指针(二)
初学C语言的福音-初识关键字和指针
58 0
初学C语言的福音-初识关键字和指针(二)