添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
闯红灯的牛肉面  ·  android sdk 大华 ...·  1 年前    · 
15328076128  ·  printf - C, unknown ...·  3 年前    · 
天涯  ·  awk基本语法格式 - 简书·  3 年前    · 
天涯  ·  unix - Use tee (or ...·  3 年前    · 
打酱油的香槟  ·  Microsoft.Management.I ...·  2 月前    · 
绅士的石榴  ·  c++ curl ...·  1 年前    · 
买醉的香蕉  ·  kotlin - Spring Boot ...·  1 年前    · 
追风的四季豆  ·  解决fatal error ...·  1 年前    · 
《C Primer Plus》·第3章·数据和C

《C Primer Plus》·第3章·数据和C

引言

本章介绍以下内容:

■关键字——int、short、long,unsigned,char,float,double、_Bool、Complex、_imaginary;

■ 运算符——sizeof();

■ 函数——scanf();

■ 整数类型和浮点数类型的区别;

■ 如何书写整型和浮点型常数,如何声明这些类型的变量;

■ 如何使用printf()和scanf()函数读写不同类型的值。

程序离不开数据。把数字、字母和文字输入计算机,就是希望它利用这些数据完成某些任务。例如,需要计算一份利息或显示一份葡萄酒商的排序列表。本章除了介绍如何读取数据外,还将教会读者如何操控数据。

C语言提供两大系列的多种数据类型。本章详细介绍两大数据类型:整数类型和浮点数类型,讲解这些数据类型是什么、如何声明它们、如何以及何时使用它们。除此之外,还将介绍常量和变量的区别。读者很快就能看到第1个交互式程序。


3.1:示例程序

本章仍从一个简单的程序开始。如果发现有不熟悉的内容,别担心,我们稍后会详细解释。该程序的意图比较明了,请试着编译并运行程序清单3.1中的源代码。为了节省时间,在输入源代码时可省略注释。

【程序3.1】

/* platinum.c -- your weight in platinum */
#include <stdio.h>
int main(void)
	float weight; /*你的体重 */
	float value; /*相等重量的白金价值 */
	printf("Are you worth your weight in platinum?\n");
	printf("Let's check it out.\n");
	printf("Please enter your weight in pounds:");
	/*获取用户的输入 */
	scanf("%f",&weight);
	/*假设白金的价格是每盎司$1700 /
	/*14.5833用于把英磅常衡蛊司转换为金衡盎司'*/
	value=1700.0*weight*14.5833;
	printf("Your weight in platinum is worth $%.2f.\n",value);
	printf("You are easily worth that! If platinum prices drop,\n");
	printf("eat more to maintain your value.\n");
	return 0;

注意,“enter your weight”的意思是输入你的体重,然后按下Enter或Return键(不要键入体重后就一直等着)。按下Enter键是告知计算机,你已完成输入数据。该程序需要你输入一个数字(如,155),而不是单词(如,too much)。 如果输入字母而不是数字,会导致程序出问题。这个问题要用if语句来解决(详见第7章),因此请先输入数字


3.1.1:程序中的新元素

程序清单3.1中包含C语言的一些新元素。

■ 注意,代码中使用了一种新的变量声明。前面的例子中只使用了整数类型的变量(int),但是本例用了 浮点数类型(float) 的变量,以便处理更大范围的数据。 float类型可以存储带小数的数字

■程序中演示了常量的几种新写法。现在可以使用带小数点的数了。

■ 为了打印新类型的变量, 在printf()中使用%f处理浮点值 %.2f中的.2用于精确控制输出,指定输出的浮点数只显示小数点后面两位

■ scanf()函数用于读取键盘的输入。%f说明scanf()要读取用户从键盘输入的浮点数, &weight告诉scanf()把输入的值赋给名为weight的变量 。scanf()函数使用&符号表明找到weight变量的地点。下一章将详细讨论&。就目前而言,请按照这样写。

■ 也许本程序最突出的新特点是它的交互性。计算机向用户询问信息,然后用户输入数字。与非交互式程序相比,交互式程序用起来更有趣。更重要的是,交互式使得程序更加灵活。例如,示例程序可以使用任何合理的体重,而不只是156磅。不必重写程序,就可以根据不同体重进行计算。scanf()和printf()函数用于实现这种交互。scanf()函数读取用户从键盘输入的数据,并把数据传递给程序;printf()函数读取程序中的数据,并把数据显示在屏幕上。把两个函数结合起来,就可以建立人机双向通信(见图3.1),这让使用计算机更加饶有趣味。


3.2:变量与常量数据

在程序的指导下,计算机可以做许多事情,如数值计算、名字排序、执行语言或视频命令、计算彗星轨道、准备邮件列表、拨电话号码、画画、做决策或其他你能想到的事情。要完成这些任务,程序需要使用数据,即承载信息的数字和字符。有些数据类型在程序使用之前已经预先设定好了,在整个程序的运行过程中没有变化,这些称为 常量(constant) 。其他数据类型在程序运行期间可能会改变或被赋值,这些称为 变量(variable) 。在示例程序中,weight是一个变量,14.5833是一个常量。那么,1700.0是常量还是变量?在现实生活中,白金的价格不会是常量,但是在程序中,像1700.0这样的价格被视为常量。


3.3:数据:数据类型关键字

不仅变量和常量不同,不同的数据类型之间也有差异。

一些数据类型表示数字,一些数据类型表示字母(更普遍地说是字符)。C通过识别一些基本的数据类型来区分和使用这些不同的数据类型。如果数据是常量,编译器一般通过用户书写的形式来识别类型(如,42是整数,42.100是浮点数)。但是, 对变量而言,要在声明时指定其类型 。稍后会详细介绍如何声明变量。

现在,我们先来了解一下 C语言的基本类型关键字 。K&RC给出了7个与类型相关的关键字。C90标准添加了2个关键字,C99标准又添加了3个关键字(见表3.1)。

在C语言中,用int关键字来表示基本的整数类型。后3个关键字(long、short和unsigned)和C90新增的signed用于提供基本整数类型的变式,例如unsigned short int和long long int。

char关键字用于指定字母和其他字符。另外,char类型也可以表示较小的整数 。float、double和long double表示带小数点的数。 _Bool类型表示布尔值(true或false),_Complex和_Imaginary分别表示复数和虚数

通过这些关键字创建的类型,按计算机的存储方式可分为两大基本类型:整数类型和浮点数类型。


位、字节和字

位、字节和字是描述计算机数据单元或存储单元的术语。这里主要指 存储单元,最小的存储单元是位(bit),可以存储0或1(或者说,位用于设置“开”或“关”) 。虽然1位存储的信息有限,但是计算机中位的数量十分庞大。

位是计算机内存的基本构建块。 字节(byte)是常用的计算机存储单位 。对于几乎所有的机器, 1字节均为8位 。这是字节的标准定义,至少在衡量存储单位时是这样(但是,C语言对此有不同的定义,请参阅本章3.4.3节)。既然1位可以表示0或1,那么8位字节就有256(2的8次方)种可能的0、1的组合。通过二进制编码(仅用0和1便可表示数字),便可表示0~255的整数或一组字符。

字(word)是设计计算机时给定的自然存储单位 。对于8位的微型计算机,1个字长只有8位。从那以后,个人计算机字长增至16位、32位,直到目前的64位。 计算机的字长越大,其数据转移越快,允许的内存访问也更多


3.3.1:整数和浮点数

整数类型?浮点数类型?如果觉得这些术语非常陌生,别担心,下面先简述它们的含义。如果不熟悉位、字节和字的概念,请阅读上面的内容。刚开始学习时,不必了解所有的细节,就像学习开车之前不必详细了解汽车内部引擎的原理一样。但是,了解一些计算机或汽车引擎内部的原理会对你有所帮助。

对我们而言,整数和浮点数的区别是它们的书写方式不同 对计算机而言,它们的区别是存储方式不同 。下面详细介绍整数和浮点数。


3.3.2:整数

和数学的概念一样, 在C语言中,整数是没有小数部分的数 。例如,2、-23和2456都是整数。而3.14、0.22和2.000都不是整数。计算机以二进制数字存储整数,例如,整数7以二进制写是111。因此,要在8位字节中存储该数字,需要把前5位都设置成0, 后3位设置成1(如图3.2所示)。


3.3.3:浮点数

浮点数与数学中实数的概念差不多。2.75、3.16E7、7.00和2e-8都是浮点数。注意, 在一个值后面加上一个小数点,该值就成为一个浮点值 。所以,7是整数,7.00是浮点数。显然,书写浮点数有多种形式。稍后将详细介绍e记数法,这里先做简要介绍:3.16E7表示 3.16×10^{7}

这里关键要理解浮点数和整数的存储方案不同。 计算机把浮点数分成小数部分和指数部分来表示,而且分开存储这两部分 。因此,虽然7.00和7在数值上相同,但是它们的存储方式不同。在十进制下,可以把7.0写成0.7E1。这里,0.7是小数部分,1是指数部分。图3.3演示了一个存储浮点数的例子。

当然, 计算机在内部使用二进制和2的幂进行存储,而不是10的幂 。第15章将详述相关内容。现在,我们着重讲解这两种类型的实际区别。

■整数没有小数部分,浮点数有小数部分。

浮点数可以表示的范围比整数大 。参见本章末的表3.3。

■对于一些算术运算(如,两个很大的数相减),浮点数损失的精度更多。

■因为在任何区间内(如,1.0到2.0之间)都存在无穷多个实数,所以计算机的浮点数不能表示区间内所有的值。 浮点数通常只是实际值的近似值 。例如,7.0可能被存储为浮点值6.99999。稍后会讨论更多精度方面的内容。

■过去,浮点运算比整数运算慢。不过,现在许多CPU都包含浮点处理器,缩小了速度上的差距。


3.4:C语言基本数据类型

本节将详细介绍C语言的基本数据类型,包括如何声明变量、如何表示字面值常量(如,5或2.78),以及典型的用法。一些老式的C语言编译器无法支持这里提到的所有类型,请查阅你使用的编译器文档,了解可以使用哪些类型。


3.4.1:int类型

C语言提供了许多整数类型,为什么一种类型不够用?因为C语言让程序员针对不同情况选择不同的类型。特别是,C语言中的整数类型可表示不同的取值范围和正负值。一般情况使用int类型即可,但是为满足特定任务和机器的要求,还可以选择其他类型。

int类型是有符号整型,即int类型的值必须是整数,可以是正整数、负整数或零 。其 取值范围依计算机系统而异 。一般而言, 存储一个int要占用一个机器字长 。因此,早期的16位IBM Pc兼容机使用16位来存储一个int值,其取值范围(即int值的取值范围)是-32768~32767。目前的个人计算机一般是32位,因此用32位存储一个int值。现在,个人计算机产业正逐步向着64位处理器发展,自然能存储更大的整数。ISO C规定int的取值范围最小为-32768~32767。 一般而言,系统用一个特殊位的值表示有符号整数的正负号 。第15章将介绍常用的方法。


——1.声明int变量

第2章中已经用int声明过基本整型变量。先写上int,然后写变量名,最后加上一个分号。要 声明多个变量,可以单独声明每个变量,也可在int后面列出多个变量名,变量名之间用逗号分隔

下面都是有效的声明:

int erns;
int hogs,cows,goats;

可以分别在4条声明中声明各变量,也可以在一条声明中声明4个变量。两种方法的效果相同,都为4个int大小的变量赋予名称并分配内存空间。

以上声明创建了变量,但是并没有给它们提供值。变量如何获得值?前面介绍过在程序中获取值的两种途径。第1种途径是赋值:

cows = 112;

第2种途径是,通过函数(如,scanf( ))获得值。接下来,我们着重介绍第3种途径。


——2.初始化变量

初始化(initialize)变量就是为变量赋一个初始值 。在C语言中,初始化可以直接在声明中完成。只需在变量名后面加上赋值运算符(=)和待赋给变量的值即可。如下所示:

int hogs = 21;
int cows = 32,goats =14;
int dogs,cats=94;//有效,但是这种格式很糟粗

以上示例的最后一行,只初始化了cats,并未初始化dogs。这种写法很容易让人误认为dogs也被初始化为94,所以最好不要把初始化的变量和未初始化的变量放在同一条声明中。

简而言之, 声明为变量创建和标记存储空间,并为其指定初始值 (如图3.4所示)。


——3. int类型常量

上面示例中出现的整数(21、32、14和94)都是整型常量或整型字面量。C语言把不含小数点和指数的数作为整数。因此,22和-44都是整型常量,但是22.0和2.2E1则不是。

C语言把大多数整型常量视为int类型,但是非常大的整数除外 。详见后面“long常量和long long常量”小节对long int类型的讨论。


——4.打印int值

可以使用printf()函数打印int类型的值。第2章中介绍过,%d指明了在一行中打印整数的位置。 %d称为转换说明,它指定了printf()应使用什么格式来显示一个值

格式化字符串中的每个%d都与待打印变量列表中相应的int值匹配。这个值可以是int类型的变量、int类型的常量或其他任何值为int类型的表达式。作为程序员,要确保转换说明的数量与待打印值的数量相同,编译器不会捕获这类型的错误。

程序清单3.2演示了一个简单的程序,程序中初始化了一个变量,并打印该变量的值、一个常量值和一个简单表达式的值。另外,程序还演示了如果粗心犯错会导致什么结果。

【程序3.2】

#include <stdio.h>
int main(void)
	int ten = 10;
	int two = 2;
	printf("Doing it right:");
	printf("%d minus %d is %d\n",ten,2,ten-two);
	printf("Doing it wrong:");
	printf("%d minus %d is %d\n",ten);//遗漏2个参数
	return 0;
}

