你可能会漏掉的C语言部分知识点

你好,我是goldsunC

让我们一起进步吧!

题外话

在学计算机相关知识的时候,基本上是离不开C语言的。并且C语言是绝大部分高校计算机相关专业都会学的。往往你说自己是学编程的,别人就基本默认你学过C语言啦。最近看操作系统、算法、计网啥的,发现总会跟C/C++扯上点关系,鉴于自己是大一上期学的C语言,到现在时间稍微有点久远没有用过且当时并没有学的很好,所以决定花点时间重新学习下C语言。

这篇文章总结一下这两天重学的C语言觉得重要的一些知识。

枚举常量

枚举相信大家都很熟悉,就是"一一列举"的意思,当一些量仅有有限个数据值组成时,通常用枚举类型来表示。枚举数据类型描述的是一组整型值的集合。在C语言中用enum关键字来定义这种类型。

例如:

enum PeopleName{goldsunC, gold, sun, C};
enum PeopleName name;

上面第一条语句定义了名为PeopleName的枚举数据类型,给它定义了四个不同的取值:goldsunC, gold, sun, C

第二条语句用该枚举类型定义了一个名为name的变量。

name这个变量可以被赋予四种取值中的任何一种,例如:

name = sun;

然后我们就可以在如条件语句中来使用name了,比如如下代码:

#include <stdio.h>
#include <stdlib.h>
int main()
{
    enum PeopleName{goldsunC, gold, sun, C};
    enum PeopleName name;
    name = sun;
    if(name == sun) {
        printf("Hello,sun\n");
    }else{
        printf("error!\n");
    }
    if(name == 2)
        printf("666\n");
    return 0;
}

上面的代码输出结果是什么?是:

Hello,sun
666

这个输出结果说明了name == sun == 2,等等,为什么等于2?这是因为在定义枚举类型的那个花括号中,我们定义的那些goldsunC, gold等都是整型常量,除非特别指定,否则它们按照顺序即值为0, 1, 2 ...依次加一。

那什么是特别指定呢?实际上你可以这样:

enum PeopleName{goldsunC = -1, gold = 1, sun = 0, C = 3}

它们它们就等于你指定的值了,在程序中可以使用这些值。

你也可以这样:

enum PeopleName{goldsunC = 2, gold, sun ,C}

我们只给第一个枚举常量指定了值为2,那么后面的枚举常量值将会从2依次向上递加的。

总结

虽然枚举常量就是简单的整型常量,不过可以提高程序的可读性,使用goldsunC肯定比使用0可读性好呀。

变量的作用域

在C语言中,被花括号括起来的区域,往往被称为语句块(Block)。无论函数体、循环体还是分支都是语句块。我们在每一个语句块的头部都可以定义变量,这个变量的作用域(Scope)为:每个变量仅在定义它的语句块(包含下级语句块)内有效,并且拥有自己的内存空间

如下代码:

#include <stdio.h>
#include <stdlib.h>
int main()
{
    int a = 1;
    {
        int a = 2;
        printf("%d\n",a);
    }
    printf("%d\n",a);
    return 0;
}

输出结果为:

2
1

也就是两个a的值并不相同,即语句块内的a和语句块里面的a是不同的变量,它们各自拥有自己的内存空间,彼此毫无瓜葛。不过在实际中建议不要定义相同的变量名。不然容易混淆、导致误解。

全局变量

我们知道,main函数也是包含一个大的语句块,那么如果我们按照变量作用域的规则,如果在与main函数平行的位置定义一个变量,那么这个变量在整个程序中的所有位置都有效,这就是全局变量了。那么相对而言,如果变量定义在内部的其它语句块中,它们就是局部变量了。

需要知道的是全局变量在程序员不指定初值的情况下会自动初始化,而局部变量在定义时不会初始化。

全局变量在程序运行时即占据内存,在程序运行过程中可以随时的访问它,而局部变量在进入语句块的时候分配内存,语句块结束的时候释放内存,不再有效。

如下示例代码:

#include <stdio.h>
#include <stdlib.h>
int global; //定义全局变量
void GlobalPlusPlus(void);
int main()
{
    global = 1;
    printf("调用函数之前, 它是 %d\n", global);
    GlobalPlusPlus();
    printf("调用函数之后, 它是 %d\n",global);
    return 0;
}
void GlobalPlusPlus(void)
{
    printf("在++之前, 它是 %d\n", global);
    global++;
    printf("在++之后, 它是 %d\n", global);
}

代码输出结果为:

调用函数之前, 它是 1
在++之前, 它是 1
在++之后, 它是 2
调用函数之后, 它是 2

上面示例中global定义在与main函数平行的位置,因此它就相当于全局变量。因此在程序中每个地方使用的时候,这个global就一直是一个变量。

如果我们现在对上面代码仅做一点改动:删除掉int global;这一行,然后给语句块中的global加上int,在函数中也定义上global,如下:

