7 基本类型

7.1 整型

可移植性技巧:对不超过 32767 的整数采用int或short int,其它情况下使用long int。
注意:不要部分差别地使用长整型,因为长整型操作比较耗时。

6种整数类型 说明 bit(16位机器) bit(32位机器) bit(64位机器) 数值范围(16位机器) 数值范围(32位机器) 数值范围(64位机器) 有无符号
short [signed] [int] 短整型 16 16 16 -2^15 ~ 2^15-1 -2^15 ~ 2^15-1 -2^15 ~ 2^15-1
short usigned [int] 无符号短整型 16 16 16 0 ~ 2^16-1 0 ~ 2^16-1 0 ~ 2^16-1
[signed] int 整型 16 32 32 -2^15 ~ 2^15-1 -2^31 ~ 2^31-1 -2^31 ~ 2^31-1
unsigned int 无符号整型 16 32 32 0 ~ 2^16-1 0 ~ 2^32-1 0 ~ 2^32-1
long [signed] [int] 长整型 32 32 64 -2^31 ~ 2^31-1 -2^31 ~ 2^31-1 -2^63 ~ 2^63-1
long unsigned [int] 无符号长整型 32 32 64 0 ~ 2^32-1 0 ~ 2^32-1 0 ~ 2^64-1

7.1.1 整型常量

进制

注意:

  1. 整数都是以二进制形式存储的,不会考虑实际的书写方式
  2. 任何时候都可以从一种书写方式切换到另一种,甚至可以混合使用
进制 包含数字 写法 举例 备注
十进制 0~9 不能以0开头 15 255
八进制 0~7 必须以0开头 017 0377
十六进制 0~9和字母a~f 字母部分大小写均可 0xf

长整型和无符号整型

注意:默认情况下编译器对处于int类型取值范围内的整数使用int类型,否则使用long int。

后缀 含义
U(u) 指定常量为无符号整型
L(l) 指定常量为长整型

7.1.2 读/写整数

溢出:当程序付给变量的值太大以至于无法存储在int类型中时,这时值会被处理为不符合预期的值。

printf函数和scanf函数

有符号整数

转换说明符 类型 进制
%d int 十进制
%hd short signed int 十进制
%ld long signed int 十进制
1
2
3
4
5
6
7
short int s;
long int l;
scanf("%hd", &s);//写:短整型
scanf("%ld", &l);//写:长整型

printf("%hd", s);//读:短整型
printf("%ld", l);//读:长整型

无符号整数

转换说明符 类型 进制
%u unsigned int 十进制数字
%o unsigned int 八进制
%x unsigned int 十六进制
1
2
3
unsigned int u;
scanf("%u", %u);//写:无符号十进制
printf("%u", u);//读:无符号十进制

7.1.3 程序:数列求和(改进版)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* Sums a series of numbers
*/


# include <stdio.h>
int main(){
int n, sum = 0;
printf("This program sums a series of intergers.\n");
printf("Enter intergers (0 to terminate:)");

scanf("%d", &n);
while(n != 0){
sum += n;
scanf("%d", &n);
}
printf("The sun is:%d\n", sum);
return 0;
}

7.2 浮点数

IEEE浮点标准:

类型 bit 最小正数 最大值 精度
float 32 1.17x10^-38 3.40x10^38 6个数字
double 64 2.22x0^-308 1.79x10^308 15个数字
long float >=43 未说明 未说明 未说明
long double >=79 未说明 未说明 未说明

c语言浮点数:

浮点数 说明 适用
float 单精度浮点数 当精度要求不严格时,float类型是很适合的类型
double 双精度浮点数 提供更高的精度,适合绝大多数
long double 扩展双精度浮点数 支持极高精度的要求,很少会用到

注意:

  1. long double类型没有出现在IEEE标准中,其长度随机器的不同而变化,最常通用的尺寸是80位和128位

7.2.1 浮点常量

语法:必须包含小数点或指数
举个栗子:57.0的多种写法

57.0 57. 57.0e0 57E0 5.7e1 5.7e+1 .57e2 570.e-1

存储方式:

情景 说明
默认情况 double
浮点常量F(f) float
浮点常量L(l) long double

注意:double类型的值在使用时,如果需要会自动转换为float类型的值。

7.2.2 读/些浮点数

浮点类型
float %e %f %g %e %f %g
double %e %f %g %le %lf %lg
long double %Le %Lf %Lg %Le %Lf %Lg
1
2
3
4
5
6
double d;
scanf("%lf", &d);

long double ld;
scanf("%Lf", &ld);
printf("%Lf", ld);

7.3 字符型

