C_基本数据类型

C_基本数据类型

基本数据类型主要包括:整数类型浮点数类型字符类型,其中字符类型也可以看作是整数类型中的一种。

整数类型

整数类型可以分为两大类:有符号整数无符号整数

默认情况下,C 语言的整数类型都是有符号的;若要声明为无符号整数,则需要加 unsigned 关键字。C 语言的整数类型有以下这些:

1
2
3
4
5
6
7
8
short (int)
unsigned short (int)
int
unsigned (int)  
long (int)
unsigned long (int)
long long (int)
unsigned long long (int)

C 语言整数类型的取值范围根据机器的不同而不同。但是编译器都必须遵循的原则:

  1. C 标准规定了 short(2), int(2), long(4), long long(8) 的最小字节长度。

  2. 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。

  • 十六进制字面值包含数字 09 和字母 af,而且总以 0x 开头。如:0xf, 0xff, 0x7ff。

    十六进制字面值中的字母即可以是大写也可以是小写,如:0xff, 0xfF, 0xFF, 0Xff, 0XfF, 0XFF。

注意:整数字面值也是有类型的

十进制整数字面值的类型通常是int,但如果该字面值超出了int的表示范围,那么它的类型是 int, longlong long 中能表示该值的 “最小” 类型。

对八进制和十六进制整数字面值来说,可能的类型顺序为:int, unsigned intlong, unsigned long, long longunsigned 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

读/写整数

使用 scanfprintf 函数读写无符号整数、短整数和长整数,则需要其它的转换说明符。(%d 只适用于读写 int 类型的数据。)

  • 读写 unsigned int 时,使用字母 u, ox 替代转换说明符中的 d。其中 u 表明无符号整数是十进制形式;o 表明无符号整数是八进制形式;x表明是十六进制形式。

    1
    2
    3
    4
    5
    6
    7
    unsigned 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
    3
    short n;
    scanf("%hd", &n);
    printf("%hd", n);
  • 读写 long 时,在d, u, o 或者 x 前面加字母 l (long):

    1
    2
    3
    long n;
    scanf("%ld", &n);
    printf("%ld", n);
  • 读写 long long 时,在 d, u, o 或者 x 前面加字母 ll (longlong):

    1
    2
    3
    long 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 类型。单精度方式存储可以在末尾加字母 Ff,如 57.0F 。以 long double 方式存储,则在后面加 Ll,如 57.0L

读/写浮点数

读写 doublelong double 类型所需的说明符与 float 略有不同。

  • 读写 float 类型的值时:

    1
    2
    3
    float d;
    scanf("%f", &d);
    printf("%f", d);
  • 读写 double 类型的值时,需要在 f 前面添加字母 l

    1
    2
    3
    double d;
    scanf("%lf", &d);
    printf("%lf", d);
  • 读写 long double 类型的值时,需要在 f 前面添加字母 L

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

字符类型

不同的机器可能使用不同的字符集,char类型的值可能不同。ASCII(American Standard Code for Information Interchange) 字符集使用 7 位来表示 128 个字符,如下图所示:

ASCII

需要记住的:' ' = 32, '0' = 48, 'A' = 65, 'a' = 97。

char 类型的变量可以存储单个字符,注意:字符字面值应该用单引号括起来。

1
2
3
4
5
char ch;
ch = 'a';
ch = 'A';
ch = '0';
ch = ' ';

字符操作

C 语言是把字符当作小的整数进行处理的。C 语言会使用字符对应的整数值,如:

1
2
3
4
5
6
7
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' */

下面的代码可以把小写字母变成大写字母:

1
2
3
/* 不推荐。toupper(),tolower()更好,在<ctype.h>头文件中*/
if (ch >= 'a' && ch <= 'z')
ch = ch - 'a' + 'A';

下面使用 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 字符分类函数
int isalnum(int c); /* Is c alphanumeric? */
int isalpha(int c); /* Is c alphabetic? */
int isblank(int c); /* Is c a blank? i.e. ' ' or '\t' */
int iscntrl(int c); /* Is c a control character? */
int isdigit(int c); /* Is c a decimal digit? */
int isgraph(int c); /* Is c a printing character (other than a space)? */
int islower(int c); /* Is c a lower-case letter? */
int isprint(int c); /* Is c a printing character (including a space)? */
int ispunct(int c); /* Is c punctuation? */
int isspace(int c); /* Is c a white-space character? */
int isupper(int c); /* Is c an upper-case letter? */
int isxdigit(int c); /* Is c a hexadecimal digit? */
int tolower(int c);// 大小写转换函数
int toupper(int c);// 大小写转换函数

读/写字符

scanfprintf 读/写字符

scanfprintf 可以使用转换说明符 %c 对单个字符进行读写操作:

1
2
3
char ch;
scanf("%c", &ch); /* reads a single character */
printf("%c", ch); /* writes a single character */

scanf 函数在读字符时,不会跳过前面的空白字符。需要跳过前面的空白字符,则要在转换说明符 %c 前面加一个空格:

1
scanf(" %c", &ch); /* skip white-space characters, then reads ch */