#include <stdio.h>
#include <stdlib.h>
void GlobalPlusPlus(void);
int main()
{
    int global = 1;
    printf("调用函数之前, 它是 %d\n", global);
    GlobalPlusPlus();
    printf("调用函数之后, 它是 %d\n",global);
    return 0;
}
void GlobalPlusPlus(void)
{
    int global = 1;
    printf("在++之前, 它是 %d\n", global);
    global++;
    printf("在++之后, 它是 %d\n", global);
}

输出结果为:

调用函数之前, 它是 1
在++之前, 它是 1
在++之后, 它是 2
调用函数之后, 它是 1

这里的global长的一模一样,可它们最多算个双胞胎,并不是一个变量。

变量的存储类型

其实C语言中有三个关键字:

  • auto:自动变量
  • register:寄存器变量
  • static:静态变量

可能大家对static还稍微熟悉一点,但是对前两个压根没啥印象啊。

实际上,我们使用的绝大部分局部变量都是自动变量,这个自动的意思是它会自动申请内存,并且在退出的时候自动释放内存。你想想我们的语句块中的变量不都是这样的吗?因此这种变量实在是太长用了,然后C语言就把它设计成可以省略了。

第二个寄存器变量又是什么东西呢?我们知道在CPU中有一种容量有限但是速度超级超级快的存储器,就是寄存器(Register),程序访问内存去取得数据是比较浪费时间的,因此我们可以把经常需要访问使用的数据存储在寄存器里面,这样的话程序的性能就会提高,使用寄存器变量就相当于你让这个变量直接使用寄存器来存储啦。

不过要注意的一点是,你可别直接就认为把所有变量都定义为register程序就会变快了。因为寄存器的个数只有有限的几个,多出来的变量编译器还是会自动处理成普通变量。同时,编译器有权以任何理由阻止你定义的任何变量成为register,它也有权以任何理由让你定义的普通变量成为register

这是因为现在的编译器太聪明了,它会自动优化程序的。因此我们平常几乎不用操心要把哪个变量放进寄存器,因此register是一个用户不怎么需要的关键字了。

对于静态变量就不需要说太多了,学过其它语言的都能很容易理解它。我们知道在语句块中,每次进入和退出时里面的变量都会重新分配内存和释放内存,也就是说你每次进入相同的语句块内其变量值都是一样的。那么如果你在退出语句块时想要保存某个变量的值怎么办?设置它为静态变量就OK了,这样你下次去访问那个局部静态变量的时候,它还保持着上次退出时的那个值。

预处理指令

我们知道C语言程序是不能直接运行的,需要使用编译器进行编译,而如果把编译的过程更加细分一下,它包含预处理、编译、链接三个步骤。

预处理在编译之前进行,根据源代码中的预处理指令,在后台调整源代码,编译器编译的都是预处理过后的源代码。

预处理指令就是那些以#开头的指令,比如#include<stdio.h>、#define PI 3.1415926还有很多其它的预处理指令。

#include

#include被称为源代码包含指令,它有两种语法:

#include <filename>
#include "filepath"

尖括号和双引号是定位文件的两种不同方式。

前者是在编译器指定的目录内查找filename文件,它通常就是一个叫"include"的目录,目录下有很多的.h文件,包括我们非常熟悉的stdio.h、math.h等等,如下所示:

在这里插入图片描述

而后者是按照filepath所描述的路径查找文件。通常我们给定的filepath里面并不含有路径,仅仅是一个文件名,表示在与源文件相同的目录下查找filepath,像我们一般自己写的一些函数文件就是这样引入的。

如果能成功定位文件(没有成功定位会出现编译错误),则预处理器会用该文件的内容替换#include指令的所在行。替换后的代码再被编译器编译。

例如在同一目录下有两个文件:file.h、file.c,其中

file.h

int var1;
int var2;

file.c

#include "file.h"
int main(void)
{
    var1 = var2 = 0;
    return 0;
}

那么file.c经过预处理就变成了:

int var1;
int var2;
int main(void)
{
    var1 = var2 = 0;
    return 0;
}

所以我们就知道为什么要包含stdio.h等文件了,因为该文件中包含了我们需要使用的一些库函数以及宏等。只有导入它们之后我们的代码才能正确编译运行。

#define和#undef

#define我们很熟悉了,就是宏定义指令,例如:

#define PI 3.1415926

那么程序中所有出现PI的地方都被3.1415926代替,然后再编译。

#undef决定了宏的有效范围,即宏的有效范围就是从定义它的那一行开始,直到遇见#undef为止结束。如果没有遇到的话,就会作用知道整个程序末尾,它的用法更简单,如下:

#undef PI

就行了。

宏在取消后可以重新定义,并且仅在被取消后才能重新定义。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阳寜

“请作者吃颗糖!”

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值