Skip to content
Gains Summary
Main Navigation 首页 / Home
C++ 编程 / C++ Programming
系统与高性能 / Systems & Performance
Web 开发 / Web Development
人工智能 / Artificial Intelligence
工业软件 / Industrial Software
其他内容 / Other Topics
C++ 编程 / C++系统与性能 / SystemsWeb 开发 / Web人工智能 / AI工业软件 / Industrial

外观

Sidebar Navigation

← C++ 编程 / C++ Programming

基础语法与编译模型 / Language & Compilation

1. C++ 语言基础完整学习手册 / Complete C++ Language Basics Guide

2. C++ 语言基础与程序运行 / C++ Language Basics and Program Execution

3. C++ 程序是怎样做出来的:从源码到可执行文件 / From Source to Executable

4. 值、类型与对象:C++ 程序中的第一层现实 / Values, Types, and Objects

5. 表达式、运算符与控制流 / Expressions, Operators, and Control Flow

6. 函数、作用域与重载 / Functions, Scope, and Overloading

7. 数组、指针与字符串:从地址到借用范围 / Arrays, Pointers, and Strings

8. 引用、const 与类型推导:从复制到借用 / References, const, and Type Deduction

9. 让数据说清楚自己:枚举、结构体、联合体与类型别名 / Enums, Structs, Unions, and Aliases

10. 一个头文件怎样影响整个程序:预处理、头文件与命名空间 / Preprocessor, Headers, and Namespaces

11. 基础 I/O、错误与调试:让程序会观察自己的失败 / Basic I/O, Errors, and Debugging

本页目录

值、类型与对象:C++ 程序中的第一层现实 / Values, Types, and Objects ​

1. 先看一行代码背后的问题 ​

cpp
int count{3};
1

初学者通常会把它读成:

定义一个叫 count 的整数,值是 3。

这个说法可以作为入口,但还不够精确。

编译器和运行时还要处理:

text
count 是名字
int 是类型
对象需要一段存储
3 是初始化值
对象有生命周期
程序通过名字访问对象
1
2
3
4
5
6

本篇要建立的核心关系是:

text
名字
  |
  v
对象
  |
  +-- 类型:怎样解释它的存储,允许哪些操作
  +-- 值:当前保存的状态
  +-- 存储:对象占用的运行时空间
  +-- 生命周期:何时开始存在,何时结束
1
2
3
4
5
6
7
8
9

2. 本文基础词小注释 ​

值:程序在某一时刻可以观察到的结果或状态。例如整数对象当前的值可以是 3,布尔对象当前的值可以是 true。

类型:对一类值、操作和表示规则的约束。类型不仅告诉我们“它是什么”,还告诉编译器哪些操作合法、如何解释存储和如何进行转换。

对象:一段在运行时承载某种类型状态的实体。C++ 中对象不等于变量名;变量名是访问对象的一个名字。

存储:承载对象的运行时空间。存储可以来自自动存储、动态分配、静态区域或其他实现机制。

初始化:对象开始生命周期时获得初始状态的过程。

赋值:一个已经存在的对象接收新值的过程。它不是对象第一次出现。

生命周期:对象从开始存在到结束存在的时间范围。对象还有地址,不代表它仍然可以被合法访问。

3. 名字、对象和存储 ​

cpp
int count{3};
1

可以画成:

text
源码名字 count
       |
       v
+------------------+
| int object       |
| current value: 3 |
+------------------+
       |
       v
运行时存储
1
2
3
4
5
6
7
8
9
10

count 是名字。

对象是运行时真正存在的 int 实体。

存储是承载这个对象的空间。

在常见实现中,局部对象可能位于线程调用栈中,但 ISO C++ 主要规定对象语义和存储期,不要求所有实现都用同一张物理布局图。

这一区分很重要:

text
名字失去作用域
  !=
存储必然立刻归还给操作系统

对象生命周期结束
  ==
不能再按这个对象类型合法访问它
1
2
3
4
5
6
7

4. int 不只是“整数类型” ​

int 用来表示离散的整数值,例如计数、索引和状态码。

cpp
int count{3};
int offset{-2};
1
2

常见实现用固定宽度的二进制表示它。

因此它有表示范围:

text
位数有限
  |
  v
可表示的整数数量有限
  |
  v
超出范围时要考虑转换或溢出规则
1
2
3
4
5
6
7

sizeof(int) 可以观察当前实现的大小:

cpp
#include <iostream>

int main() {
    std::cout << sizeof(int) << '\n';
}
1
2
3
4
5

但不要把某台机器的 sizeof(int) 当成所有平台的永恒答案。

语言标准规定最小能力和相对关系,具体宽度属于实现。

还要区分:

text
有符号 int
  +-- 可以表示负数、零和正数
  +-- 有符号溢出是危险边界

