C语言从入门到熟悉------第一阶段

本文介绍

本文创作参考如下图书:

本书作者:吴明杰、曹宇、吴丹。

ISBN:9787111553076

计算机语言发展的三个阶段

先来看计算机语言的发展主要分为如下图三个阶段。

C语言的优点

C语言的优点有三个:

1)代码量小。

2)运行速度快。

3)功能强大。

我们先看第一个优点,C语言的代码量很小,这是什么意思呢?也就是说如果你要完成同样一个功能,用C语言编写出来的程序的容量是很小的,而用其他语言编写容量就会比较大。比如Microsoft Word和WPS,它们同样都是办公软件,功能也差不多,但WPS的安装文件只有50MB左右,而Microsoft Word的安装文件超过500MB,有的甚至接近1GB。原因就是WPS的内核是用C语言编写的。

C语言的第二和第三个优点是它的运行速度比较快和功能强大,我们可以通过一个例子来解释。世界上总共有三大操作系统:UNIX操作系统是用纯C语言编写的;Windows操作系统的内核也是用C语言编写的;Linux操作系统仍是用纯C语言编写的。

这三大操作系统的内核全部都是用C语言编写的。为什么用C编写,因为C语言的速度最快。计算机中所有的软件都是在操作系统上运行的,如果操作系统的速度很慢,那么在它基础上运行的软件的速度就更慢了。而且计算机中的所有硬件都是由操作系统控制的。举个简单的例子,你可以新建一个Word文档,这就意味着在硬盘上产生了这个文件。但是你并没有直接控制硬盘,我们是通过操作系统提供的“右键单击”等功能来新建文件的。也就是说,操作系统给我们提供了一个界面,或者说是给我们提供了一种可操作的手段,通过这个手段我们可以删除文件、新建文件、打开文件或者修改文件。

所以操作系统很重要,所有的硬件全部都是由操作系统控制的。而这么重要的东西只能用C语言来写,所以C语言很重要。为什么只能用C语言写?第一,C语言速度够快;第二,C语言可以访问硬件,所以它够强大。为什么不能用Java写?首先,Java太慢了;其次,Java语言没有指针。没有指针就不能访问硬件,所以它的功能是不够的。

C的应用领域

C语言的应用领域分两大块:系统软件开发和应用软件开发。其中C语言最主要用于编写系统软件,编写应用软件不是它的强项。

1.系统软件开发

1)操作系统:UNIX、Windows、Linux。

2)驱动程序:比如主板驱动、显卡驱动、摄像头驱动。驱动一般是用C语言和汇编语言写的,C++在这方面稍弱。

3)数据库:SQL Server、Oracle、MySQL、DB2。

2.应用软件开发

1)办公软件:WPS。

2)图形图像多媒体:Photoshop、Mediaplayer。

3)嵌入式软件开发:嵌入式软件开发说得简单点就是芯片编程,比如我们以后学习在单片机和ARM上进行的开发都属于嵌入式软件开发。

4)游戏开发:2D、3D游戏。CS整个游戏的引擎全部是用纯C写的。

预备知识

学习C语言之前我们要先学习预备知识。

CPU、内存、硬盘、显卡、主板、显示器之间的关系:

它们之间的关系可以通过一个例子来说明。比如说打开一部电影,以下说明这部电影是怎么运行起来的。

首先要双击这部电影,这个“双击”是操作系统提供的一个操作。电影本身是放在硬盘上的,当我们通过鼠标对它进行双击之后,操作系统就会将硬盘上的这部电影拷贝到内存中。为什么要将它拷贝到内存中呢?因为CPU不能直接处理硬盘上的数据。所以要先将硬盘上的数据拷贝到内存中,然后再通过CPU处理内存里面的这部电影。处理的结果就是将一些数据变成图像、另一些数据变成声音。图像数据发送给显卡,通过显示器显示出来;声音数据发送给声卡,声卡将它变成声音放出来。这基本上就是一部电影的运行过程。

CPU为什么不能直接操作硬盘却能直接操作内存:

原因很简单,因为内存的速度比硬盘快很多。CPU的运行速度很快,硬盘的速度无法跟上CPU的速度,所以它们两个在运行的时候无法同步,因此CPU无法直接对硬盘进行操作。

