C_基本数据类型

C_基本数据类型
BarbecueC_基本数据类型
基本数据类型主要包括:整数类型,浮点数类型和字符类型,其中字符类型也可以看作是整数类型中的一种。
整数类型
整数类型可以分为两大类:有符号整数和无符号整数。
默认情况下,C 语言的整数类型都是有符号的;若要声明为无符号整数,则需要加 unsigned
关键字。C 语言的整数类型有以下这些:
1 | short (int) |
C 语言整数类型的取值范围根据机器的不同而不同。但是编译器都必须遵循的原则:
C 标准规定了 short(2), int(2), long(4), long long(8) 的最小字节长度。
C 标准规定了各个整数类型的字节长度满足下面的关系:
short <= int <= long <= long long
下表是64 位机器上整数类型的常见取值范围:
类型 | 字节长度 | 最小值 | 最大值 |
---|---|---|---|
short | 2 | -32768 | 32767 |
unsigned short | 2 | 0 | 65535 |
int | 4 | -2147483 648 | 2147483647 |
unsigned int | 4 | 0 | 4294967295 |
long | 8 | -9223372036854775808 | 9223372036854775807 |
unsigned long | 8 | 0 | 18446744073709551615 |
long long | 8 | -9223372036854775808 | 9223372036854775807 |
unsigned long long | 8 | 0 | 18446744073709551615 |
整数字面值
C 语言允许使用十进制 (decimal),八进制 (octal) 或者十六进制 (hexadecimal) 来书写整数字面值。
十进制字面值包含数字 0~9,但是不能以
0
开头。如:15, 255, 32767八进制字面值包含数字 0~7,而且必须以
0
开头。如:017, 0377, 077777。十六进制字面值包含数字 0
9 和字母 af,而且总以0x
开头。如:0xf, 0xff, 0x7ff。十六进制字面值中的字母即可以是大写也可以是小写,如:0xff, 0xfF, 0xFF, 0Xff, 0XfF, 0XFF。
注意:整数字面值也是有类型的
十进制整数字面值的类型通常是int
,但如果该字面值超出了int
的表示范围,那么它的类型是 int
, long
和 long long
中能表示该值的 “最小” 类型。
对八进制和十六进制整数字面值来说,可能的类型顺序为:int
, unsigned int
,long, unsigned long
, long long
和 unsigned long long
。
要指明某个整数字面值为一个 long 类型,需要在后面加字母 L (或 l):
1 | 15L, 0377L, 0x7fffL |
要指明整数字面值是 long long 类型,需要在后面加 LL (或 ll):
1 | 15LL, 0377LL, 0x7fffLL |
要指明整数常量是无符号的,需要在后面加字母 U (或 u):
1 | 15U, 0377U, 0x7fffU |
L、LL还可以和 U 结合使用,且字母 L, LL 和 U 的顺序可以颠倒。如:
1 | 0xffffffffUL, 0x12345678ULL |
读/写整数
使用 scanf
和 printf
函数读写无符号整数、短整数和长整数,则需要其它的转换说明符。(%d
只适用于读写 int
类型的数据。)
读写
unsigned int
时,使用字母u
,o
或x
替代转换说明符中的d
。其中u
表明无符号整数是十进制形式;o
表明无符号整数是八进制形式;x
表明是十六进制形式。1
2
3
4
5
6
7unsigned int n;
scanf("%u", &n); /* reads n in base 10 */
printf("%u", n); /* writes n in base 10 */
scanf("%o", &n); /* reads n in base 8 */
printf("%o", n); /* writes n in base 8 */
scanf("%x", &n); /* reads n in base 16 */
printf("%x", n); /* writes n in base 16 */读写
short
时,在d
,u
,o
或着x
前面加字母h
(short):1
2
3short n;
scanf("%hd", &n);
printf("%hd", n);读写
long
时,在d
,u
,o
或者x
前面加字母l
(long):1
2
3long n;
scanf("%ld", &n);
printf("%ld", n);读写
long long
时,在d
,u
,o
或者x
前面加字母ll
(longlong):1
2
3long long n;
scanf("%lld", &n);
printf("%lld", n);
整数类型编码
无符号整数和有符号整数的编码是不一样的。
无符号整数的编码:无符号整数采用二进制编码,只需要将二进制转换成十进制。
有符号整数的编码:有符号整数的编码方式为补码。利用加法器做运算时候更方便。
浮点数类型
C 语言提供了三种浮点数类型,对应三种不同的浮点数格式:
- float
- double
- long double
当对精度要求不高时 (一位小数的运算),可以使用 float
类型;大多数情况下,会使用 double
类型;在极少数对精度要求非常高的情况下,才会使用 long double
。
C 语言标准并没有规定 float
, double
, long double
类型精度,但大多数计算机都遵循 IEEE 754标准 。long double
类型没有显示在此表中,最常见的是 80 位和 128 位。
下表展示了遵循 IEEE 标准的浮点数的特征:
类型 | 规范化的最小正值 | 最大值 | 精度 |
---|---|---|---|
float | 1.175 49 x 10^-38 | 3.402 82 x 10^38 | 6位数字 |
double | 2.225 07 x 10^-308 | 1.797 69 x 10^308 | 15位数字 |
浮点数字面值
浮点数常量有多种书写方式。例如,下面都是 57.0
的有效表示方式:
1 | 57.0 57. 57.0e0 57E0 5.7e1 5.7e+1 .57e2 570.e-1 |
浮点数必须包含小数点或者是指数;字母 E (或 e) 后面的数字表示以 10 为底的指数。
默认情况下,浮点常量是 double
类型。单精度方式存储可以在末尾加字母 F
或 f
,如 57.0F
。以 long double
方式存储,则在后面加 L
或 l
,如 57.0L
。
读/写浮点数
读写 double
和 long double
类型所需的说明符与 float 略有不同。
读写
float
类型的值时:1
2
3float d;
scanf("%f", &d);
printf("%f", d);读写
double
类型的值时,需要在f
前面添加字母l
:1
2
3double d;
scanf("%lf", &d);
printf("%lf", d);读写
long double
类型的值时,需要在f
前面添加字母L
:1
2
3long double ld;
scanf("%Lf", &ld);
printf("%Lf", ld);
字符类型
不同的机器可能使用不同的字符集,char类型的值可能不同。ASCII(American Standard Code for Information Interchange) 字符集使用 7 位来表示 128 个字符,如下图所示:
需要记住的:
' '
= 32,'0'
= 48,'A'
= 65,'a'
= 97。
char 类型的变量可以存储单个字符,注意:字符字面值应该用单引号括起来。
1 | char ch; |
字符操作
C 语言是把字符当作小的整数进行处理的。C 语言会使用字符对应的整数值,如:
1 | char ch; |
下面的代码可以把小写字母变成大写字母:
1 | /* 不推荐。toupper(),tolower()更好,在<ctype.h>头文件中*/ |
下面使用 for 循环遍历所有的大写字母:
1 | for (ch = 'A'; ch <= 'Z'; ch++) ... |
把字符当作整数来处理,但也可以导致写出无意义的表达式:
1 | 'a' * 'b' / 'c'; /* 不要这么做 */ |
转义字符
转义序列分为两种:字符转义序列和数字转义序列。下表给出了字符转义序列的完整集合:
Name | Escape Sequence | Name | Escape Sequence |
---|---|---|---|
Alert (bell) | \a | Vertical tab | \v |
Backspace | \b | Backslash | \ |
Form feed | \f | Question mark | ? |
New line | \n | Single quote | ' |
Carriage return | \r | Double quote | " |
Horizontal tab | \t |
字符转义序列:没有包含所有的不可打印的 ASCII 字符,而且它也不能表示 ASCII 以外的字符。
数字转义序列:可以表示任意一个字符。可以使用八进制
或者十六进制
来书写数字转义序列。
八进制转义序列:由
\
和一个最多 3 位
的八进制数字组成。例如ESC
的编码为27
,用八进制表示就是33
,因此ESC
可以表示成\33
或者\033
。转义字符中的八进制数不需要以
0
开头,但八进制整数需要。十六进制转义序列:由
\x
和十六进制数组成。例如ESC
的编码为27
,用十六进制表示就是1b
,因此ESC
字符可以写成\x1b
或\x1B
的形式。转义字符中的十六进制数需要以
x
开头,且x
必须小写,但是后面的十六进制数字不限大小写。
当转义序列作为字符常量使用时,必须用一对单引号括起来。比如:
'\n'
,'\33'
或'\x1B'
。
空白字符
空白字符是指在文本中不可见的字符,空白字符不会在屏幕上显示,但可以影响文本的布局和解释。
空白字符的说明:
- 空格符(
- 制表符(
\t
):用于在文本中垂直方向上对齐文本或者在代码中用来缩进。 - 换行符(
\n
):表示文本中的换行,将光标移动到下一行开头。 - 回车符(
\r
):在某些操作系统中用来表示换行,用来实现回车换行的效果。 - 垂直制表符(
\v
):在某些系统中,用于在文本中垂直方向上对齐文本。 - 换页符(
\f
):在打印输出中用于分页,通常在控制台输出中不常见。
字符处理函数
头文件 <ctype.h>
中声明了许多字符处理函数,主要分为两大类:字符分类函数和大小写转换函数。
1 | // 字符分类函数 |
读/写字符
scanf
和 printf
读/写字符
scanf
和 printf
可以使用转换说明符 %c 对单个字符进行读写操作:
1 | char ch; |
scanf
函数在读字符时,不会跳过前面的空白字符。需要跳过前面的空白字符,则要在转换说明符 %c
前面加一个空格:
1 | scanf(" %c", &ch); /* skip white-space characters, then reads ch */ |
scanf
格式串中的空格意味着”跳过”零个或着多个空白字符。
getchar
和 putchar
读/写字符
getchar
用于读单个字符:
1 | int getchar(void); |
从 stdin
中读入一个字符,并且把读入的字符返回。读到文件的末尾,或者在读的过程中发生错误,返回 EOF
。
putchar
用于写单个字符:
1 | int putchar(int c); |
向标准输出 stdout
写入一个字符。写入成功,则返回写入的字符,否则返回 EOF
。
putchar
和getchar
函数的执行效率要高于printf
和scanf
。
getchar 函数常用于一些惯用法中。如:
1 | while (getchar() != '\n') /* skips rest of line */ |
类型转换
C 语言则允许在表达式中混合使用基本类型,表达式中可以组合整数、浮点数甚至是字符。C 编译器生成指令将某些操作数转换成其它类型的数据,使得硬件可以对表达式进行求值。
类型转换分为两种:隐式转换和显式转换
发生下列情况时,C 语言会进行隐式转换:
当算术表达式或逻辑表达式中操作数的类型不相同时。
当赋值运算符右侧表达式的类型和左侧变量的类型不匹配时。
当函数调用中的实参类型和其对应的行参类型不匹配时。
当 return 语句中表达式的类型和函数返回值的类型不匹配时
注意:尽量减少或禁止隐式转换,当给定的类型与需要的类型不同时,就可能发生隐式转换。
常用算术转换
C99 给每个整数类型都定义了”整数转换等级”,从高到低依次为:
1 | (1) long long int, unsigned long long int |
C 语言常用算术转换:
整数提升:操作数中有任何低于
int
和unsigned int
的类型,会首先将该操作数转换为int
类型或者unsigned int
类型。int -> long -> long long -> float -> double -> long double
signed -> unsigned1
2
3
3. **同一转换等级**的有符号整数和无符号整数一起参与运算时:有符号整数会转换成对应的无符号整数。1
2
3
4
5
6
7
8
9
10
11
12
最好尽量避免使用无符号整数,特别是不要把它和有符号整数混合使用。(无符号整数一般用于底层开发中,在应用层面很少会使用)。
### 赋值过程中的转换
**赋值运算规则**:把赋值运算右边的表达式转换为左边变量的类型。
当变量的类型至少和表达式类型一样"宽",转换是没有任何问题的。
```c
char c;int i;float f;double d;
i = c;f = i;d = f;
把浮点数赋值给整型变量会丢失小数部分:
1 | int i; |
值在变量类型的表示范围之外,将会得到无意义的结果 (甚至更糟):
1 | char c;int i;float f; |
将浮点常量赋值给 float
类型变量时,可以在常量后面加字符 f
,如:f = 3.14f
。如果没有后缀 f
,常量 3.14 是 double
类型,会引发隐式转换
。(尽量减少或禁止隐式转换)
强制类型转换
强制类型转换的格式如下:
1 | (type-name) expression |
C 语言把 (type_name) 看作是一元运算符,一元运算符的优先级是高于二元运算符。
使用强制类型转换计算浮点数的小数部分:
1
2float f, frac_part;
frac_part = f - (int) f;使用强制类型转换显示表明肯定会发生的转换:
1
i = (int) f;
使用强制类型转换进行需要的类型转换:
1
2
3
4float quotient;
int dividend, divisor;
quotient = dividend / divisor;
quotient = (float) dividend / divisor;使用强制类型转换来避免溢出:
1
2
3
4
5long i;
int j = 1000;
i = j * j; /* overflow may occur */
i = (long) (j * j); /* overflow may occur */
i = (long) j * j;
定义别名
使用 typedef 给类型定义别名:
1 | typedef 类型 别名; |
使用 typedef 定义别名,编译器会把别名加入其所能识别的类型名列表中。可以像使用内置类型一样使用别名。
1 | typedef int Bool; |
编译器会把 Bool
看作 int
的同义词;flag
是一个普通的 int
类型变量。
使用别名主要有以下两个优点:
- 增加代码的可读性 (前提是选择合适的类型名)。
- 增加代码的可移植性。
C 语言库也经常使用 typedef
去定义一些类型;这些类型名经常以 _t 结尾,_t
是一个结构的标注,可以理解为type
/typedef
的缩写,表示它是通过typedef
定义的,而不是其它数据类型。
如:size_t
, ptrdiff_t
和 wchar_t
,类型的定义可能不同(增加代码的可移植性)。下面是它们的典型定义:
1 | typedef long int ptrdiff_t; |
size_t
是一种无符号整数类型,通常被定义为能够表示系统中最大可能的对象大小的无符号整数类型。在大多数情况下 size_t
和 unsigned long
在大小上相似。
uint8_t, uint16_t, uint32_t, uint64_t
按照posix
标准,一般整形对应的*_t类型为:
1 | 1字节 uint8_t |
sizeof运算符
sizeof
运算符可以计算某一类型变量所需的内存空间大小 (以字节为单位)。其格式为:
1 | sizeof(type_name) |
sizeof
表达式的值为 size_t
类型(无符号长整形)。
sizeof
运算符也可以应用于常量、变量和表达式。如:
1 | int i, j; |
在一个典型的 32-bit 和 64-bit 的机器上,上面三个表达式的值都为 4
。