在第一行输出中 ,第1个%d对应int类型变量ten;第2个%d对应int类型常量2;第3个%d对应int类型表达式ten - two的值。

在第二行输出中 ,第1个%d对应ten的值,但是由于没有给后两个%d提供任何值,所以打印出的值是内存中的任意值(读者在运行该程序时显示的这两个数值会与输出示例中的数值不同,因为内存中存储的数据不同,而且编译器管理内存的位置也不同)。

你可能会抱怨编译器为何不能捕获这种明显的错误,但实际上问题出在printf()不寻常的设计。大部分函数都需要指定数目的参数,编译器会检查参数的数目是否正确。但是, printf()函数的参数数目不定,可以有1个、2个、3个或更多 ,编译器也爱莫能助。记住, 使用printf()函数时,要确保转换说明的数量与待打印值的数量相等


——5.八进制和十六进制

通常, C语言都假定整型常量是十进制数 。然而,许多程序员很喜欢使用八进制和十六进制数。因为8和16都是2的幂,而10却不是。

显然, 八进制和十六进制记数系统在表达与计算机相关的值时很方便 。例如,十进制数65536经常出现在16位机中,用十六进制表示正好是10000。另外,十六进制数的每一位的数恰好由4位二进制数表示。例如,十六进制数3的二进制数是0011,十六进制数5的二进制数是0101。因此,十六进制数35的位组合(bitpattern)是00110101,十六进制数53的位组合是01010011。这种对应关系使得十六进制和二进制的转换非常方便。