狭义上的内存指的就是我们所说的内存条,但实际上CPU也不是直接操作内存的,因为内存的速度虽然比硬盘快很多,但仍然没有CPU快。所以在内存和CPU之间还有“缓存”,比如一级缓存、二级缓存。缓存的速度比内存更快,更加接近CPU的速度,所以CPU读取数据时操作的都是缓存。

但广义上说,缓存也属于内存,所以我们就说CPU可以直接操作内存。

内存的速度为什么比硬盘的速度快:

内存的速度之所以比硬盘的速度快(不是快一点,而是快很多),是因为它们的存储原理和读取方式不一样。硬盘是机械结构,通过磁头的转动读取数据。一般情况下台式机的硬盘为每分钟7200转,而笔记本的硬盘为每分钟5400转。而内存是没有机械结构的,内存是通过电存取数据的。电的速度当然要比磁头的运动快得多。所以,有机械结构的磁头的读取速度是不能同无机械结构的电的速度相比的。

那么内存的本质是什么呢?我们应该都听说过RAM存储器,它是一种半导体存储器件。RAM是英文单词Random的缩写,即“随机”的意思。所以RAM存储器也称为“随机存储器”。那么RAM存储器和内存有什么关系呢?内存就是许多RAM存储器的集合,就是将许多RAM存储器集成在一起的电路板。RAM存储器的优点是存取速度快、读写方便,所以内存的速度当然也就快了。

内存通过电存取数据,本质上就是因为RAM存储器是通过电存储数据的。但也正因为它们是通过电存储数据的,所以一旦断电数据就都丢失了。因此内存只是供数据暂时逗留的空间,而硬盘是永久的,断电后数据也不会消失。

为什么不将内存造得跟硬盘一样大:

原因主要有三个:第一,内存的速度之所以比硬盘快,是因为它们的构造不一样。对于同样的容量,内存的价格太贵。第二,内存只是暂时存储数据的,断电后数据就丢失了,而且它的速度很快,所以不需要那么大的容量。第三个原因与地址总线的数量有关,这个后面介绍指针的时候再讲。

CPU是如何操作内存的:

那么CPU是如何操作内存的?一般是先将内存里面的数据读入CPU中,然后CPU对数据进行处理,处理完了再将结果写回内存,最后内存再将数据写入硬盘。

CPU对内存进行操作通过三根总线:控制总线、地址总线和数据总线。

控制总线是传输控制信号的,比如时钟、复位、中断、读、写等。CPU是从内存中读数据还是向内存中写数据就是通过控制总线控制的。内存中有很多存储单元,数据都存储在这些单元中,每个单元都有一个地址。所以要想得到这些单元中的数据就必须先知道每个单元的地址。地址总线就是传输地址信息的;数据总线顾名思义就是传输单元中的数据的。

主板的作用:

主板在计算机中起着重要作用。它作为计算机的核心配件之一,是计算机最重要的平台。那这个平台是干什么用的呢?台式机上所有的设备都是连在主机上的,而主机上有很多接口,这些接口全部都是连在主板上的。鼠标、键盘、耳机、显示屏等外部设备都是通过这些接口连到主板上的。在主机内部,电源、风扇、硬盘、光驱也都是连在主板上的。而CPU、内存条、显卡、声卡、网卡这些硬件则全部都是直接插在主板上的。

所以计算机中所有的硬件要么是连在主板上的,要么是直接插在主板上的,但不管怎么样都是与主板相连的。而且主板上有很多线路,所有硬件之间的信号传输也都是通过主板进行的,所有硬件通过主板被有效组织起来。

此外,主板还能接收电源提供的电能并加以分配,从而给各硬件供电。而且还能接收电源开关和操作系统发来的开机信号,从而实现开机、关机、待机、重启和休眠等操作。

HelloWorld程序是如何运行起来的:

    #include <stdio.h>

    int main(void)
    {
        printf("HelloWorld! \n");

        return 0;
    }