char:Q&A char类型的值可以根据计算机的不同而不同,因为不同的机器可能会有不同的字符集。
ASCII字符集:用7位代码表示128个字符,一些计算机把ASCII码扩展为8位代码以便可以表示256个字符。
字符常量:字符常量需要用单引号括起来,而不是双引号。
char当作整数:c语言会按小整数的方式处理字符,毕竟所有字符都是以二进制的形式进行编码的。在ASCII码中,字符的取值范围是0000000(0)~1111111(127)。

优点:利用字符和数相同的属性灵活处理字符,例如for(ch = 'A'; ch <= 'Z'; ch++) ...
缺点:

  1. 导致编译器无法检查出的多种编程错误
  2. 编写出诸如'a' * 'b' / 'c'这类无意义的表达式
  3. 妨碍程序的可移植性(因为程序可能会基于一些对字符集的假设)

有符号和无符号

编译器行为:一些编译器按照有符号数据(-128~127)处理char,另一些则为无符号(0~255)char。设置有些编译器允许程序员通过编译器选项选择char时有符号型还是无符号型
可移植性技巧:signed charunsigned char代替char

1
2
3
4
5
6
7
8
9
10
11
char ch;
int i;
i = 'a';//i is now 97
ch = 65;//ch is now 'A'
ch = ch + 1;//ch is now 'B'
ch++;//ch is now 'C'

//如果ch时小写字母,转为大写字母
if('a' <= ch && ch <= 'z'){
ch = ch - 'a' + 'A';
}

7.3.1 转义序列

用途:c语言为了处理字符集中的每一个字符,提供了转义字符用来表示一些不可见或无法从键盘输入的字符。
字符转义字符:没有包含所有无法打印的ASCII字符,只包含了最常用的字符。

名称 转移序列
警报(响铃)符 \a
回退符 \b
换页符 \f
换行符 \n
回车符 \r
横向制表符 \t
纵向制表符 \v
反斜杠 \\
问号 \?
单引号 \'
双引号 \"

数字转义字符:可以表示任何字符,突破字符转义字符的限制。
转义字符常量:有8进制和16进制两种书写方式,需要用一对单引号括起来。

进制 书写方式 特点
八进制转义序列 \最多含有三位数字的八进制数 必须表示为无符号字符型
十六进制转义序列 \x十六进制数 标准c对于数字个数没有限制,x必须为小写,十六进制数无限制
1
# define ESC '\33'  //用宏的方式定义数字转义字符常量

补充:

其它序列 说明
三字符序列(trigraph sequence) 一些特殊的ASCII字符的代码
多字节字符(multibyte character)
宽字符(nultibyte character)

7.3.2 字符处理函数

toupper:c语言的toupper库函数,用来检测自身的参数是否是小写字母,如果是则将其转换成相应的大写字母。

1
2
# include <ctype.h>
if(toupper(ch)) == 'A') ...

7.3.3 读/写字符

两种方式:printf/scanfgetChar/putchar

7.3.3.1 printf 和 scanf

转换说明:%c
技巧:

  • 在格式串转换说明%c前面加一个空格,强制scanf函数在读入字符前跳过空白字符。
1
scanf(" %c", &ch);//会跳过零个或多个空白字符
  • 通常情况下scanf函数不会跳过空白,所以很容易检查到输入行的结尾
1
2
3
do{
scanf("%c", &ch);
}while(ch != '\n');
1
2
3
char ch;
scanf("%c", &hc);//reads a single character
printf("%c", ch);//writes a single character

7.3.3.2 getchar 和 putchar

说明:

库方法 说明 备注
getchar 读入一个字符并返回这个字符 不会跳过空白字符
putchar 用来单独写一个字符

执行速度快:比 scanf 和 printf 节约时间。

  1. 这两个函数比scanf和prinf简单,因为scanf和prinf是设计用来读/写多种不同格式类型数据的
  2. 为了额外的速度提升,通常getchar函数和puchar函数是作为宏来实现的

可以应用多种不同的c语言惯用法:包括用循环搜索字符或跳过所有出现的统一字符。

1
2
3
4
5
6
//循环搜索字符
while(getchar != '\n')
;
//跳过所有出现的统一字符
while((ch = getchar()) == ' ')
;

避免混合使用scanfgetcharscanf函数有一种留下后边字符的趋势,也就是说对于输入后面的字符只是看了一下,并没有读入。

1
2
3
4
printf("Enter an interger:");
scanf("%d", &i);//读入i的同时,scanf函数调用将会留下后面没有消耗掉的人一字符,包括换行符
printf("Enter a command:");
command = getchar();//getchar会取回第一个声誉字符

7.3.4 程序:确定消息的长度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* Determines the length a message
*/


# include <stdio.h>
int main(){
int len = 0;

printf("Enter a message:");
while(getchar() != '\n'){
len ++;
}
printf("Your message was %d character(s) long.\n", len);
return 0;
}
1
2
3
./length2 
Enter a message:1234567
Your message was 7 character(s) long.