但是,计算机如何知道10000是十进制、十六进制还是二进制? 在C语言中,用特定的前缀表示使用哪种进制 0x或0X前缀表示十六进制值 ,所以十进制数16表示成十六进制是0x10或0X10。与此类似, 0前缀表示八进制 。例如,十进制数16表示成八进制是020。第15章将更全面地介绍进制相关的内容。

要清楚, 使用不同的进制数是为了方便,不会影响数被存储的方式 。也就是说,无论把数字写成16、020或0x10,存储该数的方式都相同,因为 计算机内部都以二进制进行编码


——6.显示八进制和十六进制

在C程序中,既可以使用也可以显示不同进制的数。不同的进制要使用不同的转换说明。 以十进制显示数字 ,使用%d; 以八进制显示数字 ,使用%o; 以十六进制显示数字 ,使用%x。另外,要显示各进制数的前缀0、0x和0X,必须分别使用%#0、%#x、%#X。程序清单3.3演示了一个小程序(回忆一下,在某些集成开发环境(IDE)下编写的代码中插入getchar();语句,程序在执行完毕后不会立即关闭执行窗口)。

【程序3.3】

//bases.c--以十进制、八进制、十六进制打印十进制数100
#include <stdio.h>
int main(void)
	int x= 100;
	printf("dec=%d;octal =%o;hex=%x\n",x,x,x);
	printf("dec=%d;octal = %#o;hex=%#x\n",x,x,x);
	return 0;	
}

该程序以3种不同记数系统显示同一个值。printf()函数做了相应的转换。注意, 如果要在八进制和十六进制值前显示0和0x前缀,要分别在转换说明中加入#


3.4.2:其他整数类型

初学C语言时,int类型应该能满足大多数程序的整数类型需求。尽管如此,还应了解一下整型的其他形式。当然,也可以略过本节跳至3.4.3节阅读char类型的相关内容,以后有需要时再阅读本节。

