C-premier-plus-Chapter-3

第三章 数据处理

你会在这一章学到下面的东西:

  1. 关键字
    `int,short,long,unsigned,char,float,double,_Bool,_Complex
  2. 运算符
    sizeof
  3. 函数
    scanf()
  4. 整型与浮点型的区别
  5. 书写常量与声明这些类型的变量
  6. 如何使用printf()scanf()函数来读写不同类型的值

程序依靠数据来工作。你会向电脑输入数字、字母或者单词,然后期望它能够利用这些数据做些事情。例如,你可能想要电脑去计算支付利润或者展示一个排过序的酒商列表。在这一章,你将会不只阅读数据,而且练习着去操纵数据,那是挺有趣的一件事。

这一章还会让你了解到两大类数据类型:整数与浮点数。C语言提供了这些类型的几种变体。这一章会告诉你类型是什么,如何声明它们并且如何利用它们。而且,你会发现变量与常量之间的区别,作为额外奖励,你的第一个交互式的小程序就要诞生了!

3.1 一个例子

再一次,我们还是从一个样例程序来开始,像以往一样,你会发现一些不熟悉的东西,我们待会就会为你解释。程序的总体目的应该清晰,那么就尝试把图3.1中的源码编译运行下吧。
为了节省时间,你可以省略注释。

图3.1

错误与警告
【错误与警告】
如果你把程序的某些部分打错了,还有,比如说,遗漏了一个分号,编译器就会给予你一条语法错误信息。即使你没打错,编译器也有可能给予你一条警告信息,“Warning-conversion from ‘double’ to ‘float’ , possible loss of data.” 错误信息指的是你有可能把某些事做错了,它阻止程序的编译。警告,意思是你写的代码是合法的但是有可能做的并不是你想的那样。警告并不会阻止编译过程。上文中的这条信息就会在C语言在处理像770.0这样的数时出现,但并不是这个例子的问题,这一章过会会解释这条警告。

当你在打字写这个程序的时候,你有可能会把770.0改成这种珍贵金属的当前价格。但是不要改变14.5833,那代表着一磅对应的盎司数。(盎司金衡制,是对于贵重金属使用的,常规衡制是给人们使用的,不论是珍贵的物品或是其他的东西。)

需要注意的是“输入”你的体重意味着把你的体重打字写入电脑,并且敲下Enter或者Return键(不要输入完了你的体重就等着。)按下Enter键这个动作告诉了电脑你已经完成了你的应答。这个程序想要你输入一个数字,如150而不是单词,比如too much,如果你写的是字母而不是数字,那是会导致问题的,而这个问题需要之后学习的if语句来解决。(第七章,“C语言控制语句:分支结构与跳跃结构”会讲到),所以请先平静一些,输入一个数字,这里就是一个简单的输出示例。

输出

3.1.1 例子中的新知识

在这个C语言程序中有这么几个新接触的要素。

  • 可以注意到的是,代码在变量声明中使用了新的数据类型,前面提到的例子只使用了十进制整数类型(int),但是现在的这个例子使用了一个浮点类型的变量类型,这样的话你就可以处理更大范围的数据了。float类型就可以保存含有十进制小数点的数字。
  • 这个程序给出了书写常量的一些更新的方式,你现在可以书写浮点型的常量了。
  • 为了对程序提供输入信息,你需要使用scanf()函数,%f命令scanf()从键盘输入中读入一个浮点数,&weight告知了scanf()函数把输入的值赋给名字叫做weight的变量,scanf()函数会用&符号来寻找在哪里可以找到叫做weight的变量,下一章我们会进一步地阐述&符号,现在,只需要相信我们你需要在这里添加一个&符号。
  • 可能最突出的新特性就是程序变为交互性的了。电脑会要求你输入信息,并使用你输入的数值。一个交互性的程序是比不能交互的更有趣的。更重要的是,交互的方法让程序更加的灵活。举个例子,样例程序就可以用于所有合理的体重了,而不只是150磅,你不需要换一个人就再把程序重写一遍。scanf()函数会从键盘中获取数据,并把数据传递到你的程序,printf()函数会从程序中获取数据,然后把数据传递到你的屏幕上。这两个函数在一起就可以让你建立电脑与你的双向联系(见图3,1),这就让电脑使用更加有趣了。

我们会在这一章讲述这些新特点中的前两条,后面的三条内容会在第四章-字符串与格式化输入输出完整地讲到,但是这一章还是会少量地使用print()scanf()函数。

输入输出函数的工作

3.2 变常类型

一台电脑,在程序的指导之下,可以做到很多的事情。可以做加法,可以给名称排序,可以指示讲话者或者屏幕的演出顺序,可以计算彗星的轨道,可以编排你的邮寄列表,拨叫电话号码、画出由小棒组成的人物,得出结论或者其他你可以想象到的可以被创造的东西。为了完成这些任务,程序需要处理数据,也就是你使用的保存在程序中的所有数字与字符信息。一些数据类型需要在程序使用之前进行预设,而且保证它们的值是不变的,这些就是常量。其他的数据类型的值或许会随着程序运行改变或者被赋给,这些就是变量。在这个样例程序中,weight就是一个变量,而14.5833就是一个常量。那么770.0呢?虽然铑金属在真实生活中的价格并不是个常量,但是这个程序把它作为一个常量来处理。变量与常量的区别就在值可不可以随着程序运行发生改变上。

3.3 数据类型关键字

讲完了变量与常量的区别之后,我们需要了解一下不同数据类型之间的区别。一些数据是数字形式,一些是字母,或者更加普遍的,它们是字符。电脑需要一种方式来确定与使用这些不同的类型。C语言通过对于基本的数据类型的定义完成了这个工作。如果数据是一个常量,编译器通常是可以通过它看起来的样子来区分它的。42是一个十进制整数,42.100是一个浮点数,在另一面,变量就需要声明语句来声明它的类型。随着学习进行下去,你会了解到关于声明变量的更多细节。但是还是让我们首先先研究一下C语言可以识别的基本类型,K&R C确立了有关于类型的七个关键字,C90标准增加了两个,C99标准又增加了三个。(见表3.1)
表3.1

int关键字提供了C语言中基本的十进制整数类型,下面的三个关键字(long, short, unsigned)还有ANSI添加的signed提供了基本类型的变体。接着,char关键字指明了用于字母与其他字符的类型,如%, $, #等,char类型还可以被用来代表大于0与小于10的十进制整数。然后是floatdouble,还有两者的混合long double是用来代表带有十进制小数点的数字的。_Bool类型是为布尔类型提供的(truefalse),最后是分别代表着复数与虚数的_Complex_Imaginary.

由这些关键字创造的类型又可以基于在电脑中的储存类型分为两种,十进制整数类型浮点数类型

Bits, Bytes and Words
【Bit(位)、字节与字】

bit字节可以被用来描述电脑的数据单元或者内存单元。我们会集中于第二种用法。

内存的最小单元叫做bit,它可以保存01之间的其中一种数值(或者你可以说,这个字节被设为“关”或者“开”)你并不能在一个bit中储存太多的信息,但是电脑可以储存很多的字节,bit就是电脑内存的基础组成模块。

字节是电脑内存的一般单元。对于大多数的机器来说,一个字节是8个bi,那是标准的定义,至少当测量内存的时候(C语言有不同的定义,我们会在“使用字符:char类型”这一节讲到)因为一个bit不是0就是1,那么在8位的字节中,就可以有256种可能(2的8次方),这些可能的模式就可以被用作比如代表0到255的数字或者代表一系列的字符。在二进制代码下,这种一对一的映射是可以被建立的,而且使用0与1来代表数字也使机器便利的。(在第15章-位运算,会讲述二进制代码,但是如果你想的话现在就可以去读一下)。

是电脑固定设计下的内存自然单元,对于8位的微型计算机,比如原来的苹果电脑来说,一个字就只是8个bit。早期的IBM兼容器使用的80286处理器是16位的机器,这也就是说他们把字长增加到了16bit。还有例如以Pentinum为基础的计算机,还有Mactonish PowerPC的字长是32bit。还有更强大的电脑的字长是64位的或者更大。

3.3.1 整型与浮点型

整型?浮点型?如果你感觉这些词非常不熟悉以至于干扰了你的阅读进度,我们这就要简要描述它们的意义了。如果你对位、字节还有字还是不熟悉的话,你可能需要阅读一下最近描述过的附加栏。你真的需要了解所有的细节吗?并不是,不只了解内燃机驱动车子的工作原理,而多了解一些电脑或者引擎内部发生的事情有时候可以帮到你。

对于一个人来说,整型与浮点型的区别体现在它们是怎么被写下的。对于电脑来说,那就体现在它们是怎么被储存的。让我们依次来研究一下这两种类型。

3.3.2 整数类型

整型是没有小数部分的数字。在C语言中,整型是没有小数点的数字。比如说-2、-23还有2456。像3.14, 0.22, 还有2.00这些数就不是整型。整型是以二进制数字的形式被储存起来的。比如7就是被储存为二进制数111的。因此,为了在8位字节中储存这个数字,会把前五个位设置为0,然后把后三个位设置为1。

用二进制码储存7

3.3.3 浮点数类型

浮点数多多少少是对应着数学概念中的实数,实数包含着整数之间的这些数字,浮点数基本是这些样子:2.75、3.16E7、7.00、2e-8,需要注意的是,只要有小数点就会把整型数字转换为浮点数,比如7.00是一个浮点数,而7就是一个十进制整数。很明显,书写浮点数的方式不止一种,我们会在之后更充分地讨论用e符号表示的形式。但是简单来说,3.16E7的表示方式意思就是3.16乘10的7次方,也就是1后面添7个0,7也就是10的指数

关键就是储存浮点数的方式与整数不一样。浮点数的表示涉及到把一个数字分开为小数部分与整数部分,然后分别储存。因此,7.00这个数字不会与整数7同样储存,即使它们的值都是一样的。十进制类比起来也就是把7.0写为0.7E1。这里,0.7就是小数部分,1就是指数部分,图3.3展示了浮点数储存的另一个例子。一台电脑当然会使用二进制数与二进制而不是十进制来储存。你会在第十五章了解关于这个话题的更多信息。现在我们先集中精力于在实用上区别吧。

  • 整型没有小数部分,浮点数可以有小数部分。
  • 浮点数比整数代表的数字更多,可以看一下这一篇结尾的图表3.3。
  • 对于一些算术运算符,比如一个数减去另一个数,浮点类型的精确度会有更大可能下降。
  • 因为在任何一个范围之间都有着无数的浮点数,比如在1.0与2.0之间,电脑就不能完全地代替这些值,它用实数的大概值来估计浮点数的值。比如7.0有可能被储存为6.99999的float值,过会会讲到更多有关于精确度的知识。
  • 浮点运算符的优先级通常是比整型运算符要低的,然而用来处理浮点运算的特殊微型处理器已经可用了,这个问题也就得到了解决。

十进制浮点储存方式

现在就让我们研究一下C语言中各种数据类型的特点吧。对于每一种类型,我们会描述如何声明一个变量,如何用变量代表一个常量,还有一些特殊的用法。一些年代较早的C语言编译器可能不能支持下文全部的类型,所以你可以查看一下你的编译器的说明书,看一看哪些数据类型是可用的。

基本数据类型

3.4.1 int类型

C语言提供了很多的整数类型,你可能想知道为什么只有一种还不够,答案就是这样C语言就可以让你将特定的变量用在特定的地方。具体来说,C语言的各个整数类型不同在值域的大小与是否含有负数上。int是最基本的整数类型,但是你要是为了满足机器任务的特定需求的话,其他的类型也都是可用的。

int类型是一种有符号的整数类型,有符号意味着int类型的整数可以是正数、也可以是负数,或者是0。int类型的值域是取决于计算机系统的,通常一个int类型的整数的大小就是内存的一个字长。因此,旧版的16位IBMPC适配器,就可以使用16个位来储存整数,也就是从-3276832767。现在的个人电脑一般是使用32位的整数大小,在这张结尾的表3.3可以看到一些个例子。现在个人电脑产业也正在向64位处理器发展,那样也就可以使用更大的整数了。ISO/ANSI C语言特别指出int的最小范围应该是-3276732767,通常系统会专门拿出一个位来保存符号,第十五章描述这种了一般的方法。

3.4.1.1 声明int类型

正像你在第二章看到的,关键字int可以用来声明一个基本的整型变量。声明,首先是int,然后是变量的名称,最后是一个分号。要是声明多个变量的话,可以分别声明,也可以用逗号分隔,把一系列的变量名列出来,下面的声明方法都是合法的。

你可能已经使用过分别声明变量的方法了,或者你已经尝试在一行中声明全部的变量了。效果都是一样的:就是对四个int大小的变量分配空间并与变量名相联系。

这些声明语句创造了变量,但并没有给它们赋值,变量要如何得到值呢?你已经见过两种方法了,第一种是赋值语句

第二,变量也可以使用函数来赋值,比如说scanf函数。
现在我们来了解一下第三种方法吧。

3.4.1.2 变量初始化

对变量进行初始化也就是给变量赋起始值,或者初值。在C语言中,这可以作为声明的一部分,只需要在变量名后加上赋值运算符还有你想要赋给这个变量的值就可以了。下面是一些例子。

在最后一行,只有cats被初始化了,速读一遍,你有可能觉得dogs也会被赋值为94,所以还是最好避免把未赋值与已赋值的变量放在一行之中吧。

简单来说,这些声明会为变量创造空间,并为它们贴上变量类型的标签,而且还可以给赋变量起始值。(见图3.4)

图3.4

3.4.1.3 int型常量

在上一个例子中各种各样的整数(21、32、14、94等),就是整型常量。当你书写一个没有十进制小数点并且没有指数的数时,C就会将这个数认作整型常量。因此,22与-44都是十进制整型常量,但是22.02.2E1就不是。C语言会把大多数整型认作int类型常量。但很大的数会有区别。看最后对于long常量与long long常量的描述就知道了。

3.4.1.4 打印int

你可以使用printf()函数来打印int类型的值,正像你在第二章看到的,%d符号可以在十进制整数被打印出来的地方作为占位符。%d的名称叫格式化符,因为它声明了printf()打印值的类型每一个在格式化字符串中的每一个%d都代表着一个int类型的值,它就在字符串后面的列表中。那个值可以是一个int类型变量代表的,也可以是一个int类型常量代表的,或者任何有int值的表达式。这样,你就有责任让格式化符与之后的列表中的值一致了,编译器并不会报出这种错误。图3.2就表示了一个初始化并且打印变量值,常量的值,还有简单表达式的值的程序,它也提示了你如果你不小心的话会发生什么。

图3.2

编译完运行这个程序的结果是这样的。

第一行输出,第一个%d代表着一个int类型变量ten第二个代表着常量2,第三个呈现的是ten - two的值。但第二行,使用的是ten这个变量代替了第一个%d但是之后的值全都是随机的,是从最近的两个内存空间中获取的值!(你回去的值有可能与这里显示的值相差很多,不仅内容不一样,而且不同的编译器对于这些内存区的管理方法也不一样。)

你有可能对编译器为啥并不会捕捉到这个很明显的错误,还是怪printf()函数的原始设计吧。大多数函数都会获取一个值作为参数,编译器可以检查出来你使用的值是不是恰当,但是printf()可以获取一个、两个、三个或者更多的参数,那就让编译器无法使用常用的错误检测方法了。记住,一定要检查格式化符是不是与被显示的值匹配。

3.4.1.5 八进制与十六进制

通常来说,C语言会认为整型常量是十进制数。但是八进制与十六进制也受到许多编程人员的欢迎。因为8与16都是2的幂,但10并不是,这些进制系统有时会为与计算机相关的值的表达提供便利。例如,数字65536经常会出现在16位的机器中,它只是10000的十六进制数,十六进制数中的每一位数都对应着实际的四个位。例如,十六进制数3就对应着0011,5就对应着0101,所以35就对应着0011 0101,53就对应着0101 0011。这种对应就会使十六进制与二进制的转换非常简单。但是电脑是怎么判断10000是什么进制的数字的呢?在C语言中,数字不同的前缀就显示了不同的进制,0x(0X)就意味着你正在使用十六进制值,所以16在十六进制下会被写作0x10或0X10。相似地,前缀0就表示你正在使用的是八进制值。例如,十进制数16在八进制数中就写为020,在15章我们会更加充分地讨论不同的数制。

要知道的是不同的数制系统是为你提供便利的,它并不会影响数值的储存方式,也就是说,你可以写16、020或者0x10,这个数还是会以二进制的方式储存起来。

3.4.1.6 展示八进制与十六进制数

正像C语言能够让你以三种方式书写数字一样,它也可以让你以三种方式的任意一种展示数字。展示一个八进制整数可以使用%o,十六进制使用%x,如果你想显示前缀,你可以使用修饰符#,比如%#o%#x%#X,有了它们你就可以在数字前面显示0,0x,0X前缀。图3.3给出了一个小小的例子(想到你有可能必须加上一个getchar(),它会在一些IDE中防止程序立即关闭)。

图3.3

编译运行过后结果是这样的。

你可以看到相同的数字被以三种不同的数制打印出来了,printf()函数可以实现这种转化。
注意前缀只有加上#时才会出现。

3.4.2 其他整型

当你正在学习C语言时,int类型有可能已经满足了你大部分的整型需求了。但为了内容的完整,我们会研究其他的类型。如果你喜欢的话,你可以只大体浏览一下这一部分,然后跳到对于char类型那一部分,然后如果有啥需要的可以再回来复习。

C语言使用三个形容词关键字来修饰基本整型:short, long, unsigned下面是需要记住的一些点。

  • short int类型,或者直接short,会比int使用的内存更少,由此也就可以在只使用小的数字的时候节省一些空间。像int一样,short为有符号类型。
  • long int类型,或者直接写long,占用的内存会比int更多,因此可以让你表示更大范围的数字,像int一样,long为有符号类型。
  • long long int类型,或者long long(都是在C99标准下被引入的),会比long类型占用的内存更多,也就让你可以使用比long范围更大的数字。像int一样,long long是一个有符号的类型。
  • unsigned类型,或者写成unsigned,是为非负的变量而设置的,这种类型改变了可储存数字的范围,例如,16位的unsigned int可以表示0-65535的数字,就不是-32768-32767了,用来表示符号的那一位在无符号类型这变成了另一个可以表示数字的位,也就让数字的表示范围扩大了。
  • unsigned long int类型,或者unsigned long还有unsigned short int,或者unsigned short,在C90标准下都是可以被识别的,在C99标准下又添加了unsigned long long int类型,或者unsigned long long
  • 为了让你的意图明显,关键字signed可以被用在任何有符号类型上,例如short, short int, signed short,signed short int都是一种类型。

3.4.2.1 声明其他整型

其他整型与int类型的声明方式是相似的,
下面就展示了一些例子。
并不是所有的编译器都能识别最后三种类型,最后一个例子是C99标准新加进去的。

其他整型

3.4.2.2 为什么要用多种整型?

为什么说longshortint相比会使用不同的内存呢?因为C语言会确保short不会长过intint也不会长过long,主要的目的就是为了适应不同类型的机器。例如在运行着Windows 3.1的IBM PC上,intshort都是16位的,而long是32位的,而在Mactonish Power PC上,short类型是16位的,而intlong类型是32位的。PowerPC G3/G4中的Pentium芯片的字长是32位,因为这就可以让整型超过20亿(见表3.3),C语言在处理器/操作系统的实现没有必要超出这个数值。因此,longint大小是一样的。对于许多的使用过程,那个大小的整数是不需要的,所以节省内存的short就被创造了。但另一方面,原本的IBM PC只有16位的字长,那就意味着需要更长的long类型。

现在64位的处理器都变得很常见了,比如IBM Itantium,AMD Opteron,还有PowerPC G5。因为64位整型的需要,long long类型也就应运而生。

现在最常见的实践是把long long设置为64位,把long设置为32位,把short设置为16位,把int设置为16位或者32位,它的大小取决于电脑的自然字长。原则上,这四种类型可以代表四种不同的大小。

C语言标准为确定每一种基本数据类型的最小尺寸提供了指引。shortint的最小范围是从-32,767到32,767,对应着16位的单元,而long类型的最小范围是-2,147,483,647到2,147,483,647,对应着32位的单元。(注意,为了易读,我们使用了逗号分开,但是C语言代码是不能写成这样的)。对于unsigned shortunsigned int,最小的范围是0到65535,而unsigned long的最小范围是0-4,294,967,295/,long long类型为了迎合64位的需求,它的最小范围很大,是-9,223,372,036,854,775,807到9,223,372,036,854,775,807,而unsigned long long就是0到18,446,744,073,709,551,615.(对于那些检查写的对不对的人,下面是它的英文表示,但是谁要计数呢?)

(一脸认真)

你会在什么时候使用这些不同的int类型呢?第一,考虑unsigned类型,使用它们计数是很好的,因为你不需要负数,而且unsigned类型比signed类型的范围更大,可以得到更大的整数范围。

如果你需要处理超过了int类型的范围的数字,使用long类型,但是在longint长的系统中,使用long会让计算变慢,所以没必要的话就不要用long,进一步说,如果你正在一个intlong类型长度一样的系统上写代码,而你又需要32位的整数,你就需要使用long而不是int了,那样程序才可以在转到16位机的时候正常运转。

相似地,如果你需要64位整型数的时候就使用long long吧,一些电脑已经使用着64位的处理器,而且并且正在服务器、工作站甚至桌面的64位处理也正在变得越来越普遍。

如果你需要节省内存空间的话,使用short来节省内存空间,比如如果你在一台int类型是32位的机器上,需要16位的值这种情况。通常只有当你的程序使用整型数组这种与系统的可用内存极其相关的情况下节省内存才是必须的。另一个使用short的原因是它有可能与被电脑的特定部分使用的硬件寄存器的大小相同。


整型溢出。
当一个整型相对于它的类型太大了会怎么样呢?让我们把一个整型变量设置到它可能的最大值,并用它加一些数,看看会发生什么。unsigned signed类型都可以试试(unsigned int的格式化符是%u
这就是系统给出的结果

unsigned类型好像汽车的里程计一样,当它到达了最大值,它就会返回到它的最小值,变量i也很相似,主要的区别是unsigned类型的j 是以0开始的,但是int类型的i是以-2147483647开始的。
注意的是,你如果没有被告诉这个数据已经溢出了,你还是会不断地在这个问题上打转。
这里描述的行为是由C语言unsigned类型的规范控制的,标准并不规定signed应该行为如何,这里展示的行为也是通常会发生的,但是你以后会找到不一样的东西的。

3.4.2.3 long常量与long long常量

一般地,当你在代码中使用2345这样的数字时,它会被储存为一个int类型的常量。那么如果你使用1000000这样不能被int储存下来的数字呢?编译器就会将它识别为long int,如果认为这个类型已经足够大了的话。如果比long类型的最大值还要大的话,C语言会将它当做unsigned long类型,如果那还不够的话,它就会把它当做long long类型或者unsigned long long,如果这些类型可以满足要求的话。

八进制与十六进制的常量会被认作int类型,除非这个值太大,然后还是以上文的顺序依次递加。

有些时候你有可能想让编译器把一个小点的数储存为long类型。如果你在编程的过程中需要外显地使用内存地址,例如在IBM PC上,就有可能有这样的问题。而且,一些基本的C语言函数是需要long类型的值的,你可以在数值加上后缀l或者L,第二种方式更好,因为看起来不像数字1。因此,一个有着16位大小的int与32位大小的long,会将7这个数储存在16位中,而将7L储存为32位中,而且这两个后缀对于八进制与十六进制都是适用的,像是020L与0x10L。

相似地,在那些有long long类型的系统中,你就可以使用ll或者LL后缀,比如3LL,而且还可以使用u或者U来表示unsigned long long类型,就像5ull或者10LLU或者9Ull

3.4.2.4 打印short, long, long longunsigned类型

要打印一个unsigned int类型的数字,只需使用%u符号,long类型可以使用%ld,但如果你的机器中intlong是大小一致的,那%d就可以了,但是代码有可能在别的系统中就不能正常运作了,所以对long来说最好还是使用%ld吧,对于xo都可以在前面加上l。因此对于十六进制的long类型数你会使用%lx,类似在八进制会使用%lo,需要注意的是即时C语言提供了大写的L后缀,但是在格式化符中,只能使用小写。

C语言有几种额外的printf()格式,首先,你可以在使用short类型时在%dd前加h来表示十进制的short整型,同理%ho也是可以的,而且hl是可以再加上u表示无符号的。例如,你会使用%lu符号表示unsigned long类型,图3.4就提供了一个例子。支持long long的系统也可以使用%lld或者%llu分别表示有符号与无符号的long long整型,第四章会对格式化符进行更充分的阐述。

图3.4

这是系统的输出:

这个例子就显示出了使用错误的格式化符会有意想不到的后果。第一,注意到对于变量un使用的%d格式化符结果输出了一个负值,原因就是无符号与有符号数实际上是以相同的二进制排列被储存起来的(第15张会更详细地谈论这种属性),所以如果你告诉printf()函数这个数是没有符号的,与告诉它这个数是有符号的输出结果是不一样的。这种行为常会在你输入一个超限的数值时出现。小的整数,在两种格式化符之下都是一样的。

第二,注意到无论你告诉printf()short还是int类型,short变量end都是可以打印出正常值的。这就是因为当向函数输入一个参数的时候会自动将它转化为int类型,那么你就会问了:1.为什么这种转化会发生?2.h修饰符的用处在哪里?第一个问题的答案是int类型可以被电脑更加高效地处理。所以在shortint类型大小不同的机器上,有可能传递一个int值会更快。第二个问题的答案是你可以看看一个更加长的整型数如果被缩短为short类型了会看起来怎样。第三行的输出也为这一点提供了例证,当65537被写为32位二进制数时,它看起来是00000000000000010000000000000001这样子的,使用%hd格式化符会迫使printf()只看向最后的16个位,因此它会只打印1,相似的,最后一行的verybig使用%ld就会只看向最后的32位,而不是完全的位数。

早些时候你已经了解标识符的数量与显示数字的数量匹配的重要性了,现下你也知晓了标识符的种类也要匹配。

匹配printf()函数的格式化符
记着要去检查一下你是不是对于每一个要在printf()中打印的值都给予了一个格式化符,并检查是不是每一个的格式化符都与显示的值的类型相互对应。

3.4.3 字符 char

char类型是用来储存像字母、标点符号这样的字符的,但是严格来讲它其实是一个整数类型。为什么?char这个类型实际上储存的是整数而不是字符,为了处理字符,电脑会使用数字编码来让某一个整数代表某一个字符。在美国最常用的字符编码表是ASCII编码表,它已经在书的前封的背面给出来了,是这本书使用的编码表。举些例子,65在ASCII码表中代表的是大写的A,所以如果需要储存字母A,只需要储存数字65即可,(许多IBM主机用的是不一样的编码系统,它叫做EBCDIC,但是准则都是一样的。但在美国以外的计算机系统使用的编码表有可能是完全不同的。)

基础的ASCII码是从0到127的,这个范围小到一个字节就可以保存下来,char类型通常会被定义成8位的内存单元,所以它不仅仅可以储存下来基础的ASCII码。许多系统,如IBM PC与苹果公司的Macintosh,可以提供更大的ASCII码表(这两个系统是不一样的),但是还是在8位范围之内。更加普遍的,C语言会确保char类型的大小是足够储存系统中C语言实现的需要的基础字符的。

许多的字符集会有不只127而是255分值,例如日本的kanji字符集,商业的Unicode协议为代表全世界的各种字符创造了一个系统,现在已经有96000个字符了。ISO(国际标准组织)与IEC(国际电子委员会)为字符集创造了ISO/IEC10646标准。幸运的是,Unicode标准与ISO/IEC10646标准是始终协调的。

使用这些字符集的平台有可能会使用16位或者32位的char类型代表,C语言定义下char的长度是1个字节,现在在C语言的记录中,一个字节会是16位或者32位,而不是那些系统上的8位。

3.4.3.1 定义char类型变量

正像你会期望的,char类型变量与其他变量的定义方式相同,这里就是一些例子。

这几行代码创造了三个char类型的变量:responseitablelatan

3.4.3.2 字符常量及其初始化

假定你想要初始化一个字符常量为大写字母A,电脑语言是要让事情变得简单起来的,你不需要记忆ASCII码,你也不会,你可以直接以下面的方式给grade变量赋值'A'

被双单引号框定的单一字符就是C语言中的字符常量,当编译器看到了'A',它会将A字符转化成相应的字符编码值,单引号是必须的,下面就是例子。

如果你遗漏了引号,那么编译器就会认为T是一个变量名,如果你使用双引号的话,它会认为你正在使用字符串,我们会在第四章讲解字符串。

因为字符确实是以数字的形式被储存起来的,你还是可以使用数字编码为char类型变量赋值。

在这个例子中65是一个int类型的,但是因为这个值是小于最大的char类型值的,所以可以无误地赋给grade变量,因为65对应着ASCII码中的字母A,所以A就被赋给了grade变量。但注意,这个例子只是为了表示系统用的是ASCII码系统,但用65'A'都是一样的,因此,使用字符常量要比使用数字编码是要好的。

有点奇怪的是,C语言会将字符常量当做int类型常量而不是char来看待。例如,在32位使用ASCII字符编码,char类型为8位的系统中,这一行代码:

就会将'B'呈现为66这个值储存起来,但是grade是以8位单元储存的66,这种特点就会使字符常量不能存放多个字符,像'FATE'这样,因为4个单独的ASCII码被储存在32位的单元中,但是试图将这样一个字符常量赋给char类型变量会导致只有最后的8位被使用,也就是变量被赋给的是'E'

3.4.3.3 不会被打印出来的字符

单引号对符号、数字还有标点符号都是适用的,但是如果你浏览过这本书前封里的那张表,你会看到一些ASCII字符是打印不出来的。例如,一些代表着回车或者跳到下一行,又或是会让终止铃响的符号,这些符号是怎么被表示的呢?C语言提供了三种方法:

第一种方法就是我们已经提到过的,使用ASCII表编码的方法。例如,蜂鸣符对应的值就是7,所以你可以这样写。

1
char beep = 7;

表示这些字符的第二种方法是使用特殊的符号序列,它们叫做转义序列,表3.2给出了转义序列与它们的含义。

表3.2

在赋值给字符常量的时候,转义序列必须要由单引号框定,例如,你可以这样赋值:

1
char nerf = '\n';

然后打印变量nerf,打印器或者屏幕上的行就会往前一行(回车)。

现在我们可以看看每一个转义序列是做什么的,警告符\a是在C90标准中添加的,它会生成一条有声有色的警告,警告的类型因硬件而定,一般蜂鸣声是最常见的(在有些系统中,警告符是没有用的。)ANSI标准规定了警告符是不能改变活跃位置的。活跃位置这个概念指的是在显示设备(如屏幕、电报机、打印机等等)上下一个字符会出现的地方。简单来说,它就是屏幕上你已经用惯了的光标的总称。在程序中使用警告符最终只会发出蜂鸣声,但并不会移动屏幕上的光标。

然后,是\b, \f, \n, \r, \t还有\v这些转义序列,它们都是常见的输出设备的控制符,它们在影响活跃位置上很出色,回车(\b),会将活跃位置往前挪一个空格。换页符(\f)会将活跃位置转移到下一页的开头。换行符(\n)会将活跃位置切换到下一行的开始,(\r),回车符(\r)会让活跃位置回到一行的开头位置,水平制表符(\t)会将活跃位置向右移动一个Tab键的位置(通常,这些是以字符位置1、9、17、25这样的格式),垂直制表符(\v),会让活跃位置移动到下一个垂向的tab位置。

这些转义序列并不一定在所有的显示设备中起作用。例如,换页符与垂直制表符在电脑屏幕上只会打印出来一个奇怪的字符,并没有任何光标的移动。但是如果在打印机上使用就可以按照描述工作了。

最后的三个转义序列(\\, \', \"),能够以字符常量的形式让你使用\, ', "(因为这些符号是作为printf()函数的一部分用来定义字符常量的,如果你要把它们字面上打印出来,那将会非常令编译器困惑。)假设你想要打印下来下面的这一行字。

1
Gramps sez, "a \ is a backslash."

就需要用下面代码的形式来写。

1
printf("Gramps sez, \" a \\ is a backslash.\"\n");

最后的两种形式(\0oo, \xhh)是ASCII码的特殊标识,是要以八进制ASCII码的形式来代表字符,在前面需要加上\然后用单引号来框定整个字符,例如,如果你的编译器不能识别警告符,你就可以使用下面的ASCII码:

1
beep = '\007';

你也可以省略前面的0,所以'\07''\7'都是可以的。这个符号会让数字被理解成八进制,尽管前面没有0。

从C90开始,C语言提供了第三种选择,使用十六进制的字符常量,这样说来,斜杠后面是x或者X与1到3位的十六进制数。例如,Ctrl+P符号在ASCII十六进制符码里就是10(十进制下就是16),所以它是可以被表达为'\x10'或者\'x010'的,图3.5展示了一些整型的代表。

图3.5

当你使用ASCII码的时候,要注意数字与字符之间的差异。例如,字符4在ASCII中的值是52,'4'是一个符号而不是一个数字。

现下,你可能有这几个问题:

  • 为什么转义序列在最后一个例子中没有用单引号括起来?
    最后一个例子printf("Gramps sez, \" a \\ is a backslash.\"\n");)。
    只要是一个字符,不管它是不是转义字符,当它是字符串的一部分的时候,都是需要用双引号框起来的,而不是单引号,但单个字符是无一例外由单引号括起来的。由双引号括起来的是
    字符串
    。(第四章会讲到)相似地,不属于转义字符的数字会被当做一般字符打印出来(如printf("Hello!7\n");打印出来的是Hello7,但printf("Hello!\007");输出的是一行Hello!加上一声警告)。
  • 我应该什么时候使用转义序列?什么时候使用对应的ASCII码?
    如果你需要使用其中一个转义序列,比如说'\f',或者作为替代的'\014',最好使用前者。首先,这种表示方法更容易记忆:其次,它更容易在机器之间转移,如果其他系统不适用ASCII码,'\f'还是可以使用。
  • 如果我需要使用数字变啊,为什么使用的是'\032'而不是032
    第一,使用转义字符的方式会让阅读你代码的人轻松地知道这是一个字符编码。第二,如\037这样的转义序列是可以内嵌于C语言字符串的(?[就像\007一样])。

3.4.3.4 打印字符

printf()函数可以使用格式化符%c表示一个字符将要在这个位置被打印。你想到字符常量是1位的整型值,因此你也可以用%d符号,那样的话你就会得到一个整数,%c格式化符会告诉printf()函数来打印那个编码值对应的字符,例3.5就以两种方式展示了char类型变量。

例3.5

尝试运行,输出是这样的。

当你使用程序的时候,不要忘记在写完了字符之后按下回车键。scanf函数会接收你写下的字符,然后取地址符(&)会将字符赋给字符变量ch。接下来,printf()函数会打印两次ch这个变量,第一次是以字符的形式打印的(使用%c格式化符的效果),第二次是以十进制整数的形式(同理,%d的效果)。需要注意,printf()的标识符会决定数据被如何呈现,呈现方式与数据的储存方式没有关系。

数据储存与输出

3.4.3.5 有符号还是无符号?

有的C语言实现里会使char是一个有符号的类型,那也就意味着char可以接受从-128到127的值。其他的C语言实现会让char是一个无符号的类型,那么它的范围就变成了0到255,你的编译器的使用手册应该会告诉你char是什么类型,或者你可以看看limits.h头文件,下一章会讲述。

在C90标准下,C语言允许你使用关键字unsignedsigned来修饰char,那样的话不管原本的char是有符号还是无符号。你都可以任意变化它们的类型。它们在处理小范围的十进制整数时是很有用的。要是只是为了使用字符,只使用char就可以了。

3.4.4 _Bool类型

_Bool类型是C99标准添加的,它们是被用来代表布尔值的,也就是逻辑上的truefalse。因为C语言是使用0表示false,用1表示true的,所以布尔类型实际上只是一个整型,但是只需要一位的内存,因为要储存0与1一位就足够了。

程序会使用布尔值来选择下一步运行哪个代码,代码执行会在第六章(C语言控制语句:循环)与第七章(C语言控制语句:分支结构与跳跃结构)更详细地讲到,所以我们到那个时候再讨论这个问题。

3.4.5 可转移的类型:inttypes.h

还有更多的整数类型吗?没有了,但是还有更多你可以使用与已经存在的类型的名字。你或许会认为你已经看到的整型名已经很多了,但是原本的名字确实存在问题。知晓一个变量是int并不会让你了解它会占用多少内存,除非你检查了系统的描述文件。为了解决这个问题,int16_t可以声明一个16位的有符号整型,uint32_t声明了一个32位的无符号类型。

要让这些名字可用,你需要包含一个inttypes.h头文件(注意在这一版时候已经有了,但是一些编译器上还不支持这个功能),这个文件使用了typedef(第一次讲到是在第五章:运算符、表达式与语句),来创造新的类型名。例如,它会让uint32_t成为基本类型的一种别名或者同义替换,有可能是unsigned int也有可能是unsigned long。你的编译器会提供一个与你的计算机系统协调的头文件,这些新的设计被称为精确长度类型,例如,int_least8_t会是最小的类型,它可以承载8位的有符号整型值。如果某个系统上最小的类型是8位的。int8_t类型是不会被定义的,但int_least8_int是可以被定义的,或许会被实现为16位的整型。

当然,一些编程人员会更关心速度而不是空间。对于它们,C99定义了允许快速计算的一系列类型,它们被称为最快最小长度类型,例如,int_fast8_t会被定义为你的系统上能够最快运算的8位有符号值。

最后,对于其他的编程人员,只有系统上最大的一些整型才有用,intmax_t就代表了那种类型,它可以储存任何合法的有符号整型值,同样,uintmax_t会代表可用的最大的无符号类型。这些类型有时是比long long或者unsigned long还要长的,因为C语言实现是准许定义超过需要的类型的。

C99不仅提供了这些新的而且可运输的类型名,它还有有利于输入输出的符号表示。比如说,printf()对于每一种类型都需要特别的修饰符,所以你要打印int32,你要使用的是%d还是%ld?不同的机器的标识符并不一样,但没问题,C99标准下是有字符串宏命令来打印这些值的,我们会在第四章详细讲述的。例如,inttype.h头文件会将字符中的PRId16作为对于不同对应的类型的合适的格式化符。(例如hd(short)或者d),例3.6是一个讲述了如何使用可运输类型与相对应的格式化符的简单的例子。

例3.6

在最后的printf语句中,PRId16inttypes.hhd的类型定义替换了,把这一行代码变成了这个样子。

但是C语言需要连贯的字符串,所以引号需要是连续的,然后这一行就变成了这个样子。

这就是输出,注意这个例子也使用了\"转义字符来打印双引号。

参考单元VI“更多的整型”提供了inttypes.h头文件添加的内容的完整解释,也列举了所有的格式化宏。


【C99支持】
编译器供应商已经开始以不同的速度与顺序开始实现C99的新特性了,但在这本书出来的时候,一些编译器还没有实现inttypes.h的头文件与特征。

3.4.6 float, doublelong double类型

对于大多数的软件开发项目来说,这些多种多样的整型已经能够很好地满足要求了。但是,对于财务或者面向数学的程序,我们通常会使用浮点数值,在C语言中,这些数字就对应着float, double, long double这几种类型,对应着FORTRAN与Pascal语言中的real类型。使用浮点数,正像上文所说,能够让你呈现更大范围的数字。包括十进制小数值。浮点数值的表示与科学计数法是很相似的,那是科学家用来表示非常大或者非常小的数字的一种体系。

在科学计数法中,数字会被表示成十进制数乘10的整数幂,这里就是一些例子。

第一列是通常的表示方法,第二列是科学计数法的表示,第三列是质数的表示方法,或者说是e - notation(e表示法),通常是给计算机或者计算机写的,在e的后面是10的指数,图3.7展示了更多的浮点表示法。

C语言标准规定了float类型应该至少能够代表6位有效数字并且范围在10的-37次方到37次方。第一个要求的意思是,比如一个float必须要精确地表示至少前六位有效数字,就像33.333333。如果你喜欢使用较大或者较小数字(2.0e30(太阳质量)或1.6e-19(元电荷))的话,第二条要求会为你提供方便。通常,系统会使用32位类储存一个浮点类型的数字,8位用来给予指数值与符号,24位来表示非指数部分,叫尾数或者有效数字与它的符号。

一些浮点数表示

C语言也有一个double(双(double)精度)浮点型,double类型与float类型需要的最小值是一样的,但是它把最小值的有效数字位数拓展到了10位。通常的double表示会使用64位而不是32位,一些系统会使用32个位来表示非指数部分,这也就增加了数字有效的有效数字位数,减少了取整错误。其他的系统会使用一些位储存更大的指数,这也就增加了可以容纳数字的范围。每一张方法都至少有13位的有效数字,不只能满足最小标准的要求。

C语言还允许你使用一种浮点类型:long double,目的就是进一步增加double类型的准确度。但是C语言只保证long double至少与double类型的精度是一样的。

3.4.6.1 声明浮点变量

浮点变量与它们的整型兄弟的声明与定义的方式相似,这里是一些例子,

3.4.6.2 浮点常量

当你书写一个浮点常量时,你会有很多的选择,形式是很多,有符号的一串数字。包括十进制的小数点,或是由e或者E跟着的10的指数。这里是两个合法的声明浮点类型的例子。。

你可以遗漏正号,没有关系(2E5)或者可以遗漏指数部分(19.28),但是并不能同时存在。你可以遗漏小数部分或者整数部分,但也不能全都没有(那也不剩什么了。)下面是更多的合法的浮点常量。

在使用浮点常量的时候,不要乱添加空格。

错的

一般来说,编译器会认为浮点常量都是double精度的,假设some是一个float类型的变量,并且你有下面的一行代码。

4.02.0都是以double类型储存起来的,一般每一个使用64位,结果是使用浮点运算获得的,而且只有结果转换成了正常的float大小范围内才可以使用float。这也就为运算提供了准确度,但是会让你的程序变慢。

C语言还是你能够超越默认方法,你可以使用f或者F后缀来让编译器把一个浮点常量储存为float类型,比如2.3f9.11E9F还有l或者L后缀,可以储存为long double类型,例如54.3l4.23e4L。注意的是,比起lL更不容易被看成1,如果浮点数没有后缀,它的类型就是double

C99添加了表示浮点数的另一种方式,它是使用十六进制来表达的(0x或者0X加上十六进制数)。那样指数就是p或者P而不是e或者E了,指数是2的,也不再是10的了。这就是数字看起来的样子。

a是十进制的10,.1f是1/16加上15/256,然后p10是2的10次方,或者1024,用十进制表示这就是10364.0。

并不是所有的编译器都已经支持C99的特性了。

3.4.6.3 打印浮点数值

printf()函数会使用%f格式化符来打印floatdouble类型的数值,这个符号使用的是十进制小数符的表示方法,还可以使用%e来使用指数表示法来打印它们,如果你的系统支持C99中提供的小数的十六进制格式,你就可以使用a或者A代替eElong double类型是以%Lf或者%Le%La标识符来分别使用这三种方式的。需要注意的是,floatdouble都可以使用%f, %e, %a标识符来输出。这是因为当一个float类型的变量作为语句传递到任何函数时,C语言会自动把它转化成double类型,而不是它本来的类型,比如说printf()函数就是一个例子。例3.7说明了这种行为。

例3.7

这里是输出:

这个例子说给出了默认的输出,下一章会讨论如何通过设置输出域长度与十进制的位权数,控制输出的外形。

3.4.6.4 浮点上溢与下溢

可能最大的float类型就是值大概是3.4E38,然后你执行了下面的代码。

输出(个人测试)

发生了什么?这就是溢出的一个例子-运算得出的结果大到无法表示。对于这种情况,这种行为之前是没有被定义的,但是现在C语言指定toobig是一个几乎代表无限的数字,printf()就会显示inf(infinity)(或者那个样子的一些变体)

那么如果是要分辨非常小的数字呢?这是会遇到更多的情况。你可以记起float是以指数部分与数字部分(或尾数)来储存的。如果会有一个数字有着最小的指数与最小的值,而且仍然可以使用所有可以使用的位来表达尾数,在float的精度范围内表示,这就是float类型下的最小值。现在我们把它除2,一般地,这会减少指数,但是指数也是能够表达的最小值了。所以就要到尾数的空余位那里减了,把第一位清空,然后丢掉最后的二进制值,类比一下就是,用四位有效数字的10进制值,比如0.1234E-10,除10,结果就是0.01234E-10,你就可以得到答案了,但是你会在这个过程中丢掉一个数字。这种情况就叫做下溢,C语言对于失去完全精度的浮点数值叫做subnormal(反常值)。所以把最小的正常浮点值除2就会有反常值。如果你除了一个太大的数字,你就会失去所有的精度,最后只剩下0,C语言库现在提供了函数来检查是否你的运算产生了反常数值。

这里还有另一种特别的浮点值,NaN。例如你给予asin()函数一个名字它会返回所给数值的sin值,但是sin的数值并不能大于1,所以函数对于大于1的数值是未定义的,在这种情况下函数就会返回NaN值,那会被printf打印为nanNaN或者相似的东西。

四舍五入问题
拿一个数字,加1,然后减去原来的数,你会得到几?肯定是1是吧?像下面的浮点运算给予了你不同的答案。

输出是这个样子的:

这三种奇怪的结果的原因是电脑并不能够跟踪需要多少十进制空间来进行恰当的运算,2.0e20是2后面跟着20个0,通过加1的操作,你要改变这个数字的第21位数字,要正确地执行这个操作,程序需要能够储存21位数字的类型,float类型数通常只是六位或者七位数带着指数这个范围(最后的一位只能是0,不能改变)所以这种尝试在劫难逃。另一方面,如果你使用2.0e4的话你就可以得到正确的结果,因为你改变的是第5位数,float类型的精度允许你这样做。

3.4.7 复数与虚数类型

许多科学与工程运算会使用复数与虚数,C99为这些数提供了支持,(with some reservations),它是一个独立的实现,就像内置预处理命令一样,不需要其他类型的支持。(VCR芯片很可能并不需要复数来工作)。并且,更一般地。虚数类型是可选择的。简单来说,有三种复数类型,float_Conplex,_Complexlong double _Complex,举个例子,一个float _Complex变量,会包含两个float类型的值,一个代表复数的实部,另一个代表虚部。相思地,有三种虚数类型,它们叫做float _Imaginary, double _Imaginarylong double _Imaginary

包含complex.h头文件可以使你把_Complex_Imaginary分别更换为compleximaginary。而且还能够让你使用I来代表-1的平方根。

3.4.8 超越基本类型

这样我们已经讲完了一系列基本的数据类型了,对于你们其中一些人来说,数据类型可能很多,还有一些人可能会想着还需要其他的类型,比如说字符串类型。但C语言并没有。但是还是能够很好的处理字符串,你会在第四章第一次见到字符串。

C语言确实有其他的类型,他们是从基本类型中衍生出来的。这些类型包括数组、指针、结构体、联合体。即使它们都是后面章节才要讲到的,我们已经在这一章的例子中已经偷摸添加了一些关于指针的东西了。(指针会指向变量或者其他数据对象的地址,scanf()中使用的&前缀就会创造一个指针,他会告诉scanf()在哪储存信息)


总结:基本数据类型
关键字
基本的数据类型是由11个关键字组成的:int, long, short, unsigned, char, float, double, signed, _Bool, _Complex, _Imaginary
有符号整型
可以是整数值也可以是负值
1. int- 系统给定的基本整型。C语言保证int至少有16位。
2. shortshort int - 最大的短整型不大于最大的int,并有可能更小,C语言保证short类型至少有16位。
3. longlong int - 能够保存至少int的最大值大小的数,可以更大。C语言会确保long long类型至少有32位。
4. long longlong long int - 这个类型至少能够保存long类型数的最大范围,可以更大,long long类型最小64位。

通常,long类型是比short类型更短的,int会与他们之中的一个一样大。例如,以DOS系统为基础的PC系统,short是16位长的,而int是32位长的,以Windows 95为基础的系统会提供16位的
short,而longint类型是32位的。

如果你喜欢的话,你可以在任何有符号类型前使用signed关键字,将它们有符号这个事实显露出来。
无符号整型:
这些类型只能从0到整数,因为去掉了符号位,这种类型的范围得到了扩大,使用关键字unsigned可以把你想要的类型变为无符号类型:比如unsigned int, unsigned long, unisgned short.单个的unsignedunisgned int是一样的。
字符:
它们是像A, &这样的印刷字符。在定义上,字符类型会使用1个字节的内存来代表一个字符。在以往,字符字节大多数是8位,但现在它可以是16位或者更大,可以迎合使用的基准字符集的大小。
char - 这种类型的关键字,一些实现中会使用有符号的signed char,但其他还是使用无符号字符。C语言允许你使用signed或者unsigned来划定你想要的类型。
布尔类型:
布尔值代表truefalse,C语言会使用0与1代表这两个值。
_ Bool - 是这种类型的关键字,它是一个无符号整型,只需要保存0与1这两个值的内存。
实数浮点型:
可以正数、负数或者零。
[float] 是系统的基本浮点类型,可以至少呈现6位精确的有效数字。
[double] 一种(可能是)更大的浮点储存单元,允许更大的有效数字位数(至少10位,通常会更多。)而且有可能比float也有更多的指数位数。
[long double] 一种(可能是)更大的浮点储存单元,它可能允许更多的有效数字位数,也有可能有着比double类型更大的指数部分。
复数与虚数浮点型:
虚数类型是可以选择的,实部与虚部是基于实数类型的:
float _Complex
double _Complex
long double _Complex
float _Imaginary
double _Imaginary
long double _Imaginary

总结:如何声明一个简单的变量

  1. 选择你需要的类型
  2. 使用允许的字符为你的变量起个名字
  3. 使用以下形式的声明语句
    1
    2
    3
    //type-specifier variable-name;
    //类型标识符 变量名;
    int Iint1;
  4. 你可以通过用逗号把变量名隔开来声明多个变量,下面就是一个例子。
    1
    char ch, init, ans;
  5. 你可以在声明语句中对变量进行初始化:
    1
    float mass = 6.0E24;

3.4.9 类型大小

表3.3与3.4展示了一些C语言环境下的类型大小(在一些环境中,你可以选择)你的系统是什么样子的?尝试运行例3.8中的程序,你会弄明白的。

表3.3与3.4
(分别是整型与浮点型的大小。)

对于每一种类型,最上面的一行是有效数字的位数,第二行是指数的范围(十进制)。

1
2
3
4
5
6
7
8
9
10
11
12
//(例 3.8)
#include <stdio.h>

int main(void)
{
/*C99为大小提供了%zd格式化符*/
printf("Type int has a size of %u bytes.\n", sizeof(int));
printf("Type char has a size of %u bytes.\n", sizeof(char));
printf("Type long has a size of %u bytes.\n", sizeof(long));
printf("Type double has a size of %u bytes.\n", sizeof(double));
return 0;
}

C语言有一种内置的运算符,它叫sizeof(),它会给出以比特为单位的大小范围。(一些编译器需要%lu而不是%u来打印sizeof数值,这是因为C语言对于sizeof用来报告的真实的无符号整型是有多个范围的。C99对于这种类型给出了%zd标识符,如果编译器支持的话你应该使用这个。)例3.8的输出是这个样子的。

这个程序只发现了4种类型的范围,但是你可以简单修改一下这个程序来找寻你感兴趣的任何一种类型,要注意的是char类型的大小一定是1个字节,因为C语言对于char类型定义的就是一个字节。所以在有16位的char与64位的double的系统上,sizeof会报告double类型有4个字节的大小。你可以看看limits.h与float.h头文件来获取更多关于类型最大最小值的细节信息。(下一章会进一步阐述这两个文件。)

顺便一说。看看最后一行printf()是如何延伸到两行的,只要不是在双引号部分或者在一个单词的中间断开,你可以这样做。

3.4.10 可用数据类型

当你正在开发一个程序时,需要注意你需要的变量与它们应该使用的类型。大多数情况,你可以使用int或者float来表示一个数,用char来表示字符,在使用它们的函数的开头先声明好它们。为变量取一个能够暗示它们的意义的名字。初始化一个变量时,也要让变量类型与赋给的常量类型匹配。这里就是两个例子。

1
2
int apples = 3;
int oranges = 3.0;

C语言对于类型不搭配比起Pascal是很宽容的,C语言的编译器允许你进行二次初始化,但是它们有可能会引起报错,尤其是当你触发了一个高级警告时。最好不要养成这种马马虎虎的习惯。

当你使用其他类型的值给一个数字类型的值初始化时,C语言会将这个值进行转化,来让它与这个变量的类型相配。这也就意味着你有可能会失去一些数据,比如思考一下下面的初始化过程。

1
2
int cost = 12.99;        /*使用double值来初始化int*/
float pi = 3.1415926536; /*使用double值来初始化float*/

第一个声明语句会将12赋给cost变量,当把浮点数转换为整数值时,C语言简单地把小数部分丢掉(截断),而不是四舍五入。第二行的声明会失去精度,因为float类型是只能呈现小数点后6位精确数的。如果进行这样的初始化,编译器有可能会发出一个警告(但不是一定会发),你有可能在编译例3.1的时候遇到这个问题。

许多的编程者与组织人员拥有设置变量名称的体系化传统方法,按照这种方式就可以显示出变量的类型。例如你可以使用i_prefix来表示这是个int类型变量,同理us_来表示unsigned_short,这样的话对于类型就好辨认一些。

3.4.11 参数陷阱

在这一章的前半部分,有一个关于printf()使用的警告还是很值得重复强调的。你可能还记得传递给函数的信息在术语上称为参数。例如,printf("Hello, pal.")就有一个参数:"Hello, Pal."。我们把在双引号中的一系列字符叫做字符串,我们会在第四章讨论。现在的重点是,一个字符串,即使包含着好几个单词与标点符号,也仍然被算作是一个参数。

相似地,scanf("%d", weight)有两个参数:"%d"&weight。C语言会使用逗号来分开函数中的各个参数,printf()scanf()函数还挺不寻常的,它的参数数量是不限的。例如,我们曾经在使用printf()时传递过一个、两个甚至是三个参数。对于一个恰当运行的程序,它需要知道参数的数量。printf()scanf()函数会使用第一个参数来表达之后会有多少个其他的参数。这里的诀窍就是在最前的字符串中的每一个格式化符都代表着后面有一个参数,例如下面的语句就有两个格式化符:%d%d

1
printf("%d cats ate %d cans of tuna\n", cats, cans);

这也就告诉程序后面要接收两个额外的参数,而且后面确实有两个——catscans

作为一个编程人员,你的责任就是确保格式化符的数量与其他的参数的数量相同,类型相同。C语言现在已经有函数原型机制了,它能够检查一次函数调用是否使用了正确的数字与正确的参数类型,但是对于printf()scanf()是不适用的,因为它们参数的数量是可变的。当你并没有接收程序员的负担会怎样呢?举个例子,假设你写了例3.9那样的程序。

例3.9

下面是从微软VisualC++ 7.1(WinXP系统)上运行的结果

1
2
3
4
4 34603777
0 0.000000

这是在Digital Mars(WinXP系统)上运行的结果

1
2
3
4
4 4239476
0 0.000000

这是在Metrowerks Codewarrior Development Studio 9(MacOSX系统)

1
2
3
4
4 3327456
1075052544 0.000000

你可以看到,使用%d来表示一个float类型的值并不会将它变成最近的int类型值,而是直接输出垃圾值。相似地,使用%f来表示一个int类型的值也并不会将整数转化为浮点数值。参数太少或者类型不对得到的结果不同的平台也不一样。

没有一个编译器对于这个代码有报错或者警告,在运行时也没有。一些编译器确实会捕捉到这种错误,但是C语言标准并没有要求它们这样做。因此,电脑就不一定能够补货到这种类型的错误,因为程序反而还能够正常地运行,你也不一定能够注意到这种错误。如果程序并没有打印正确的数值数量或者是数值,那就回去看看printf()的参数数量与类型是否使用正确。(顺便一说,Unix语法检测程序lint,它比Unix编译器检查得更加细致,能够对于printf()的参数错误进行报错)。

3.5 又一个例子:转义序列。

让我们在程序中打印更多东西吧,这一次我们会使用C语言中的一些针对于字符的特殊转义序列。尤其地,例3.10中的程序显示了回车符(\b),水平制表符(\t)以及回行符(\r)是怎么工作的。这些概念要追溯到电脑还在使用电传打字机的时代,它们并不总是会在现代的图形界面上正确地翻译。例如,例3.10在一些Mac系统的实现上就不能够按照想要的方式来运行。

例3.10

3.5.1 当程序运行的时候发生了什么?

让我们一步步地浏览这个程序,好让它能够在ANSI实现下运行。第一个printf()函数语句(标1的那个)会发出警告信号(由\a引起),然后会打印如下的句子:

1
Enter your desired monthly salary:

因为在字符串最后没有\n符号,所以光标还是在冒号后面。

第二个printf()语句会接着第一句结束的地方开始,所以当它执行完毕时,屏幕上看起来是这个样子的。

1
Enter your desired monthly salary: $______

在冒号与美元符号之间有一个空格,因为第二个printf()的字符串是以空格开始的,7个退格符的作用是把光标向左移动7个单位。这就会将光标移到下划线字符前面,正好放在美元符号后面。通常地,退格符并不会移除前面的字符,但是有一些实现是有可能移除的,那样这个小练习就没有用了。

现在,你会输入你的回答,比如说你输入了2000.00,那么这一行看起来就会是这个样子。

1
Enter your desired monthly salary: $2000.00

你输入的字符将会覆盖换行符,而且当你点Enter键(或者Return)来输入你的回答时,光标就会跳转到下一行。

第三个printf()语句是以\n\t开始的,换行符将会将光标移动到下一行的最开始,而tab键通常会将光标移动到那一行的下一个制表位(但并不是一定),也就是第9列。然后字符串的剩余部分就会被打印下来。在这个语句执行之后,屏幕上看起来就会变成这样。

1
2
Enter your desired monthly salary: $2000.00
$2000.00 a month is $24000.00 a year.

因为这个printf()语句并没有使用换行符,光标还是在这一句的最后位置。

第四个printf()使用了\r符号,它会将光标移动到一行的开头位置,这个语句执行之后屏幕上就成了这样。

1
2
Enter your desired monthly salary $2000.00
Gee! $2000.00 a month is $24000.00 a year.

3.5.2 输出清理

printf()函数什么时候才会将输出发送到屏幕上呢?首先,printf()语句会将输出发送到缓冲区,时不时地,在缓冲区的内容就会被发送到屏幕上。对于输出什么时候从缓冲区发送到屏幕上,基础的C语言规则已经很清晰了:当缓冲区被充满时,当遇到一个换行符的时候,以及当有即将发生的输出行为时。(从缓冲区发送输出信息到屏幕或者是文件上叫做刷新缓冲区),例如,最先的两个printf()函数语句并不会填充缓冲区,也并没有包含换行符。在那种情况下,你可以使用一个换行符来刷新缓冲区,那样的话代码就可以写成这个样子:

1
2
printf("Enter your desired monthly salary: \n");
scanf("%f", &salary);

不管即将到来的输入信息是不是会刷新缓冲区,这几行代码都是可以正常工作的。但是,它也会将光标移到下一行,防止你把数据与提示字符串写到一行。另一种方式是使用fflush()函数,它会在第13章,文件输入与输出中讲到。

关键概念

C语言有着许多种数字类型,这也反映了C语言避免让编程人员陷入障碍的目的。比起用一种类型包管全部数字类型,C语言尽力给予编程人员对于数字的特定种类(比如说有符号与无符号),以及最能够适合特定程序的数字范围大小的选择。

在一台电脑上,浮点数与整型的基础是不一样的。它们会被以不同的形式储存于处理。两个32位的内存单元能够保存相同的位形式(二进制数表示是一样的。)但是如果其中一个被理解为float而另一个被理解为long,这两个内存单元就会代表完全不同也不相关的两个数,例如在个人电脑上,如果你使用代表256.0的浮点储存单元,但是让它被理解为long,你就会得到113246208这个数。C语言确实允许你使用多种数据类型来书写表达式,但是它会自动进行类型转换,所以真正的运算只有一种数据类型。

在电脑内存中,字符是以数字编码的形式呈现的,ASCII码是美国最常用的数字代码标准,但是C语言也支持其他代码集。字符常量是电脑上数字编码的符号表示-它是由单引号框定的单字符,比如'A'

总结

C语言有各种各样的数据类型,基本的数据类型可以分为两类:整型与浮点型,对于整型,两个最鲜明的区分就是分配给不同类型的储存空间,还有是否有符号。最小的整型是char,它可以有符号,也可以没有,这取决于不同的C语言实现,你可以根据自己的需求来选择signed charunsigned char,但是那通常是在你使用范围比较小的数字的情况下,而不是使用字符编码时。其他的整型还有short, int, long, long long等。C语言确保了所有的类型都至少会与它前面的类型一样大,它们都是有符号类型,但你还是可以加上unsigned修饰符来创造相应的无符号类型:unsigned short, unsigned int, unsigned long, unsigned long long或者你也可以使用signed修饰符来明显地写出它是有符号的。最后,还有_Bool类型,一种无符号类型,它只能保存0与1,分别代表着falsetrue

浮点类型有三种,分别是float, double与在ANSI C中的long double,每一个类型也是至少比它前面的那个类型一样大的。要是想的话,你也可以用_Complex_Imaginary与浮点类型关键字连用,来使用虚数与复数类型。例如,有double _Complexfloat _Imaginary类型。

整型可以被以十进制、八进制与十六进制的形式被输出,开头写0可以使一个数变为八进制数,写0x或者0X可以变为十六进制数。例如32, 040还有0x20就分别是十进制、八进制与十六进制的数字,但是它们代表的都是同一个值。另外使用l或者L符号可以声明一个long类型的常量值,ll或者LL可以声明一个long long类型的值。

字符常量是由单引号及其中的字符代表的:比如'Q', '8'。字符还包括转义序列,比如\n,它们代表着不会被打印出来的特定字符,你可以使用类似\007的形式来使用ASCII码的形式来代表字符。

浮点数可以以十进制小数点的形式来表示,也可以以指数符号的形式来表示,例如7.38E10

printf()函数能够让你利用格式化转换符,以最简洁的方式打印各种各样的数值,它由一个百分号符%与一个表示类型的字符组成,比如%d或者%f

回顾问题

你会在附录A中找到这些问题的答案。
1.你对于这几种数据你会使用哪种数据类型?

1.东斯普尔顿的人数 – int类型(16位即可)
2.在DVD上电影的价格–float类型
3.这一章最常见的字母–char类型
4.一个字母在这一章中出现的次数。– int类型(。)

2.为什么有时候你会使用long而不是int

有些时候表示的范围会更大,但是并没有超过long,使用long类型可以保证数据因为存放不下而溢出,从而导致不同机型显示不一而且不会报错的问题。

3.如果你想要得到一个32位有符号的整型,你会使用哪种可转移的类型,请写出所有类型,并说说每一种的合理性。

1.在<inttypes.h>中的int32_t类型
2.基本类型中的long或者int类型(对于int来说不一定)

long合理,因为关键字更简洁容易记,格式化符也不容易弄错
int32_tPRId32,long%ld
int32_t能够让编程人员选择适合的大小范围来进行编程,便于大小的记忆。

4.确认每种类型与它的含义。

1.'\b'回车符(转义序列)
2.1066一个整数,一千零六十六
3.99.44 一个两位小数,九十九点四四
4.0XAA,十六进制数,10 * 16 + 10 = 170.
5.2.0e30,指数形式的小数,2 * 10^30(10的30次方)

5.Dottie Cawm 编造了这个满是错误的程序,请找到这个程序中问题。

1
2
3
4
5
6
7
8
9
10
include <stdio.h>

main
{
float g; h;
float tax, rate;

g = e21;
tax = rate * g;
}

(实在是太明显了啊。。)
1.include前面忘了#
2.main。。。(int main(void),或者void main())
3.g后面用了分号来分隔,相当于h未规定类型。
4.应该是1e21,e21啥也不是。
5.tax类型与g的类型大小都太小了,应该用double,float表示不了精确的21位数,或者直接使用long long也可以,反正是整型。
6.忘了写return 0;

6.确定下面几种常量数据的类型,与打印他们所使用的格式化标识符。

·····12为整型,(int(作为常量,默认为int)),输出可以使用%d,%hd,%u,%LLu,%lu
·····0X3,十六进制数,%X
·····'C',字符常量,%c
·····2.34E07,后面看成八进制,但仍然是7,小数的指数形式,用%f或者%lf都可以。
·····\040,十进制的040为32,此即32,即' '(空格字符)
·····7.0,一位小数,可以使用%f或者%lf
·····6L,为long形式整型,可以使用%ld输出。
·····6.0f,为float形式小数,可以使用%f

7.同上

·····012,八进制数,十进制下为10,可以用%o输出。
·····2.9e05L,long double,2.9 * 10^5.
·····'s',字符常量,用%c.
·····100000,32位的int%ld,或者%lu或者更大的类型long long
·····'\n',转义序列换行符,直接打印即可。
·····20.0f,float类型,%f
·····0x44,十六进制数,68.用%x输出。

8.假设你的程序是以这几行声明语句开头的。

1
2
3
4
int imate = 2;
long shot = 53456;
char grade = 'A';
float log = 2.71828;

在下面的printf()函数语句中填入适当的格式化符。

1
2
printf("The odds against the %__ were %__ to 1.\n",imate, shot);
printf("A score of %__ is not an %__ grade.\n", log, grade);
1
2
printf("The odds against the %d were %ld to 1.\n",imate, shot);
printf("A score of %f is not an %c grade.\n", log, grade);

9.假设ch是一个字符变量,展示一下如何将回行符通过转义序列、十进制数值与八进制及十六进制字符常量的形式赋给ch的。

1
2
3
4
5
6
char ch;

ch = '\r';
ch = 13;
ch = '\0x0D';
ch = '\015';

10.纠正这个愚蠢的程序。
"Perfect..."
(在C语言中\表示除)

1.未指定形式参数的变量名。(main(int a))
2.注释符号没用对(要么//,要么/**/.)
3.声明变量使用关键字。(int cows, legs;
4.printf()字符串缺少了右边的引号。
5.scanf()少了取地址符&,而且legs的输入形式写错了,为'%d'
6.printf()格式化符为%d

11.说说下面的转义序列代表啥意思?

  1. \n,换行符,10.
  2. \\,相当于\.
  3. \",相当于".
  4. \t,相当于tab键。

编程训练

1.通过实验方式,弄清你的电脑是如何处理整型上溢、浮点数上溢以及浮点下溢的,也就是写一个拥有数据溢出问题的程序。

这个肯定会溢出,可以修改程序看看会发生什么。
我观察到的是:整型溢出会直接跳到它的最小范围开始加
比如2146483650 = - 2147483646

浮点上溢,会失去精度,但是只要未超过位数仍然可以表示,如果把指数调到40就会出INF(infinity),具体可以看上文对于浮点溢出的讲解。。

浮点下溢,会直接被看成0。

2.写一个能够根据你输入的ASCII码值返回对应字符的程序。

包含了转义序列,使用了switchif嵌套的结构。

3.写一个能够发出警报声,并且能够打印以下句子的程序。

1
2
Startled by the sudden sound, Sally shouted, "By the Great Pumpkin,
what was that!"

重点有两个,一个是掌握好换行,可以用一个printf加上一个换行符实现,也可以用两个。
另一个是如何在字符串内输出”,这也是我们周赛第一道题的考点

4.书写一个能够读入浮点数据,然后将它先以十进制小数的形式输出,再以指数形式输出。输出类似下方,但指数形式的实数部分是视系统而变的。

5.一年大概有3.156 * 10^7秒,写一个要求你输入以年计数的年龄并将它转化为秒数的程序。

My life...

6.一分子水大概是3.0 * 10^23克, 一夸脱水是950克。写一个要求你输入夸脱单位的水量,返回水的分子数的程序。

Too much...

7.一英寸是2.54厘米, 写一个要求你输入英寸高度,返回厘米高度的程序。你要是想的话也可以反过来转换。

1.inch to cm

2.cm to inch

End...
打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2023-2024 大学生暮暖
  • 访问人数: | 浏览次数:

请我喝杯奶茶吧~

支付宝
微信