通过编译和链接这两个步骤会产生一个.exe可执行文件。这个可执行文件是由VC++这个软件生成的。当单击“执行”或按Ctrl+F5时,执行的就是这个.exe文件。但这个文件并不是由VC++执行的,而是由CPU执行的。当单击“执行”或按Ctrl+F5时,VC++就会向操作系统发出请求,让操作系统执行这个.exe文件。而当操作系统收到VC++的请求时,它就会调用CPU,让CPU来执行。执行的结果就是在显示器输出"HelloWorld! "。这就是这个程序的执行过程。(VC++在现在可能已经不大常用了,但是作为举例来说明还是可以的。)

如果没有操作系统,所有的软件都是不能运行的。所以不要以为VC++可以解决任何问题。它的所有操作也都要靠底层操作系统的支持,并最终靠CPU来执行。因为只有操作系统才能控制硬件,所有的软件都不能直接访问硬件。

字节:

字节是存储数据的基本单位,并且是硬件所能访问的最小单位。字节是存储数据的基本单位,位是存储数据的最小单位,不要混淆了。

内存里面有很多“小格子”,每个“格子”中只能存放一个0或1。一个“小格子”就是一位,所以“位”要么是0,要么是1,不可能有比位更小的单位。

一字节等于8位。

那么为什么硬件所能访问的最小单位是字节,而不是位呢?因为硬件是通过地址总线访问内存的,而地址是以字节为单位进行分配的,所以地址总线只能精确到字节。那如何控制到它的某一位呢?这个只能通过“位运算符”,即通过软件的方式来控制。

字节换算:

常见的存储单位主要有bit(位)、B(字节)、KB(千字节)、MB(兆字节)、GB(千兆字节)。

1B=8bit;1KB=1024B;1MB=1024KB;1GB=1024MB;

其中B是Byte的缩写。

最后讲一个小常识。比如你买了一个500GB的硬盘,但是真正显示出来的肯定没有500GB。那么这是为什么呢?因为计量单位不一样!在买硬盘或U盘的时候,卖家所说的,包括包装盒上写的都是以1000为单位的,而计算机是以1024为单位的。

到这里预备知识就讲解完了。

数据类型

C语言中“常用数据”的分类如下图所示。其他不常用或不常见的就不列出来了,比如“共用体”和“枚举”。“共用体”早就被淘汰,所以我们不讲。“枚举”用得也不多,几乎用不到。

数学中我们称小数叫实数,而在C语言中换了一个名字,叫浮点数,但意思与实数是一样的。

为什么要这么麻烦分成short int、int和long int呢?全部放在long int里面不就行了吗?这个涉及内存使用的问题。内存和硬盘相比容量是很小的。所以在编程的时候一定要考虑“内存节约”的问题。比如存储数字10只需要用2字节的short int就够了,而如果用8字节的long int就会很浪费。所以C语言就划分了多种长度的数据类型,使用原则就是节约内存。

对于long int和short int,可以省略int而直接写成long和short,系统自动识别为long int和short int。

int、short、long按正负又可分为有符号型(signed)和无符号型(unsigned)。有符号型表示定义的变量既可以存放正整数,也可以存放负整数。无符号型表示定义的变量只能存放正整数,不能存放负整数。通常定义的整型变量默认是有符号型的,所以signed可以省略。但是如果要将变量定义为无符号型的,那么就必须要加上unsigned。

unsigned int也可以省略int而直接写成unsigned。

基本数据类型及其所占的字节数

因为不同的操作系统为同一数据类型分配的字节数是不同的,这主要取决于操作系统是多少位的。有人说int是2字节的,有人说int是4字节的,那么int到底是多少字节呢?在C语言中有一个关键字叫sizeof,它的用法是:

    sizeof (对象)

它的功能是“求出对象在计算机内存中所占的字节数”。对象可以是定义好的变量名、数组名或直接是数据类型。如果是变量名或数组名,那么可以不加括号,但与sizeof之间必须要用空格隔开;如果是数据类型,那么必须用括号括起来。

这里需要注意的是,sizeof是C语言里面的关键字,不是函数,所以也没有头文件。

一般计算机分为32位64位。  32位  -   x86    64位   -   x64

目前的计算机几乎都是64位的,所以我做了测试,占用字节数如下:

本人电脑是64位的。

