HiHuo
首页
博客
手册
工具
关于
首页
博客
手册
工具
关于
  • C 语言入门教程

    • C语言系列教程
    • 第01章 基础概念与编译
    • 第02章 数据类型与变量
    • 第03章 运算符
    • 第04章 输入输出
    • 第05章 流程控制
    • 第06章 数组
    • 第07章 指针
    • 第08章 函数
    • 第09章 构造类型

第02章 数据类型与变量

本章介绍C语言的数据类型体系、IEEE754浮点数标准、变量的存储类型以及宏定义

2.1 新的C语言新增bool类型

#include <stdbool.h>
bool a = false;
printf("a = %d\n", a);

2.2 C语言数据类型体系

C语言数据类型体系

C语言数据类型
├── 基本数据类型
│   ├── 数值类型
│   │   ├── 整数(整型)
│   │   │   ├── 短整型short
│   │   │   ├── 整型int
│   │   │   └── 长整型long
│   │   └── 浮点数
│   │       ├── 单精度float
│   │       └── 双精度double
│   └── 字符类型char
├── 构造类型
│   ├── 数组
│   ├── 结构体struct
│   ├── 共用体union
│   └── 枚举enum
├── 指针类型
└── 空类型void

2.3 变量的定义

变量定义语法

[存储类型] 数据类型 标识符 = 值;

存储类型:

  • auto - 自动变量(默认,存储在栈中)
  • static - 静态变量
  • register - 寄存器变量(建议型)
  • extern - 外部变量(说明型)

示例

#include <stdio.h>
#include <stdlib.h>

int main()
{
    auto int a = 10;        // auto可省略,默认就是auto
    static int b = 20;      // 静态变量
    register int c = 30;    // 建议存储在寄存器

    printf("a = %d\n", a);
    printf("b = %d\n", b);
    printf("c = %d\n", c);

    exit(0);
}

2.4 存储类型详解

2.4.1 auto - 自动变量

  • 默认的存储类型,可以省略
  • 存储在栈中
  • 函数结束后自动释放
int main()
{
    auto int a = 10;  // 等同于: int a = 10;
    // 函数结束,a被自动释放
    return 0;
}

2.4.2 static - 静态变量

特点:

  • 自动初始化为0值或空值
  • 值具有继承性(保持上次的值)
  • 存储在静态存储区
  • 具有全局寿命,局部可见性

修饰变量:

#include <stdio.h>

void func()
{
    static int count = 0;  // 只初始化一次
    count++;
    printf("count = %d\n", count);
}

int main()
{
    func();  // 输出: count = 1
    func();  // 输出: count = 2
    func();  // 输出: count = 3
    return 0;
}

修饰函数:

// file1.c
static void helper()  // static函数,外部文件不可访问
{
    printf("Helper function\n");
}

// file2.c
void helper();  // 编译错误:找不到helper函数

2.4.3 register - 寄存器变量

特点:

  • 建议编译器将变量存储在寄存器中(仅建议)
  • 只能定义局部变量
  • 不能定义全局变量
  • 只能定义4字节数据类型
  • 没有地址,无法使用&取址

示例:

int main()
{
    register int i;
    for (i = 0; i < 1000000; i++)
    {
        // 频繁使用的循环变量适合用register
    }

    // printf("%p\n", &i);  // 错误:寄存器变量没有地址
    return 0;
}

⚠️ 注意: 现代编译器已经很智能,会自动优化,register关键字基本不再使用。

2.4.4 extern - 外部变量

特点:

  • 说明型,不能改变被说明变量的值或类型
  • 用于声明定义在其他文件中的全局变量

示例:

// file1.c
int global_var = 100;  // 定义全局变量

// file2.c
extern int global_var;  // 声明外部变量

int main()
{
    printf("%d\n", global_var);  // 可以使用file1.c中的变量
    return 0;
}

2.5 IEEE754浮点数存储

2.5.1 浮点数表示法

根据IEEE754标准,任意一个二进制浮点数V可以表示为:

V = (-1)^S × M × 2^E
  • S: 符号位 (0表示正数,1表示负数)
  • M: 尾数 (有效数字,大于等于1,小于2)
  • E: 指数

2.5.2 32位单精度浮点数存储

32位浮点数的内存分配:

|  S  |    E (8位)    |        M (23位)           |
| 1位 |    指数位     |         尾数位            |
  31    30        23    22                      0

示例: 5.25的二进制存储

  1. 5.25的二进制: 101.01
  2. 规范化: 1.0101 × 2²
  3. S = 0 (正数)
  4. M = 0101 (隐藏高位1)
  5. E = 2 + 127 = 129 = 10000001 (偏移127)

2.5.3 隐藏高位1

尾数部分的最高位始终为1,因此可以省略,节约1位存储空间。

示例:

  • 实际尾数: 1.0101
  • 存储尾数: 0101 (省略"1.")

2.5.4 浮点数精度问题

#include <stdio.h>

int main()
{
    float f = 3.14159265358979323846;
    double d = 3.14159265358979323846;

    printf("float:  %.20f\n", f);   // 精度丢失
    printf("double: %.20f\n", d);   // 精度更高

    return 0;
}

输出:

float:  3.14159274101257324219
double: 3.14159265358979311600

⚠️ 注意: 浮点数存在精度问题,不能用于精确计算(如金融)。

2.6 基本数据类型

2.6.1 整数类型

#include <stdio.h>

int main()
{
    short s = 10;           // 短整型,2字节
    int i = 100;            // 整型,4字节
    long l = 1000L;         // 长整型,4或8字节
    long long ll = 10000LL; // 长长整型,8字节

    printf("short:     %d bytes\n", sizeof(s));
    printf("int:       %d bytes\n", sizeof(i));
    printf("long:      %d bytes\n", sizeof(l));
    printf("long long: %d bytes\n", sizeof(ll));

    return 0;
}

有符号与无符号:

int main()
{
    signed int a = -10;      // 有符号(默认)
    unsigned int b = 10;     // 无符号

    printf("signed:   %d\n", a);
    printf("unsigned: %u\n", b);

    return 0;
}

2.6.2 浮点类型

int main()
{
    float f = 3.14f;         // 单精度,4字节
    double d = 3.14;         // 双精度,8字节
    long double ld = 3.14L;  // 扩展精度,10/12/16字节

    printf("float:       %d bytes\n", sizeof(f));
    printf("double:      %d bytes\n", sizeof(d));
    printf("long double: %d bytes\n", sizeof(ld));

    return 0;
}

2.6.3 字符类型

int main()
{
    char c = 'A';            // 字符,1字节

    printf("char: %c\n", c);       // 输出字符: A
    printf("ASCII: %d\n", c);      // 输出ASCII码: 65
    printf("size: %d bytes\n", sizeof(c));  // 1字节

    return 0;
}

2.6.4 布尔类型

C99标准新增布尔类型:

#include <stdio.h>
#include <stdbool.h>  // 需要包含此头文件

int main()
{
    bool flag = false;

    printf("flag = %d\n", flag);  // 输出: 0

    flag = true;
    printf("flag = %d\n", flag);  // 输出: 1

    return 0;
}

2.7 宏定义与常量

2.7.1 宏定义 #define

特点:

  • 在预处理阶段进行文本替换
  • 不检查语法
  • 不占用内存

对象宏:

#include <stdio.h>

#define PI 3.14asd  // 语法错误,但预处理阶段不检查

int main()
{
    printf("%d\n", PI);  // 编译阶段才报错
    return 0;
}

预处理后:

int main()
{
    printf("%d\n", 3.14asd);  // 此时才发现语法错误
    return 0;
}

函数宏:

#define ADD 2 + 3
#define MAX(a, b) (a > b ? a : b)

int main()
{
    printf("%d\n", ADD * ADD);      // 2 + 3 * 2 + 3 = 11(不是25!)
    printf("%d\n", MAX(1, 2));      // (1 > 2 ? 1 : 2) = 2

    return 0;
}

