Skip to main content

数据

C语言有5种基本数据类型:charintfloatdoublevoid(不能被修饰)

通过2个长度修饰符:

  • short:只能修饰int
  • long :可修饰1~2次 int 或1次 double

以及2个符号修饰符(只能修饰整型类型,可以和长度修饰符组合):

  • signed :可以缺省,不写符号修饰符等同于signed。有符号则表示正数和负数,绝对值范围减半。
  • unsigned:不可以缺省。无符号则只表示正数,不表示负数。

可以组合出14种基本数据类型。

一级分类二级分类字节数类型完整形式简化形式取值范围描述
整型类型字符型1charsigned charchar-2⁷ ~ 2⁷-1字符类型(8位,不支持中文
整型类型字符型1unsigned charunsigned charunsigned char0 ~ 2⁸-1无符号字符型(8位,不支持中文
整型类型短整型2shortsigned short intshort/short int-2¹⁵ ~ 2¹⁵-1有符号短整型(16位)
整型类型短整型2unsigned shortunsigned short intunsigned short0 ~ 2¹⁶-1无符号短整型(16位)
整型类型标准整型4intsigned intint/signed-2³¹ ~ 2³¹-1有符号整型(32位,默认)
整型类型标准整型4unsigned intunsigned intunsigned int/unsigned0 ~ 2³²-1无符号整型(32位)
整型类型长整型4/8longsigned long intlong-2³¹ ~ 2³¹-1 或 -2⁶³ ~ 2⁶³-1有符号长整型(32位或64位)
整型类型长整型4/8unsigned longunsigned long intunsigned long0 ~ 2³²-1 或 0 ~ 2⁶⁴-1无符号长整型(32位或64位)
整型类型长长整型8long longsigned long long intlong long int/signed long long/long long-2⁶³ ~ 2⁶³-1有符号长长整型(64位)
整型类型长长整型8unsigned long longunsigned long long intunsigned long long0 ~ 2⁶⁴-1无符号长长整型(64位)
浮点型类型单精度4floatfloatfloat±1.18×2⁻¹²⁶ ~ ±3.40×2¹²⁷单精度浮点型(32位IEEE754)
浮点型类型双精度8doubledoubledouble±2.23×2⁻¹⁰²² ~ ±1.80×2¹⁰²³小数默认类型,双精度浮点型(64位IEEE754)
浮点型类型扩展精度8/12/16long doublelong doublelong double平台相关扩展精度浮点型(64/80/128位)
特殊类型-0voidvoidvoid-空类型,不占存储空间

char

用于存储一个基本的 ASCII 字符(不可以存储中文)。

ASCII 码表很小,只包含大小写字母、数字、标点、打印机用的换行、换页等符号。

  • 用单引号括起来:例如 'a', '1', '$'
  • 转义字符:例如 '\n'(换行), '\t'(制表符), '\\'(反斜杠)
#include <stdio.h>
int main(){
const char letter = 'A';
const char newline = '\n';
const char tab = '\t';

printf("字符: %c\n", letter);
printf("换行前%c换行后\n", newline);
printf("制表符前%c制表符后\n", tab);
return 0;
}

charint类型转换时,int转为char时会是对应的ASCII码表中字符。char转为int时会是对应的ASCII码表中对应的数字。

ASCII 表

tip

想要存储中文可以试试字符串数组。

  • 用双引号括起来:例如 "Hello", "C语言"
  • 系统会自动在字符串末尾添加 '\0' 作为结束标志
#include <stdio.h>
#include <string.h>
int main(){
const char greeting[] = "Hello World";
const char empty[] = "";

printf("问候语: %s\n", greeting);
printf("空字符串长度: %lu\n", strlen(empty));
return 0;
}

int

定义时可以带正负号,默认是正数。支持四种进制格式:

  • 十进制整数:例如 666, -120, 0
  • 二进制整数:以 0b 开头,例如 0b1010(十进制的10)
  • 八进制整数:以 0 开头,例如 0123(十进制的83)
  • 十六进制整数:以 0x 开头,例如 0x123(十进制的291)
#include <stdio.h>
int main(){
printf("十进制: %d\n", 123); // 123
printf("八进制: %d\n", 0123); // 83
printf("十六进制: %d\n", 0x123); // 291
printf("二进制: %d\n", 0b1010); // 10
return 0;
}

判断下列数字是否合理

+178

0b325 // 二进制只能是0-1
0b0010

0986 // 八进制只能是0-7
00011

0x001
0x7h4 // 十六进制只能是0-9,a-f,A-F
0xffdc

signed 和 unsigned

符号修饰符(仅适用于整数类型):

  • signed - 有符号位
  • unsigned - 无符号位

signedunsigned 的区别就是它们的最高位是否要当做符号位,并不会像 shortlong 那样改变数据的长度,即所占的字节数。

tip

浮点数类型(floatdoublelong double)天然支持正负值,不能使用符号修饰符

signed 表示有符号的,也就是说最高位要当做符号位。刚好 int 的最高位本来就是符号位。

因此 signed 等价 int 等价 signed int

short 和 long

  • short:只能修饰int
  • long :可修饰1~2次 int 或1次 double
tip

🤔 为什么 long 不一定比 int 大?有时是4字节,有时是8字节?

核心原因:C标准只规定了相对大小关系,没有规定绝对大小!

// C标准只保证这些相对关系:
sizeof(char) <= sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(long long)
// 1字节 ≥2字节 ≥2字节 ≥4字节 ≥8字节

1980年代-1990年代(32位时代):

// 当时的"标准"配置
char = 1字节 (8)
short = 2字节 (16)
int = 4字节 (32) ← 与CPU字长匹配
long = 4字节 (32) ← 也是32位,够用了

2000年代后(64位时代):

// 两种不同的选择
// Linux/macOS (LP64模型)
int = 4字节 (32) ← 保持兼容性
long = 8字节 (64) ← 升级到64

// Windows (LLP64模型)
int = 4字节 (32) ← 保持兼容性
long = 4字节 (32) ← 与32位系统保持一致,向前兼容
long long = 8字节 (64) ← 用long long64

如果你需要特定大小:

// ✅ 推荐:使用标准库的固定大小类型
#include <stdint.h>
int32_t x; // 明确32位
int64_t y; // 明确64位
uint32_t z; // 明确32位无符号

// ⚠️ 不推荐:依赖long的大小
long maybe_big; // 大小不确定

float与double

实型常量

  • 单精度小数?:以字母 fF 结尾,例如 3.14f
  • 双精度小数?:默认的小数形式,例如 3.14159
  • 指数形式:使用 eE 表示科学计数法,例如 1.23e5(表示123000)
#include <stdio.h>
int main(){
const float pi_f = 3.14f; // 单精度
const double pi_d = 3.14159; // 双精度(默认的小数形式就是双精度)
const double big_num = 1.23e5; // 科学计数法
const double small_num = 1.23e-3; // 0.00123

printf("单精度PI: %.7f\n", pi_f);
printf("双精度PI: %.15f\n", pi_d);
printf("大数: %.0f\n", big_num);
printf("小数: %.5f\n", small_num);
return 0;
}

判断下列数字是否合理

10.98    // ✅ 合理:标准的十进制小数
.089 // ✅ 合理:浮点数,前面的0可以省略,等同于0.089
-.003 // ✅ 合理:负数浮点常量
96.0f // ✅ 合理:单精度浮点常量
3.14E-2 // ✅ 合理:科学计数法,等同于0.0314

96f // ❌ 错误:缺少小数点
96.oF // ❌ 错误:用了字母'o'而不是数字'0',应该是96.0F
3.14.15 // ❌ 错误:多个小数点
info

long double 精度差异的技术演进

如果说long int字节不一致是处理器字长升级导致的兼容性问题,那么long double字节差异则反映了不同厂商在"精度与效率"之间的技术权衡。

C标准只规定了相对大小关系,没有规定绝对大小,因此long double的精度取决于各大硬件厂商的具体实现。

1980年代初期

Intel开发了x87浮点协处理器(8087),采用80位(10字节)扩展精度格式,提供了比标准64位double更高的计算精度。这在当时的科学计算领域具有重要意义。

Linux和GCC 选择充分利用Intel的80位精度能力,为需要高精度计算的应用提供支持。

Microsoft 则采用了更为保守的策略,在Visual Studio中将long double实现为64位,与double相同。这样做的考虑是:

  • 简化编译器实现和调试
  • 保持与早期Windows系统的兼容性
  • 64位精度已能满足大多数应用需求

2000年代后期

随着移动设备兴起,ARM架构 开始在嵌入式和移动领域占主导地位。ARM处理器专注于功耗优化:

  • 80位运算增加功耗和复杂度
  • 移动设备电池容量有限
  • 64位精度足够满足移动应用需求

苹果、Google、高通、三星 等移动平台厂商都选择了ARM的64位实现方案。

同时期:高性能计算的野心

就在移动厂商追求效率的同时,高性能计算领域却在追求更极致的精度:

IBM AIX/PowerPC 拿出了"双倍双精度"方案:"既然单个64位不够,那就用两个64位组合!" 这种Double-Double格式提供了约31位十进制精度。

GCC社区 更进一步,推出了__float128:"我们要真正的128位IEEE标准实现!" 34位十进制精度,满足最苛刻的科学计算需求。

现在的状况:

平台/编译器long double大小设计考量
Windows + Visual Studio8字节(64位)兼容性优先,简化实现
Linux + GCC (x86)12/16字节(80位)充分利用x87硬件能力
Mac + Clang (Intel)16字节(80位)遵循x86-64 ABI标准
Mac + Clang (Apple Silicon)8字节(64位)ARM架构的效率优化
Android开发8字节(64位)移动平台功耗考量
IBM AIX/PowerPC16字节(128位)双倍双精度,超高精度计算
GCC __float12816字节(128位)IEEE 754四精度扩展

32位处理器一次操作处理32位(4字节)数据,64位处理器一次操作处理64位(8字节)数据。

80位(10字节)数据在32位处理器需要操作3次,等价处理了3x4=12字节。在64位处理器需要操作2次,等价处理了2x8=16字节。

开发建议:

  • 不要假设long double一定比double
  • 需要特定精度时,考虑使用专门的高精度数学库
  • 跨平台项目应该测试各平台的精度差异

void

void 的主要用途:

用途语法示例说明
函数返回类型void 函数名()void print_hello() { printf("Hello"); }表示函数不返回任何值
函数参数函数名(void)int get_random(void) { return 42; }表示函数不接受任何参数
通用指针void*void *ptr; ptr = &some_variable;可以指向任何类型的数据
// ✅ void 的正确用法
void print_message(void) { // 无返回值,无参数
printf("Hello World\n");
}

int main(void) { // 返回int,无参数
void *generic_ptr; // 通用指针
int num = 10;
generic_ptr = &num; // 指向int类型数据

print_message(); // 调用void函数
return 0;
}

// ❌ void 的错误用法
void variable; // 错误!不能声明void类型变量
void array[10]; // 错误!不能创建void数组

重要提醒:

  • void 本身不能存储数据,只用于类型声明
  • void* 是通用指针,使用前需要强制类型转换
  • 函数参数写 void 比省略更明确(C89标准推荐)

类型转换

显式转换

显式转换也叫强制类型转换:(需要转换的类型)(表达式)

// 将double转换为int
int a = (int)(10.5 + 0.6);
// C 取整时,总是向0取整。(计算余数和四舍五入时也总是向0取整)
// 所以结果为11

// 当只有一个数时,括号可以省略
int a = (int) 10.5;
// 结果为10

// 括号省略会导致可读性变差
int a = (int) 10.5 + 0.6;
// 结果为10
// 第一步:(int) 10.5 被强制转换为 10(int类型)
// 第二步:10(int)+ 0.6(double)→ 10(int)被自动提升为10.0(double),计算得10.6(double)
// 第三步:将10.6(double)隐式转换为int类型赋值给a,结果为10
// C 取整时,总是向0取整(截断小数部分)

赋值转换

// 赋值时左边是什么类型,就会自动将右边转换为什么类型再保存
int a = 10.6;
// 结果为10
// C 取整时,总是向0取整。(计算余数和四舍五入时也总是向0取整)

运算时转换

当运算符左右两边类型不一致时,系统会自动对占用内存较少的类型做一个“自动类型提升”的操作。

先将其转换为当前算数表达式中占用内存高的类型, 然后再参与运算。

// 结果为0, 因为参与运算的都是整型,先得出0,再转换为double类型
double a = (double)(1 / 2);

// 结果为0.5, 因为1被强制转换为了double类型, 2也会被自动提升为double类型
double b = (double)1 / 2;
// 等价于
double b = 1.0 / 2;
  • 类型转换并不会影响到原有变量的值
#include <stdio.h>
int main(){
double d = 3.14;
int num = (int)d;
printf("num = %i\n", num); // 3
printf("d = %lf\n", d); // 3.140000
}