拓展:此外还有一种数据类型就是long long int,int也可以省略。这个测试后是占用8个字节的。

常量

所谓常量就是程序在运行时不会被修改的量。说得通俗点就是数学中所说的“常数”。在C语言中,基本类型数据分为三类:整数、浮点数和字符,它们实际上都是常量,分别称为整型常量、浮点型常量和字符型常量。

整型常量

下面写一个程序:

#include<stdio.h>

int main(void) {
	int i = 10;

	printf("%d\n", i);

	return 0;
}

变量i定义的是整型,10是整型常量,程序中将整型常量赋给整型变量,合情合理,所以该程序编译、链接、执行都是正确的。

但是如果将10改成10.6会怎么样?原理上是错的,但编译的时候不会报错,只会警告。继续单击链接、执行,这时会发现,虽然程序中写的是10.6,但输出的还是10。这是因为当将一个小数赋给一个整型变量时,程序只会将整数部分赋给整型变量,小数部分会被舍弃。

浮点型常量

浮点型常量又叫浮点数,就是数学中的实数,即带小数点的数。

在C语言中浮点数有两种表示方法,一种是传统的写法,如3.1415926;另一种是科学计数法,如2.856e3。e后面的3表示指数,2.856e3就表示2.856×10的3次方。e也可以写成大写E,两个是一样的。但需要注意的是,字母e(或E)之前必须要有数字,且e(或E)后面的指数必须为整数,如e36、2.1e3.5等都是不合法的形式。此外,如果你要表示0.01,你可以写成1e-2,千万不要写成1e(-2),不要加括号,直接写就行了。在C编程中,科学计数法的表示方法一般不用,但是你要了解。

注意:在C语言中,对于所有的实数,不管是用什么形式的写法,传统的写法也好,科学计数法也好,默认的都是double类型的。加F(或f)就表示将这个数当成float来处理。加F(或f)是给float型变量赋值最标准的写法,这种写法在所有编译器中都是能通过的。

float i=1e-2F 

如果输出浮点数时printf中不小心将%f写成了%d,那结果会怎么样?如果这样的话,那么不管浮点数是多少,输出的都是0。同样,如果输出整数时在printf中不小心将%d写成%f,那么不管整数是多少,输出的都是0.000000。那么这是为什么呢?这个涉及浮点数在内存中的存储,稍后会介绍整数在内存中的存储。整数和浮点数在内存中的存储方式是不同的。浮点数在内存中的存储方式很复杂,而且一般用不到,所以不讲。

字符型常量

字符型常量是用单撇号括起来的一个字符。还有一种比较特殊的字符型常量,就是以一个字符 '\' 开始的字符序列,又叫“转义字符。

在C语言中,定义存储字符常量的变量要用char,且输入字符常量时printf中要写%c或%C。

在C语言中没有任何一种数据类型可以直接存储字符串,因为字符串不属于数据类型。

常量是以什么样的二进制代码存储在计算机中的

在计算机中不管什么数据都是以二进制的形式存储的,因为计算机只认识“0”和“1”。只不过不同类型的数据存储在计算机中时转化为二进制的规则不一样,这个问题实际上就是编码的问题。比如说:

int i = 86;

该语句的意思是直接将十进制数86放到变量i中吗?不是,而是将86的二进制代码放进去。那么它到底是以什么形式的二进制代码放进去的呢?整数是以补码的形式转化为二进制代码存储在计算机中的。

而实数是以IEEE 754标准转化为二进制代码存储在计算机中的。我们在前面说过,浮点数的存储比整数的存储要复杂得多。

字符的存储方式本质上与整数的存储方式相同。如字符'A',它是先通过ASCII码转化为一个十进制整数,然后就同十进制整数的存储一样了。

补码

补码很简单,但是很重要。

原码和反码

讲述补码之前首先要了解两个预备知识:原码和反码。

原码也叫“符号绝对值”码,最高位0表示正,1表示负,其余二进制数是该数字绝对值的二进制数。

正整数的原码就是它本身的二进制数,比如5的原码就是0101。那么-5的原码呢?负号用1表示,-5的绝对值是5,5的二进制是0101,所以-5的原码就是10101。

