目录
前言
人生若只如初见,何处秋风悲画扇。每个人对于每一个事物都有从初识到熟悉的过程,作为本篇的作者也一样。
本文章将会全面系统的介绍C语言的知识点,但是不会对某一个具体的内容过多的深入,如同这篇文章的标题,初始C语言,点到为止,帮助想要学习C语言的读者揭开C语言的神秘面纱,窥见C语言的全貌。
但我会尽量避免使用过于专业枯燥的语言去介绍C语言的知识点,我的目标是用生动具体的语言帮助各位读者保持初见C语言时的好奇心,而不是初见时就被专业的术语吓到,秋风悲画扇,从入门到放弃。
由于作者目前的阶段也是刚刚初阶掌握了C语言,比初始C语言的阶段并没有高到哪里去,所以难免会有一些地方会有错误和局限,欢迎各位读者贡献智慧帮忙指正!
最后,本文的目的不仅是作为作者本身学习C语言过程的总结和复盘,更是帮助各位读者了解C语言的全貌,能看懂他人写的代码,在这个阶段先看懂了他人写的代码我们就可以在下一个阶段写出自己的代码,走得更远。千里之行,始于足下。那么就让我们开始接下来的C语言的冒险征途吧!
一,什么是C语言
正所谓知己知彼,百战不殆。在对C语言进行出征之前,我们首先要先知道它是什么,有什么特点,怎么来的,如此我们才能对接下来的一个个知识点的战役的攻克胸有成竹。
那么,C语言是什么呢?正如它的名字,C语言,它首先是一门语言,语言是用来交流,交换信息的工具,人与人交流有语言,如汉语,英语,德语……同理,人与计算机交流也需要新的语言,让计算机能听明白,理解,帮助我们做事情,这种语言叫做计算机语言。就像世界上有很多种语言一样,计算机语言也有很多种,而C语言就是计算机语言中的一种。到此就解决完了它是什么。
那么C语言有什么特点呢?C语言的特点当然很多,但我认为最重要的特点就是它适合底层开发。那么问题又来了,什么是底层开发呢?我们先来看一下下面这张作者小学六年级美术水平手绘的图
我们最先看到的是位于最下层面积最大的电脑,这也是我们最直观能先感受到的东西,我们买回来的电脑,里面有很多的硬件,如显卡,内存,处理器等等,但是,我们仔细想想的话,我们买回来的这一个铁壳加这几个部件怎么就可以让我们使用计算机进行这么多复杂的操作?
答案是在电脑之上有一个东西叫做驱动,驱动就可以驱使硬件去帮助我们能够完成一些复杂的操作,而在驱动之上,还有操作系统,向我们通常使用的windows操作系统,mac操作系统,而在操作系统的更上面,就是软件了,也叫应用层,比如说我们的浏览器,QQ,微信等等。于是,我们把应用层和操作系统之间划分出一条界限,线以上的就叫做应用层,而线以下的就叫做底层。我们说C语言的特点是擅长底层开发,就是说C语言擅长属于底层的电脑(硬件),驱动,操作系统的开发。
明白了C语言的特点之后,我们最后来再看一看它是怎么来的,它的发展过程。
最初的计算机语言是二进制语言,也就是我们熟悉的0和1,但是这样会有一个弊端,假设你是那个年代的科学家,你打算计算3+3的结果,可是计算机不认识3,也不认识加号,它只认0和1,于是你一顿操作猛如虎,一看屏幕密密麻麻的0和1,只为算这简单的加法,可以很明显的看出,虽然这种二进制语言看起来神秘高大上,但很明显它很繁琐,效率低。
然后,人们为了提高效率,发明出了一种计算机语言叫做汇编语言。这种汇编语言最大的特点就是引入了助记符,所谓助记符,就是帮助记忆的符号,如ADD(加),SUB(减)等等。这样,不同于二进制语言一堆看不出意义的0和1,我们总算有一些有意义的,能够看懂的单词了,于是交流沟通的效率就提高了。
可是,人们不仅仅满足于此,为了进一步提高效率(其实正所谓人之初,性本懒,正是因为我们懒得本性的动力催生出了很多科技的进步,带来生产力的不断提升),人们在之后又发明出了B语言,然后又不断迭代,于是终于来到了我们的C语言。
C语言是一门高级的计算机语言,但新事物的发展不是一帆风顺的,它刚出来时侯仍有很多的不足,人们为了弥补这些不足,一些人优化后出一个C语言2.0版,另一些人改良后又出一个3.0版,不同的人针对C语言不同的不足进行改良,出现了很多不同的C语言,就导致你的C语言不是我的C语言,于是为了解决这种C语言泛滥的问题,推出了一个国际标准,ANSIC,来对C语言进行大一统,这也就是C89,C90这些国际标准的由来,并且这也是衡量一个语言发展得好不好的一个标准,试想一下,如果一个语言发展得不好,用的人不多,版本差异不大,又何必大费周章的制定出一个什么国际标准呢?所以从C语言有国际标准这一个角度看,它还是混得挺好的。到此为止,我们对C语言的认识就差不多了。接下来就迎来我们与C语言的第一场正面交锋。
二,第一个C程序
C语言是一门编译型语言,我们写出来的代码需要通过类似于翻译的过程,转化成计算机能看懂的0和1,它才能帮助我们做事情,这个类似于翻译的过程,就叫做编译,而用于完成编译的工具,就叫做编译器。
用于编译的编译器有很多,有GCC,MSVC等等,这里不过多的介绍,作者在这里使用的编译器是MSVC。当然,为了写一个C语言的程序,我们不用专门的大费周章的真的下载一个编译器,我们可以直接下载一个IDE(集成开发环境),里面就自然集成了我们的编译器。作者在这里使用的是VS2022,里面就集成了MSVC,可以直接编写和编译C语言代码。关于VS2022的安装教程,我觉得现在的时代与众不同了,不如问一问神秘的Deepseek小海豚吧^ ^!
在准备完了我们的工作环境的问题后,我们就终于可以正式的进行我们的编程了。
我们首先打开我们的VS2022,第一步选择右边的创建新项目
接下来第二步选择空项目,然后点击下一步
第三步设置项目名称(英文命名,避免不必要的麻烦),选择存放项目的位置后继续点击下一步
到这里我们就创建好了我们的项目,进入到如下页面,再进行第四步如下操作创建我们的源文件
进入到如下页面后,我们按照如下操作,在设置源文件名称时尽量使用英文,并且把文件的后缀.cpp改成.c,因为.cpp后缀是C plus plus也就是C++的缩写,是C++文件,而我们写的是C语言代码,所以使用.c后缀,然后选择好我们存放的位置,点击添加,我们的源文件就正式创建成功了,我们也终于真的正式进入到写代码环节了。
那么,我们的第一个程序要写什么呢?怎么写呢?如果你曾经学过编程,你一定会听过大名鼎鼎的
Hello World,这几乎是每一个学写代码的初学者躲不过的白月光。不过今天,既然我们在CSDN这个大家庭里,我们不如给我们的白月光简单的画一下妆吧,我们写一个Hello CSDN,实现在屏幕上的打印,以下就是这个程序的具体实现代码
我们也许会注意到第一行的那句话,我猜你们一定想问,唉作者你那句话是什么啊?我们创建好的源文件怎么没有的? 这句话暂时不会对我们今天的第一个程序产生影响,所以关于这句话的作用,我们暂且先按下不谈,未来我们还会再和这句话有重逢的缘分,不必留念于现在。接下来我们一个一个解释剩下的3—9行的内容是什么意思。
在C语言规定,每一个程序的入口是主函数,也即图中的main函数,也就是说,一个程序运行的前提是要有主函数,那么主函数是越多越好吗?也不是,入口多了容易迷路,主函数只能有一个,更具体的说,我们在一个工程里面,可以有多个源文件(.c后缀的文件),但是多个源文件中,我们有且只能有一个主函数,每一个程序都是从主函数开始,从主函数结束,主函数就是程序开始与结束的地方,并且我们现阶段所有的操作基本都在主函数中进行。所以下面我们先来学习一下主函数怎么书写。
int main() //int是函数的类型,叫做整型,这个我们之后会讲到,main是函数的名字,后面接一对()
{
//然后是打出一对{},注意都是英文状态下的,最后在括号内最后一句打上return 0结束
return 0;
} //现学阶段的所有程序,主函数都是这个模板,多打几遍就会了然于胸
学完了主函数,我们 再来关注一下主函数中的printf。
printf也是一个函数,是C语言自带的库函数,后面也是跟一对(),任何函数后面都跟一对(),并且这个括号是不能省略的,这一点我们也会在后面的函数部分详细讲到,这里暂时先提一嘴。
printf的作用就是在屏幕上打印信息,也即我们上面图片右侧的控制台黑框框,打印出来的Hello CSDN就是printf的功劳。在printf的括号里面,Hello CSDN是我们要打印的信息,是一个个的字符,我们把这一个个的字符叫做字符串,字符串需要使用英文的双引号来把它给括起来。
\n是一个转义字符,在这里的作用是打印完这个信息后换一行,可以看到,上面图中的控制台黑框框中,我们打印完Hello CSDN后,与下方的那些文字中间空了一行,这空出来的一行就是\n导致的,关于转义字符的内容,我们也会在之后详细的讲到,这里也只是拉出来先见一见面缓解下次见面的陌生感。
既然printf是C语言提供的函数,那么是别人的东西,就像现实生活中借别人东西要打招呼一样,我们使用printf函数也要打招呼,在C语言中的打招呼就是我们上图中的第3行的#include <stdio.h>,这里的#include叫做预处理指令,预处理什么呢?预处理引入一个叫stdio.h的文件,用<>括起来,这个以.h为后缀的文件就叫做头文件,里面就包括了我们要使用的printf函数,关于这个头文件,我们在之后还会继续讲到,在这里我们先知道它里面有printf函数,所以我们需要用#include <stdio,h>这句话打招呼借来用。至此,printf函数也简单介绍了一下,我们最后再来注意一下,每一行都以英文的分号结束,我们把以分号结束的每一行叫做一个语句。
于是我们终于了解完了第一个程序里的所有陌生的词汇,我们可以按下我们的F5(笔记本通常时Fn+F5)或者如下步骤来编译+链接+运行我们的代码,执行我们的程序
打印出我们的Hello CSDN
以上就是我们的第一个C语言程序,在我们与C语言的第一次战役中,我们知道了程序的入口和出口都是主函数,并且一个工程里有且只有一个主函数,知道了主函数的写法,printf的用法,知道了用printf要借,引入头文件#include <stdio.h>,知道了\n表示换行,字符串要用双引号括起来,我们在第一次战役中就能取得如此诸多的收获,相信我们在日后对C语言的征途中必能九战九捷!
三,数据类型
我们说,计算机语言的出现,是为了我们与计算机交流,也就是写程序,来让计算机帮助我们解决生活中的问题,那么我们在生活中通常会遇到那些问题呢?比如说,我们说计算一个人的年龄,年龄通常是整数,整数是一种数据类型,化学试剂的配比,通常要精确到小数,小数也是一种类型,再比如上一节打印我们的Hello,CSDN,这是一个字符串,字符也是一种数字类型。所以,我们生活中问题会充斥着各种各样的数据,每一种数据又会有不同的类型,就像全世界有各种各样的人,每一个国籍都是不同的身份,在C语言中为了精确的描述这些复杂的数据,就出现了诸多的数据类型。数据类型的来源我们清楚了,那么,数据类型又有哪几类呢?数据类型大致可以划分为下面几类:字符型,整型,与浮点型。下面我们就来逐一的介绍。
3.1 字符型
字符型的名字叫做char,它怎么用呢?我们可以用字符型这种类型创建一个变量(所谓变量,就是可以向内存申请空间来存放我们数据的东西,我们会在之后的变量部分详细讲解),用来存放字符型的数据,这种数据包括单个的字符,和多个字符组成的字符串。
3.2 整型
所谓整型,就是整数的数据类型。它有可以分为下面的几类:短整型short,整型int,长整型long和更长的整型long long。那么,它们有什么区别呢?它们的区别就是能存储的数据的大小不同,从左往右(除了int和long相等,关于为什么相等会在接下来提到),能存储的数据逐渐变大,至于能存储多大的数据,我们会在稍后讲到。
3.3 浮点型
浮点型,就是小数的数据类型。那么为什么叫浮点型呢?比如15.1,它又可以写成1.51*10,我们发现,它的小数点是可以通过科学计数法来左右移动的,所以叫做浮点型。浮点型可以分为两种,一种是单精度浮点型float,另一种是双精度浮点型double。它们又有什么区别呢?单精度,双精度,我们但从字面意思来看的话可以知道它们的精度不同,double可以表示的精度比float要更高,意味着它可以表示更多位的小数,对小数的精确度也就越高。
说完了字符型,整型,浮点型这三中数据类型之后,我们接下来来研究一下每一种数据类型的大小。
3.4 数据类型的大小
在介绍具体的每一种数据类型的大小是多少之前,我们先来讲解一下计算机中关于内存单位的名称以及它们之间的转换关系。
在计算机中,我们知道,计算机本质上只明白二进制语言,也即0和1,我们把计算机中用来存放一个0或1的这样大小的内存单位叫做比特(bit),即比特位是用来存放一个二进制位的,是计算机中最小的内存单位。在比特之上,叫做字节(byte),像我们熟知的日常使用的抖音的公司字节跳动,其中的字节就是取自这里。它们之间的换算是8个bit等于1个字节。字节之上,叫做KB,1024个字节等于1KB。KB之上,是MB,1024KB等于1MB,MB之上是GB,1024MB等于1GB,GB之上是TB,1024GB等于1TB,TB之上是PB,1024TB等于1PB。关于内存的单位,我们大概了解到这里就好了,我们可以总结出来,除了比特到字节的换算是8之外,其他的换算都是1024。下面是更加详细的图片形式的换算图,方便大家记忆。
讲解完内存单位的知识,我们就可以来看一下具体的每一种数据类型的大小如何求了。我们可以使用sizeof(一种关键字和操作符)来求,它的作用是计算变量或者类型的大小。 单位是字节。以下是它的具体用法。
因为最后我们是通过将每种数据类型的大小打印到屏幕上来看到不同数据类型的大小的,所以我们使用printf函数来打印。这时候,出现了一个新的东西,%d。这是什么意思呢?
说的是将数据以十进制整型的类型打印出来,是什么数据呢?就是printf函数里面逗号后面的那一部分,以第7行为例,也即sizeof(char)。于是我们自然也就可以直观感受到了sizeof的用法,即sizeof+(括号里面加具体数据类型),我们就可以打印出来具体数据类型的大小,那为什么这句话不能直接放在双引号里面呢?还要大费周章的搞一个%d再打出来呢?
这是因为如果sizeof之间放在双引号里面,我们之前说过,双引号里面的是字符串,计算机就会自动的把这一句话当成字符串,而不会执行sizeof的这句话,实现求类型的大小的功能,所以,为了实现sizeof求类型大小的功能,我们就需要把sizeof这句话放到后面,用逗号隔开,告诉%d,我们需要打印的数据就在逗号的后面,于是自然就可以正常的显示出我们每一种数据类型的具体大小了,如下图一一对应所示。
我们可以看到,char类型大小是1个字节,也即8个比特位,short是2个字节,16个比特位,以此类推。我们会发现,int是4个字节,32个比特位,以此类推。我们会发现,int和long的大小是相等的,为什么呢?long难道不是因该要比单独的int更长才对吗?
这其实是C语言规定了,只要sizeof(long)>=sizeof(int)即可,这里 VS2022采取的是取等,所以在这里没有任何问题。那每种数据类型的大小我们知道了,那它能存多大的数呢?
以short和int为例。short是2个字节,16个比特位,每一个比特位只要0和1这两种结果,根据分布乘法原理可以得到short最大可以存储2的15次方+2的14次方+……+2的1次方+2的0次方这么大的十进制整数(关于二进制如何转十进制我们也会在之后详细讲解,这里了解为主) ,而int类型32个比特位,最多可以存储2的31次方+2的30次方+……+2的1次方+2的0次方这么大恶的十进制整数,这也证明了int类型可以比short类型存放更大的整数。与我们上面提到的结论想吻合,其他的数据类型也是相似的道理。
至此,我们就讲解完了数据类型的重要性,有那些类型,计算机内存的单位及其换算,以及通过sizeof求数据类型的大小,%d的意思及使用的内容了。
在介绍数据类型的开头,我们说到了,单有数据类型是不够的,各种数据类型的出现最终是为了存放各种各样的数据,提高空间的整体利用率的,所以我们需要把数据给存起来,存在哪?如何存?这就要提到与数据类型形影相关,密切不离的变量了,变量的作用就是向内存申请空间用来存放数据,接下来,我们就来讲一讲与数据类型的天作之合——变量。
四,变量和常量
4.1 变量
上文所述,数据类型的出现最终是为了创建变量,而变量的出现则是为了向内存申请空间,存放数据,变量变量,它首先是一个可以改变的量,意味着我们可以用变量来存放不同的数据。所以接下来我们就来学习一下变量如何使用。
4.1.1 变量的使用
我们可以通过数据类型+变量名的形式创建出一个变量,如下图
我们可以注意到,我在创建的时候不单单只是创建了这个变量,我还给这些变量存了一个初值,其中=不同于我们数学中的等于的意思,在C语言中,=的意思叫做赋值,即把右边的值传给左边,我们把这种在变量创建时就进行的赋值操作就叫做变量的初始化。 这其中,int,char,double是我们上一节所讲的数据类型,而a,b,c就是上面说的变量名,也就是变量的名字。
变量名的命名有以下几点要求:只能由数字,字母,和下划线_组成,C语言中的关键字不能做变量名(关于关键字的内容会在后面提到),并且不能数字开头。
讲完了变量的创建和初始化,变量的命名规则后,我们接下来学习一下与printf函数相呼应的另一个函数,scanf函数。我们知道,printf函数是C语言提供的库函数,作用是输出,使用前需要引入头文件。那么,scanf函数又是什么呢?scanf函数也是C语言提供的一个库函数,它的作用是接收用户输入的信息,即我们可以用键盘来进行输入的操作,输入的数据就可以存放在我们创建的变量中。我们引入的头文件stdio.h中的i和o其实就是英文中的input(输入)和output(输出)的缩写。scanf函数具体怎么使用呢?我们来看下图。
我们先看第10行,与printf类似,%d在printf是以十进制整数输出,在scanf里就是以十进制整数输入,输入的数据就放在逗号后面的变量中,只不过与printf不一样的是,变量a的前面多了一个&,这个符号叫做取地址操作符,我们会在指针部分详细讲到,在这里只用暂时先记着,整句话的意思就是以十进制整数数据接收数据,接收到的数据存放到变量a中,a前面加上一个&符号。11,12行类似,%c是单个字符的数据,对应的变量的数据类型就是char,%lf就是双精度浮点型数据,对于的数据类型是double,还有一个图中没有的%f是单精度浮点型数据,对应的数据类型就是float。以上就是各种百分号与它们变量的数据类型的对应关系,多写几遍就可以记得非常清楚了。
于是我们怀着激动的心情跃跃欲试输入数据,按下F5运行程序,却发现
于是有读者就会问了,怎么回事啊?我明明是一步步按照你说的敲的,怎么发生错误了,是不是你讲错了。
遇到这种情况,我们先不用着急,这其实是VS这个编译器里面的特殊现象,我们点击否,然后来看一下代码下面的错误提示。
这句话的意思是scanf函数可能出现安全隐患,考虑使用scanf_s函数来代替。如果仍继续使用scanf,不想再看到出现错误,则需要加上这么一句话_CRT_SECURE_NO_WARNINGS。安全隐患是什么意思?加上这句话又是什么意思?别着急,我们一句一句来解释。
首先说的scanf_s函数,是VS里提供的一个函数,它的作用也是输入,那我们就想既然scanf函数存在安全隐患,那我们是不是把sancf换成scanf_s就可以了?其实不然,有两点原因。首先是scanf_s函数与scanf函数的使用规则并不相同,替换后可能会出现其他的问题,然后是scanf_s这个函数是VS提供的,也就是说只有在VS这个环境下才认,在其他环境下就不知道这个函数是什么意思,并不是C语言提供库函数,适用性很窄。综上所述我们还是需要使用scanf函数,尽管它存在一些现阶段我也不知道会有什么的安全隐患。那么怎么能顺利使用scanf函数呢?就是需要加上错误提示上面说的这句话。怎么加呢?如下图 所示
我们把上面的那句话复制下来。然后在整个程序的第一行,注意一定是第一行写上#define然后一个空格,再粘贴上面那句话。这样就大功告成了,我们的scanf函数就可以顺利使用了。可是,这不免就会有一个问题,每次如果我要使用scanf函数的时候都要写这句话,是不是太麻烦了,有没有什么一劳永逸的方法。这时候有注意到细节的读者就会发现了,你有没有觉得这句话特别熟悉呢?没错,这句话其实就是我们在第一个C程序中所说的还会再重逢的这句话,读者就会发现我在创建新的项时是直接自带这句话的,那么这是怎么做到的呢?其实也非常的简单。我们可以先在浏览器搜索一个软件叫做everything,这个软件可以帮我们快速的找到一个文件所在的位置,而不用我们自己苦哈哈的找。下载好后在桌面上就是这么一个图标的东西。
我们打开后在搜索栏上搜索这么一个文件:newc++file.cpp,然后来到如图这个界面
右键找到这个文件所在的路径,然后打开这个文件
把之前的那句话复制进去
里面的1这个数字可有可无,然后点击保存,这时候其实又会有一个问题,就是你没有权限,解决的办法也很简单,复制一个快捷方式在桌面,然后再重新打开,复制上面这句话,保存到桌面后,再拉回原来文件所在的位置,选择替换文件即可,这时候我们的文件就可以包含下面这句话了,以后我们新建项的时候也会自动包含这句话,我们就可以顺利的使用scanf函数了。
讲解完了scanf函数怎么用以及VS特有的报错问题后,我们接下来了解一下变量的类型。
4.1.2 变量的类型
变量可以简单的分为两类:一类叫做全局变量,一类叫做局部变量。所谓全局变量,就是在一个工程中的所有源文件都可以使用的变量,而局部变量,就是只在它的局部的范围内可以使用的变量。我们接下来看一看如何区分全局变量和局部变量。全局变量既是在{}外定义的变量,而局部变量即是在{}内定义的变量。我们依靠下图来更具体的说明。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int Max()
{
int c = 0;//局部变量,可用范围是Max这个函数的{},这里的Max这个函数,是我们自己创建的,关于如何创建函数,我们会在函数部分介绍
}
int a = 0;//全局变量,可用范围是整个工程
int main()
{
int b = 0;//局部变量,可用范围是main函数的这个括号
return 0;
}
全局变量还有另外一种创建方法。我们可以在工程中的源文件里再新建一个项
在我这里我新建了一个源文件叫做test_1.c,然后因为一个工程中可以有多个源文件,多个源文件有且只有一个main函数,所以我们不用再写main函数了,这是直接创建一个变量,这时候我们创建的变量就相当于在括号外定义的,,所以它是全局变量,可以在整个工程中使用,即我们也可以在另外的源文件中(作者这里是test.c)中使用,如打印出来,我们接下来来试一试。
我们在test_1.c中创建了一个全局变量d,初始化为0,现在我们回到test.c中把这个变量d用printf函数打印出来,运行程序,然后我们就会成功的得到下面的报错信息
不是说全局变量可以在整个工程中都可以使用吗?为什么这里错误提示说为声明。的确,全局变量确实可以在整个工程中都可以使用,但因为我们这个全局变量它不在我们现在的这个源文件里,所以我们在使用这个全局变量的时候我们要先做一件事——声明。如何声明,我们需要用到一个关键字叫做extern,如图,我们可以在使用这个变量之前的上面任意行声明这个全局变量(extern+类型+变量名)
加上这句话后,我们的全局变量就可以正常的使用了。
然后我们再来看一个关于全局变量和局部变量的有趣的现象,如下图所示:
问当全局变量与局部变量名字冲突时,我们的printf打印的是哪个a的值?
下面我们揭晓问题的答案:答案是局部变量a的值。这个现象就告诉我们,当全局变量和局部变量的名字起冲突时,局部变量优先使用。注意注意,只有全局变量和局部变量之间可以重名,其他变量在创建时可千万不要重名哦!
介绍完了变量的类型,我们再来看看与变量密切相关的两个概念——作用域和生命周期。
4.1.3 变量的作用域和生命周期
变量的作用域,顾名思义就是变量可以使用的范围。简单来说,那里可用,哪里就是变量的作用域,局部变量的作用域就是变量所在的局部范围,也即局部变量自己所在的括号内,而全局变量的作用域就是整个工程。
讲完了作用域,我们再来看一下变量的生命周期。变量的生命周期与作用域也有相当紧密的联系,可以这么说,变量的生命周期约等于作用域。所谓生命周期,就是变量创建和销毁之间的时间段。局部变量的生命周期从进入局部范围内开始,变量自动创建,生命开始,出局部范围后,变量自动销毁,生命周期结束 。而全局变量因为在整个工程都可以使用,所以全局变量的生命周期就是整个程序的生命周期,随着程序即生即灭。
介绍完了变量,我们再来看一看与变量截然不同的一个东西——常量。
4.2 常量
常量其实就是创建后不能改变的量。常量一共可以分为四类:字符常量,const修饰的常变量,#define定义的标识符常量,和枚举常量。下面我们逐一进行攻克。
第一种字符常量,就是所谓的数字,如3.14,10之类的,这种单独存在没有什么意义,但部分情况下是需要有一个地方存起来。
第二种const修饰的常变量。这是什么意思呢?就是我们在创建变量的时候在前面加一个const,如图所示
这样我们在给这个变量进行初始化赋值后,它里面的值就不能改变了,具有常属性,相当于常量,但其实它的本质还是变量,我们只是依据它有常属性这个特性而放到常量这个分类中。
第三种是#define定义的标识符常量。如下图所示
这种常量一般是用大写的英文字母命名,中间空格后再进行相当于初始化的操作,注意这里最后不用分号。定义这种常量的好处是我们如果一直要用某个值,可以一直使用这个常量,而不用重复的创建变量,从而提高效率。
第四种是枚举常量,我们在初始C语言这个阶段只是略讲一下,了解即可。
在生活中,我们知道学科是可以枚举出来的,比如说小学,一共有语文,数学,英语三门学科。争对这种可以枚举出来的量,我们可以运用enum这个关键字创建出一种类型,这种类型的名字叫做Subject,如上图所示,然后打一个大括号,括号外是一个分号,在括号里面枚举出学科可能出现的值,如小学中的Chinese,Math,English,注意每个使用逗号隔开,最后一个不用符号,于是括号内的这一个个量,就是我们说的枚举常量。接下来我们可以试着把它打印一下,看看它的值是什么?
如图所示,C语言规定,如果枚举常量没有初始化,默认把第一个初始化为0,然后以此类推依次向下递增。当然,我们也可以在创建这个枚举常量的时候就初始化一个值,然后也是依次顺着这个初始化的值递增。,这并不意味着改变了常量的值,我们只是在创建的时候对这个常量进行初始化操作而已。常量常量,首先要有值,如果没有初始化没有值,又和谈改变一说呢?本来无一物,何处惹尘埃?
这个世界上唯一不变的就是变,这或许就是常量和变量想告诉我们的秘密吧。
五,字符串,转义字符和注释
5.1 字符串
字符串在之前提及过,所谓字符串,就是用双引号引起来的一个字符。如“abc”,“1234”。这些字符串其实是用一种叫做字符数组的数组来存储(关于数组,我们会在后面的内容继续讲到)。什么是数组呢?其实就是一组相同类型的元素的集合,其实也是一种变量。用于存储数据,只不过变量创建的时候是一个一个创建的,而我们的数组呢,就相当于一次就可以创建很多的变量,从而提高效率。下面我们就来学习一下字符数组如何创建。
如图,我们可以用数据类型+数组名+[常量(注意不能是变量)]来创建一个数组,以上图第六行为例,它表示的意思创建一个字符型的数组,数组的元素是10个,也就相当于10个变量,可以存储,然后我们在右边给它一个初始化的赋值。赋值可以有两种方式,第一种是给它赋值一个字符串,用一对双引号引起来,也可以用一个大括号括起来,用单引号一个字符一个字符的来赋值。
然后我们就可以用printf把这个字符串给打印出来,因为字符串本质就是以字符数组存储,%s就表示以字符串形式打印,逗号后面写上我们的数组名(注意只用写数组名,而不用加上[])。然后我们就会得到下面的结果。
接下来,我们还可以玩一些有意思的事情。我们在创建数组的时候也可以不用在括号内加上具体的字数,而是留空,直接进行初始化操作,这样子它就会依据我们字符串的长度来自动规划出一个刚刚合适的长度用于存储。如图所示
我们接下来再运行一次程序,把这两个数组以字符串打印出来,这次的结果又会是一样的吗?我们不妨先暂停思考一下然后再来看下面的结果。
我们可以看到,第一个字符数组正常打印abc,而第二个似乎有一点敏感了,我们就运行了一下下,它就烫起来了,是我们有什么魔力,给这个字符数组赋予了什么感觉吗?
其实并不是,这其实就要涉及到一个新的知识点,也就是字符串是以\0为结束标志的。当我们[]内部为空时,我们以字符串进行初始化的时候它会自动的多开一个位置,补一个\0,而当我们以一个一个字符进行初始化的时候,它就不会自动的帮我们补上\0,而要我们自己补上去。
而当[]内我们给了一个具体的数字时,无论是以字符串进行初始化还是一个一个字符进行初始化,我们都可以看到由最开始运行的那样,字符数组正常的打印出来,也即都会自动的补上\0。
现在当我们[]内为空的时候,我们以一个一个字符的方式去进行赋值,它不会去自动补上\0,而我们现在要以%s字符串的形式去打印字符数组,而因为这种赋值方式没有\0,所以此时这个数组就不是字符串,因为字符串以\0为结束标志,而是一个字符数组,而我们要以字符串形式打印啊,因为没有\0,它就不会结束,它就会一直在字符数组后内内存单元里一直找找找,而我们并不知道其他的内存单元里哪里由\0,整个寻找过程完全是随机的,直到找到我们的\0为止,这也是为什么我们上面运行时第二个数组打印时会出现烫烫烫乱码的原因。
于是我们在字符数组b后面再补上一个字符\0后,我们的两个字符数组就都可以正常打印出来了.
讲完了字符串以\0为结束标志后,我们再来看一些初始化的技巧。我们可以在创建数组直接以这样的方式赋值。这样子我们只用打一个0后,后面的都会默认的补上0。
#include <stdio.h>
int main()
{
char b[10] = {0};
return 0;
}
在讲解完一些字符数组的创建和初始化以及字符串的结束标志后,我们再来看一看字符串中一个重要的问题:如何求字符串的长度?
求字符串的长度,用到一个C语言提供的函数,strlen()。同printf,scanf函数一样,我们在使用这个函数的时候也需要打一声招呼,我们又要引入一个新的头文件,叫做<string.h>,引入的方式类似于<stdio.h>这个头文件,都是#include <string.h>。其中string其实就是字符串的意思,而strlen其实就是string length的缩写,我们很容易去记忆。string函数的用法如下
我们先创建a,b,c,d四个不同的字符数组,分别对应4中不同的情况,用strlen求字符串的长度,类似于sizeof求类型的大小,我们用一对括号括起来,里面放上我们的数组名或者直接所要求的字符串,然后我们运行一下我们的代码。
我们可以看到,在计算字符串的长度的时候其实是不算上\0的,这是因为\0只是字符串结束的标志而已,我们直到它有,但在计算长度时并不参与计算。而c数组由于没有\0,它就会一直向后找直到找到\0,这个过程完全是随机的,所以导致它的字符串长度也是随机值。
虽然我们说字符串在计算长度的时候不算上\0,但我们的\0总要有地方存储,所以我们的字符数组在创建的时候其实都最少要比我们需要输入的字符串长度多一个位置,用于存放\0,这也是为什么我们在创建数组d的时候虽然输入的字符串是"abc",只用三个长度,但我们要创建4个元素的空间的原因。所以,当我们在创建数组时[]内 没有给一个具体的常量的值的时候,就会默认我们的字符数组的大小就是我们输入的字符串的大小的长度的基础上+1,这就是因为还要存储\0的原因。所以我们平时在创建字符数组的时候,也要注意留有充足的大小来存放\0。讲完了字符串之后,我们再来看一看一个新的概念,转义字符。
5.2 转义字符
我们知道,\n是一个转义字符,它的作用是换行。那么\n它原本的意思是什么呢?其实就是一个\和一个n。可是它们结合在一起,构成了一个新的字符,与原来的意义不同了。像这种转变了它原来意思的字符,就叫做转义字符, 它首先是一个字符,所以我们再计算字符串大的长度时把一个转义字符算作一个字符。
转义字符常用的有以下这么几类。第一类是\t和\n。我们已经知道了\n的作用是什么,所以我们现在来看一看\t的作用。\t其实就是我们键盘中的Tab键,它叫做水平制表符,就可以在所在的行空比空格更多的距离,如图所示:
我们再来看第二类,有三种:\' ,\" ,\\。它们的作用是分别是打印单引号双引号和斜杠。要打印这些东西,只要在它们前面加上一个\,它们就可以正常打印了。
第三类是\a,一个比较好玩的东西,打印这个转移字符会在电脑上发出一声蜂鸣声,你们可以在屏幕上打印一下就知道是什么声音了。
最后就是我们要重点介绍一下的第四类了。分别是\ddd 和\xdd。\ddd表示1~3个八进制数。比如说我们打印\130,因为这是一个转义字符,表示的是八进制数字,而我们printf的%d打印的是十进制整数,所以八进制要转化成十进制后是78,但是我们并不是直接打印十进制那个数字,因为转义字符本质是字符,字符在内存中的存储本质是ASCII值,相当于整型,所以我们还要通过ASCII值表来找到78这个十进制数字对应的字符是X,所以我们最后打印出来的是X,并且,由于\ddd是转义字符,所以printf逗号后面我们的这个转义字符需要用单引号给引起来。
下面我们介绍以下八进制和十进制之间如何进行相互转化?
八进制转化为十进制使用权值展开法:即每一位乘以8的幂次后求和。如八进制的116换十进制,1×8² + 1×8¹ + 6×8⁰ = 64 + 8 + 6 = 78 。
而十进制转化为八进制使用除基取余法:即除8取余法,逆向排列余数。如十进制的78换八进制,78 ÷ 8 = 9余6 → 9 ÷ 8 = 1余1 → 1 ÷ 8 = 0余1 → 逆向排列余数八进制116。
无论是八进制转十进制的权值展开法,还是十进制转化为八进制的除基取余法,都是不同进制之间转化的基本方法,类似的,二进制转十进制,十六进制等转十进制,都可以用权值展开法,而十进制转二进制,十六进制等,都可以用除基取余法。
介绍完了\ddd,我们来看\xdd。其中的x只是一个标志,而后面的dd表示2个十六进制数字。 如: \x30,表示30这是一个十六进制数字,同样的,使用printf以%d格式打印时,也是需要十六进制转十进制再查ASCll值表看其对应的字符,具体的十六与十进制之间的相互转化,与上文提到的八进制类似,这里不再赘述。
既然我们重点介绍这两个,那它一定是有用的,就比如下面的一道笔试题:
//程序输出什么?
#include <stdio.h>
int main()
{
printf("%d\n", strlen("abcdef"));
// \62被解析成一个转义字符
printf("%d\n", strlen("c:\test\628\test.c"));
return 0;
}
我们可以先自行思考以下,然后再看下面的答案。
答案是分别是6和14。第一个考察的是字符串的长度,在字符串部分详细讲解过如何计算。第二个考察的是转义字符,特征是出现了\,所以我们就想到在计算字符串长度的时候,转义字符算一个字符的问题。以上就是转义字符的基本内容以及具体的考法。下面我们来看看在我们写代码和我们上面的文章中一直出现,非常使用的一个东西——注释。
5.3 注释
什么是注释呢?字面意思就是标注起来的解释,注释在我们运行程序时是不会被计算机读取运行的。注释就是用来帮助我们解释复杂代码的,从而是我们写的代码更容易理解,提高代码的可读性。
注释一共有两种风格。一种是C语言的注释风格,也叫做块注释。怎么添加注释呢?我们打开我们的VS2022,然后如图进行操作
我们选中我们的代码部分,然后点击箭头所指示的注释选中行,或者对应的快捷键,我们就可以添加这样的块注释。
它的结构是/* */,里面是我们需要注释的内容,这种注释风格就是C语言的注释风格,但它有一个缺陷就是不支持嵌套注释,因为如果嵌套里遇到我们只要遇到*就算注释结束了,后面就不算注释的内容了,如下图,所以这种注释里面不能有注释。
而取消我们的注释也很简单,我们选中我们的被注释起来的代码,要去掉哪部分的注释就选择哪部分, 然后点击箭头所指的图标或者对应的快捷键,就可以去掉注释。
讲完了C语言风格的块注释,我们再来看另外一种注释,即C++注释风格的注释,也叫做行注释,这种注释支持嵌套注释,所以我们通常更多的使用这种注释方法。怎么来添加呢?与上面的方法类似,但略有一点不同,我们需要在选择我们的代码的同时也要把左边的空余的部分全部也选中,填充满,如图所示(注意对比蓝色选择区域的面积的不同)
然后还是那个图标或者快捷键,我们就可以添加这种行注释。
它的形式就是两条斜杠\\,后面就跟着我们需要注释的内容。取消注释的方法也与块注释取消的方法相同,这里不在赘述。这就是关于注释的所有技巧和内容。
我们说C语言是结构化的程序设计语言,是用来解决生活中的问题的,生活中的所有问题都可以抽象成三种结构,顺序结构,选择结构和循环结构。其中的选择结构,循环结构分别对于分支语句,循环语句 。所以接下来,我们就来学习一下分支语句和循环语句的内容。
六,分支语句和循环语句
所谓分支语句,就是选择。在我们生活中无时不刻都在选择,所以为了实现这种选择的逻辑,我们再C语言中就出现了分支语句。分支语句在初始C语言中我们只需要知道if—else的分支语句就足够了。if就表示如果,即我们条件的逻辑值为真执行的语句,而else表示否则,即我们条件不成立的时候执行的语句。关于分支语句的所有知识的系统介绍,请参考作者的这篇文章:[C语言初阶]分支语句—重走天涯路-CSDN博客
而循环语句呢?就是执行重复的事情的操作。生活中也由很多的事情在不断的循环,不断地重复,比如你每天不断地学习,就是一个循环,所以为了实现这种重复的逻辑,C语言中出现了循环语句,在初始C语言中,我们只需要了解到有while语句表示循环就行了,括号里面同样是逻辑值,如果为真则循环进行,如果为假则跳出循环。关于循环语句的所有知识的系统介绍,请参考作者的这篇文章:[C语言初阶]循环语句—漫步人生路_do while循环语句-CSDN博客
七,函数与数组
在数学中,我们已经学过了函数的概念。在数学中的函数即是一种映射,一种对应关系,我输入什么值它又会进行表达式的计算返回什么值,这是数学中的函数。那么C语言中的函数呢?在C语言中,函数就相当于实现某种功能的模块,它相当于一个委托人,外包,我们把需要实现的功能交给函数去做,然后做好后它就会返回结果。
函数的作用是使得我们的代码不会冗余,全集中在main函数里,而是将我们需要使用的功能封装成一个函数,这样一个个的函数就相当于一个个的零部件,模块,如此,全部模块结合组装起来,就跟拼积木一样,就变成了我们完整的程序。在初始C语言中,我们只需要知道函数是什么,有什么用就可以了。
简单介绍完了函数,我们再来看一下数组。数组又是什么呢?我们知道变量可以用来存储数据,而数组就相当于一次创建了多个变量,用来存放一组数据。所以我们给数组下一个定义:数组就是用来存放一组数的相同类型的元素的集合。
数组的使用如同变量一样,都要先赋值初始化的操作,数组的使用通过下标来访问,比如int a[5]={1,2,3,4,5},我们先创建了一个类型为整型的数组,a表示数组名,[]内的数字为一个常量,注意不能是变量。
此时[]内是5,表示我们的数组的长度为5,就相当于一次性创建了5个整型变量。我们在创建的同时对它进行了初始化的操作,这点和变量一样。然后说到使用通过下标来访问,怎么访问呢?数组的下标是从0开始,所以a[0]就表示第一个元素,里面的值是1,以此类推,变量能做什么,我们的数组元素就都能做什么,一个个的数组元素,就相当于一个个的变量 。我们在初始C语言中,我们只要知道数组是什么,怎么使用就可以了,如同函数一样,关于数组的系统知识介绍,我们会在C语言初阶系列中来进一步讲解。
(未完待续,持续更新中,麻烦换您一个免费的赞鸭!您的点赞就是对我最大的动力(✪▽✪))