scanf 格式串中的空格意味着”跳过”零个或着多个空白字符。

getcharputchar 读/写字符

getchar 用于读单个字符:

1
int getchar(void);

stdin 中读入一个字符,并且把读入的字符返回。读到文件的末尾,或者在读的过程中发生错误,返回 EOF

putchar 用于写单个字符:

1
int putchar(int c);

向标准输出 stdout 写入一个字符。写入成功,则返回写入的字符,否则返回 EOF

putchargetchar 函数的执行效率要高于 printfscanf

getchar 函数常用于一些惯用法中。如:

1
2
while (getchar() != '\n') /* skips rest of line */
;

类型转换

C 语言则允许在表达式中混合使用基本类型,表达式中可以组合整数、浮点数甚至是字符。C 编译器生成指令将某些操作数转换成其它类型的数据,使得硬件可以对表达式进行求值。

类型转换分为两种:隐式转换显式转换

发生下列情况时,C 语言会进行隐式转换

  • 当算术表达式或逻辑表达式中操作数的类型不相同时。

  • 当赋值运算符右侧表达式的类型和左侧变量的类型不匹配时。

  • 当函数调用中的实参类型和其对应的行参类型不匹配时。

  • 当 return 语句中表达式的类型和函数返回值的类型不匹配时

注意:尽量减少或禁止隐式转换,当给定的类型与需要的类型不同时,就可能发生隐式转换。

常用算术转换

C99 给每个整数类型都定义了”整数转换等级”,从高到低依次为:

1
2
3
4
5
(1) long long int, unsigned long long int
(2) long int, unsigned long int
(3) int, unsigned int
(4) short int, unsigned short int
(5) char, signed char, unsigned char

C 语言常用算术转换:

  1. 整数提升:操作数中有任何低于 intunsigned int 的类型,会首先将该操作数转换为 int类型或者 unsigned int 类型。

  2. int -> long -> long long -> float -> double -> long double
    
    1
    2
    3

    3. **同一转换等级**的有符号整数和无符号整数一起参与运算时:有符号整数会转换成对应的无符号整数。

    signed -> unsigned
    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
2
3
int i;
i = 842.97; /* i is now 842 */
i = -842.97; /* i is now -842 */

值在变量类型的表示范围之外,将会得到无意义的结果 (甚至更糟):

1
2
3
4
char c;int i;float f;
c = 10000; /* WRONG */
i = 1.0e20; /* WRONG */
f = 1.0e100; /* WRONG */

将浮点常量赋值给 float 类型变量时,可以在常量后面加字符 f,如:f = 3.14f 。如果没有后缀 f,常量 3.14 是 double 类型,会引发隐式转换。(尽量减少或禁止隐式转换)

强制类型转换

强制类型转换的格式如下:

1
(type-name) expression

C 语言把 (type_name) 看作是一元运算符,一元运算符的优先级是高于二元运算符。

  • 使用强制类型转换计算浮点数的小数部分:

    1
    2
    float f, frac_part;
    frac_part = f - (int) f;
  • 使用强制类型转换显示表明肯定会发生的转换:

    1
    i = (int) f;
  • 使用强制类型转换进行需要的类型转换:

    1
    2
    3
    4
    float quotient;
    int dividend, divisor;
    quotient = dividend / divisor;
    quotient = (float) dividend / divisor;
  • 使用强制类型转换来避免溢出:

    1
    2
    3
    4
    5
    long 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
2
3
typedef int Bool;
...
Bool flag; /* same as int flag */

编译器会把 Bool 看作 int 的同义词;flag 是一个普通的 int 类型变量。

使用别名主要有以下两个优点:

  1. 增加代码的可读性 (前提是选择合适的类型名)。
  2. 增加代码的可移植性。

C 语言库也经常使用 typedef 去定义一些类型;这些类型名经常以 _t 结尾,_t是一个结构的标注,可以理解为type/typedef的缩写,表示它是通过typedef定义的,而不是其它数据类型。

如:size_t, ptrdiff_twchar_t,类型的定义可能不同(增加代码的可移植性)。下面是它们的典型定义:

1
2
3
typedef long int ptrdiff_t;
typedef unsigned long int size_t;
typedef int wchar_t;

size_t 是一种无符号整数类型,通常被定义为能够表示系统中最大可能的对象大小的无符号整数类型。在大多数情况下 size_tunsigned long 在大小上相似。

uint8_t, uint16_t, uint32_t, uint64_t

按照posix标准,一般整形对应的*_t类型为:

1
2
3
4
1字节     uint8_t
2字节 uint16_t
4字节 uint32_t
8字节 uint64_t

sizeof运算符

sizeof 运算符可以计算某一类型变量所需的内存空间大小 (以字节为单位)。其格式为:

1
sizeof(type_name)

sizeof 表达式的值为 size_t 类型(无符号长整形)。

sizeof 运算符也可以应用于常量、变量和表达式。如:

1
2
3
4
int i, j;
sizeof(10);
sizeof(i);
sizeof(i + j);

在一个典型的 32-bit 和 64-bit 的机器上,上面三个表达式的值都为 4