编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • C语言入门精通

  • Cpp入门到精通

    • README
    • 入门教程

      • README
      • Cpp简史
      • 基础语法
      • 数据类型
        • 3.1 基本数据
          • 3.1.1 基本数据类型
          • 3.1.2 固定宽度类型
          • 3.1.3 空类型 (void)
          • 3.1.4 综合案例与思考
        • 3.2 整数类型
          • 3.2.1 有符号类型
          • 3.2.2 无符号类型
          • 3.2.3 固定宽度整数类型
          • 3.2.4 综合案例与思考
        • 3.3 字符类型
          • 3.3.1 字符类型列表
          • 3.3.2 字符类型特点
          • 3.3.3 字符使用场景
          • 3.3.4 字符注意事项
          • 3.3.5 综合案例与思考
        • 3.4 浮点类型
          • 3.4.1 浮点类型列表
          • 3.4.2 浮点类型特点
          • 3.4.3 使用场景
          • 3.4.4 注意事项
          • 3.4.5 浮点数特殊值
          • 3.4.6 综合案例与思考
        • 3.5 布尔类型
          • 3.5.1 布尔类型使用
          • 3.5.2 布尔类型底层
          • 3.5.3 综合案例与思考
        • 3.6 变量和常量
          • 3.6.1 变量
          • 3.6.2 常量
          • 3.6.3 综合案例与思考
        • 3.7 类型转换
          • 3.7.1 隐式类型转换
          • 3.7.2 显式类型转换
          • 3.7.2.1 static_cast
          • 3.7.2.2 dynamic_cast
          • 3.7.2.3 const_cast
          • 3.7.3.4 reinterpret_cast
          • 3.7.3 类型转换建议
          • 3.7.4 类型转换总结
          • 3.7.5 综合案例与思考
        • 3.8 练习题
          • 3.8.1 类型转换案例
          • 3.8.2 键盘数据输入
          • 3.8.3 nullptr和NULL
        • 3.9 卷一改造增补:现代 C++ 类型补充(C++11~C++23)
          • 3.9.1 auto 与 decltype:让编译器替你写类型
          • 3.9.2 std::byte(C++17):取代 unsigned char 表示「裸字节」
          • 3.9.3 std::optional<T>(C++17):表达「可能没有值」
          • 3.9.4 std::variant<T...>(C++17):类型安全的联合体
          • 3.9.5 std::any(C++17):装任意类型
          • 3.9.6 std::string_view(C++17):零拷贝字符串视图
          • 3.9.7 <cstdint> 固定宽度整数:跨平台安全
        • 3.10 新手陷阱 Top 5
      • 运算符
      • 复合类型
      • 流程语句
      • 函数
      • 指针引用
      • 类和对象
      • 继承多态
      • 内存模型
      • 动态内存
      • IO和文件
      • 异常处理
      • 线程和锁
      • STL模版
      • 预处理器
      • 特性图谱
    • 综合案例

    • 专栏博客

    • 开发技巧

  • Java入门精通

  • Go入门到精通

  • JavaScript入门

  • CodeX
  • Cpp入门到精通
  • 入门教程
杨充
2026-05-07
目录

数据类型

# 第 3 章 C++ 数据类型

# 目录介绍

  • 3.1 基本数据
    • 3.1.1 基本数据类型
    • 3.1.2 固定宽度类型
    • 3.1.3 空类型 (void)
    • 3.1.4 综合案例与思考
  • 3.2 整数类型
    • 3.2.1 有符号类型
    • 3.2.2 无符号类型
    • 3.2.3 固定宽度整数类型
    • 3.2.4 综合案例与思考
  • 3.3 字符类型
    • 3.3.1 字符类型列表
    • 3.3.2 字符类型特点
    • 3.3.3 字符使用场景
    • 3.3.4 字符注意事项
    • 3.3.5 综合案例与思考
  • 3.4 浮点类型
    • 3.4.1 浮点类型列表
    • 3.4.2 浮点类型特点
    • 3.4.3 使用场景
    • 3.4.4 注意事项
    • 3.4.5 浮点数特殊值
    • 3.4.6 综合案例与思考
  • 3.5 布尔类型
    • 3.5.1 布尔类型使用
    • 3.5.2 布尔类型底层
    • 3.5.3 综合案例与思考
  • 3.6 变量和常量
    • 3.6.1 变量
    • 3.6.2 常量
    • 3.6.3 综合案例与思考
  • 3.7 类型转换
    • 3.7.1 隐式类型转换
    • 3.7.2 显式类型转换
      • 3.7.2.1 static_cast
      • 3.7.2.2 dynamic_cast
      • 3.7.2.3 const_cast
      • 3.7.3.4 reinterpret_cast
    • 3.7.3 类型转换建议
    • 3.7.4 类型转换总结
    • 3.7.5 综合案例与思考
  • 3.8 练习题
    • 3.8.1 类型转换案例
    • 3.8.2 键盘数据输入
    • 3.8.3 nullptr和NULL

# 3.1 基本数据

# 3.1.1 基本数据类型

C++的基本数据类型包括整型、浮点型、字符型、布尔型等。如下所示:

  1. int:整数类型,用于表示整数值。
  2. float:单精度浮点数类型,用于表示小数值。
  3. double:双精度浮点数类型,用于表示更大范围和更高精度的小数值。
  4. char:字符类型,用于表示单个字符。
  5. bool:布尔类型,用于表示真或假的值。

# 3.1.2 固定宽度类型

传统整数类型的问题: 在 C++ 中,int、long 等类型的宽度(即占用的字节数)是由编译器和平台决定的。例如,int 在某些平台上可能是 16 位,而在另一些平台上可能是 32 位。

C++11引入了固定宽度的整数类型,如int8_t, int16_t, int32_t, int64_t等,定义在头文件中。

为什么要引入这种类型?是为了解决传统整数类型(如 int、long 等)在不同平台上的宽度不一致问题。

# 3.1.3 空类型 (void)

void类型表示“无类型”或“无值”。它主要有三种用途:

  1. 函数不返回任何值:
void printMessage() {
    cout << "This function returns nothing." << endl;
    // 无需 return 语句,或使用 return;
}
1
2
3
4
  1. 函数无参数(在C++中,空参数列表最好用void明确写出,但留空也可):
int getValue(void) { // 明确表示该函数不接受任何参数
    return 42;
}
1
2
3
  1. 指向未指定类型的指针 (void*):void*是一种通用指针类型,可以指向任何数据类型的数据。在使用前,必须将其强制转换为具体的指针类型。
int num = 100;
void* genericPtr = # // 可以指向int
// cout << *genericPtr << endl; // 错误!void* 不能直接解引用
int* intPtr = static_cast<int*>(genericPtr); // 必须转换
cout << *intPtr << endl; // 正确,输出 100
1
2
3
4
5

