C_Primer第3章 数据和C

本章介绍以下内容

关键字:int、short、long、unsigned、char、float、double、_Bool、_Complex、_Imaginary

运算符:sizeof()

函数:scanf()

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

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

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

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

C语言提供两大系列的多种数据类型。本章详细介绍两大数据类型:整数类型和浮点数类型,讲解这些数据类型是什么、如何声明它们、如何以及何时使用它们。除此之外,还将介绍常量和变量的区别。读者很快就能看到第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");
}

程序输出

Are you worth your weight in platinum?
Let's check it out.
Please enter your weight in pounds: 156
Your weight in platinum is worth $3867491.25.
you are easily worth that! If platinum prices drop,
eat more to maintain your value.

程序调整

即使用第2章介绍的方法,在程序中添加下面的一行代码:

getchar();;

程序的输出是否依旧在屏幕上一闪而过?本例,需要调用 两次getchar()函数:

getchar();

getchar();

getchar()函数读取下一个输入字符,因此程序会等待用户输入,在这种情况下,键入156并按下Enter(或Return)键(发送一个换行符),然后scanf()读取键入的数字,第1个getchar()读取换行符,第2个getchar()让程序暂停,等待输入。

3.1.1 程序中的新元素

3.2 变量常量数据

3.3 数据:数据类型关键字

不仅变量和常量不同,不同的数据类型之间也有差异。一些数据类型表示数字,一些数字类型表示字母(更普遍地谙是字符)。C通过识别一些基本的数据类型来区分和使用这些不同的数据类型。如果数据是常量,编译器一般通过用户书写的形式来识别类型(如,42是整数,32.00是浮点数)。但是,对变量而言,要在声明时指定其类型。盘后会详细介绍如何声明变量。现在,我们先来了解一下C语言的基本类型关键字。K&C给出了7个与类型相关的关键字。C90标准添加了2个关键字,C99标准又添加了3个关键字(见表3.1)。

在C语言中,用int关键字来表示基的整数类型。char关键字用于指定字母和其他字符(如,#、$、%和*)。

_Bool类型表示布尔值,_complex和_Imaginary分别表示复数和虚数。 

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

位、字节和字

3.3.1 整数和浮点数

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

3.3.2 整数

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

3.2.3 浮点数

浮点数丐数学中实数的概念差不多。2.75、3.16E7、7.00和2e-8都是浮点数。注意,在一个值后面加上一个小数点,该 值就成为一个浮点值。所以,7是整数,7.00是浮点数。显然,书写浮点数有多种形式。稍后将详细介绍e户数法,这里先做简要介绍:3.16E7表示3.16\LARGE \times\LARGE 10^7(3.16乘以10的7次方)。其中,\LARGE 10^7=10000000,7被称为10的指数。

这里关键要理解浮点数和整数的储存方案不同。计算机把浮点数分成小数部分和指数部分来表示,而且分开储存这两部分。因此,虽然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要占用一个机器字长。ISO C规定int的取值范围最小为-32768~32767.一般而言,系统用一个特殊位的值表示有符号整数的正负号。第15章将介绍常用的方法。

1.声明int变量

2.初始化变量

3.int类型常量

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

4.打印int值

可以使用printf()函数打印int类型的值。

程序清单3.2 print1.c程序

/*print1.c --演示printf()的一些特性*/
#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;
	
}

编译并运行该程序,输出如下:

Doing it right: 10 minus 2 is 8
Doing it wrong:10 minus 2 is 8

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

5.八进制和十六进制

程序清单3.3 bases.c程序