⚠️ 宏定义的危险性:

  • 容易出现运算优先级问题
  • 不进行类型检查
  • 调试困难

2.7.2 const常量

特点:

  • 编译时检查类型
  • 占用内存
  • 不能修改值
int main()
{
    const float pi = 3.14;  // 常量

    printf("%f\n", pi);
    // pi = 3.14159;  // 错误:不能修改const变量

    return 0;
}

2.7.3 const vs #define

特性#defineconst
类型检查否是
占用内存否是
调试困难容易
作用域全局可限定
推荐度低高

推荐使用const:

const float PI = 3.14159;  // 推荐
// #define PI 3.14159      // 不推荐

2.8 类型转换

2.8.1 隐式转换

int main()
{
    int a = 10;
    float b = 3.14;

    float c = a + b;  // int自动转换为float
    printf("%f\n", c);  // 13.140000

    return 0;
}

2.8.2 显式转换(强制转换)

int main()
{
    float f = 3.14;
    int i = (int)f;  // 强制转换,小数部分被截断

    printf("f = %f\n", f);  // 3.140000
    printf("i = %d\n", i);  // 3

    return 0;
}

2.9 sizeof运算符

sizeof是编译时运算符,返回类型或变量的字节数:

#include <stdio.h>

int main()
{
    printf("char:   %lu bytes\n", sizeof(char));
    printf("short:  %lu bytes\n", sizeof(short));
    printf("int:    %lu bytes\n", sizeof(int));
    printf("long:   %lu bytes\n", sizeof(long));
    printf("float:  %lu bytes\n", sizeof(float));
    printf("double: %lu bytes\n", sizeof(double));
    printf("pointer:%lu bytes\n", sizeof(void*));

    int arr[10];
    printf("array:  %lu bytes\n", sizeof(arr));  // 40字节

    return 0;
}

2.10 实践练习

练习1: 测试存储类型

编写程序测试static变量的特性:

#include <stdio.h>

void test()
{
    static int count = 0;
    count++;
    printf("count = %d\n", count);
}

int main()
{
    test();
    test();
    test();
    return 0;
}

练习2: 浮点数精度

比较float和double的精度差异:

#include <stdio.h>

int main()
{
    float f = 3.141592653589793;
    double d = 3.141592653589793;

    printf("float:  %.15f\n", f);
    printf("double: %.15f\n", d);

    return 0;
}

练习3: 宏定义陷阱

理解宏定义的运算优先级问题:

#include <stdio.h>

#define SQUARE(x) x * x
#define SQUARE_FIX(x) ((x) * (x))

int main()
{
    int a = 5;
    printf("%d\n", SQUARE(a + 1));      // 5 + 1 * 5 + 1 = 11
    printf("%d\n", SQUARE_FIX(a + 1));  // (5+1) * (5+1) = 36

    return 0;
}

2.11 常见错误

错误1: 混淆宏定义和函数

#define MAX(a, b) a > b ? a : b  // 缺少括号

int x = MAX(1, 2) + 3;  // 展开为: 1 > 2 ? 1 : 2 + 3 = 5 (错误!)

正确写法:

#define MAX(a, b) ((a) > (b) ? (a) : (b))

错误2: 修改const变量

const int a = 10;
a = 20;  // 错误:不能修改const变量

错误3: register变量取地址

register int a = 10;
printf("%p", &a);  // 错误:register变量没有地址

2.12 小结

本章学习了:

C语言的数据类型体系 变量的定义和存储类型(auto, static, register, extern) IEEE754浮点数存储原理 基本数据类型(整型、浮点型、字符型、布尔型) 宏定义与const常量的区别 类型转换(隐式和显式) sizeof运算符

重点提示:

  1. 优先使用const而非#define定义常量
  2. 理解static的全局寿命、局部可见性特性
  3. 浮点数存在精度问题,避免用于精确计算
  4. 宏定义要注意括号,避免运算优先级错误

上一章: 第01章 C语言基础与编译下一章: 第03章 运算符

Prev
第01章 基础概念与编译
Next
第03章 运算符