C语言提供3个附属关键字修饰基本整数类型:short、long和unsigned。应记住以下几点:

■ short int类型(或者简写为short)占用的存储空间可能比int类型少,常用于较小数值的场合以节省空间。与int类似,short是有符号类型。

■ long int或long占用的存储空间可能比int多,适用于较大数值的场合。与int类似,long是有符号类型。

■ long long int或long long(C99标准加入)占用的存储空间可能比long多,适用于更大数值的场合。 该类型至少占64位 。与int类似,long long是有符号类型。

unsigned int或unsigned只用于非负值的场合 。这种类型与有符号类型表示的范围不同。

例如,16位unsigned int允许的取值范围是0~65535,而不是-32768~32767。用于表示正负号的位现在用于表示另一个二进制位,所以 无符号整型可以表示更大的数

■ 在C90标准中,添加了unsigned long int或unsigned long和unsigned short int 或unsigned short类型。C99标准又添加了unsigned long long int或unsigned long long。

在任何有符号类型前面添加关键字signed,可强调使用有符号类型的意图 。例如,short、short int、signed short、signed short int都表示同一种类型。


——1.声明其他整数类型

其他整数类型的声明方式与int类型相同,下面列出了一些例子。不是所有的C编译器都能识别最后3条声明,最后一个例子所有的类型是C99标准新增的。

long int estine;
long johns;
short int erns;
short ribs;
unsigned int s_count;
unsigned players;
unsigned long headcount;
unsigned short yesvotes;
long long ago;

——2.使用多种整数类型的原因

为什么说short类型“可能”比int类型占用的空间少,long类型“可能”比int类型占用的空间多?因为 C语言只规定了short占用的存储空间不能多于int,long占用的存储空间不能少于int 。这样规定是为了 适应不同的机器

例如,过去的一台运行windows 3.x的机器上,int类型和short类型都占16位,long类型占32位。后来Windows和苹果系统都使用16位存储short类型,32位存储int类型和long类型(使用32位可以表示的整数数值超过20亿)。现在,计算机普遍使用64位处理器, 为了存储64位的整数,才引入了long long类型

现在,个人计算机上最常见的设置是, long long占64位,long占32位,short占16位,int占16位或32位 (依计算机的自然字长而定)。原则上,这4种类型代表4种不同的大小,但是在实际使用中,有些类型之间通常有重叠。

C标准对基本数据类型只规定了允许的最小大小。对于16位机,short和int的最小取值范围是[-32768,32767];对于32位机,long的最小取值范围是[-2147483648,2147483647]。对于unsigned short和unsigned int,最小取值范围是[0,65535]:对于unsigned long,最小取值范围是[0,4294967295]。long long类型是为了支持64位的需求,最小取值范围是[-9223372036854775808,9223372036854775807];unsigned long long的最小取值范围是 [0,18446744073709551615]。如果要开支票,这个数是一千八百亿亿六千七百四十四万亿零七百三十七亿零九百五十五万一千六百一十五。但是,谁会去数?

int类型那么多,应该如何选择?首先,考虑 unsigned类型。这种类型的数常用于计数 ,因为计数不用负数。而且,unsigned类型可以表示更大的正数。 如果一个数超出了int类型的取值范围,且在long类型的取值范围内时,使用long类型。然而,对于 那些long占用的空间比int大的系统,使用long类型会减慢运算速度 。因此,如非必要,请不要使用long类型。另外要注意一点:如果在long类型和int类型占用空间相同的机器上编写代码,当确实需要32位的整数时,应使用long类型而不是int类型,以便把程序移植到16位机后仍然可以正常工作。类似地,如果确实需要64位的整数,应使用long long类型。 如果在int设置为32位的系统中要使用16位的值,应使用short类型以节省存储空间。通常,只有当程序使用相对于系统可用内存较大的整型数组时,才需要重点考虑节省空间的问题。使用short类型的另一个原因是,计算机中某些组件使用的硬件寄存器是16位。


——3. long常量和long long常量

通常,程序代码中使用的数字(如,2345)都被存储为int类型。如果使用1000000这样的大数字,超出了int类型能表示的范围,编译器会将其视为long int类型(假设这种类型可以表示该数字)。如果数字超出long可表示的最大值,编译器则将其视为unsigned long类型。如果还不够大,编译器则将其视为long long或unsigned long long类型(前提是编译器能识别这些类型)。

八进制和十六进制常量被视为int类型。如果值太大,编译器会尝试使用unsigned int。如果还不够大,编译器会依次使用long、unsigned long、long long和unsigned long long类型。

有些情况下,需要编译器以long类型存储一个小数字。例如,编程时要显式使用IBM PC上的内存地址时。另外,一些C标准函数也要求使用long类型的值。要把一个较小的常量作为long类型对待,

可以在值的末尾加上l(小写的L)或L后缀。使用L后缀更好,因为l看上去和数字1很像。因此,在int为16位、long为32位的系统中,会把7作为16位存储,把7L作为32位存储。l或L后缀也可用于八进制和十六进制整数,如020L和0x10L。

类似地,在支持long long类型的系统中,也可以使用ll或LL后缀来表示long long类型的值,如3LL。另外,u或U后缀表示unsigned long long,如5u11、10LLU、6LLU或9011。


整数溢出

如果整数超出了相应类型的取值范围会怎样?下面分别将有符号类型和无符号类型的整数设置为比最大值略大,看看会发生什么( printf()函数使用u说明显示unsigned int类型的值 )。

【程序3.4】

/*toobig.c--超出系统允许的最大int值*/ 
#include <stdio.h> 
int main(void) 
	int i=2147483647; 
	unsigned int j=4294967295; 
	printf("%d %d %d\n",i,i+1,i+2); 
	printf("%u %u %u\n",j,j+1,j+2); 
	return 0; 
}

