解开C语言的秘密《关键字》(第六期)

篮球哥温馨提示:编程的同时不要忘记锻炼哦!

To shine, not to be illuminated.


目录

1、struct 关键字的理解和柔性数组

1.1 结构体传参问题

1.2 柔性数组的理解与简单使用:

2、union 内存级布局理解

2.1大小端对 union 的影响:

2.2 内存对齐的概念:

3、enum 是你想的这样吗

3.1 枚举类型的简单使用 

 3.2 #define宏 与 枚举 的区别

4、typedef 深入理解

4.1 typedef 的使用

4.2 typedef 跟 #define 的区别

4.3 typedef 细节上的问题

5、关键字的分类总结


1、struct 关键字的理解和柔性数组

说起 struct 关键字,学过 C 语言的小伙伴应该都有了解,struct 也可以理解成自定义类型,我们知道 C 语言内置类型远远还不够满足我们的需求,假设说我们要录入一个学生的信息,简简单单的 int char 这些是完全不够的,一个学生的信息拥有姓名,性别,年龄,身高,地址... 所以 C 语言就提供了 struct 可以使我们程序猿自定义想要的类型,我们就先简单看下它的语法吧:

解开C语言的秘密《关键字》(第六期)_第1张图片但是我们通常在创建结构体的时候会用 typedef 来重命名一下,避免书写的复杂:

typedef struct stu
{
	char name[10];
	char sex;
	int age;
	float high;
	char addr[30];
}stu;

int main()
{
	stu s;
	return 0;
}

1.1 结构体传参问题

虽然现在我们还没讲到指针这个章节,所以这部分内容有指针基础的小伙伴可以看下,没基础的小伙伴可以等学完指针再回过头来看这个问题:

解开C语言的秘密《关键字》(第六期)_第2张图片

上面两个打印函数哪个函数会更好呢?

这里我们简单了解一个知识点,当结构体作为参数传递给函数时,我们可以选择传值调用或者是传址调用,简单来说,传值是需要在函数接收时在开辟一个相同大小的结构体来接收,(形参只是实参的一份临时拷贝)传址则只需要用一个同类型指针变量来即可,然后通过指针可以直接访问到这个结构体。

有了这样一个概念我们就应该能明白,显然是 Printf2 这个函数会更好一些!

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

结论: 结构体传参的时候,要传结构体的地址。

1.2 柔性数组的理解与简单使用:

或许有很多小伙伴没听过柔性数组的概念,但它确实存在,C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做柔性数组,但结构体中的柔性数组成员前必须至少有一个其他成员。包含柔性数成员的结构用 malloc( ) 函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。

struct Data
{
	int num;
	int arr[];
};

 定义好了之后,我们通常就可以使用了(简单使用):

解开C语言的秘密《关键字》(第六期)_第3张图片

通过以上代码我们可以发现,我们用 malloc( ) 函数申请了一个 struct Data 类型大小的空间,并加上 10 个整型的空间,这里我想问小伙伴们一个问题,此时如果我们用 sizeof 求结构体的大小它会是多大呢?

解开C语言的秘密《关键字》(第六期)_第4张图片

在 Linux 平台环境下我们也做了一个测试:

解开C语言的秘密《关键字》(第六期)_第5张图片这里有小伙伴就纳闷了,我们明明已经给结构体里数组分配空间了啊,但是为何只占 4 个字节呢?

其实在定义这个结构体的时候,已经确定不包含柔性数组的内存大小了,柔性数组可以理解不占结构体的内存,只是说我们在使用柔性数组时需要把它当作结构体的一个成员!

没完全理解没关系,接下来我们用图解让小伙伴们可以更直观的理解柔性数组:

在看下面一张图片之前,我们得先了解 malloc 函数开辟的空间是在堆区开辟的,堆区是先使用低地址后使用高地址(栈区相反),数组的下标是随着地址的增长而增长的!

解开C语言的秘密《关键字》(第六期)_第6张图片

通过上面的讲解,相信你们已经了解了柔性数组这个概念,实在不了解也无所谓,这个东西确实不常用。


2、union 内存级布局理解

union 关键字的用法与 struct 关键字的用法非常相似,union 并不会为每一个数据成员分配空间,在 union 中所有的数据成员共用一个空间,联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)。所有数据成员具有相同的起始地址

解开C语言的秘密《关键字》(第六期)_第7张图片

一个 union 只分配一个足够大的空间来容纳最大长度的数据成员,比如上面的例子,最大成员是 int ,所以 union Data 大小就是 int 数据类型的大小! 而且,union 中的成员,同一时间只能存储其中一个数据成员!

上例注意:c 变量永远在 a 变量的低地址处!每一个都是第一个元素!

2.1大小端对 union 的影响:

前几期中我们了解了大小端的概念,既然联合体是共用内存的,而且联合体中每个元素都能称得上是第一个元素,我们不妨来思考如下这样一段代码:

解开C语言的秘密《关键字》(第六期)_第8张图片

显然打印小端的结果我们并不意外,但是这段代码内存中的布局是怎么样的呢?这跟大小端存储又有什么关系呢?