/* 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);
	printf("dec = %d; octal = %#o; hex = %#X\n",x,x,x);

	return 0;
}

编译并运行该程序,输出如下:

dec = 100; octal = 144; hex = 64
dec = 100; octal = 0144; hex = 0x64
dec = 100; octal = 0144; hex = 0X64

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

3.4.2 其他整数类型

整数溢出

 程序清单toobig.c

#include <stdio.h>
int main(void)
{
	int i = 2147483647;//有符号32位最大值。范围[-2147483648---2147483647]
	unsigned int j = 4294967295;//无符号32位最大值。范围[0---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则从-214783648开始。注意,当i超出(溢出)其相应类型所能表示地的最大值时,系统并未通知用户。因此,在编程时必须自己注意这类问题。

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

+++++++++++++++++++++

补数
假设当前时针指向11点,而准确时间是8点,调整时间可有以下两种拨法:

  • 一种是倒拨3小时,即:11-3=8
  • 另一种是顺拨9小时:11+9=12+8=8

在以模为12的系统中,加9和减3效果是一样的,因此凡是减3运算,都可以用加9来代替。对“模”12而言,9和3互为补数(二者相加等于模)。所以我们可以得出一个结论,即在有模的计量系统中,减一个数等于加上它的补数,从而实现将减法运算转化为加法运算的目的。

计算机上的补码就是算术里的补数。 

设我们有一个 4 位的计算机,则其计量范围即模是 
2^4 = 16,所以其能够表示的范围是0~15,现在以计算 5 - 3为例,我们知道在计算机中,加法器实现最简单,所以很多运算最终都要转为加法运算,因此5-3就要转化为加法:

 # 按以上理论,减一个数等于加上它的补数,所以
 5 - 3
 # 等价于 
 5 + (16 - 3)   // 算术运算单元将减法转化为加法
 # 用二进制表示则为:
 0101 + (10000 - 0011)
 # 等价于
 0101 + ((1 + 1111) - 0011)
 # 等价于
 0101 + (1 + (1111 - 0011))
 # 等价于
 0101 + (1 + 1100) // 括号内是3(0011)的反码+1,正是补码的定义
 # 等价于
 0101 + 1101
 # 所以从这里可以得到
 -3 = 1101
 # 即 `-3` 在计算机中的二进制表示为 `1101`,正是“ -3 的正值 3(`0011`)的补码(`1101`)”。
 # 最后一步 0101 + 1101 等于
 10010

+++++++++++++++++++++

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.4i演示了一些例子。对于支持long long类型的系统,%lld和%llu分别表示有符号和无符号类型。第4章将详细介绍转换说明。

#include <stdio.h>
int main (void)
{
    unsigned int un = 3000000000;
    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 %ld\n",verybig,verybig);

    return 0;
}

程序输出结果

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


有符号32位最大值。范围[-2147483648---2147483647]
无符号32位最大值。范围[0---4294967295]

3000000000的原码:1011 0010 1101 0000 0101 1110 0000 0000

1294967296的原码:0100 1101 0010 1111 1010 0010 0000 0000

1294967296的反码:1011 0010 1101 0000 0101 1101 1111 1111

1294967296的补码:1011 0010 1101 0000 0101 1110 0000 0000

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

第2行输出,对于short类型的变量end,在printf()中无论指定以short类型(%hd)还是int类型(%d)打印,打印出来的值都相同。这是因为在给函数传递参数时,C编译器把short类型的值自动转换成int类型的值。你可能会提出疑问:为什么要进行转换?h修饰符有什么用?第1个问题的答案是,int类型被认为是计算机处理整数类型时最高效的类型。因此,在short和int类型的大小不同的计算机中,用int类型的参数传递速度更快。第2个问题的答案是,使用h修饰符可以显示较大整数被截断成short类型值的情况。第3行输出就演示了这种情况。把65537以二进制格式写成一个32位数是0000 0000 0000  0001 0000 0000 0000 0001。使用%hd,printf()只会查看后16位,所显示的值是1。与此类似,输出的最后一行先显示了verybig的完整值,然后由于使用了%ld,printf()只显示了储存在后32位的值。

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

提示:匹配printf(()说明符的类型

在使用printf()函数时,切记检查每个待打印值都对就的转换说明,还要检查转换说明的类型是否与待打印值的类型相匹配。

3.4.3 使用字符:char类型

char 类型用于储存字符(如,字母或标点符号),但是从技术层面看,char是整数类型。因为char类型实际上储存的是整数而不是字符。计算机使用数字编码来处理字符,即用特定的整数表示特定的字符。美国最常用的编码是ASCII编码,本书也使用此编码。例如,在ASCII码中,整数65代表大写字母A。因此储存字母A实际上储存的是整数65(许多IBM的大型主机使用另一种编码------EBCDIC,其原理相同)。另外,其他国家的计算机系统可能使用完全不同的编码)。

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

许多字符集都超过了127,甚至多于255.例如,日本汉字(kanji)字符集。商用的统一码(Unicode)创建了一个能表示世界范围内多种字符的系统,目前包括的字符已超过110000个。国际标准化组织(ISO)和国际电工技术委员会(IEC)为字符集开发了ISO/IEC 1066标准。统一码标准也与ISO/IEC 10646标准兼容。

C语言把1字节定义为char类型占用的位(bit)数,因此无论是16位还是32位系统,都可以使用char类型。

1.声明char类型变量

char response;

char itable,latan;

2.字符常量和初始化

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

char grade = 'A';

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

char broiled;/*声明一个char类型的变量*/

broiled = 'T'/*为其赋值,正确*/

broiled = T;/*错误!此时T是一个变量*/

broiled = "T";/*错误! 此时“T"是一个字符串*/

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

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

在本例中,虽然65是int类型,但是它在char类型能表示的范围内,所以将其赋值给grade没问题。由于65是字母A对应的ASCII对应的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类型变量gradde, 只有最后8位有效。因此,grade的值是'E'。

3. 非打印字符

/*非打印字符 \a,蜂鸣声*/
#include <stdio.h>
int main(void)
{	char beep = 7;
	char beep1 = '\7'; /*八进制ASCII表示一个字符 \007,\07,\7都可以*/
	char beep2 = '\a';
	printf("beep %c\n",beep);
	printf("beep %c\n",beep1);
	printf("beep %c\n",beep2);
	printf("Gramps sez, \" \\ is a backslash.\"\n");/*输出 Gramps sez, " \ is a backslash." */
	printf("Hello!\007\n");
	printf("Hello!7\n");	
	return 0;
	
}

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

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