unsigned int
  +-- 只表示非负范围
  +-- 运算和比较可能触发隐式转换
1
2
3
4
5
6
7

例如:

cpp
int negative{-1};
unsigned int size{1};

if (negative < size) {
    // 结果可能与直觉不同
}
1
2
3
4
5
6

问题不是“负数突然变成正数”这么简单,而是二者参与比较时会经过通常算术转换。

5. float 不只是“带小数的类型” ​

float 用有限位数近似表示实数。

cpp
float price{0.1f};
1

常见实现把一个浮点值拆成大致几部分:

text
+--------+----------+----------------+
| sign   | exponent | significand    |
+--------+----------+----------------+
1
2
3

这意味着它不是把十进制字符 0.1 原封不动放进内存。

很多十进制小数在二进制浮点中只能近似表示:

cpp
#include <iostream>
#include <iomanip>

int main() {
    float value{0.1f};
    std::cout << std::setprecision(20) << value << '\n';
}
1
2
3
4
5
6
7

因此浮点数需要讨论:

  • 有限精度。
  • 舍入误差。
  • 数值范围。
  • NaN 和无穷。
  • 近似比较,而不是随意使用 ==。

float 是否适合某个问题,不只取决于“能不能存小数”,还取决于误差预算。

6. 初始化不是赋值 ​

cpp
int a{7};  // 定义并初始化
int b{};   // 值初始化为 0
a = b;     // a 已经存在,现在赋值
1
2
3

时间线:

text
a 的存储准备好
      |
      v
a 的生命周期开始
      |
      v
a 获得初始值 7
      |
      v
后续赋值改变 a 的当前值
1
2
3
4
5
6
7
8
9
10

初始化决定对象从什么状态开始。

赋值改变已经存在对象的状态。

这一区分会影响:

  • const 对象。
  • 引用成员。
  • 类的构造函数。
  • RAII。
  • 异常安全。

例如:

cpp
const int limit{10};
// limit = 20; // 不能赋值
1
2

因为 limit 从一开始就是一个不可修改的对象。

7. 初始化形式和窄化转换 ​

cpp
int a = 3.9;   // 可能丢失小数
int b(3.9);    // 也可能发生转换
int c{3.9};    // 列表初始化通常拒绝窄化
1
2
3

列表初始化的价值不是“写法更漂亮”。

它让某些可能丢失信息的转换更早暴露:

text
更宽的数值
  |
  v
更窄的目标类型
  |
  +-- 值可能改变
  +-- 列表初始化倾向于在编译期拒绝
1
2
3
4
5
6
7

这是 C++ 把一部分错误提前到编译期的例子。

8. const 的含义 ​

cpp
const int limit{10};
1

这里的 const 表示通过这个对象名不能修改对象的值。

它还会影响接口:

cpp
void inspect(const int& value);
1

这个函数表达了一个意图:

text
调用者提供一个已经存在的 int
函数借用它
函数不应该通过这个参数修改它
1
2
3

但 const 不等于线程安全,也不等于对象永远不会变化。

如果对象通过其他合法路径被修改,或者内部状态使用 mutable,具体语义仍要看完整类型和程序设计。

9. 类型如何影响机器执行 ​

表达式:

cpp
int sum = left + right;
1

编译器要根据类型决定:

text
读取多少字节
  |
  v
把比特解释成什么数值
  |
  v
选择什么算术指令或转换
  |
  v
把结果放在哪里
1
2
3
4
5
6
7
8
9
10

类型不是运行时贴在对象上的文字标签。

它既是语言层面的约束,也是编译器生成代码时的重要信息。

不过不要把“类型影响代码生成”理解成“所有类型都能直接看出固定机器布局”。

优化、ABI 和目标架构都会影响最终实现。

10. 未初始化和不确定状态 ​

cpp
int value;
std::cout << value << '\n';
1
2

不要把它理解成“系统自动给了一个随机数”。

更准确地说,程序没有先建立一个可用的 int 初始状态,就尝试读取它。

应该写:

cpp
int value{};
1

或者:

cpp
int value = 0;
1

初始化不是装饰。

它是建立对象不变量的第一步。

11. 整数、浮点和字符的对比 ​

类型主要用途关键限制
int离散整数、计数、索引范围有限,转换和溢出要小心
unsigned非负范围、位操作等混合比较可能产生意外转换
float有限精度数值计算舍入误差、范围和特殊值
double更高精度的浮点计算仍不是任意实数的精确表示
char字节或字符单元是否有符号、编码语义需区分
bool真值状态参与算术时会发生转换

选类型不是只问“能不能存这个值”。

还要问:

text
这个值的数学意义是什么?
精度要求是什么?
是否需要负数?
是否会和其他类型混合?
接口是否要表达状态?
1
2
3
4
5