这里我们来回忆一下大小端存储的概念:

  • 大端:按照字节为单位,低权值位数据存储在高地址处;
  • 小端:按照字节为单位,低权值位数据存储再低地址处;

解开C语言的秘密《关键字》(第六期)_第9张图片

这里也就验证了我们之前的一个结论:在 union 中里面每个成员都是第一个元素!

2.2 内存对齐的概念:

可能有的小伙伴写过 union 包括 struct 的代码,在求这些自定义类型大小的时候, 可能求出的大小并不是我们想的一样,这就要考虑到我们的内存对齐了,这里我们不细讲,只是一笔带过,小伙伴感兴趣可以自行下来了解!

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

对齐数 = 编译器默认的一个对齐数 该成员大小的较小值

VS 中默认的值为 8

解开C语言的秘密《关键字》(第六期)_第10张图片


3、enum 是你想的这样吗

3.1 枚举类型的简单使用 

enum 是枚举关键字,枚举字面意思上理解就是一一列举,在我们生活中,比如从周一到周日,这是一个集合,也可以一一列举出来,一年有12个月,同理。

枚举的一般的定义方式:

enum Color
{
    RED,
    GREEN,
    BLUE,
    YELLOW,
};

以上就是枚举的一般定义方式,在 { } 中的内容是枚举类型的可能取值,也叫枚举常量

RED,GREEN,BLUE,YELLOW 这些成员都是常量,也就是我们所说的枚举常量(常量一般用大写),这些可能取值都是有值的,默认从0开始,一次递增1,当然在定义的时候也可以赋初值:

解开C语言的秘密《关键字》(第六期)_第11张图片

 3.2 #define宏 与 枚举 的区别

我们可以使用 #define 定义常量,为什么非要使用枚举?

枚举的优点:

  1. 增加代码的可读性和可维护性
  2. 和#define定义的标识符比较枚举有类型检查,更加严谨。
  3. #define 宏常量是在预编译截断进行简单替换,枚举常量则是在编译的时候确定其值。
  4. 防止了命名污染(封装)
  5. 在一般调试器里,可以体调试枚举常量,但不能调试宏常量。
  6. 使用方便,枚举一次可以定义大量相关的常量,而 #define 宏一次只能定义一个。

4、typedef 深入理解

4.1 typedef 的使用

或许有很多人认为 typedef 是定义一个新的类型,如果从字面意思理解的话 type 是数据类型的意思,def(lne) 是定义的意思,这么一看好像真实定义数据类型,不过这种理解是不正确的。

typedef 是给一个已存在的数据类型(不是变量)取一个别名,而非定义一个新的数据类型。我们通常称 typedef 为类型重命名。

那么那一般如何使用呢?

  1. 对一般类型进行重命名
  2. 对结构体类型进行重命名
  3. 对指针进行重命名
  4. 对复杂结构进行重命名

解开C语言的秘密《关键字》(第六期)_第12张图片

4.2 typedef 跟 #define 的区别

在编程中,每个人都有自己写代码的习惯,我不知道有没有小伙伴喜欢这样定义变量:

int a, b, c;

这种写法我是不推荐的,代码可读性不高,调试起来不方便....

我们来看一个例子:

解开C语言的秘密《关键字》(第六期)_第13张图片

为什么会出现以上情况呢?因为 * 号先跟 ptr1 结合了,所以 ptr2 用的是 int  类型。

那么这里我们在思考一个问题,如果用 typedef 或者 #define 可以解决这个问题吗?

解开C语言的秘密《关键字》(第六期)_第14张图片

通过实验我们可以看出,如果是用 tpyedef 是可以解决这个问题的,为什么呢?因为 typedef 是类型重命名,我们把 int* 类型重新给它取了一个名字而已,这个定义的每个变量都是 int* 类型,而 #define 并不一样,他只是简单的宏替换而已!

4.3 typedef 细节上的问题

对于一些编程的初学者来说,如果一些知识点没有了解透彻,在写代码碰到问题却又不明白其中的原理是很苦恼的一件问题,接下来就带小伙伴们看一个细节上使用 typedef 可能会碰到的问题:

解开C语言的秘密《关键字》(第六期)_第15张图片

如上这串代码显然是不可行的,那么肯定有疑问,为什么不可以呢?

C 中 typedef 不支持这种类型的扩展,不能当成简单的宏替换!但是 #define 可以,因为他只是宏替换。

typedef staict int sint32_t;

如上代码看似是合理的,但是编译都会出错, 因为 typedef 是属于存储关键字,存储关键字,不可以同时出现,也就是说,在一个变量定义的时候,只能有一个。


5、关键字的分类总结

看到这里,相信你们对 C 语言(C89 C90) 所有的关键字都有所理解了,这里最后会给关键字进行分类,方便小伙伴们的理解。

解开C语言的秘密《关键字》(第六期)_第16张图片

 C语言关键字的内容在这里就全部讲完,下期就开始进入到我们深入理解符号的讲解哦!


解开C语言的秘密《关键字》(第六期)_第17张图片

 前路浩浩荡荡,万物皆可期待!

你可能感兴趣的:(C语言深度解剖,c语言,开发语言)