计算机中所有的整数都是以补码的形式存储的。原码虽然简单、易懂,但因为存在加、减、乘、除四种运算,且加减运算复杂,使用原码增加了CPU的复杂度,使CPU设计起来比较困难。而且如果用原码的话,0的表示不唯一,可以是正0,也可以是负0。

而补码不一样,使用补码则减法、乘法都可以用加法进行处理,所以用补码比用原码好。

原码在计算机里面从来没有被使用过。

而反码就是所有位都取反,1变成0,0变成1。反码的运算也很不方便,也没有在计算机中用过。

所以对于原码和反码大家不用深究,只需要知道什么是原码,什么是反码就行了,因为学习补码的时候要用到这两个知识点。

补码

补码主要是用来解决整数的存储,包括0、正整数和负整数。

关于补码主要有两个问题,将这两个问题弄清楚了,补码自然而然就掌握了:一是如何将一个十进制数转换成它的二进制补码;二是如何将一个二进制补码转换成对应的十进制数。

十进制数转换为二进制补码:

1.正整数的补码与原码相同。

2.负整数补码求法:先求该负数绝对值的二进制数,然后将所有位取反,末位加1,不够位数时左边补1。

比如int型的-3,因为-3的绝对值是3, int型占32位,所以int型3的二进制代码为:

0000 0000 0000 0000 0000 0000 0000 0011

然后所有位取反,就是:

1111 1111 1111 1111 1111 1111 1111 1100

最后末位加1就是:

1111 1111 1111 1111 1111 1111 1111 1101

这就是int型-3在计算机中存储的二进制形式。

零的补码是唯一的,全是0。

二进制补码转换为十进制数:

如果最高位是0,则表明是正整数,原码和补码相同。比如补码为:

00000000000000000000000000001111

最高位是0,表明这个数是正整数。则原码和补码相同,所以直接转换成十进制数为15。

如果最高位是1,则表明是负整数,则原码为:将所有位取反(不管是符号位还是有效数字位),然后末位加1。此时得到的是无符号型的二进制数,即所有位数都是数字位,没有符号位。所以算出来的肯定是一个正整数,这个正整数的相反数就是该补码对应的十进制数。

比如补码:

11111111111111111111111111111000

最高位是1,表明这个数是负整数。则将包括最高符号位在内的所有位全部取反得:

00000000000000000000000000000111

然后末位加1得:

00000000000000000000000000001000

此时就得到了一个正整数,直接转换成十进制数为8,所以补码所对应的十进制数为-8。

又比如补码:

10000000000000000000000000000000

最高位是1,表明这个数是负整数。则将包括最高位符号位在内的所有位全部取反得:01111111111111111111111111111111

然后末位加1得:

10000000000000000000000000000000

此时虽然最高位是1但它表示的不是符号位,而是数字位。该二进制代码所表示的十进制数为2的31次方,即2147483648。所以补码所对应的十进制数就是该数的相反数,即-2147483648。

如果全是0,则对应的十进制数就是0。

int型变量所能存储的范围

int型变量占4字节,就是32位,所以它所能存储的最大的正整数是01111111111111111111111111111111,即2147483647。注意,最高位是符号位,正数用0表示。那么为什么最大整数是01111111111111111111111111111111?再加1会怎样?我们来试一下,二进制逢二进一,再加1就变成10000000000000000000000000000000。最左边是1,就变成负数了,所以不能再大了。这个负数的值是-2147483648,它是int型变量所能存储的最小的负整数。

我们前面讲过short类型变量占两字节,就是16位,它所能存储的最大的正整数是0111111111111111,把它转换成十进制数就是32767。如果再加1就会变成1000000000000000,最左边是1,就变成负数了。这个负数的值是-32768,大家可以自己算一下,它是short类型变量所能存储的最小的负数。

如果是unsigned short,那么就没有符号位,所有位都是数字位,它所能存储的最大的数是1111111111111111,即65535,所能存储的最小的数就是全零,即0。

int型和char型变量是如何相互赋值的

下面直接写一个程序:

#include<stdio.h>

int main(void) {
	int i = 128;
	char ch = i;

	printf("%d\n", ch);

	return 0;
}

