说明符的顺序没有什么影响,所以unsigned short int 和short unsigned int是一样的。
标准要求int类型不能比short int类型短,long int类型不能比int类型段。但是short int 类型的取值范围有可能和int类型的范围是一样的,int类型的取值范围也可以和long int的一样。
注意:无论是哪一种类型,它们的取值范围都不是C标准强制的,会随着编译器的不同而不同。对于特定的实现,确定整数类型范围的一种方法是检查
八进制常量只包含0~7中的数字,而且必须要以0开头。
十六进制常量包含0~9中的数字和a~f中的字母,而且总是以0x开头。
注意:十六进制常量中的字母既可以是大写字母,也可以是小写字母。
为了强制编译器把常量当作长整数来处理,只需要在后边加上一个字母L(或l),为了指明是无符号常量,可以在常量后边加上字母U(或u),U和L可以结合使用(字母U、L的顺序和大小写无所谓)。
有符号整数运算中发生溢出时,程序的行为是未定义的。
无符号整数运算过程中发生溢出时,结果是有定义的:正确答案是对2n取模,其中n是用于存储结果的位数。例如,如果无符号的16位数65535加1,其结果可以保证为0。
假设有一个程序因为其中一个int变量发生溢出而无法工作。我们的第一反应是把变量的类型从int变为long int。但仅仅这样是不够的,我们还必须检查数据类型的改变对程序其它部分的影响,尤其是需要检查该变量是否用在printf函数或者scanf函数的调用中。如果用了,需要改变调用的格式串,因为%d只适用于int类型。
读/写无符号整数时,使用字母u、o或x代替转换说明中的d。如果使用u说明符,该数将以十进制形式读写,o表示八进制形式,而x表示十六进制形式。
读写短整数时,在d、o、u或x前面加上字符h。
读写长整数时,在d、o、u或x前面加上字符l。
(C99)读写长长整数时,在d、o、u或x前面加上字母ll。
long double支持极高精度的要求,很少会用到。
默认情况下,浮点常量都以双精度的形式进行存储。
为了表明只需要单精度,可以在常量的末尾处加上字母F或f;而为了说明常量必须以long double格式存储,可以在常量的末尾处加上字母L或l。
转换说明符%e、%f和%g用于读写单精度浮点数。
C99允许printf函数调用中使用%le、%lf和%lg,不过字母l不起作用。
字符常量需要用单引号括起来,而不是双引号。
标准C允许使用单词signed和unsigned来修饰char类型:
unsigned char x;
signed char x;
可移植性技巧 不要假设char类型默认为signed或unsigned。如果有区别,用signed char或unsigned char代替char。
名称 | 转义序列 |
---|---|
警报(响铃)符 | \a |
回退符 | \b |
换页符 | \f |
换行符 | \n |
回车符 | \r |
水平制表符 | \t |
垂直制表符 | \v |
反斜杠 | \\ |
问号 | ? |
单引号 | \’ |
双引号 | \" |
toupper()函数在被调用时检测参数是否为小写字母,如果是,他会把参数转换成相应的大写字母;否则,该函数会返回参数的值。
调用toupper函数的程序要添加预处理指令包含相应的头文件
scanf函数不会跳过空白字符。为了强制scanf函数在读入字符前跳过空白字符,需要在格式串中的转换说明%c前面加上一个 空格。
注意:scanf格式串中的空白意味着“跳过零个或多个空白字符”。
注意:getchar含糊返回的是一个int类型的值而不是char类型的值。和scanf函数一样,getchar函数也不会在读取时跳过空白字符。
在执行程序时,使用getchar函数和putchar函数可以节约不少时间(胜于scanf和printf)。原因有两个:
- 这两个函数比scanf函数和printf函数简单的多,因为scanf函数和printf函数时设计用来按照不同的格式读/写多种不同类型的数据的。
- 为了额外的速度提升,通常getchar函数和putchar函数是作为宏来实现的。
惯用法:
while(getchar()!='\n')
···
while((ch = getchar())==' ')
···
隐式转换:
整值提升:它把字符或短整型整数转换成int类型(或者某些情况下是unsigned int类型)。
整值提升有下列两种情况:
注意:只要双目运算符左右的数据有一个的类型是某一个类型,那么另一个比其等级低的类型将会向上进行转换。也就是说,如果一个操作数的类型为long double,那么另一个操作数的类型转换成long double。
两个操作数的类型都不是浮点类型的情况。首先对其两个操作数进行整值提升(保证没有一个操作数的是字符类型或短整型)。然后按照下图对类型较狭小的操作数进行提升:
有一种特殊情况,只有在long int类型和unsigned int类型长度(比如36位)相同时才会发生。在这种情况下,如果一个操作数的类型是long int,而另一个的类型是unsigned int,那么两个操作数都会转换成unsigned long int类型。
注意:strlen()函数和sizeof()操作数的类型都是unsigned int类型。
常用算术转换不适用于赋值运算。C语言会遵循另一条简单的转换规则,那就是把赋值运算右边的表达式转换成左边变量的类型。如果变量的类型至少和表达式类型一样“宽”,那么这种转换都将没有任何障碍。当然,把某种类型的值赋给类型更狭小的变量,也将会得到无意义的结果。
下面是一个例子:
int a = 0;
unsigned x = 5;
int y = 5;
a = x + y;
//上面这个表达式将会发生两次类型转换,第一次,y将从有符号类型转换为有符号类型,第二次,x+y表达式的结果,将从无符号类型转换成有符号类型。
为了定义转换规则,C99中允许每个整数类型具有“整数转换等级”。下面按从最高级到最低级的顺序排列。
- long long int、unsigned long long int
- long int、unsigned long int
- int、unsigned int
- short int、unsigned short int
- char、signed char、unsigned char
- _Bool
C99用整数提升取代了C89中的整值提升,可以将任何等级低于int和unsigned int的类型转换为itn(只要该类型的所有值都可以用int类型表示)或unsigned int。
C99中执行常用算术替换的规则可以划分为两种情况:
另外,==所有算术类型都可以转换为_Bool类型。==如果原始值为0则转换结果为0,否则结果为1。
C语言把(类型名)视为一元运算符。一元运算符的优先级高于二元运算符。所以编译器会把表达式
(float)dividend / divisor
解释为
((float)dividend)/divisor
有时候,需要使用强制类型转换来避免溢出。
long i;
int j = 1000;
i = j*j;
当两个int类型值相乘时,结果也应该是int类型的,但是j*j的结果太大,以致在机器上无法表示成int型,从而导致溢出,此时就可以使用强制类型转换避免这种问题的发生:
i = (long)j*j;
类型定义可以设置一个新类型。
typedef int Bool;
==注意:所定义的类型的名字放在最后。(#define是把新的名字(我们使用的字符名)放在前面)==同时,我们常常将定义的类型名的首字母设置为大写。
类型定义更加有利于理解,因为我们可以将类型名赋予实际的意义。
类型定义使程序员更加便于理解。
类型定义是产生新的数据类型,不像#define只是简单的替换。下面将给出例子:
#define Pint int*;
typedef int* Ptr_int;
Pint a,b;//被替换后是这样的 int *a,b;
Ptr_int c,d;
此处a是指针类型,b是整型类型,c和d都是整型指针数据类型。
typedef命名的对象具有和变量相同的作用域规则:定义在函数体内的typedef名字在函数外是无法识别的。
类型定义能够使程序具有更好的可移植性。
在C99中,
sizeof(表达式);
注意:
sizeof()的值是一个无符号整数,代表存储属于类型名的值所需要的字节数。
sizeof后面当加某一种数据类型时,必须加括号,但是如果加一个变量名的时候,就可以不加括号。
sizeof()运算符是一种特殊的运算符,因为编译器本身通常就能够确定sizeof表达式的值。
一般表达式的运算是在运行时执行的,而sizeof是一个编译阶段就执行的运算符,在其内的任何运算都不执行,只推测出其中表达式结果的类型求其大小。**所以sizeof里面的表达式并没有真正的进行执行,即使里面的表达式有副作用,也不会对变量带来任何的影响。下面是例子:
虽然在sizeof()括号中变量a进行了++操作,但是a仍然没有改变。
注意:在C89中,编译器本身通常就能确定sizeof表达式的值。但是编译器不能确定变长数组的大小,因为数组中的元素个数在程序执行期间是可变的。
问:%o和%x分别用于以八进制和十六进制书写无符号整数,那么如何以八进制和十六进制书写普通的(有符号)整数呢?
答:只要有符号整数的值不是负值,就可以用%o和%x显示。这些转换导致print函数会把有符号整数看成是无符号的;换句话说,printf函数将假设符号位是数的绝对值部分。只要符号位为0,就没有问题。如果符号位为1,那么print函数将显示出一个超出预取的大数。
问:但是,如果数是负数怎么办呢?如何以八进制或十六进制形式书写它?
答:我们可以判定这个数是否是负数,然后自己显示一个负号:
if(i < 0)
printf("-%x",-i);
else
printf("%x",i);
问:为什么使用%lf读取double类型的值,而用%f进行显示呢?
答:这是一个十分难回答的问题。首先,注意,scanf函数和printf函数都是不同寻常的函数,因为它们都没有将函数的参数限制为固定数量。scanf函数和prinf函数有可变长度的参数列表。当调用带有可变长度参数列表的函数时,编译器会安排float参数自动转换成为double类型,其结果是printf函数无法区分float类型和double类型的参数。这解释了在printf函数调用中为何可以用%f既表示float类型又表示double类型的参数。
另一方面,scanf函数是通过指针指向变量的。%f告诉scanf函数在所传地址位置上存储一个float类型值,而%lf告诉scanf函数在该地址上存储一个double类型值。这里float和double的区别是非常重要的。如果给出了错误的转换说明,那么scanf函数将可能存储错误的字节数量(没有提到的是,float类型的位模式可能不同于double类型的位模式)。
问:使用转义序列?的目的是什么?
答:转义序列?与三字符序列有关,因为三字符序列以??开头。如果需要在字符串中加入??,那么编译器很可能会把它当作三字符序列的开始。用?代替第二个?可以解决这个问题。
问:既然getchar函数的读取速度更快,为什么仍然需要使用scanf函数读取单个的字符呢?
答:虽然scanf函数没有getchar速度快,但是它更加灵活。正如前面已经看到的,格式串"%c",可以使scanf函数读入下一个输入字符;" %c"可以读入下一个非空白字符。而且,scanf很擅长读取混入了其它数据类型的字符。
问:在什么情况下,整值提升会把字符或短整数转换为unsigned int类型?
答:如果int型整数没有大到足以包含所有可能的原始类型值,整值提升会产生unsigned int类型。因为字符通常是8位的长度,几乎总会转化为int类型(可以保证int类型至少为16位长度)。有符号短整数也总可以转换为int类型,但无符号短整数却是有疑问的。如果短整数和普通整数的长度相同(例如在16位机上),那么无符号短整数必须转化为unsigned int类型,因为最大的无符号短整数(在16位机上为65535)要大于最大的int类型整数(32767)。
问:如果把超出变量取值范围的值赋给变量,究竟会发生什么?
答:如果是整值类型并且变量是无符号类型,那么会丢掉超出的位数:如果变量是有符号类型,那么结果是由实现定义的。把符号数赋值给整型或浮点型变量的话,如果变量太小而无法承受,会产生未定义的行为:任何事都有可能发生,包括程序终止。