7.4 sizeof运算符

说明:编译器本身就可以计算sizeof表达式的值,所以sizeof 是一种特殊的运算符(一元运算符)
用途:用来计算制定类型值所需空间的大小。
sizeof表达式:sizeof(类型名)
参数:常量、变量或表达式
优先级:高于二元运算符(比如+)
返回值:字节数,类型由实现定义,通常是无符号整数
圆括号:当应用于表示式时不需要圆括号,比如sizeof i,但是由于运算符优先级的问题,建议始终在sizeof表达式中采用圆括号。
int为例:

处理器架构 字节数
16 2(通常)
32 4(通常)
1
2
//把sizeof表达式转换成`unsigned long`(最大的无符号类型)
printf("Size of int:%lu\n", (unsigned long)sizeof(int));

7.5 类型转换

计算机执行算术运算的限制:要求操作数大小相同,存储方式也相同。

7.5.1 隐式转换(implicit conversion)

说明:c语言允许在表达式中混合使用基本数据类型(整数、浮点数甚至是字符)。此时c语言编译器需要生成一些指令将某些操作数转换成不同类型,是的硬件可以对表达式进行计算。
隐式转换情景:

  • 当算术表达式或逻辑表达式中操作数的类型不相同时(常用算数转换)
  • 当赋值运算符右侧表达式的类型和左侧变量的类型不匹配时
  • 当函数调用中使用的参数类型与其对应的参数的类型不匹配时
  • 当 return语句中表达式的类型和函数返回值的类型不匹配时

7.5.1 常用算数转换

原理:为了统一操作数的类型,通常可以将相对狭小的操作数转换成另一个操作数的类型来实现(即提升)。
整型提升(integral promition):charshort int转换成int(或unsigned int)。

两种情况:

情况 提升方式
任一操作数的类型时浮点型的情况 long double<-double<-float
两个操作数都不是浮点型的情况 unsigned long int<-long int<-usigned int<-int
long intunsigned int 两个操作数都会转换成usigned long int

注意:尽量避免使用unsigned int,特别不要把它和有符号整数混合使用。

1
2
3
4
5
int i ,u;
i = -10;
u = 10;
//int转换为usigned int时最高位的符号位不再是符号位
i < u;//0,因为会进行隐式转换导致讲-10当作unsigned int
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
char c;
short int s;
int i;
unsigned int u;
long int 1;
unsigned long int ul;
float f;
double d;
long double ld;

i = i + c;//c被转为int
i = i + s;//s被转为int
u = u + i;//i被转为unsigned int
l = l + u;//l被转为long int
ul = ul + 1;//l被转为unsigned long int
f = f + ul;//f被转为double
d = d + f;//f被转为double
ld = ld + d;//d被转为long double

7.5.2 赋值中的转换

转化规则:把赋值运算右边的表达式转换成左边变量的类型。

7.5.2.1 两边类型一样“宽”

1
2
3
4
5
6
7
8
char c;
int i;
float f;
double d;

i = c;//c被转为int
f = i;//i被转为float
d = f;//f被转为double

7.5.2.2 浮点数赋值给整型

规则:去掉小数部分。

1
2
3
int i;
i = 842.97;//i为842
i = -842.87;//i为-842

7.5.2.3 溢出

说明:Q&A如果取值在变量类型范围之外,那么把值赋给一个狭小类型的变量将会得到无意义的结果(设置更糟)。

1
2
3
c = 10000;
i = 1.0e20;
f = 1.0e100;

7.5.3 强制类型转换

强制转换表达式:(类型名)表达式
优先级:被当作一元运算符,因此高于二元运算符

1
2
3
4
5
6
float f, frac_part;
frac_part = f - (int)f;//获得浮点数f的小数部分

float quotient;
int divided, divisor;
quotient = (float)dividend / divisor;//divisor会被迫使编译器把divisor也转换成为float类型。

7.6 类型定义

语法:typedef 原本类型名 类型别名;
说明:采用typedef定义类型别名会导致编译器将其加入到类型列表中。
用途:

  1. 如果程序员使用有意义的类型名,会使程序更加易于理解
  2. 使程序更加易于维护
  3. 是编写可移植程序的一种重要工具

可移植性技巧:为了更大的可移植性,可以考虑使用typedef定义新的整型名。

1
2
3
4
5
6
7
//在16位机器上
typedef int Quantity;
Quantity q;

//在32位机器上
typedef long int Quantity;
Quantity q;

编译器库中(可能)自带的类型定义:

1
2
3
typedef int ptrdiff_t;
typedef unsigned size_t;
typedef char wchar_t;
1
2
typedef float Dollars;
Dollars cash_in, cash_out;