大家想想输出的结果会是几?char类型占1字节,就是8位,它最大所能存储的正整数是01111111,即127。现在将int型的整数128赋予它,它放不下,这个叫“溢出”。那么溢出的话输出会是多少呢?128是int型,占4字节,它的二进制代码为00000000000000000000000010000000。但是现在要将它赋给一个只有8位的char型变量,所以只能将低8位的10000000放进去,其他的都会被截掉。整数在计算机中都是以补码的形式存储的,此时10000000在“计算机的眼里”是一个补码。最左边是1表示是负数。补码10000000所对应的十进制数是-128,所以最后输出的就是-128。因此溢出会使最大正整数变成最小负整数。

运行结果图:

不同数据类型之间最好不要相互赋值转换。需要存储什么类型的数据就定义什么类型的变量。

什么是ASCII

这个非常重要,先写一个程序:

#include<stdio.h>

int main(void) {
	char ch = ' A' ;
	printf("ch = %c\n", ch);

	return 0;
}

这个是将字符 'A' 输出了,但是如果将printf中的%c改成%d会怎样?编译一下试试,它不会报错。想想为什么不会报错?因为不管是什么类型的数据,在内存中都是二进制的,所以不会报错。%c和%d只是输出时显示的方式不一样而已。

当将%c改成%d并链接、执行后输出的就不是字符'A',而是一个数字“65”。这说明字符 'A' 是以65这个十进制数对应的二进制代码存储的。

ASCII就是规定了某个字符使用哪个整数保存。因为计算机中保存的都是二进制代码,所以不可能将一个字母直接保存到计算机中。它要先转化为二进制代码才能保存进去。而每个二进制代码都对应一个十进制数,这就是ASCII。

ASCII不仅是一个值,更是一种规定。它规定了每个字符使用哪个整数表示。比如它规定了'0'用48表示、' A' 用65表示,' a' 用97表示……为了便于表述,通常也称这些值为ASCII值。那么ASCII值的范围是多少呢?一个字符占一字节,而一字节所能表示的十进制数范围为0~255,所以ASCII值的范围就是0~255。其中常用的是0~127,剩下的128~255称为扩展ASCII。扩展ASCII我们一般不用,知道就行了。

此外除了ASCII这种规定外还有GB2321码、UTF-8码等,它们都是规定一个字符用哪个整数表示。

变量

定义变量的格式非常简单,如下所示:

    数据类型   变量名;

定义的同时赋值: 

    数据类型 变量名 = 要赋的值;

先定义后面再赋值:

    变量名 = 要赋的值;

在定义变量时也可以一次性定义多个变量,比如:

    int i, j;

 同样也可以在定义多个变量的同时给它们赋值:

    int i = 3, j = 4;

变量只能在程序的开头定义,或者说变量定义的前面不能有其他非声明或非定义的语句。比如在C语言中像下面这种写法就是错误的:

#include<stdio.h>

int main(void) {
	int i;

	i = 3;

	int j = 4;  //这句是错误的,因为在它前面有一个给变量i赋值的非定义语句

	return 0;
}

其实这与语言本身并没有什么关系,并不是说C语言中变量就不能定义在程序中间。这实际上是与标准有关系的,有些编译器使用的是C89标准,C89标准要求所有声明(比如函数声明、变量定义)必须要写在程序、函数或复合语句的开头,而C99的新特性允许语句和声明按任意顺序排列,只要遵循“先声明后使用”的原则就行。

但是尽管如此,大家在编程的时候还是尽量按照C89的标准编写,即使在完美支持C99标准的编译器中也最好不要将声明写在程序的中间。因为C89历时较长,现在它仍然是主流,很多编译器仍然只支持C89标准或不能很好地支持C99标准。所以按C89标准编写可移植性更强。

上面提到“复合语句”,在复合语句中也可以定义变量。所谓语句就是以分号结尾的,而复合语句就是多个语句组合在一起的语句。在C语言中,用大括号“{}”括起来的多个语句称为复合语句。复合语句在流程控制中用得特别多,比如后面将要学习的if、for、while一般都是跟复合语句的。此时在这些复合语句中也可以定义变量,但是也只能定义在它的开头。主函数main下面也是用大括号括起来的,所以主函数main下的函数体本质上也是一个复合语句,是一个大的复合语句。所以它们还是有共性的,也就是说,在C语言中,只要是用大括号“{}”括起来的,那么在它的开头都可以定义变量。而且这些在大括号中定义的变量都称为局部变量。此外,在大括号外面也可以定义变量,叫全局变量。