上面的例子( printf("Gramps sez, \" \\ is a backslash.\"\n"); ),为何没有用单引号把转义序列括起来?无论是普通字符还是转义序列,只要是双引号括起来的字符集合,就无需用单引号括起来。双引号中的字符集俣叫作字符串。

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

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

4.打印字符

/*charcode.c --显示字符的代码编号*/
#include <stdio.h>
int main(void)
{	
	char ch;

	printf("Please enter a character.\n");
	scanf("%c",&ch);/* 用户输入字符 */
	printf("The code for S%c is %d.\n",ch,ch);
	return 0;
	
}

输出结果如下:

Please enter a character.
C
The code for SC is 67.

运行该 程序,在输入字母后不要忘记按下Enter或Return键。随后,scanf()函数会读取用户输入的字符,&符号表示把输入的字符赋给变量ch。接着,printf()函数打印ch的值两次,第1次打印一个字符(对应代码中的%c).第2次打印一个十进制整数值(对应代码中的%d)。注意,printf()函数中的转换说明决定了数据的显示方式,而不是数据的储存方式(见图3.6)。

5. 有符号还是无符号

有此C编译器把char实现为有符号类型,这意味着char可表示的范围是-128到127.而有些C编译器char实现为无符号类型,那么char可表示的范围是0~255. 

3.4.4 _Bool类型

C99标准添加了_Bool类型,用于表示布尔值,即逻辑值true 和false。

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类型编写程序,并包含stdio.h头文件,编译器会把int或long替换成与当前系统匹配的类型。

精确宽度整数类型(exact-width integer type)

最小宽度类型(minimum width type)

最快最小宽度类型(fastst minimun width type)

字符串宏(例如PRId32)

/* altnames.c --可移植整数类型名*/
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
int main(void)
{
	int32_t me32; //me32是一个32位有符号整型变量
	
	me32 = 45933945;
	
	printf("INT_8_MAX : %d\n",INT8_MAX);
	printf("INT_8_MAX : %" PRId8 "\n",INT8_MAX);
	
	printf("INT_16_MAX : %d\n",INT16_MAX);
	printf("INT_16_MAX : %" PRId16 "\n",INT16_MAX);
	
	printf("INT_32_MAX : %d\n",INT32_MAX);
	printf("INT_32_MAX : %" PRId32 "\n",INT32_MAX);
	
	printf("INT_64_MAX : %lld\n",INT64_MAX);
	printf("INT_64_MAX : %" PRId64 "\n",INT64_MAX);
	
	printf("INT_LEAST8_MAX : %d\n",INT_LEAST8_MAX);
	printf("INT_LEAST8_MAX : %" PRIdLEAST8 "\n",INT_LEAST8_MAX);
	
	printf("INT_LEAST16_MAX : %d\n",INT_LEAST16_MAX);
	printf("INT_LEAST16_MAX : %" PRIdLEAST16 "\n",INT_LEAST16_MAX);
	
	
	printf("INT_FAST32_MAX : %d\n",INT_FAST32_MAX);
	printf("INT_FAST32_MAX : %" PRIdFAST32 "\n",INT_FAST32_MAX);
	
	printf("First ,assume int32_t is int:");
	printf("me32 = %d\n",me32);
	printf("Next,let's not make any assumptions.\n");
	printf("Instead,use a \"macro\" from inttypes.h: ");
	printf("me32 = %" PRId32 "\n",me32);
	
	return 0;
	
}

程序输出
INT_8_MAX : 127
INT_8_MAX : 127
INT_16_MAX : 32767
INT_16_MAX : 32767
INT_32_MAX : 2147483647
INT_32_MAX : 2147483647
INT_64_MAX : 9223372036854775807
INT_64_MAX : 9223372036854775807
INT_LEAST8_MAX : 127
INT_LEAST8_MAX : 127
INT_LEAST16_MAX : 32767
INT_LEAST16_MAX : 32767
INT_FAST32_MAX : 2147483647
INT_FAST32_MAX : 2147483647
First ,assume int32_t is int:me32 = 45933945
Next,let's not make any assumptions.
Instead,use a "macro" from inttypes.h: me32 = 45933945

3.4.6 float、double和long double

float类型必须至少能表示6位有效数字。

double类型和float类型的最小取值范围相同,但至少必须表示10位有效数字。

long double类型运至少下double类型的精度相同。

1.声明浮点型变量

float noat,jonah;

double trouble;

float planck = 6.63e-34;

long double gnp;

2.浮点型常量

-1.56E+12

2.77e-3

3.14159

.2

4e16

.8E-5

100.

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

建议使用L后缀,因为字母l和数字1很容易混淆。没有后缀的浮点型常量是double类型。

C99标准添加了一种新的浮点型常量格式------用十六制表示浮点型常量,即在十六进制数前加上十六进制前缀(0x或)X),用p和P分别代替e和E,用2的幂代替10的幂( 即,p计数法)。如下所示:

0xa.1fp10

猜你喜欢

转载自blog.csdn.net/tjjingpan/article/details/84026523