12. 编译期错误和运行时错误 ​

cpp
int value{"42"};
1

通常在编译阶段就会被拒绝,因为初始化类型不匹配。

而下面的问题可能要到运行时才暴露:

cpp
int values[2]{1, 2};
std::cout << values[5] << '\n';
1
2

这说明不同阶段负责不同工作:

text
编译期
  +-- 语法、类型、名字和部分规则

运行期
  +-- 实际输入、索引、状态和执行路径
1
2
3
4
5

编译器能检查的越多,程序越容易维护。

但 C++ 允许很多低层操作,所以仍然需要测试、调试器和 Sanitizer。

13. 最小实验 ​

text
实验 1:比较 int、long、float、double 的 sizeof。
实验 2:观察有符号和无符号混合比较。
实验 3:比较 0.1f 的输出精度。
实验 4:比较 = 初始化、() 初始化和 {} 初始化。
实验 5:开启 -Wall -Wextra,观察未初始化警告。
实验 6:制造数组越界,使用 AddressSanitizer 观察。
1
2
3
4
5
6

建议编译:

bash
c++ -std=c++17 -Wall -Wextra -Wpedantic -g demo.cpp -o demo
c++ -std=c++17 -fsanitize=address,undefined -g demo.cpp -o demo-sanitized
1
2

14. 常见误解 ​

“变量就是一块内存” ​

不准确。变量是名字;对象是运行时实体;存储是承载对象的空间。

“初始化就是第一次赋值” ​

不准确。初始化建立对象,赋值改变已经存在的对象。

“float 就是精确的小数” ​

不对。它通常是有限精度的二进制近似。

“const 就是线程安全” ​

不对。const 主要表达访问和修改约束,不自动建立线程同步。

“编译通过就没有未定义行为” ​

不对。许多地址、生命周期、越界和并发问题只能在运行时路径中暴露。

15. 用类型描述程序意图 ​

比较:

cpp
void set_count(int value);
void set_count(unsigned value);
1
2

它们不只是能接受的数字范围不同,也表达了不同意图:

text
int
  +-- 可能有负值
  +-- 适合差值和部分状态码

unsigned
  +-- 只表达非负范围
  +-- 混合比较时要小心转换
1
2
3
4
5
6
7

类型选择是一种接口设计。

如果参数表示文件大小、数组长度或资源数量,还要考虑明确宽度整数、std::size_t 或领域类型,而不是机械地把所有数字都写成 int。

16. 位、字节和对象表示 ​

计算机最终保存的是比特:

text
bit
  |
  v
byte
  |
  v
object representation
  |
  v
value interpreted through a type
1
2
3
4
5
6
7
8
9
10

同一组比特按不同类型解释,意义可能不同。

但“内存里有这些比特”和“程序可以按这种类型合法访问这些比特”是两个问题。

这也是指针、结构体、序列化和内存映射必须分开学习的原因。

17. 数值边界实验 ​

cpp
#include <limits>
#include <iostream>

int main() {
    std::cout << std::numeric_limits<int>::min() << '\n';
    std::cout << std::numeric_limits<int>::max() << '\n';
    std::cout << std::numeric_limits<float>::epsilon() << '\n';
}
1
2
3
4
5
6
7
8

epsilon 不是所有浮点误差都相等。

它描述的是某个实现下与 1 附近相关的精度尺度。

真实数值算法还要考虑数量级、运算次数、舍入方向和误差传播。

18. 语言规则、实现和平台 ​

text
问题:int 到底几字节?
  |
  +-- ISO C++:规定最小范围和类型关系
  +-- 实现:选择具体宽度和表示
  +-- ABI:规定跨目标文件如何传递
  +-- 平台:决定编译器、运行库和目标架构
1
2
3
4
5
6

遇到“在我的电脑上就是这样”,不要马上把观察提升为语言规则。

更好的回答方式是:

text
标准保证什么?
常见实现是什么?
当前平台观察到什么?
换平台后哪些内容需要重新验证?
1
2
3
4

19. 总结与下一篇 ​

本篇最重要的模型:

text
名字
  |
  v
对象
  |
  +-- 有类型
  +-- 占用存储
  +-- 有生命周期
  +-- 保存值
  +-- 受到访问规则约束
1
2
3
4
5
6
7
8
9
10

下一篇进入表达式和控制流。

我们会继续追问:

text
表达式怎样产生值?
隐式转换在哪里发生?
为什么 && 可以短路?
if 和 for 最后如何改变 CPU 的控制流?
1
2
3
4

最后更新于:

Pager
上一篇3. C++ 程序是怎样做出来的:从源码到可执行文件 / From Source to Executable
下一篇5. 表达式、运算符与控制流 / Expressions, Operators, and Control Flow

持续记录,持续成长

Copyright © Tidenflow