变量的本质

那么到底什么是变量?看看下面这个程序:

#include<stdio.h>

int main(void) {
	int i;

	i = 3;

	printf("i = %d\n", i);

	return 0;
}

“int i; ”表示定义了一个变量i。那么程序在运行时定义的变量i存储在什么地方呢?存储在内存还是硬盘上?答案是存储在内存。当单击编译、链接、执行后弹出一个黑色窗口,关闭那个黑色窗口后,就意味着这个程序运行了一遍,而且这一遍已经终止了。此时存储3的内存空间就没有了,要是有的话就麻烦了!如果任何软件运行一遍后为它分配的空间仍保留着,那么计算机再运行几次就不行了。程序运行完之后,为这个程序所分配的所有内存空间通通都会被释放掉,以便被下一个程序使用。

所谓“释放”并不是指清空该内存空间,而是指将该内存空间标记为“可用”状态,使得系统在分配内存的时候可以将它重新分配给其他变量使用。

首先看程序是如何运行“int i; ”的:当VC++(拿VC++来举例,虽然现在可能已经不常用了)运行第一条语句的时候,它会请求操作系统在内存中寻找一个空闲的存储单元,然后把它当作变量i来使用。也就是说这个存储单元的地址和i产生了一种关联。即变量i现在就是这个存储单元,这个存储单元现在就是变量i。然后“i=3; ”的结果是把3存放到变量i所关联的那个存储单元中。前两条语句就是这么运行的。以后只要使用i,操作系统就会自动找到那个与它关联的存储单元。

为什么要使用变量

为什么要使用变量?如果不使用变量的话,那就意味着我们要自己编程寻找内存里面的那个存储单元。

变量的命名规则

变量名的开头必须是字母或下划线,不能是数字。中间和结尾可以有数字,唯独开头不能有数字。而以下划线开头的变量名是系统专用的。随便打开一个头文件就会看到,它里面所有的变量名、宏名、函数名全是以下划线开始的。所以为了避免与系统定义的名字产生冲突,在编程的时候,除非要求这么定义,否则永远都不要使用下划线作为一个变量名的开头。

在定义变量的时候,变量名最好要有明确的含义,比如表示“个数”的count可以缩写成cnt;较长的单词可取单词中的几个字母形成缩写;对于一些单词还有大家公认的缩写,比如:

temp可缩写为tmp; //temp是“临时”的意思

flag可缩写为flg; //flag是“标志位”的意思

statistic可缩写为stat ; //statistic是“统计”的意思

increment可缩写为inc;

message可缩写为msg;

如果变量名由多个单词组成而且不用缩写,那么每个单词的首字母全部都要用大写,必要时可用下划线分隔。

变量名绝对不可以是关键字,这一点一定要记住!

变量名中不能有空格。这个可以这样理解:因为我们说过,变量名是字母、数字、下划线的组合,没有空格这一项。

为什么必须要初始化变量

所谓初始化就是“第一次赋值”的意思。

我们先来看一个程序:

#include<stdio.h>

int main(void) {
	int i;

	printf("i = %d\n", i);

	return 0;
}

在上面程序中没有给这个内存单元赋值就把它给输出了,我们来看看里面放的是什么值。

首先,内存条是一个硬件设备,硬件设备里面要么是0要么是1。计算机肯定得通电,没有电就运行不起来,有电之后它就会通过各种二极管、三极管之类的元器件产生高低电平。如果电压在某一个范围之上,就认为是1,在某一个范围之下,就认为是0。所以1就表示高电平,0就表示低电平,即电压的高或低产生1或0。这时候只有两种状态,要么是1,要么是0。所以说i里面存放的就是由0和1组成的代码,不可能里面什么都没有!

