数据类型
C语言中,数据类型包括:基本类型(整型、浮点型、字符型),复合类型(数组、结构体、联合体、枚举),指针类型。这篇笔记我们介绍C语言中基本数据类型相关的知识,有关复合数据类型和指针将在后续章节介绍。
什么是数据类型
数据类型是对数据的抽象,相同类型的数据有相同的表示形式、存储格式以及相关操作。编程语言中所有的数据都必定属于某种类型。可以这样简单的理解:数据类型就是创建变量的模具。
整型
C语言中,整型包括char,short,int,long int(简写为long),long long int(简写为long long),signed和unsigned表示有符号数或无符号数。八进制数以0开头,十六进制以0x开头,字面值结尾加u表示unsigned,加L表示long。
整型的长度可参考下表。
| 类型 | ILP32 | LP64 |
|---|---|---|
| char | 8 | 8 |
| short | 16 | 16 |
| int | 32 | 32 |
| long | 32 | 64 |
| long long | 64 | 64 |
| 指针 | 32 | 64 |
注:ILP32/LP64指Intel x86/64平台,Linux,gcc编译器。
浮点型
C语言中,浮点型包括float,double,long double。IEEE754标准中,float为32位,double为64位,long double则平台差异较大,不能一概而论。
类型转换
C语言中,类型转换可以分为隐式类型转换和强制类型转换。其中,隐式转换虽然规则看起来很复杂,但实际使用时并不需要考虑太多,下面我们简单介绍。
Integer Promotion(整型提升)
在一个表达式中,凡是可以使用int或unsigned int类型做右值的地方也都可以使用有符号或无符号的char类型、short类型。如果原始类型的取值范围都能用int类型表示,则其类型被提升为int,如果原始类型的取值范围用int类型表示不了,则提升为unsigned int型,这称为Integer Promotion(整型提升)。
整型提升只影响上述几种类型的值,对其它类型无影响。C99规定整型提升适用于以下几种情况:
- 如果一个函数的形参类型未知,或者函数的参数列表中有
...,那么调用函数时要对相应的实参做整型提升。此外,相应的实参如果是float类型的也要被提升为double类型,这条规则称为Default Argument Promotion(默认参数提升)。 - 算术运算中存在类型转换,有符号或无符号的
char型、short型在做算术运算之前首先要做整型提升,然后才能参与计算。例:
unsigned char c1 = 255, c2 = 2;
int n = c1 + c2;
Usual Arithmetic Conversion(常用算数转换)
- 如果有一边的类型是
long double,则把另一边也转成long double。 - 否则,如果有一边的类型是
double,则把另一边也转成double。 - 否则,如果有一边的类型是
float,则把另一边也转成float。 - 否则,两边应该都是整型,首先按上一小节讲过的规则对
a和b做整型提升,如果类型仍不相同,则需要继续转换。首先我们规定char、short、int、long、long long的转换级别( Integer Conversion Rank) 一个比一个高,同一类型的有符号和无符号数具有相同的Rank。转换规则如下:- 如果两边都是有符号数,或者都是无符号数,那么较低Rank的类型转换成较高Rank的类型。例如
unsigned int和unsigned long做算术运算时都转成unsigned long。 - 否则,如果一边是无符号数另一边是有符号数,无符号数的Rank不低于有符号数的Rank,则把有符号数转成另一边的无符号类型。例如
unsigned long和int做算术运算时都转成unsigned long,unsigned long和long做算术运算时也都转成unsigned long。 - 剩下的情况是:一边有符号另一边无符号,并且无符号数的Rank低于有符号数的Rank。这时又分为两种情况,如果这个有符号数类型能够覆盖这个无符号数类型的取值范围,则把无符号数转成另一边的有符号类型。例如遵循LP64的平台上
unsigned int和long在做算术运算时都转成long。 - 否则,也就是这个有符号数类型不足以覆盖这个无符号数类型的取值范围,则把两边都转成有符号数的Rank对应的无符号类型。例如在遵循ILP32的平台上
unsigned int和long在做算术运算时都转成unsigned long。
- 如果两边都是有符号数,或者都是无符号数,那么较低Rank的类型转换成较高Rank的类型。例如
赋值产生的类型转换
如果赋值或初始化时等号两边的类型不相同,则编译器会把等号右边的类型转换成等号左边的类型再做赋值。函数参数、返回值也是赋值的过程,也是同理的。
强制类型转换
例如计算表达式(double)3 + i,首先将整数3强制转换成double型(值为3.0),然后和整型变量i相加,这时适用Usual Arithmetic Conversion规则,首先把i也转成double型,然后两者相加,最后整个表达式也是double型的。
编译器处理类型转换
现在要把一个M位的类型(值为X)转换成一个N位的类型,所有可能的情况如下表所示。实际上下图中容易出错的做法很少出现,全部转换不需要记住,遇到bug能想到这方面现查即可。

typedef 数据类型别名
typedef可以给一个数据类型起别名,下面是一个例子。
typedef unsigned int u32;
typedef struct complex_struct
{
double x, y;
} complex_struct;
代码中,我们给unsigned int起了别名u32,给struct complex_struct起了别名complex_struct,使用这些数据类型声明变量时我们可以直接使用别名。
注意:不要滥用别名!过度使用别名是一种画蛇添足的行为,不仅不会提升代码可读性,反而会严重降低代码可读性。
void类型
C语言中有一种特殊的“空”类型void,它也有对应的指针类型void *(参考指针相关章节)。void类型单独使用没有意义,它的意义在于对函数参数和函数返回进行限定。
void printMessage(void)
{
printf("Hello, world!\n");
}
参数中的void表示该函数不接受任何参数,返回值中的void表示该函数不返回任何值。
sizeof操作符
sizeof是一个特殊的运算符,它能返回变量或类型在内存中分配空间时分配的字节数。
#include <stdio.h>
int main(void)
{
printf("%zu\n", sizeof(int));
printf("%zu\n", sizeof 1);
return 0;
}