在我们的系统下输出的结果是:

2147483647-2147483648 -2147483647
4294967295 0 1

可以把无符号整数j看作是汽车的里程表。 当达到它能表示的最大值时,会重新从起始点开始 。整数i也是类似的情况。它们主要的区别是, 在超过最大值时,unsigned int类型的变量j从0开始 ;而int类型的变量i从-2147483648开始。注意, 当i超出(溢出)其相应类型所能表示的最大值时,系统并未通知用户 。因此,在编程时必须自己注意这类问题。

溢出行为是未定义的行为,C标准并未定义有符号类型的溢出规则。以上描述的溢出行为比较有代表性,但是也可能会出现其他情况。


——4.打印short、long、long long和unsigned类型

打印unsigned int类型的值,使用%u换说明;打印long类型的值,使用号%ld转换说明。如果系统中int和long的大小相同,使用%d就行。但是,这样的程序被移植到其他系统(int和long类型的大小不同)中会无法正常工作。

在x和o前面可以使用l前缀,%lx表示以十六进制格式打印long类型整数,%lo表示以八进制格式打印long类型整数。注意, 虽然C允许使用大写或小写的常量后缀,但是在转换说明中只能用小写

C语言有多种printf()格式。对于short类型,可以使用h前缀。%hd表示以十进制显示short类型的整数,%ho表示以八进制显示short类型的整数。h和l前缀都可以和u一起使用,用于表示无符号类型。例如,%lu表示打印unsigned long类型的值。

程序清单3.5演示了一些例子。对于支持long long类型的系统,%lld和号%llu分别表示有符号和无符号类型。第4章将详细介绍转换说明。

【程序3.5】

/* print2.c--更多printf()的特性*/
#include <stdio.h>
int main(void)
	unsigned int un=3000000000;//int为32位和short为16位的系统
	short end = 200;
	long big = 65537;
	long long verybig =12345678908642;
	printf("un = %u and not %d\n",un,un);
	printf("end = %hd and %d\n",end,end);
	printf("big = %ld and not %hd\n",big,big);
	printf("verybig= %lld and not %lld\n",verybig,verybig);
	return 0;

在特定的系统中输出如下(输出的结果可能不同):

un = 3000000000 and not -1294967296
end = 200 and 200
big = 65537 and not 1
verybig= 12345678908642 and not 1942899938

该例表明,使用错误的转换说明会得到意想不到的结果。 第1行输出 ,对于无符号变量un,使用%d会生成负值!其原因是,无符号值3000000000和有符号值-129496296在系统内存中的内部表示完全相同(详见第15章)。因此,如果告诉printf()该数是无符号数,它打印一个值;如果告诉它该数是有符号数,它将打印另一个值。在待打印的值大于有符号值的最大值时,会发生这种情况。对于较小的正数(如96),有符号和无符号类型的存储、显示都相同。

第2行输出 ,对于short类型的变量end,在printf()中无论指定以short类型(%hd)还是int类型打印,打印出来的值都相同。这是因为 在给函数传递参数时,C编译器把short类型的值自动转换成int类型的值

你可能会提出疑问:为什么要进行转换?h修饰符有什么用?第1个问题的答案是, int类型被认为是计算机处理整数类型时最高效的类型 。因此, 在short和int类型的大小不同的计算机中,用int类型的参数传递速度更快 。第2个问题的答案是, 使用h修饰符可以显示较大整数被截断成short类型值的情况

第3行输出就演示了这种情况。把65537以二进制格式写成一个32位数是00000000000000010000000000000001。使用%hd,printf()只会查看后16位,所以显示的值是1。与此类似,输出的最后一行先显示了verybig的完整值,然后由于使用了%ld,printf()只显示了存储在后32位的值。

本章前面介绍过,程序员必须确保转换说明的数量和待打印值的数量相同。以上内容也提醒读者,程序员还必须根据待打印值的类型使用正确的转换说明。


3.4.3:使用字符:char类型

char类型用于存储字符(如,字母或标点符号),但是 从技术层面看,char是整数类型 。因为 char类型实际上存储的是整数而不是字符 。计算机使用数字编码来处理字符,即 用特定的整数表示特定的字符

美国最常用的编码是ASCII编码,本书也使用此编码。例如,在ASCII码中,整数65代表大写字母A。因此,存储字母A实际上存储的是整数65。 标准ASCII码的范围是0~127,只需7位二进制数即可表示

通常,char类型被定义为8位的存储单元 ,因此容纳标准ASCII码绰绰有余。许多其他系统(如IMB PC和苹果Macs)还提供扩展ASCII码,也在8位的表示范围之内。一般而言,C语言会保证char类型足够大,以存储系统(实现C语言的系统)的基本字符集。


——1.声明char类型变量

char类型变量的声明方式与其他类型变量的声明方式相同。下面是一些例子:

char response;
char itable,latan;

以上声明创建了3个char类型的变量:response、itable和latan。


——2.字符常量和初始化

如果要把一个字符常量初始化为字母A,不必背下ASCII码,用计算机语言很容易做到。通过以下初始化把字母A赋给grade即可:

char grade ='A';

在C语言中, 用单引号括起来的单个字符被称为字符常量 (character constant)。编译器一发现'A',就会将其转换成相应的代码值。 单引号必不可少 。下面还有一些其他的例子:

char broiled; //声明一个char类型的变量
broiled='T'; //为其赋值,正确
broiled = T;
broiled =“T"; //错误!此时"y"是一个字符串

如上所示,如果省略单引号,编译器认为T是一个变量名;如果把T用双引号括起来,编译器则认为“T”是一个字符串。字符串的内容将在第4章中介绍。

实际上,字符是以数值形式存储的,所以也可使用数字代码值来赋值;

char grade=65;//对于ASCII,这样做没问题,但这是一种不好的编程风格