这里i单元虽然没有给它赋值,但这个空间在之前可能被其他软件使用过。比如放电影的时候将电影中的某一部分数据放到了这个单元中。电影看完之后一关闭,这个单元空间就释放了,即操作系统就会回收该内存空间,以便分配给其他软件使用。但需要注意的是,操作系统虽然回收了该内存空间,但它并不清空该内存空间中遗留下来的数据,这句话很重要。但这样的话就会出现一个问题:以前的软件如果使用过i这个空间的话,那么那些垃圾数据还在里面保留着,如果没有初始化,那么它里面就应该是一个随机的垃圾值。但是当我们多次执行的时候发现它不是一个随机的值,而是一个不变的值。这是为什么呢?这个值叫“填充数字”或叫“填充字”。微软公司在设计VC++这个软件的时候,在它内部添加了一些特殊的处理。即如果发现一个变量里面存放的是一个垃圾值,就认为没有给它赋初值,那么系统就会自动将一个很小的,如-858993460这个填充数字给放进去。所以我们看到的结果都是一个不变的值。

上面只是VC++的处理方式,现在有了新的规则:

对于全局变量和静态变量(在函数内部定义的静态变量和在函数外部定义的全局变量),它们的默认初始值为零。变量会按照如下规则默认赋值:

  • 整型变量(int、short、long等):默认值为0。
  • 浮点型变量(float、double等):默认值为0.0。
  • 字符型变量(char):默认值为'\0',即空字符。
  • 指针变量:默认值为NULL,表示指针不指向任何有效的内存地址。

需要注意的是,局部变量(在函数内部定义的非静态变量)不会自动初始化为默认值,它们的初始值是未定义的(包含垃圾值)。因此,在使用局部变量之前,应该显式地为其赋予一个初始值。

总之,我们要在定义变量地时候习惯性的初始化,但是,很多在开头定义但到后面才会用到的变量,在定义的时候如何对它们进行初始化呢?此时习惯上将它们初始化为0,然后等后面真正用到它们的时候再重新赋值。 

各类型数据之间的混合运算

首先变量的数据类型是可以转换的。转换的方法有两种,一种是自动转换,另一种是强制转换。

自动转换即当不同类型的数据进行混合运算时,编译系统将按照一定的规则自动完成。而强制类型转换是由程序员通过编程强制转换数据的类型。

自动转换的规则如下:

当参与运算的数据的类型不同时,编译系统会自动先将它们转换成同一类型,然后再进行运算。

转换的基本规则是“按数据长度增加的方向进行转换”,以保证精度不降低。

比如int型数据和long型数据进行相加或相减运算时,系统会先将int型数据转换成long型,然后再进行运算。这样的话运算结果的精度就不会降低。long是“大水桶”, int是“小水桶”。int能存放的,long肯定能存放;而long能存放的,int不一定能存放。

所有的浮点运算都是以双精度进行的。在运算时,程序中所有的float型数据全部都会先转换成double型。即使只有一个float型数据,也会先转换成double型,然后再进行运算。为什么要这样呢?因为CPU在运算的时候有“字节对齐”的要求,这样运算的速度是最快的。这个现在先不管,如果以后有机会学习汇编的话你就知道原因了。

char型和short型数据参与运算时,必须先转换成int型。

有符号整型和无符号整型混合运算时,有符号型要转换成无符号型,运算的结果是无符号的。这条规则经常使人纠结,可以写一个程序看一下。

#include<stdio.h>

int main(void) {
	int a = -10;

	unsigned b = 5;

	if ((a+b) > 0) {
		printf("Hello\n");  //会打印Hello,是不是很奇怪? 
	}

	return 0;
}

程序的意思是分别定义一个有符号整型a和无符号整型b。然后分别将-10和5赋给a和b,如果a+b的值大于0就输出Hello。理论上讲-10+5的值为-5,所以不可能输出Hello。但是我们从输出的结果可以看出,Hello被输出了。这说明a+b的值是正数,即是无符号的。这就是这个规则。

在赋值运算中,当赋值号两边的数据类型不同时,右边的类型会转换为左边的类型,然后再赋给左边。如果右边数据类型的长度比左边长,那么将会丢失数据,这样就会降低精度,所以编译的时候会产生警告。

  • 29
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值