# 3.1.4 综合案例与思考

综合案例:数据类型信息查询工具

#include <iostream>
#include <cstdint>
#include <limits>
using namespace std;

int main() {
    // 1. 展示所有基本数据类型的大小和范围
    cout << "=== 基本数据类型信息 ===" << endl;
    cout << "int:    " << sizeof(int) << " 字节, 范围: ["
         << numeric_limits<int>::min() << ", "
         << numeric_limits<int>::max() << "]" << endl;
    cout << "float:  " << sizeof(float) << " 字节, 精度: "
         << numeric_limits<float>::digits10 << " 位" << endl;
    cout << "double: " << sizeof(double) << " 字节, 精度: "
         << numeric_limits<double>::digits10 << " 位" << endl;
    cout << "char:   " << sizeof(char) << " 字节" << endl;
    cout << "bool:   " << sizeof(bool) << " 字节" << endl;

    // 2. 固定宽度类型确保跨平台一致
    cout << "\n=== 固定宽度类型 ===" << endl;
    int32_t fixedInt = 100;
    uint64_t bigNum = 18446744073709551615ULL;
    cout << "int32_t: " << sizeof(fixedInt) << " 字节, 值: " << fixedInt << endl;
    cout << "uint64_t: " << sizeof(bigNum) << " 字节, 值: " << bigNum << endl;

    // 3. void指针的通用性
    cout << "\n=== void指针 ===" << endl;
    int a = 42;
    double b = 3.14;
    void* genericPtr;

    genericPtr = &a;
    cout << "指向int: " << *static_cast<int*>(genericPtr) << endl;
    genericPtr = &b;
    cout << "指向double: " << *static_cast<double*>(genericPtr) << endl;

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

案例知识融合:这个案例使用sizeof查看各基本类型的内存大小,用numeric_limits获取取值范围和精度,展示了固定宽度类型int32_t/uint64_t的跨平台一致性,以及void*通用指针配合static_cast的用法。涵盖了2.1节的基本数据类型、固定宽度类型和void类型三个知识点。

思考题:

  1. int在不同平台上大小可能不同(16位或32位),这对可移植性有什么影响?什么时候应该使用int32_t替代int?
  2. void*指针可以指向任何类型,那为什么不能直接解引用?这种设计背后的安全考量是什么?
  3. numeric_limits<float>::digits10返回6-7,这意味着什么?为什么说float"只有6-9位有效数字"?

# 3.2 整数类型

作用:整型变量表示的是==整数类型==的数据

这些整型数据类型可以使用带符号(可以表示正数和负数)或无符号(仅表示非负数)修饰符进行声明。

# 3.2.1 有符号类型

有符号类型可以存储负数、正数和零,其中最高位用于表示符号(0 表示正数,1 表示负数)。

在 C++ 中,默认情况下,整数类型是有符号的,除非显式地使用 unsigned 关键字声明为无符号类型。

C++中能够表示整型的类型有以下几种方式,区别在于所占内存空间不同:

数据类型 占用空间 取值范围
short(短整型) 2字节 (-2^15 ~ 2^15-1)
int(整型) 4字节 (-2^31 ~ 2^31-1)
long(长整形) Windows为4字节,Linux为4字节(32位),8字节(64位) (-2^31 ~ 2^31-1)
long long(长长整形) 8字节 (-2^63 ~ 2^63-1)

# 3.2.2 无符号类型

无符号类型是指只能表示非负数(包括零)的数据类型。例如,unsigned int表示无符号整数。

此外,C++还提供了其他整型数据类型,如signed、unsigned short、unsigned long等,它们提供了不同的位数和范围,以满足不同的需求。

数据类型 占用空间(字节) 取值范围(最小值到最大值)
unsigned char 1 0 到 255
unsigned short 2 0 到 65,535
unsigned int 4 0 到 4,294,967,295
unsigned long 4 或 8 0 到 4,294,967,295(4 字节)
0 到 18,446,744,073,709,551,615(8 字节)
unsigned long long 8 0 到 18,446,744,073,709,551,615

无符号整数类型的特点

  1. 只能表示非负数:无符号整数类型不能表示负数,最小值为 0。
  2. 更大的正数范围:由于不需要存储符号位,无符号整数类型可以表示比有符号类型更大的正数。
  3. 溢出行为:如果超出取值范围,会发生溢出,结果会从 0 重新开始(模运算)。

# 3.2.3 固定宽度整数类型

C++11 引入了 固定宽度的整数类型,这些类型定义在 <cstdint> 头文件中。它们提供了明确大小的整数类型,方便跨平台开发时确保数据大小的一致性。

数据类型 占用空间(字节) 取值范围(有符号) 取值范围(无符号)
int8_t 1 -128 到 127 -
uint8_t 1 - 0 到 255
int16_t 2 -32,768 到 32,767 -
uint16_t 2 - 0 到 65,535
int32_t 4 -2,147,483,648 到 2,147,483,647 -
uint32_t 4 - 0 到 4,294,967,295
int64_t 8 -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 -
uint64_t 8 - 0 到 18,446,744,073,709,551,615

固定宽度整数类型的特点

  1. 明确的大小: 固定宽度整数类型的大小是明确的,例如 int32_t 始终是 4 字节。
  2. 跨平台一致性: 使用固定宽度整数类型可以确保代码在不同平台上具有相同的行为。
  3. 可选性: 如果平台不支持某种固定宽度类型(例如 int8_t),则编译器可能不会定义该类型。

示例代码

#include <iostream>
#include <cstdint> // 包含固定宽度整数类型
using namespace std;

int main() {
    // 固定宽度有符号整数类型
    int8_t i8 = -128;
    int16_t i16 = -32768;
    int32_t i32 = -2147483648;
    int64_t i64 = -9223372036854775807LL;

    // 固定宽度无符号整数类型
    uint8_t u8 = 255;
    uint16_t u16 = 65535;
    uint32_t u32 = 4294967295;
    uint64_t u64 = 18446744073709551615ULL;

    // 输出
    cout << "int8_t: " << sizeof(i8) << " bytes, value: " << (int)i8 << endl;
    cout << "int16_t: " << sizeof(i16) << " bytes, value: " << i16 << endl;
    cout << "int32_t: " << sizeof(i32) << " bytes, value: " << i32 << endl;
    cout << "int64_t: " << sizeof(i64) << " bytes, value: " << i64 << endl;

    cout << "uint8_t: " << sizeof(u8) << " bytes, value: " << (int)u8 << endl;
    cout << "uint16_t: " << sizeof(u16) << " bytes, value: " << u16 << endl;
    cout << "uint32_t: " << sizeof(u32) << " bytes, value: " << u32 << endl;
    cout << "uint64_t: " << sizeof(u64) << " bytes, value: " << u64 << endl;

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

输出示例

int8_t: 1 bytes, value: -128
int16_t: 2 bytes, value: -32768
int32_t: 4 bytes, value: -2147483648
int64_t: 8 bytes, value: -9223372036854775807
uint8_t: 1 bytes, value: 255
uint16_t: 2 bytes, value: 65535
uint32_t: 4 bytes, value: 4294967295
uint64_t: 8 bytes, value: 18446744073709551615
1
2
3
4
5
6
7
8

使用场景

  1. 跨平台开发:确保整数类型在不同平台上具有相同的大小和行为。
  2. 网络编程:处理网络协议时,通常需要明确的数据大小。
  3. 文件格式:读写二进制文件时,使用固定宽度类型可以确保数据的一致性。
  4. 硬件编程:与硬件寄存器交互时,通常需要明确的数据大小。

# 3.2.4 综合案例与思考

综合案例:整数类型的边界与溢出实验

#include <iostream>
#include <cstdint>
#include <limits>
#include <climits>
using namespace std;

int main() {
    // 1. 有符号整数的范围
    cout << "=== 有符号整数范围 ===" << endl;
    cout << "short:     [" << SHRT_MIN << ", " << SHRT_MAX << "]" << endl;
    cout << "int:       [" << INT_MIN << ", " << INT_MAX << "]" << endl;
    cout << "long long: [" << LLONG_MIN << ", " << LLONG_MAX << "]" << endl;

    // 2. 无符号整数特点:范围更大但只能表示非负数
    cout << "\n=== 无符号整数 ===" << endl;
    unsigned int ua = 0;
    unsigned int ub = ua - 1;  // 无符号溢出是定义良好的
    cout << "unsigned 0 - 1 = " << ub << " (模2^32运算)" << endl;
    cout << "UINT_MAX = " << UINT_MAX << endl;

    // 3. 有符号与无符号混合运算的陷阱
    cout << "\n=== 混合运算陷阱 ===" << endl;
    int signedVal = -1;
    unsigned int unsignedVal = 1;
    // 比较时signed会被隐式转换为unsigned
    if (signedVal < unsignedVal) {
        cout << "-1 < 1 (预期)" << endl;
    } else {
        cout << "-1 >= 1 (意外!因为-1转为unsigned变成了很大的数)" << endl;
    }

    // 4. 固定宽度类型用于网络编程
    cout << "\n=== 固定宽度类型应用 ===" << endl;
    // 模拟网络数据包头
    struct PacketHeader {
        uint16_t type;     // 2字节,包类型
        uint32_t length;   // 4字节,数据长度
        uint64_t timestamp; // 8字节,时间戳
    };
    cout << "PacketHeader大小: " << sizeof(PacketHeader) << " 字节" << endl;

    // 5. 安全的整数范围检查
    cout << "\n=== 安全范围检查 ===" << endl;
    long long bigValue = 3000000000LL;  // 30亿
    if (bigValue > numeric_limits<int>::max()) {
        cout << bigValue << " 超出int范围,不能安全转换" << endl;
    }

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

案例知识融合:这个案例演示了有符号整数的范围限制、无符号整数的溢出行为(模运算)、有符号与无符号混合运算的经典陷阱、固定宽度类型在网络编程中的应用,以及安全的范围检查方法。通过实际的溢出实验,深刻理解整数类型的底层行为。

思考题:

  1. 为什么C++标准规定有符号整数溢出是"未定义行为",而无符号整数溢出是"定义良好的"(模运算)?
  2. 在比较 int 和 unsigned int 时,编译器会进行隐式转换导致意外结果。如何避免这类bug?
  3. 为什么网络编程中通常使用 uint32_t 而不是 int?字节序(大端/小端)又会带来什么问题?

# 3.3 字符类型

C++ 中的字符类型用于存储单个字符或小整数值。字符类型在 C++ 中非常重要,因为它们不仅用于表示字符,还可以用于处理 ASCII 值或进行位操作。

# 3.3.1 字符类型列表

作用:字符型变量用于显示单个字符

数据类型 占用空间(字节) 描述
char 1 默认字符类型,通常用于存储 ASCII 字符。可以是有符号或无符号,取决于平台。
signed char 1 明确表示有符号的字符类型,取值范围:-128 到 127。
unsigned char 1 明确表示无符号的字符类型,取值范围:0 到 255。
wchar_t 2 或 4 宽字符类型,用于存储 Unicode 字符。大小取决于平台(Windows 通常为 2 字节,Linux 通常为 4 字节)。
char16_t 2 用于存储 UTF-16 编码的字符(C++11 引入)。
char32_t 4 用于存储 UTF-32 编码的字符(C++11 引入)。

语法:char ch = 'a';

注意1:在显示字符型变量时,用单引号将字符括起来,不要用双引号

注意2:单引号内只能有一个字符,不可以是字符串

  • C和C++中字符型变量只占用==1个字节==。
  • 字符型变量并不是把字符本身放到内存中存储,而是将对应的ASCII编码放入到存储单元。

示例:

#include <iostream>
using namespace std;
int main() {
    char ch = 'a';
    cout << ch << endl;
    cout << sizeof(char) << endl;
    //ch = "abcde"; //错误,不可以用双引号
    //ch = 'abcde'; //错误,单引号内只能引用一个字符
    cout << (int) ch << endl;  //查看字符a对应的ASCII码
    ch = 97; //可以直接用ASCII给字符型变量赋值
    cout << ch << endl;
    return 0;
}
//a
//1
//97
//a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 3.3.2 字符类型特点

  1. char 类型:默认情况下,char 可以是有符号或无符号,具体取决于编译器和平台。 通常用于存储 ASCII 字符(0 到 127)。
  2. signed char 和 unsigned char: 明确表示有符号或无符号的字符类型。常用于处理小整数值或进行位操作。
  3. 宽字符类型:wchar_t、char16_t 和 char32_t 用于存储 Unicode 字符。wchar_t 的大小取决于平台,而 char16_t 和 char32_t 的大小是固定的。
  4. 字符字面量:单引号用于表示字符字面量,例如 'A'。宽字符字面量使用前缀 L(wchar_t)、u(char16_t)或 U(char32_t),例如 L'中'、u'中'、U'中'。

# 3.3.3 字符使用场景

  1. 文本处理:char 类型用于处理 ASCII 字符或字符串。宽字符类型用于处理 Unicode 字符。
  2. 位操作:unsigned char 常用于位操作,因为它可以表示 0 到 255 的值。
  3. 小整数存储:signed char 和 unsigned char 可以用于存储小整数值。
  4. 国际化支持:wchar_t、char16_t 和 char32_t 用于支持多语言字符集。

# 3.3.4 字符注意事项

  1. char 的符号性:char 的符号性取决于平台,如果需要明确的有符号或无符号行为,请使用 signed char 或 unsigned char。
  2. 宽字符类型的大小:wchar_t 的大小因平台而异,而 char16_t 和 char32_t 的大小是固定的。
  3. 字符字面量的前缀:使用宽字符字面量时,确保使用正确的前缀(L、u 或 U)。
  4. 字符与整数的转换:字符类型可以隐式转换为整数类型,但需要注意取值范围。

# 3.3.5 综合案例与思考

综合案例:字符处理工具集

#include <iostream>
#include <string>
#include <cctype>
using namespace std;

int main() {
    // 1. 字符与ASCII码的转换
    cout << "=== 字符与ASCII ===" << endl;
    for (char c = 'A'; c <= 'Z'; ++c) {
        cout << c << "=" << (int)c << " ";
    }
    cout << endl;

    // 2. 大小写转换(利用ASCII差值)
    cout << "\n=== 大小写转换 ===" << endl;
    char upper = 'H';
    char lower = upper + 32;  // 大写转小写:+32
    cout << upper << " -> " << lower << endl;
    // 推荐方式:使用标准库函数
    cout << (char)toupper('a') << " " << (char)tolower('Z') << endl;

    // 3. 字符分类判断
    cout << "\n=== 字符分类 ===" << endl;
    string testStr = "Hello 123!";
    for (char c : testStr) {
        cout << "'" << c << "': ";
        if (isalpha(c)) cout << "字母 ";
        if (isdigit(c)) cout << "数字 ";
        if (isspace(c)) cout << "空格 ";
        if (ispunct(c)) cout << "标点 ";
        cout << endl;
    }

    // 4. 宽字符处理中文
    cout << "\n=== 宽字符 ===" << endl;
    wchar_t wc = L'中';
    char16_t c16 = u'国';
    char32_t c32 = U'人';
    cout << "wchar_t大小: " << sizeof(wc) << " 字节" << endl;
    cout << "char16_t大小: " << sizeof(c16) << " 字节" << endl;
    cout << "char32_t大小: " << sizeof(c32) << " 字节" << endl;

    // 5. unsigned char用于字节操作
    cout << "\n=== 字节操作 ===" << endl;
    unsigned char byte = 0xFF;  // 255
    cout << "byte值: " << (int)byte << endl;
    cout << "高4位: " << ((byte >> 4) & 0x0F) << endl;
    cout << "低4位: " << (byte & 0x0F) << endl;

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

案例知识融合:这个案例涵盖了字符类型的核心知识——ASCII编码转换、大小写转换的两种方式(手动计算和toupper/tolower函数)、字符分类判断(isalpha/isdigit等)、宽字符类型处理Unicode(wchar_t/char16_t/char32_t)、以及unsigned char在字节级位操作中的应用。

思考题:

  1. char类型的符号性是"实现定义的",这意味着同一段代码在不同平台上可能有不同行为。如何编写可移植的字符处理代码?
  2. 大小写转换时为什么 'A' + 32 == 'a'?如果ASCII编码表的设计不是这样排列的,会对编程产生什么影响?
  3. C++11引入了char16_t和char32_t,而之前只有wchar_t。为什么需要三种宽字符类型?它们各自适用什么场景?

# 3.4 浮点类型

C++ 中的浮点类型用于表示实数(即带小数点的数字)。它们基于 IEEE 754 标准,提供了不同精度的浮点数表示。

作用:用于==表示小数==

浮点型变量分为两种:

  1. 单精度float
  2. 双精度double

两者的区别在于表示的有效数字范围不同。

# 3.4.1 浮点类型列表

数据类型 占用空间(字节) 精度(有效数字) 取值范围(近似)
float 4 6-9 位 ±1.18e-38 到 ±2.4e38
double 8 15-17 位 ±2.23e-308 到 ±1.8e308
long double 8、12 或 16 18-21 位 取决于平台,通常与 double 相同或更高

# 3.4.2 浮点类型特点

  1. 精度:float 提供单精度浮点数,适合对精度要求不高的场景。double 提供双精度浮点数,适合大多数科学计算和工程应用。long double 提供扩展精度,适合对精度要求极高的场景。
  2. 取值范围: 浮点类型的取值范围非常大,可以表示非常小或非常大的数。
  3. 存储格式: 浮点数采用 IEEE 754 标准存储,分为符号位、指数位和尾数位。
  4. 字面量:浮点数字面量默认是 double 类型。使用后缀 f 或 F 表示 float 类型,例如 3.14f。使用后缀 l 或 L 表示 long double 类型,例如 2.14L。

# 3.4.3 使用场景

  1. 科学计算:double 是科学计算中最常用的浮点类型,提供了足够的精度和范围。
  2. 图形处理:float 常用于图形处理,因为其精度足够且占用内存较少。
  3. 金融计算: 对于高精度计算(如货币计算),可以使用 long double 或专门的库(如 decimal)。
  4. 物理模拟: 物理模拟中通常使用 double 或 long double 以确保精度。

# 3.4.4 注意事项

  1. 精度损失: 浮点数在计算时可能会产生精度损失,尤其是在进行大量运算时。
  2. 比较浮点数: 由于浮点数的精度问题,直接比较两个浮点数是否相等可能会导致错误。通常使用一个很小的误差范围(如 1e-9)进行比较。
  3. 溢出和下溢: 浮点数可能会溢出(超过最大值)或下溢(接近零),导致结果不准确。
  4. 平台差异:long double 的大小和精度可能因平台而异。

# 3.4.5 浮点数特殊值

浮点数可以表示一些特殊值:

  • 无穷大:INFINITY(正无穷)和 -INFINITY(负无穷)。
  • 非数字:NaN(Not a Number),表示无效的浮点数操作结果。
#include <iostream>
#include <cmath> // 包含 INFINITY 和 NaN
using namespace std;
int main() {
    double inf = INFINITY;
    double nan = NAN;
    cout << "Infinity: " << inf << endl;
    cout << "NaN: " << nan << endl;
    return 0;
}
1
2
3
4
5
6
7
8
9
10

输出:

Infinity: inf
NaN: nan
1
2

# 3.4.6 综合案例与思考

综合案例:浮点数精度陷阱与正确比较

#include <iostream>
#include <cmath>
#include <iomanip>
#include <limits>
using namespace std;

int main() {
    // 1. 经典精度问题
    cout << "=== 精度陷阱 ===" << endl;
    double a = 0.1 + 0.2;
    cout << fixed << setprecision(20);
    cout << "0.1 + 0.2 = " << a << endl;   // 不等于0.3!
    cout << "0.3       = " << 0.3 << endl;
    cout << "相等吗?" << (a == 0.3 ? "是" : "否") << endl;  // 否!

    // 2. 正确的浮点数比较方式
    cout << "\n=== 正确比较方式 ===" << endl;
    double epsilon = 1e-9;  // 误差范围
    if (fabs(a - 0.3) < epsilon) {
        cout << "使用epsilon比较:0.1+0.2 ≈ 0.3 (正确)" << endl;
    }

    // 3. float vs double精度对比
    cout << "\n=== 精度对比 ===" << endl;
    float  fVal = 1.0f / 3.0f;
    double dVal = 1.0 / 3.0;
    cout << "float  1/3 = " << fVal << endl;
    cout << "double 1/3 = " << dVal << endl;

    // 4. 大数精度丢失
    cout << "\n=== 大数精度丢失 ===" << endl;
    float bigFloat = 16777216.0f;  // 2^24
    cout << "bigFloat     = " << bigFloat << endl;
    cout << "bigFloat + 1 = " << (bigFloat + 1.0f) << endl;  // 仍然等于16777216!
    cout << "相等吗?" << (bigFloat == bigFloat + 1.0f ? "是" : "否") << endl;

    // 5. 特殊值处理
    cout << "\n=== 特殊值 ===" << endl;
    double inf = 1.0 / 0.0;    // 正无穷
    double nan = 0.0 / 0.0;    // NaN
    cout << "1/0 = " << inf << endl;
    cout << "0/0 = " << nan << endl;
    cout << "NaN == NaN? " << (nan == nan ? "是" : "否") << endl;  // 否!NaN不等于自身
    cout << "isnan(nan)? " << (isnan(nan) ? "是" : "否") << endl;

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

案例知识融合:这个案例深入演示了浮点数的核心问题——0.1+0.2!=0.3的经典精度问题、使用epsilon的正确比较方法、float和double的精度差异、大数累加的精度丢失(float只有约7位有效数字),以及NaN不等于自身的特殊性质。

思考题:

  1. 为什么 0.1 + 0.2 != 0.3?这和IEEE 754标准的二进制浮点表示有什么关系?
  2. 金融计算为什么不能直接使用 float 或 double?实际项目中如何解决货币精度问题?
  3. NaN != NaN 为 true,这看起来很反直觉。这种设计的理由是什么?如何正确判断一个值是否为NaN?

# 3.5 布尔类型

# 3.5.1 布尔类型使用

作用:布尔数据类型代表真或假的值

类型 大小(字节) 值
bool 1 true 或 false
  • true --- 真(本质是1)
  • false --- 假(本质是0)

示例:

#include <iostream>
using namespace std;

int main() {
    bool isCppFun = true;
    bool isFishWet = false;
    int x = 10, y = 20;

    bool isEqual = (x == y); // 比较运算的结果是bool类型
    bool isGreater = (x > y);

    cout << "isCppFun: " << isCppFun << endl;    // 输出 1
    cout << "isFishWet: " << isFishWet << endl;  // 输出 0

    // 使用 boolalpha 操纵符以文本形式输出 true/false
    cout << boolalpha;
    cout << "isEqual: " << isEqual << endl;     // 输出 false
    cout << "isGreater: " << isGreater << endl; // 输出 false

    // 任何非零值都可以隐式转换为true,零值转换为false
    if (x) {
        cout << "x is non-zero, which is true." << endl;
    }
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 3.5.2 布尔类型底层

在内存中,bool类型通常占用 1 个字节(这是最常见的情况,C++ 标准只规定它至少能存放 true或 false中的一个值,具体大小依赖于编译器和平台)。

true在底层通常用整数值 1 表示。false在底层通常用整数值 0 表示。

注意: 虽然底层用 1 和 0 表示,但在代码层面应始终使用 true和 false以提高可读性和避免混淆。

类型提升:在需要整数类型的表达式中(如算术运算),bool值会先被提升为 int(true提升为 1, false提升为 0),然后再参与运算。

# 3.5.3 综合案例与思考

综合案例:布尔类型在实际编程中的应用

#include <iostream>
#include <string>
#include <vector>
using namespace std;

// 用bool作为函数返回值表示成功/失败
bool login(const string& user, const string& pass) {
    return (user == "admin" && pass == "123456");
}

// 用bool控制程序逻辑
bool isEven(int n) { return n % 2 == 0; }
bool isPrime(int n) {
    if (n < 2) return false;
    for (int i = 2; i * i <= n; ++i) {
        if (n % i == 0) return false;
    }
    return true;
}

int main() {
    // 1. bool与条件判断
    cout << "=== 登录验证 ===" << endl;
    bool success = login("admin", "123456");
    cout << boolalpha;  // 输出true/false而非1/0
    cout << "登录结果: " << success << endl;

    // 2. bool的隐式转换
    cout << "\n=== 隐式转换 ===" << endl;
    int zero = 0, nonZero = 42;
    string empty = "", nonEmpty = "hello";
    void* nullPtr = nullptr;
    cout << "0 -> bool: " << (bool)zero << endl;      // false
    cout << "42 -> bool: " << (bool)nonZero << endl;   // true
    cout << "nullptr -> bool: " << (bool)nullPtr << endl; // false

    // 3. bool参与算术运算(会提升为int)
    cout << "\n=== bool算术运算 ===" << endl;
    bool a = true, b = true;
    int sum = a + b;  // true(1) + true(1) = 2
    cout << "true + true = " << sum << endl;

    // 4. 统计满足条件的元素
    cout << "\n=== 条件统计 ===" << endl;
    vector<int> numbers = {2, 3, 4, 5, 6, 7, 8, 9, 10};
    int evenCount = 0, primeCount = 0;
    for (int n : numbers) {
        evenCount += isEven(n);   // bool转int:true为1
        primeCount += isPrime(n);
    }
    cout << "偶数个数: " << evenCount << endl;
    cout << "质数个数: " << primeCount << endl;

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

案例知识融合:这个案例展示了bool类型的实际应用——作为函数返回值表示操作结果、与整数的隐式转换规则(非零为true,零为false)、boolalpha格式化输出、以及利用bool参与算术运算的特性进行条件计数。

思考题:

  1. bool类型在内存中占1个字节,但只需要1个bit就够了。为什么C++标准不让bool只占1bit?
  2. C语言没有原生的bool类型(C99引入了_Bool),早期C程序是如何模拟布尔逻辑的?
  3. if (ptr) 和 if (ptr != nullptr) 效果一样吗?你更推荐哪种写法?为什么?

# 3.6 变量和常量

# 3.6.1 变量

作用:给一段指定的内存空间起名,方便操作这段内存。

语法:数据类型 变量名 = 初始值;

示例:

#include <iostream>
using namespace std;

int main() {
    //变量的定义
    //语法:数据类型  变量名 = 初始值
    int a = 10;
    cout << "a = " << a << endl;
    return 0;
}
1
2
3
4
5
6
7
8
9
10

注意:C++在创建变量时,必须给变量一个初始值,否则会报错

# 3.6.2 常量

作用:用于记录程序中不可更改的数据

C++定义常量两种方式

  1. #define 宏常量: #define 常量名 常量值

==通常在文件上方定义==,表示一个常量

  1. const修饰的变量 const 数据类型 常量名 = 常量值

==通常在变量定义前加关键字const==,修饰该变量为常量,不可修改

示例:

#include <iostream>
using namespace std;

//1.宏常量
#define day = 7;

int main() {
    //cout << "一周里总共有 %d" << day << " 天" << endl;
    //printf("一周里总共有:%d\n",day);
    //day = 8;  //报错,宏常量不可以修改
    //2、const修饰变量
    const int month = 12;
    cout << "一年里总共有 " << month << " 个月份" << endl;
    //month = 24; //报错,常量是不可以修改的
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 3.6.3 综合案例与思考

综合案例:变量和常量在配置管理中的应用

#include <iostream>
#include <string>
using namespace std;

// 宏常量:编译前的文本替换
#define APP_VERSION "1.0.0"
#define MAX_RETRY 3

// const常量:类型安全
const string APP_NAME = "MyApp";
const int DEFAULT_PORT = 8080;

// constexpr常量:编译时计算(C++11)
constexpr int BUFFER_SIZE = 1024 * 4;  // 4KB
constexpr double PI = 3.14159265358979;

int main() {
    // 1. 变量的声明和初始化
    cout << "=== 变量初始化方式 ===" << endl;
    int a = 10;          // C风格初始化
    int b(20);           // 构造函数风格
    int c{30};           // 统一初始化(C++11,推荐)
    int d = {40};        // 赋值+列表初始化
    auto e = 50;         // 自动类型推导
    cout << a << " " << b << " " << c << " " << d << " " << e << endl;

    // 2. 常量的使用
    cout << "\n=== 常量使用 ===" << endl;
    cout << "应用: " << APP_NAME << " v" << APP_VERSION << endl;
    cout << "端口: " << DEFAULT_PORT << endl;
    cout << "缓冲区: " << BUFFER_SIZE << " 字节" << endl;

    // 3. const vs #define 的区别
    cout << "\n=== const vs #define ===" << endl;
    // const有类型检查
    const int maxUsers = 100;
    // maxUsers = 200;  // 编译错误!不能修改const
    cout << "最大用户数: " << maxUsers << endl;
    cout << "maxUsers大小: " << sizeof(maxUsers) << " 字节" << endl;
    // #define没有类型,sizeof无法直接使用

    // 4. 变量的作用域
    cout << "\n=== 变量作用域 ===" << endl;
    int x = 100;  // 外层作用域
    {
        int x = 200;  // 内层作用域(遮蔽外层x)
        cout << "内层 x = " << x << endl;  // 200
    }
    cout << "外层 x = " << x << endl;  // 100

    // 5. constexpr在编译时计算
    constexpr int arraySize = BUFFER_SIZE / sizeof(int);
    int buffer[arraySize];  // 编译时确定数组大小
    cout << "\n数组大小: " << arraySize << " 个int" << endl;

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

案例知识融合:这个案例展示了变量的多种初始化方式(C风格、构造函数风格、统一初始化、auto推导)、三种常量定义方式(#define宏常量、const常量、constexpr编译时常量)的区别和使用场景,以及变量作用域的遮蔽规则。

思考题:

  1. #define 宏常量和 const 常量各有什么优缺点?Effective C++为什么建议"尽量使用const替代#define"?
  2. C++11的统一初始化(花括号{})相比传统初始化有什么优势?它能防止什么类型的错误?
  3. const 和 constexpr 都表示"常量",它们的区别是什么?什么时候必须用 constexpr?

# 3.7 类型转换

在 C++ 中,类型转换 是将一种数据类型转换为另一种数据类型的过程。C++ 提供了多种类型转换方式,包括隐式转换和显式转换。

显式转换又分为 C 风格转换和 C++ 风格转换(static_cast、dynamic_cast、const_cast、reinterpret_cast)。

# 3.7.1 隐式类型转换

隐式类型转换由编译器自动完成,通常发生在赋值、表达式计算或函数调用时。

示例

int i = 3.14;    // double 被隐式转换为 int,i 的值为 3(截断小数部分)
double d = i;    // int 被隐式转换为 double,d 的值为 3.0
bool b = d;      // double 被隐式转换为 bool,任何非零值为 true
1
2
3

# 3.7.2 显式类型转换

1.C风格转换,C 风格转换使用 (目标类型) 的语法。

double d = 3.14;
int e = (int)d; // 显式将 double 转换为 int
1
2

2.C++风格转换,C++ 提供了四种显式转换运算符,更加安全和明确。

static_cast:用于良定义的、低风险的转换(如数值类型转换、基类指针到派生类指针)。

const_cast:用于移除const或volatile属性。

dynamic_cast:主要用于安全地沿继承层次结构进行向下或交叉转换(需要多态类型)。

reinterpret_cast:用于低级的、依赖于实现的重新解释,非常危险(如将指针转换为整数)。

# 3.7.2.1 static_cast

用于基本数据类型之间的转换,以及具有继承关系的类之间的转换。

double f = 3.14;
int g = static_cast<int>(f); // 显式将 double 转换为 int

class Base {};
class Derived : public Base {};
Base* basePtr = new Derived;
Derived* derivedPtr = static_cast<Derived*>(basePtr); // 向下转型
1
2
3
4
5
6
7

# 3.7.2.2 dynamic_cast

用于具有多态性的类之间的转换(通常用于向下转型),转换失败时返回 nullptr。

class Base { virtual void foo() {} };
class Derived : public Base {};

Base* basePtr = new Derived;
Derived* derivedPtr = dynamic_cast<Derived*>(basePtr); // 向下转型
if (derivedPtr) {
    cout << "转换成功" << endl;
} else {
    cout << "转换失败" << endl;
}
1
2
3
4
5
6
7
8
9
10

# 3.7.2.3 const_cast

用于移除或添加 const 或 volatile 修饰符。

const int h = 10;
int* i = const_cast<int*>(&h); // 移除 const 修饰符
*i = 20; // 修改值(未定义行为,不推荐)
1
2
3

# 3.7.3.4 reinterpret_cast

用于低级别的类型转换,通常用于指针或整数之间的转换。

int j = 42;
int* ptr = &j;
long k = reinterpret_cast<long>(ptr); // 将指针转换为整数
1
2
3

# 3.7.3 类型转换建议

  1. 优先使用 C++ 风格转换: C++ 风格转换更加安全和明确,避免使用 C 风格转换。
  2. 避免不必要的转换: 类型转换可能导致数据丢失或未定义行为,尽量避免。
  3. 谨慎使用 const_cast: 移除 const 修饰符可能导致未定义行为,应谨慎使用。
  4. 使用 dynamic_cast 进行安全的向下转型:在具有多态性的类之间转换时,优先使用 dynamic_cast。

# 3.7.4 类型转换总结

  • 隐式转换由编译器自动完成,显式转换需要程序员明确指定。
  • C++ 提供了四种显式转换运算符:static_cast、dynamic_cast、const_cast、reinterpret_cast。
  • 优先使用 C++ 风格转换,避免不必要的转换,谨慎使用 const_cast。

# 3.7.5 综合案例与思考

综合案例:类型转换的实际应用与陷阱

#include <iostream>
#include <string>
using namespace std;

class Animal {
public:
    virtual ~Animal() = default;
    virtual string speak() const { return "..."; }
};

class Dog : public Animal {
public:
    string speak() const override { return "汪汪!"; }
    void fetch() const { cout << "捡球!" << endl; }
};

class Cat : public Animal {
public:
    string speak() const override { return "喵喵~"; }
};

int main() {
    // 1. 隐式转换的数据丢失
    cout << "=== 隐式转换陷阱 ===" << endl;
    int bigNum = 1000000;
    short smallNum = bigNum;  // 数据截断!
    cout << "int: " << bigNum << " -> short: " << smallNum << endl;

    double precise = 3.99;
    int truncated = precise;  // 小数部分丢失
    cout << "double: " << precise << " -> int: " << truncated << endl;

    // 2. static_cast:安全的数值转换
    cout << "\n=== static_cast ===" << endl;
    int total = 17, count = 5;
    // 错误做法:整数除法丢失小数
    cout << "整数除: " << total / count << endl;
    // 正确做法:先转为double再除
    cout << "浮点除: " << static_cast<double>(total) / count << endl;

    // 3. dynamic_cast:安全的多态转换
    cout << "\n=== dynamic_cast ===" << endl;
    Animal* animals[] = { new Dog(), new Cat(), new Dog() };
    for (int i = 0; i < 3; ++i) {
        cout << "动物" << i << " 说: " << animals[i]->speak() << endl;
        // 安全地尝试转为Dog
        Dog* dog = dynamic_cast<Dog*>(animals[i]);
        if (dog) {
            cout << "  这是一只狗: ";
            dog->fetch();
        } else {
            cout << "  这不是狗,跳过fetch()" << endl;
        }
    }

    // 4. const_cast:移除const(谨慎使用)
    cout << "\n=== const_cast ===" << endl;
    const string greeting = "Hello";
    // 当需要传给不接受const参数的旧API时
    string& ref = const_cast<string&>(greeting);
    cout << "const_cast结果: " << ref << endl;
    // 注意:修改原本的const对象是未定义行为!

    // 释放内存
    for (auto* a : animals) delete a;

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68

案例知识融合:这个案例展示了C++类型转换的四种场景——隐式转换的数据丢失风险、static_cast解决整数除法精度问题、dynamic_cast实现安全的多态向下转型(配合nullptr检查)、以及const_cast移除const属性的谨慎使用。每种转换都有明确的适用场景和注意事项。

思考题:

  1. C风格的强制转换 (int)x 和C++的 static_cast<int>(x) 有什么本质区别?为什么C++推荐使用后者?
  2. dynamic_cast 失败时返回 nullptr(指针)或抛出异常(引用),它的运行时开销来自哪里?什么时候可以用 static_cast 替代 dynamic_cast?
  3. 如果对一个真正的 const 对象使用 const_cast 去掉 const 后修改它的值,会发生什么?为什么这是"未定义行为"?

# 3.8 练习题

# 3.8.1 类型转换案例

#include <iostream>
using namespace std;

class Base {
public:
    virtual void foo() { cout << "Base::foo" << endl; }
};

class Derived : public Base {
public:
    void foo() override { cout << "Derived::foo" << endl; }
};

int main() {
    // 隐式转换
    int a = 10;
    double b = a;
    cout << "b = " << b << endl;

    // C 风格转换
    double c = 3.14;
    int d = (int)c;
    cout << "d = " << d << endl;

    // C++ 风格转换
    double e = 3.14;
    int f = static_cast<int>(e);
    cout << "f = " << f << endl;

    // dynamic_cast
    Base* basePtr = new Derived;
    Derived* derivedPtr = dynamic_cast<Derived*>(basePtr);
    if (derivedPtr) {
        cout << "dynamic_cast 成功" << endl;
    } else {
        cout << "dynamic_cast 失败" << endl;
    }

    // const_cast
    const int g = 10;
    int* h = const_cast<int*>(&g);
    *h = 20;
    cout << "g = " << g << ", *h = " << *h << endl;

    // reinterpret_cast
    int i = 42;
    int* ptr = &i;
    long j = reinterpret_cast<long>(ptr);
    cout << "j = " << j << endl;

    delete basePtr;
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

# 3.8.2 键盘数据输入

作用:用于从键盘获取数据

关键字:cin

语法: cin >> 变量

示例:

#include <iostream>
using namespace std;
int main(){
    //整型输入
    int a = 0;
    cout << "请输入整型变量:" << endl;
    cin >> a;
    cout << a << endl;

    //浮点型输入
    double d = 0;
    cout << "请输入浮点型变量:" << endl;
    cin >> d;
    cout << d << endl;

    //字符型输入
    char ch = 0;
    cout << "请输入字符型变量:" << endl;
    cin >> ch;
    cout << ch << endl;

    //字符串型输入
    string str;
    cout << "请输入字符串型变量:" << endl;
    cin >> str;
    cout << str << endl;

    //布尔类型输入
    bool flag = true;
    cout << "请输入布尔型变量:" << endl;
    cin >> flag;
    cout << flag << endl;
    return EXIT_SUCCESS;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

# 3.8.3 nullptr和NULL

以下是一个完整的示例,展示了 nullptr 和 NULL 的区别、使用场景以及相关问题:

#include <iostream>
using namespace std;

// 演示重载问题:一个函数接受整数,一个函数接受指针
void printValue(int num) {
    cout << "整数: " << num << endl;
}

void printValue(int* ptr) {
    if (ptr) {
        cout << "指针: 指向的值是 " << *ptr << endl;
    } else {
        cout << "指针: 是空指针" << endl;
    }
}

int main() {
    cout << "=== nullptr 和 NULL 简单对比 ===" << endl << endl;
    
    int number = 100;
    int* ptr1 = NULL;      // 传统方式
    int* ptr2 = nullptr;   // 现代方式
    
    // 1. 基本使用对比
    cout << "1. 基本使用:" << endl;
    if (ptr1 == NULL) {
        cout << "ptr1 是空指针" << endl;
    }
    
    if (ptr2 == nullptr) {
        cout << "ptr2 是空指针" << endl;
    }
    
    // 2. 赋值非空值
    cout << endl << "2. 指针赋值:" << endl;
    ptr1 = &number;
    cout << "ptr1 指向: " << *ptr1 << endl;
    
    // 3. 重置为空
    cout << endl << "3. 重置指针:" << endl;
    ptr1 = nullptr;  // 推荐用 nullptr
    if (!ptr1) {  // 检查是否为空
        cout << "ptr1 已被重置为空" << endl;
    }
    
    // 4. 重载问题演示(主要区别!)
    cout << endl << "4. 重载问题(主要区别):" << endl;
    printValue(0);        // 调用整数版本
    printValue(&number);  // 调用指针版本
    // printValue(NULL);  // 有歧义!在有些编译器会报错
    printValue(nullptr);  // 明确调用指针版本
    
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

这个案例演示的要点:

  1. 基本使用:NULL 和 nullptr 都可以表示空指针
  2. 安全性:nullptr 是类型安全的
  3. 可读性:if(ptr1 == nullptr) 比 if(ptr1 == NULL) 更明确
  4. 重载问题:printValue(NULL) 有歧义,printValue(nullptr) 是明确的

关键区别总结

特性 nullptr (C++11+) NULL (传统C/C++)
类型 std::nullptr_t 通常是int或long
类型安全 ✅ 指针类型安全 ❌ 可能被当作整数
重载函数 ✅ 明确调用指针版本 ⚠️ 可能调用整数版本
模板推导 ✅ 推导为nullptr_t ❌ 推导为整数类型
可读性 ✅ 明确表示空指针 ⚠️ 含义不够明确
向后兼容 ✅ 兼容C++11+ ✅ 完全兼容

# 3.9 卷一改造增补:现代 C++ 类型补充(C++11~C++23)

本节为卷一新增。前 8 节讲的是「类 C 时代」的传统类型;本节补齐现代 C++ 中真正常用、几乎每个项目都会用到的类型工具。

# 3.9.1 auto 与 decltype:让编译器替你写类型

// C++03 写法
std::map<std::string, std::vector<int>>::const_iterator it = m.begin();

// C++11 起
auto it = m.begin();                  // 类型推导
const auto& ref = expensive_call();   // 必须保留 const/&
decltype(x + y) z = x + y;            // 取表达式类型
1
2
3
4
5
6
7

注意三处坑:

  1. auto 默认会丢掉引用和 const,需要时显式加 const auto&、auto&&。
  2. auto x = {1, 2, 3}; 推导成 std::initializer_list<int>,不是 vector。
  3. 函数返回值用 auto 时,多个 return 语句类型必须一致,否则编译失败。

# 3.9.2 std::byte(C++17):取代 unsigned char 表示「裸字节」

#include <cstddef>
std::byte buf[1024];                  // 明确:这是字节缓冲区,不是字符串
buf[0] = std::byte{0xAB};
auto v = std::to_integer<int>(buf[0]);
1
2
3
4

std::byte 只支持位运算、比较、to_integer<T>,不支持算术运算——从类型层面阻止「把字节当数字加减」的常见错误。处理网络协议、二进制文件、内存拷贝时一律用它。

# 3.9.3 std::optional<T>(C++17):表达「可能没有值」

#include <optional>
std::optional<int> parse_int(const std::string& s) {
    try { return std::stoi(s); }
    catch (...) { return std::nullopt; }
}

if (auto v = parse_int("42"); v.has_value()) {
    std::cout << *v;                   // 解引用拿值
}
int x = parse_int("oops").value_or(-1);
1
2
3
4
5
6
7
8
9
10

用它替代:用 -1 表示「无效结果」、用空指针表示「没有」、用额外的 bool found 输出参数。

# 3.9.4 std::variant<T...>(C++17):类型安全的联合体

#include <variant>
std::variant<int, double, std::string> v;
v = 3.14;
v = std::string{"hello"};

// 访问方式 1:std::visit + lambda
std::visit([](auto&& arg) { std::cout << arg; }, v);
// 访问方式 2:std::get_if(不抛异常)
if (auto* p = std::get_if<std::string>(&v)) { /* ... */ }
1
2
3
4
5
6
7
8
9

替代传统 union + enum tag 的「手写代数数据类型」组合。

# 3.9.5 std::any(C++17):装任意类型

#include <any>
std::any a = 42;
a = std::string{"hello"};
auto s = std::any_cast<std::string>(a);   // 取错类型会抛 bad_any_cast
1
2
3
4

用于跨 API 边界传未知类型(如插件系统)。日常业务代码请优先用 variant,因为 any 失去了静态类型检查。

# 3.9.6 std::string_view(C++17):零拷贝字符串视图

#include <string_view>
void log(std::string_view sv) { /* 不拷贝、不分配 */ }
log("literal");                       // OK
log(std::string{"hello"});            // OK
log(some_char_array);                 // OK
1
2
3
4
5

形参传入「只读字符串」时,统一用 string_view 替代 const std::string&——既能省一次构造,又能兼容 C 字符串字面量。生命周期警告:string_view 不持有数据,不要让它指向已销毁的临时 string。

# 3.9.7 <cstdint> 固定宽度整数:跨平台安全

#include <cstdint>
int32_t  count;       // 任何平台都恰好 32 位
uint64_t size;        // 任何平台都恰好 64 位
int_fast16_t  i;      // 至少 16 位,本平台最快的整数
1
2
3
4

写文件格式、网络协议、跨平台代码时,禁止用 int/long,统一使用 int32_t/int64_t 等,避免 Windows/Linux/嵌入式平台位宽不一致导致的诡异 bug。


# 3.10 新手陷阱 Top 5

# 陷阱 错误示例 正确做法
1 隐式窄化 int i = 3.14; 默默截断 C++11 起用 int i{3.14}; 编译报错
2 有符号 / 无符号比较 if (i < v.size()) 警告 i 改用 size_t,或用 std::ssize
3 char 当数字用 char 在不同平台有/无符号性不同 用 int8_t / uint8_t
4 float 比较相等 if (a == 0.1) 永假 std::abs(a-b) < eps 或 <=>
5 auto 丢掉引用 auto x = vec[0]; 触发拷贝 auto& x = vec[0];

上次更新: 2026/06/10, 11:13:41
基础语法
运算符

← 基础语法 运算符→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式