在本例中,虽然65是int类型,但是它在char类型能表示的范围内,所以将其赋值给grade没问题。由于65是字母A对应的ASCII码,因此本例是把A赋给grade。注意,能这样做的前提是系统使用ASCII码。其实,用'A'代替65才是较为妥当的做法,这样在任何系统中都不会出问题。因此,最好使用字符常量,而不是数字代码值。

奇怪的是,C语言将字符常量视为int类型而非char类型。例如,在int为32位、char为8位的ASCII系统中,有下面的代码:

char grade ='B';

本来'B'对应的数值66存储在32位的存储单元中,现在却可以存储在8位的存储单元中(grade)。利用字符常量的这种特性,可以定义一个字符常量'FATE',即把4个独立的8位ASCII码存储在一个32位存储单元中。如果把这样的字符常量赋给char类型变量grade,只有最后8位有效。因此,grade的值是 'E'。


——3.非打印字符

单引号只适用于字符、数字和标点符号,浏览ASCII表会发现,有些ASCII字符打印不出来。例如,一些代表行为的字符(如,退格、换行、终端响铃或蜂鸣)。C语言提供了3种方法表示这些字符。

第1种方法前面介绍过——使用ASCII码。例如,蜂鸣字符的ASCII值是7,因此可以这样写;

char beepm 7;

第2种方法是,使用特殊的符号序列表示一些特殊的字符。这些符号序列叫作转义序列(escape sequence)。表3.2列出了转义序列及其含义。

把转义序列赋给字符变量时,必须用单引号把转义序列括起来 。例如,假设有下面一行代码:

char nerf='\n';

稍后打印变量nerf的效果是,在打印机或屏幕上另起一行。

现在,我们来仔细分析一下转义序列。使用C90新增的警报字符(\a)是否能产生听到或看到的警报,取决于计算机的硬件,蜂鸣是最常见的警报(在一些系统中,警报字符不起作用)。

C标准规定警报字符不得改变活跃位置。标准中的活跃位置(acrive position)指的是显示设备(屏幕、电传打字机、打印机等)中下一个字符将出现的位置。简而言之,平时常说的 屏幕光标位置就是活跃位置 。在程序中把警报字符输出在屏幕上的效果是,发出一声蜂鸣,但不会移动屏幕光标。

接下来的转义字符\b、\f、\n、\r、\t和\v是常用的输出设备控制字符。了解它们最好的方式是查看它们对活跃位置的影响。换页符(\f)把活跃位置移至下一页的开始处;换行符(\n)把活跃位置移至下一行的开始处;回车符(\r)把活跃位置移动到当前行的开始处;水平制表符(\t)将活跃位置移至下一个水平制表点(通常是第1个、第9个、第17个、第25个等字符位置);垂直制表符(\v)把活跃位置移至下一个垂直制表点。

这些转义序列字符不一定在所有的显示设备上都起作用 。例如,换页符和垂直制表符在PC屏幕上会生成奇怪的符号,光标并不会移动。只有将其输出到打印机上时才会产生前面描述的效果。

接下来的3个转义序列 (\\、\’、\”)用于打印\、’、”字符(由于这些字符用于定义字符常量,是printf()函数的一部分,若直接使用它们会造成混乱) 。如果打印下面一行内容:

Gramps sez,"a \ is a backslash.”

应这样编写代码:

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

表3.2中的最后两个转义序列(\0oo和\xhh)是ASCII码的特殊表示。如果要用八进制ASCII码表示一个字符,可以在编码值前面加一个反斜杠(\)并用单引号括起来。例如,如果编译器不识别警报字符(\a),可以使用ASCII码来代替:

beep ='\007';

可以省略前面的0,'\07'甚至'\7'都可以。即使没有前缀0,编译器在处理这种写法时,仍会解释为八进制。

从C90开始,不仅可以用十进制、八进制形式表示字符常量,C语言还提供了第3种选择——用十六进制形式表示字符常量,即反斜杠后面跟一个x或X,再加上1~3位十六进制数字。例如,Ctrl+P字符的ASCII十六进制码是10(相当于十进制的16),可表示为'\x10'或'\×010'。图3.5列出了一些整数类型的不同进制形式。

使用ASCII码时,注意数字和数字字符的区别 。例如,字符4对应的ASCII码是52。'4'表示字符4,而不是数值4.

关于转义序列,读者可能有下面3个问题。

■ 上面最后一个例子printf("Gramps sez,\"a \\ is a backslash.\"\n");,为何没有用单引号把转义序列括起来? 无论是普通字符还是转义序列,只要是双引号括起来的字符集合,就无需用单引号括起来 。双引号中的字符集合叫作字符串(详见第4章)。注意,该例中的其他字符(G、r、a、m、p、s等)都没有用单引号括起来。

与此类似,printf("Hello!\007\n");将打印Hello!并发出一声蜂鸣,而printf("Hello!7\n");则打印Hello!7。不是转义序列中的数字将作为普通字符被打印出来。

■何时使用ASCII码?何时使用转义序列?如果要在转义序列(假设使用‘\f')和ASCII码('\014')之间选择,请选择前者(即‘\f')。这样的写法不仅更好记,而且可移植性更高。‘\f'在不使用ASCII码的系统中,仍然有效。

■如果要使用ASCII码,为何要写成'\032'而不是032?首先,'\032'能更清晰地表达程序员使用字符编码的意图。其次,类似032这样的转义序列可以嵌入C的字符串中,如printf("Hello!\007\n");中就嵌入了\007。


——4.打印字符

printf()函数用%c指明待打印的字符

前面介绍过,一个字符变量实际上被存储为1字节的整数值。因此,如果用%d转换说明打印char类型变量的值,打印的是一个整数。而转换说明告诉printf()打印该整数值对应的字符。

程序清单3.6演示了打印char类型变量的两种方式。

//charcode.c-显示字符的代码编号
#include <stdio.h>
int main(void)
	char ch;
	printf("Please enter a character.\n");
	scanf("%c",&ch); //用户输入字苻
	printf("The code for %c is %d.\n",ch,ch);
	return 0;
}

运行该程序时,在输入字母后不要忘记按下Enter或Return键。随后,scanf()函数会读取用户输入的字符, &符号表示把输入的字符赋给变量ch 。接着,printf()函数打印ch的值两次,第1次打印一个字符(对应代码中的%c,第2次打印一个十进制整数值(对应代码中的%d)。

注意,printf()函数中的转换说明决定了数据的显示方式,而不是数据的存储方式(见图3.6)。


——5.有符号还是无符号

有些C编译器把char实现为有符号类型,这意味着char可表示的范围是-128~127。而有些C编译器把char实现为无符号类型,那么char可表示的范围是0~255。请查阅相应的编译器手册,确定正在使用的编译器如何实现char类型。或者,可以查阅limits.h头文件。下一章将详细介绍头文件的内容。

根据C90标准,C语言允许在关键字char前面使用signed或unsigned。这样,无论编译器默认char是什么类型, signed char表示有符号类型,而unsigned char表示无符号类型。这在用char类型处理小整数时很有用

如果只用char处理字符,那么char前面无需使用任何修饰符。


3.4.4 :_Bool类型

C99标准添加了_Bool类型,用于表示 布尔值,即逻辑值true和false 。因为C语言用值1表示true,值0表示false,所以 _Bool类型实际上也是一种整数类型 。但原则上它仅占用1位存储空间,因为对0和1而言,1位的存储空间足够了。

程序通过布尔值可选择执行哪部分代码 。我们将在第6章和第7章中详述相关内容。


3.4.5:可移植类型:stdint.h和inttypes.h

c语言提供了许多有用的整数类型。但是,某些类型名在不同系统中的功能不一样。 c99新增了两个头文件stdint.h和inttypes.h,以确保c语言的类型在各系统中的功能相同

C语言为现有类型创建了更多类型名。这些新的类型名定义在stdint.h头文件中。例如,int32_t表示32位的有符号整数类型。在使用32位int的系统中,头文件会把int32_t作为int的别名。

不同的系统也可以定义相同的类型名。例如,int为16位、long为32位的系统会把int32_t作为long的别名。然后,使用int32_t类型编写程序,并包含stdint.h头文件时,编译器会把int或long替换成与当前系统匹配的类型。

上面讨论的类型别名是精确宽度整数类型(exact-width integer type)的示例。int32_t表示整数类型的宽度正好是32位。但是,计算机的底层系统可能不支持。因此,精确宽度整数类型是可选项。

如果系统不支持精确宽度整数类型怎么办?C99和C11提供了第2类别名集合。一些类型名保证所表示的类型一定是至少有指定宽度的最小整数类型。这组类型集合被称为最小宽度类型(minimum width type)。例如,int_least8_t是可容纳8位有符号整数值的类型中宽度最小的类型的一个别名。如果某系统的最小整数类型是16位,可能不会定义int8_t类型。尽管如此,该系统仍可使用int_least8_t类型,但可能把该类型实现为16位的整数类型。

当然,一些程序员更关心速度而非空间。为此,C99和C11定义了一组可使计算达到最快的类型集合。

这组类型集合被称为最快最小宽度类型(fastst minimum width type)。例如,int_fast8_t被定义为系统中对8位有符号值而言运算最快的整数类型的别名。

另外,有些程序员需要系统的最大整数类型。为此,C99定义了最大的有符号整数类型intmax_t, 可存储任何有效的有符号整数值。类似地,uintmax_t表示最大的无符号整数类型。顺带一提,这些类型有可能比long long和unsigned long类型更大,因为C编译器除了实现标准规定的类型以外,还可利用C语言实现其他类型。例如,一些编译器在标准引入long long类型之前,已提前实现了该类型。


3.4.6:float、double和long double

各种整数类型对大多数软件开发项目而言够用了。然而, 面向金融和数学的程序经常使用浮点数

C语言中的浮点类型有float、double和long double类型。它们与FORTRAN和Pascal中的real类型一致。前面提到过, 浮点类型能表示包括小数在内更大范围的数 。浮点数的表示类似于科学记数法(即用小数乘以10的幂来表示数字)。 该记数系统常用于表示非常大或非常小的数 。表3.3列出了一些示例。

第1列是一般记数法;第2列是科学记数法;第3列是指数记数法(或称为e记数法),这是科学记数法在计算机中的写法,e后面的数字代表10的指数。图3.7演示了更多的浮点数写法。

C标准规定,float类型必须至少能表示6位有效数字 ,且取值范围至少是 10^{-37} 10^{37} 。前一项规定指float类型必须能够表示33.333333的前6位数字,而不是精确到小数点后6位数字。后一项规定用于方便地表示诸如太阳质量(2.0e30千克)、一个质子的电荷量(1.6e-19库仑) 或国家债务之类的数字。

通常, 系统存储一个浮点数要占用32位 。其中 8位用于表示指数的值和符号 剩下24位用于表示非指数部分 (也叫作尾数或有效数)及其符号。

C语言提供的另一种浮点类型是double(意为双精度)。 double类型和float类型的最小取值范围相同,但至少必须能表示10位有效数字 。 一般情况下, double占用64位而不是32位

一些系统将多出的32位全部用来表示非指数部分,这不仅增加了有效数字的位数(即提高了精度),而且还减少了舍入误差。另一些系统把其中的一些位分配给指数部分,以容纳更大的指数,从而增加了可表示数的范围。无论哪种方法,double类型的值至少有13位有效数字,超过了标准的最低位数规定。

C语言的第3种浮点类型是long double,以满足比double类型更高的精度要求。不过,C只保证long double类型至少与double类型的精度相同。


——1.声明浮点型变量

浮点型变量的声明和初始化方式与整型变量相同,下面是一些例子:

float noah,jonah;
double trouble;
float planck = 6.63e-34;
long double gnp;

——2.浮点型常量

在代码中,可以用多种形式书写浮点型常量。浮点型常量的基本形式是:有符号的数字(包括小数点),后面紧跟e或E,最后是一个有符号数表示10的指数。下面是两个有效的浮点型常量:

-1.56E+12
2.87e-3

正号可以省略。可以没有小数点(如, 2E5 )或指数部分(如, 19.28 ),但是不能同时省略两者。可以省略小数部分(如, 3.E16 )或整数部分(如, .45E-6 ),但是不能同时省略两者。下面是更多的有效浮点型常量示例:

3.14159
.2
4e16
.8E-5
100.

不要在浮点型常量中间加空格:1.56 E+12(错误!)

默认情况下,编译器假定浮点型常量是double类型的精度 。例如,假设some是float类型的变量,编写下面的语句;

some=4.0*2.0;

通常,4.0和2.0被存储为64位的double类型,使用双精度进行乘法运算,然后将乘积截断成float类型的宽度。这样做虽然计算精度更高,但是会减慢程序的运行速度。

在浮点数后面加上f或F后缀可覆盖默认设置,编译器会将浮点型常量看作float类型 ,如2.3f和9.11E9F。使用l或L后缀使得数字成为long double类型,如54.3l和4.32L。注意,建议使用L后缀,因为字母l和数字1很容易混淆。 没有后缀的浮点型常量是double类型。


——3.打印浮点值

printf()函数使用 %f转换说明打印十进制记数法的float和double类型浮点数 ,用 %e打印指数记数法的浮点数 。如果系统支持十六进制格式的浮点数,可用a和A分别代替e和E。给那些未在函数原型中显式说明参数类型的函数(如,printf()) 传递参数时,C编译器会把float类型的值自动转换成double类型。


——4.浮点值的上溢和下溢

假设系统的最大float类型值是3.4E38,编写如下代码:

float toobig =3.4E38*100.0f;

printf("%e\n",toobig);

会发生什么?这是一个上溢(overflow)的示例。 当计算导致数字过大,超过当前类型能表达的范围时,就会发生上溢 。这种行为在过去是未定义的,不过现在C语言规定,在这种情况下会 给toobig赋一个表示无穷大的特定值,而且printf()显示该值为inf或infinity (或者具有无穷含义的其他内容)。

当对一个很小的数做除法时,情况更为复杂。回忆一下,float类型的数以指数和尾数部分来存储。 存在这样一个数,它的指数部分是最小值,即由全部可用位表示的最小尾数值。该数字是float类型能用全部精度表示的最小数字。现在把它除以2。通常,这个操作会减小指数部分,但是假设的情况中,指数已经是最小值了。所以计算机只好把尾数部分的位向右移,空出第1个二进制位,并丢弃最后一个二进制数。以十进制为例,把一个有4位有效数字的数(如,0.1234E-10)除以10,得到的结果是0.0123E-10。

虽然得到了结果,但是 在计算过程中却损失了原末尾有效位上的数字。这种情况叫作下溢(underflow)

C语言把损失了类型全精度的浮点值称为低于正常的(subnormal)浮点值。因此,把最小的正浮点数除以2将得到一个低于正常的值。如果除以一个非常大的值,会导致所有的位都为0。现在,C库已提供了用于检查计算是否会产生低于正常值的函数。

还有另一个特殊的浮点值NaN(not a number的缩写)。例如,给asin()函数传递一个值,该函数将返回一个角度,该角度的正弦就是传入函数的值。但是正弦值不能大于1,因此,如果传入的参数大于1,该函数的行为是未定义的。在这种情况下,该函数将返回NaN值,printf()函数可将其显示为nan、NaN或其他类似的内容。


3.4.7:复数和虚数类型

许多科学和工程计算都要用到复数和虚数。C99标准支持复数类型和虚数类型,但是有所保留。一些独立实现,如嵌入式处理器的实现,就不需要使用复数和虚数(VCR芯片就不需要复数)。一般而言,虚数类型都是可选项。C11标准把整个复数软件包都作为可选项。

简而言之,C语言有3种复数类型:float_Complex,double_Complex和long double_Complex.

例如,float_Complex类型的变量应包含两个float类型的值,分别表示复数的实部和虚部。类似地,C语言的3种虚数类型是float_Imaginary、double_Imaginary和long double_Imaginary。

如果包含complex.h头文件,便可用complex代替_Complex,用imaginary代替_Imaginary,还可以用I代替-1的平方根。


3.4.8:其他类型

现在已经介绍完C语言的所有基本数据类型。有些人认为这些类型实在太多了,但有些人觉得还不够用。注意,虽然C语言没有字符串类型,但也能很好地处理字符串。第4章将详细介绍相关内容。

C语言还有一些从基本类型衍生的其他类型,包括数组、指针、结构和联合。尽管后面章节中会详细介绍这些类型,但是本章的程序示例中已经用到了指针(指针(pointer)指向变量或其他数据对象位置)。例如,在scanf()函数中用到的前缀&,便创建了一个指针,告诉scanf()把数据放在何处。


3.4.9:类型大小

如何知道当前系统的指定类型的大小是多少?运行程序清单3.6,会列出当前系统的各类型的大小 。

#include <stdio.h> 
int main(void) 
	//C99为类型大小提供%zd转换说明
	printf("Type int has a size of %zd bytes.\n",sizeof(int)); 
	printf("Type char has a size of %zd bytes.\n",sizeof(char));