为什么想系统了解C++语言
学习过程中的困扰
中文跟英文有巨大的区别,计算机语言又都是用的英文,这很容易导致同样的概念用中文翻译过来之后的就让人晕头转向。
比如C++ Primer Plus中文版,从第2章涉及到C++程序开始,cout就没翻译清楚。首先英文版刚开始介绍cout时就用括号标注了读音(pronounced"see-out"),中文翻译视而不见,简单的东西硬是漏翻掉让人去猜;其次,cout在英文版里就对应两个词,object和facility,对C++来说是对象,对程序员来说是工具,但中文翻译却在初学者还没熟悉概念的时候,紧随其后蹦出一个cout函数概念,cout这玩意一会儿是对象,一会儿是工具,一会儿给翻成了函数,问题是后面详细介绍的时候又明确指出它是对象,这不禁让初学者困惑。如果翻译校订仔细,相信能发现这个错误:“seeing cout instead of the printf() function”被翻译成“看到cout函数(而不是printf()函数)时”,C语言中printf()确实是函数,但是C++中cout不是函数,用C++的语言来说它就是对象。其他地方这里就不举例了,建议如果看得仔细的话,可以配上英文版纸质或电子版。
所以,其实我们有必要去梳理下C++语言的设计逻辑,这样凭着自己的深层次的基础性理解,无论看中文还是英文,都不容易被带偏,毕竟尽信书不如无书,实践出真知,书本只是基础。
剖析思考C++语言
计算机语言其实跟人类的语言是类似的东西,毕竟都叫做语言。人类的语言也有千差万别,机器的语言也是一样,这很容易让人联想到圣经里的巴别塔,其实上帝不光让人类说不同的语言,也让万物之间不能沟通,人类向往天堂,且一直在建造通往天堂的巴别塔,可以说任重而道远。
华为鸿蒙的野心是做物联网时代万物互联的基础操作系统,允许我断章取义,真正能做到万物互联的是神而不是人。我们对未来还是充满信心,能做的要做的事情太多了。什么时候能用中文做操作系统底层架构那咱们就真牛了,咱也不用被汉化的知识整懵逼了。
回归正题:为了搞清楚C++语言,我们还是得从最底层的最基础也是最简单的逻辑去剖析理解。作为初学者我个人粗略认为,C++程序是由C++语言内置的词汇或者说预定义内容和程序员自定义的内容组成。
---2025-11-21---👇
一开始之所以联想到预定义和自定义,一方面困惑于符号与标识符的区别,还有就是看到过很多C++预定义的内容比如#define,一方面受语言这个词汇影响自然容易想到人类语言,人类语言的汉字和名字就能对应上符号和标识符,一个是预定义的一个是自定义的。
---2025-11-21---👆
当我询问AI可以这么理解吗?得到了更详细的解读,经过多番查询整理如下:
你的理解基本正确,但可以更精确地分层表述。C++程序的构成确实可以看作语言内置词汇(预定义内容)与程序员定义(自定义内容)的交互,但需要进一步明确其层次关系和交互规则。以下是更系统的分析:
C++程序基本内容系统分析
1. 构成要素分类
(1) 语言内置词汇(预定义内容)
- 关键词(Keywords)
int,if,class,template等,不可被重定义或修改。 - 运算符(Operators)
+,<<,->等,部分可重载(如operator+),但核心语义固定。 - 特殊标识符(Reserved Identifiers)
main,operator new,typeid等,具有标准规定的特殊行为。 - 字面量(Literals)
42,"hello",nullptr等,语法形式固定。 - 预处理器符号
#define,#include等,在编译前处理。
(2) 程序员定义(自定义内容)
- 标识符(Identifiers)
变量名(count)、函数名(calculate)、类名(MyClass)等。 - 运算符重载
自定义operator+等,但必须遵循语言规定的重载规则。 - 类型系统扩展
通过class/struct定义新类型,或通过typedef/using创建类型别名。 - 宏(谨慎使用)
#define MAX 100,但可能污染命名空间。
2. 二者的交互逻辑
graph LR
A[语言内置词汇] -->|提供基础规则| B[程序员自定义内容]
B -->|遵循| C[语法约束]
B -->|扩展| D[新功能]
A -->|限制| B
(1) 依赖关系
- 自定义内容必须基于内置词汇的规则
例如:class MyClass { // 使用关键词`class` int value; // 使用内置类型`int` void print() const; // 使用`const`修饰符 };
(2) 扩展机制
- 有限度的自由
- 可重载运算符,但不能创建新运算符(如不能定义
**表示幂运算)。 - 可定义新类型,但必须通过已有类型组合(如
class/enum)。
- 可重载运算符,但不能创建新运算符(如不能定义
(3) 边界控制
| 自定义行为 | 语言限制 | 示例 |
|---|---|---|
| 定义变量名 | 不能与关键词冲突 | int class; ❌ |
| 重载运算符 | 必须保留原始优先级和结合性 | a + b 永远先于 a << b |
实现main函数 | 必须符合标准签名 | int main(int, char*[]) ✅ |
3. 设计哲学体现
(1) 约束与自由的平衡
-
约束性规则(保证可预测性)
int x = 42; // 类型必须明确(或通过`auto`推导) x = "hello"; ❌ // 禁止隐式类型不匹配赋值 -
扩展性自由(支持领域特定设计)
class Matrix { public: Matrix operator*(const Matrix& rhs); // 自定义矩阵乘法 };
(2) 显式优于隐式
- 自定义内容需显式声明:
explicit Data(int bytes); // 禁止隐式转换 override void draw(); // 显式标记覆盖
(3) 零开销抽象
- 自定义类型与内置类型性能等价:
std::array<int, 100> arr; // 编译时确定大小,与内置数组性能相同
4. 常见误解澄清
(1) "所有符号都可自定义" ❌
- 不可自定义的要素:
- 关键词(如不能将
class改为myclass) - 语法结构(如不能将
if (x) {...}改为when (x) {...}) - 核心运算符语义(如
a + b永远表示加法,不能全局改为减法)
- 关键词(如不能将
(2) "宏是彻底的自定义" ❌
- 宏在预处理阶段展开,但:
#define BEGIN { // 不推荐:破坏代码可读性 BEGIN // 实际展开为 `{`- 仍受限于C++语法规则(如宏展开后必须形成合法代码)。
5. 正确理解范式
// 语言提供"积木块"(内置词汇)
int, +, if, class, ...
// 程序员用积木搭建"建筑"(自定义内容)
class Widget { // 使用`class`关键词
int priority; // 使用`int`类型
public:
bool operator<(Widget other) { // 重载`<`
return priority < other.priority;
}
};
// 但无法修改积木本身(如不能把`int`改成`integer`)
总结
你的理解方向正确,但需注意:
- 分层关系:自定义内容必须基于语言提供的底层规则
- 边界明确:语言核心词汇不可修改,只能在允许范围内扩展
- 设计意图:通过约束保证基础可靠性,通过扩展机制提供灵活性
这种设计使C++既能严格保证系统级编程的精确性,又能支持高层抽象表达。
C++内置词汇的完整分类
以下是C++内置词汇的完整分类及设计逻辑分析,基于C++20标准整理:
1. 关键词(Keywords)
(1) 基础类型声明
| 关键词 | 用途 | 设计逻辑 |
|---|---|---|
int | 整型(≥16位) | 硬件直接映射 |
double | 双精度浮点 | IEEE 754标准支持 |
bool | 布尔型(true/false) | 逻辑运算优化 |
void | 无类型/空返回值 | 泛型编程基础 |
auto | 自动类型推导(C++11) | 简化复杂类型声明 |
(2) 类型修饰符
| 关键词 | 组合示例 | 设计逻辑 |
|---|---|---|
const | const int | 编译期常量/不可修改 |
volatile | volatile char | 防止编译器优化(嵌入式) |
mutable | mutable int | 突破const限制(如缓存) |
constexpr | constexpr int | 编译期计算(C++11) |
(3) 流程控制
if (cond) {} else {} // 条件分支
for (init; cond; inc) {} // 循环结构
while (cond) {} // 前置判断循环
do {} while (cond); // 后置判断循环
switch (val) { case 1: } // 多路分支
break/continue/return // 流程跳转
设计逻辑:
- 结构化编程基础(源自C语言)
switch的case必须为整型常量表达式(性能优化)
(4) 面向对象
| 关键词 | 用途 | 设计逻辑 |
|---|---|---|
class | 类声明 | 数据封装 |
struct | 默认public的类 | C兼容性 |
union | 共用内存体 | 内存敏感操作 |
virtual | 虚函数/多态 | 运行时动态绑定 |
override | 显式覆盖(C++11) | 增强代码可读性 |
final | 禁止继承/覆盖(C++11) | 设计约束 |
(5) 内存管理
new/delete // 堆内存分配
new[]/delete[] // 数组版本
static // 静态存储期
thread_local // 线程局部存储(C++11)
设计逻辑:
- 对称性设计(new/delete配对)
thread_local支持并发编程
(6) 模板与泛型
template<typename T> // 模板声明
typename // 依赖类型名
concept // 概念约束(C++20)
requires // 约束表达式(C++20)
2. 运算符(Operators)
(1) 算术运算
| 运算符 | 示例 | 可重载 | 设计逻辑 |
|---|---|---|---|
+ - * / % | a + b | 是 | 数学直觉 |
++ -- | i++ | 是 | 前置/后置区分 |
(2) 位操作
| 运算符 | 示例 | 可重载 | 设计逻辑 |
|---|---|---|---|
| `& | ^ ~` | a & b | 是 |
<< >> | a << 2 | 是 | 移位/流输出双重语义 |
(3) 逻辑运算
&& || ! // 逻辑与/或/非(短路求值)
== != < > <= >= // 比较运算符
<=> // 三向比较(C++20)
(4) 内存访问
| 运算符 | 示例 | 可重载 | 设计逻辑 |
|---|---|---|---|
* | *ptr | 是 | 解引用/乘法双重语义 |
& | &var | 是 | 取地址/位与双重语义 |
-> | ptr->m | 是 | 指针成员访问 |
->* | ptr->*m | 是 | 成员指针访问 |
(5) 特殊运算符
, (逗号运算符) // 表达式分隔
?: (三目运算符) // 条件表达式
sizeof // 类型大小
typeid // 类型信息
3. 特殊标识符(Reserved Identifiers)
(1) 典型特殊标识符列表
|
标识符 |
类别 |
用途 |
是否可重载 |
|---|---|---|---|
|
|
程序入口 |
应用程序启动点 |
❌ |
|
|
内存管理 |
动态内存分配 |
✅(可替换) |
|
|
内存管理 |
内存释放 |
✅(可替换) |
|
|
运行时类型信息 |
类型查询 |
❌ |
|
|
类型转换 |
安全向下转型 |
❌ |
|
|
编译时操作符 |
类型/对象大小计算 |
❌ |
|
|
类成员函数 |
当前对象指针 |
❌ |
(2) 设计逻辑分析
①为什么需要特殊标识符?
-
保证关键功能的一致性
例如所有C++程序必须通过
main启动,确保操作系统接口统一。 -
提供可扩展的底层控制
如允许重载
operator new实现自定义内存池,但保留默认行为。
② 与关键词的区别
|
特性 |
特殊标识符 |
关键词 |
|---|---|---|
|
可重用性 |
部分可重载/替换 |
完全不可重定义 |
|
语法角色 |
标识符(可包含字母数字) |
固定词法单元(如 |
|
示例 |
|
|
③与普通标识符的对比
// 特殊标识符(语言预定义行为)
int main() {} // 必须存在且符合签名
// 普通标识符(完全自定义)
int my_function() { return 42; } // 名称、行为均可自由定义
它们填补了关键词的刚性约束与普通标识符的完全自由之间的空白,是C++“给你足够绳子,但不会让你轻易上吊”设计哲学的典型体现。
4. 字面量(Literals)
(1) 数值字面量
42 // 十进制
0x2A // 十六进制
0b101010 // 二进制(C++14)
3.14f // float
6.02e23 // 科学计数法
(2) 字符串字面量
"hello" // 普通字符串
u8"UTF-8" // UTF-8编码(C++11)
L"wide" // 宽字符
R"(raw\nstring)" // 原始字符串(C++11)
(3) 自定义字面量(C++11)
auto x = 10_km; // 用户定义字面量
auto y = 3.14_rad; // 需定义operator""_rad
5. 预处理器符号
| 符号 | 示例 | 用途 |
|---|---|---|
#define | #define PI 3.14 | 宏定义 |
#include | #include <vector> | 文件包含 |
#ifdef | #ifdef DEBUG | 条件编译 |
#pragma | #pragma once | 编译器指令 |
完整关键词列表(C++20)
alignas alignof and and_eq asm auto bitand bitor bool
break case catch char char8_t char16_t char32_t class
compl concept const consteval constexpr constinit const_cast
continue co_await co_return co_yield decltype default delete
do double dynamic_cast else enum explicit export extern
false float for friend goto if inline int long
mutable namespace new noexcept not not_eq nullptr
operator or or_eq private protected public register
reinterpret_cast requires return short signed sizeof
static static_assert static_cast struct switch synchronized
template this thread_local throw true try typedef
typeid typename union unsigned using virtual void
volatile wchar_t while xor xor_eq
以上体系展示了C++如何通过精心设计的词汇系统平衡底层控制与高级抽象,既保留C的硬件访问能力,又支持现代软件工程范式。
C++初学者常见的困惑(未完待续)
C++符号与标识符的区别是什么?
在 C++ 中,符号(Symbols/Punctuators) 和标识符(Identifiers)是两类完全不同的语法元素,它们在语言中扮演不同的角色。以下是它们的核心区别及具体分类:
1. 标识符(Identifiers)
定义:由程序员自定义的名称,用于标识变量、函数、类、命名空间等程序实体。
规则:
- 组成:字母(`A-Z, a-z`)、数字(`0-9`)、下划线(`_`)。
- 开头:不能是数字(如 `1var` 非法)。
- 区分大小写:`var` 和 `Var` 是不同的标识符。
- 不能是关键字(如 `int`、`class` 不能作为标识符)。
示例:
int age; // "age" 是标识符
void calculate(); // "calculate" 是标识符
class MyClass; // "MyClass" 是标识符
2. 符号(Symbols/Punctuators)
定义:语言预定义的标点或操作符,用于语法结构或运算。
分类:
(1) 标点符号(Punctuators)
- 用于分隔或终止代码单元:
; // 语句结束
, // 参数或列表分隔
{} // 代码块边界
[] // 数组下标或Lambda捕获
() // 函数调用或优先级分组
:: // 作用域解析
. -> // 成员访问
(2) 运算符(Operators)
- 用于数学、逻辑或内存操作:
+ - * / % // 算术运算
== != < > // 比较运算
&& || ! // 逻辑运算
= += -= // 赋值运算
& * // 指针运算(取地址/解引用)
(3) 预处理符号
- 以 `#` 开头的预处理指令:
#include <iostream>
#define PI 3.14
#ifdef DEBUG
(4) 字面量分隔符
- 修饰字面量的符号:
'A' // 字符字面量
"Hello" // 字符串字面量
3.14f // float字面量(f后缀)
3. 标识符 vs 符号的关键区别
| **特征** | **标识符** | **符号** |
|--------------------|------------------------------------|----------------------------------|
| **来源** | 程序员自定义 | 语言预定义 |
| **作用** | 命名程序实体(变量、函数等) | 语法结构或运算 |
| **是否可自定义** | 是(需遵守命名规则) | 否(固定用途) |
| **示例** | `count`, `calculate`, `MyClass` | `;`, `+`, `::`, `#include` |
4. 易混淆场景
(1) 下划线 `_` 的双重角色**
- 在标识符中:合法字符(如 `_value`)。
- 作为符号:单独使用时是运算符(如 `a _ b` 可能被解析为减法或用户定义运算符)。
(2) `*` 和 `&` 的多义性
- 在声明中:`int* p;` 的 `*` 是类型符号(指针)。
- 在表达式中:`*p` 的 `*` 是运算符(解引用)。
(3) 作用域解析符 `::`
- 符号 `::` 不是标识符,但连接标识符:
std::cout; // "std" 和 "cout" 是标识符,`::` 是符号
5. 为什么需要区分?
- 语法解析:编译器依赖符号确定代码结构(如分号结束语句)。
- 代码可读性:正确使用符号避免歧义(如 `a+++b` 应写作 `a++ + b`)。
- 错误排查:混淆二者会导致编译错误(如误将 `:` 用作变量名)。
结合前面的分析总结
- **标识符**是“名字”,由程序员定义,用于指代程序实体。
- **符号**是“语法工具”,由语言定义,用于构建代码逻辑。
- **特殊标识符**也是符号,由语言定义,具有标准规定的特殊行为。
C++数据类型与变量类型的区别
在 C++ 中,**数据类型(Data Type)** 和 **变量类型(Variable Type)** 是两个相关但不同的概念,初学者容易混淆。以下是它们的区别和联系:
1. 数据类型(Data Type)
定义:数据类型是编译器或系统定义的 **数据分类**,决定了数据的存储方式、取值范围和可执行的操作。
特点:
- 由语言本身或标准库定义(如 `int`, `float`, `std::string`)。
- 与内存分配直接相关(如 `int` 通常占 4 字节)。
- 规定了数据的操作规则(如 `int` 支持加减乘除,`bool` 支持逻辑运算)。
分类:
1. 基本数据类型(Primitive Types)**
- 整数:`int`, `short`, `long`, `char`
- 浮点:`float`, `double`
- 布尔:`bool`
- 无类型:`void`
2. 复合数据类型(Derived/Composite Types)**
- 指针:`int*`, `char*`
- 数组:`int[10]`
- 引用:`int&`
- 结构体/类:`struct S`, `class C`
3. 标准库类型
- `std::string`, `std::vector<T>`, `std::function` 等。
2. 变量类型(Variable Type)
定义:变量类型是 **变量在声明时的具体类型**,由数据类型 + 修饰符(如 `const`、指针、引用等)组合而成。
特点:
- 变量是数据的具名存储单元,类型决定了变量能存储的数据种类。
- 变量类型 = 数据类型 + 附加属性**(如 `const int`、`int*`)。
int a; // 变量类型是 `int`(基本数据类型)
const float b; // 变量类型是 `const float`(数据类型 + const修饰)
int* ptr; // 变量类型是 `int*`(指针类型)
std::string s; // 变量类型是 `std::string`(标准库类型)
3. 关键区别
| **维度** | **数据类型** | **变量类型** |
|----------------|-----------------------------|-----------------------------|
| **定义范围** | 语言或库预定义的分类 | 变量声明时使用的具体类型 |
| **组成** | 基础类型或复合类型(如 `int`) | 数据类型 + 修饰符(如 `const int*`) |
| **内存关联** | 决定数据存储格式和大小 | 决定变量的存储和行为规则 |
| **例子** | `double` 是数据类型 | `volatile double*` 是变量类型 |
4. 常见混淆场景
场景1:指针和引用的类型
int x = 10;
int* p = &x; // 变量类型是 `int*`,但 `int` 是数据类型
int& r = x; // 变量类型是 `int&`,但 `int` 是数据类型
- `int*` 和 `int&` 是变量类型,但它们的底层数据类型是 `int`。
场景2:`const` 修饰符
const int y = 20; // 变量类型是 `const int`,数据类型是 `int`
- `const` 是变量类型的修饰符,不属于数据类型本身。
场景3:标准库模板类型
std::vector<int> v; // 变量类型是 `std::vector<int>`,数据类型是模板实例化的结果
- `std::vector<int>` 既是数据类型,也是变量类型(模板实例化后)。
5. 总结
- **数据类型** 是语言层面的抽象分类,**变量类型** 是具体变量声明时的类型描述。
- 变量类型可以包含数据类型以外的信息(如 `const`、`volatile`、指针/引用修饰符等)。
空白符号是否属于预定义内容
在C++中,空白符号(包括空格、制表符、换行符等)不属于预定义内容,但它们在预处理和编译过程中具有特殊作用。以下是关键点说明:
1. 空白符号的分类
- 空格 (
) - 水平制表符 (
\t) - 换行符 (
\n) - 垂直制表符 (
\v) - 换页符 (
\f) - 注释(虽然注释在预处理后会被替换为空格,但本身不是空白符号)
2. 预处理阶段的行为
- 宏展开前:空白符号用于分隔预处理指令(如
#include、#define)的组成部分,但不会作为宏或标识符的一部分。#define X 1 // 空格分隔宏名和值 - 宏展开后:空白符号可能影响替换结果,但不会被预处理器“预定义”为特定内容。
#define F(a,b) a##b F(x, y) // 需要无空格,否则展开失败
3. 与预定义内容的区别
- 预定义内容:指语言或预处理器内置的符号(如
__LINE__、__FILE__)或关键字(如int、if)。 - 空白符号:是语法分隔符,无预定义的语义含义,仅影响代码的解析方式。
4. 特殊情况
- 字符串/字符常量:空白符号在引号内是字面量的一部分,不属于语法分隔符。
char c = ' '; // 空格作为字符内容 - 原始字符串字面量:换行符等会保留原样。
const char* s = R"(Line1 Line2)";
总结
空白符号是C++语法中的分隔符,而非预定义的语义内容。它们在预处理和编译阶段仅用于控制代码解析,本身不具预定义的逻辑含义。
空白符号是否属于自定义内容
在 C++ 中,空白符号(空格、制表符、换行符等)既不属于预定义内容,也不属于自定义内容,而是语法分隔符,用于控制代码的解析方式。
1. 空白符号的定位
- 不是预定义内容:预定义内容指语言内置的符号(如
__LINE__、#define、关键字int等),而空白符号是语法结构的一部分,没有预定义的逻辑含义。 - 不是自定义内容:自定义内容通常是程序员定义的标识符(如变量名、宏名、函数名等),而空白符号是语言规定的分隔符,不能由程序员重新定义或改变其基本作用。
2. 空白符号的作用
空白符号的主要功能是分隔代码元素,例如:
- 分隔关键字和变量名:
int x; // 空格分隔 `int` 和 `x` - 分隔宏定义:
#define MAX 100 // 空格分隔 `#define`、`MAX` 和 `100` - 控制代码格式(不影响语义,但影响可读性):
if (x > 0) { // 换行和缩进仅用于可读性 cout << "x is positive"; }
3. 特殊情况
- 字符串/字符常量中的空白:
如果空白符号出现在引号内(如" "或'\t'),则它们是字面量的一部分,属于程序数据而非语法分隔符。char space = ' '; // 空格是字符数据 string tab = "\t"; // 制表符是字符串数据 - 原始字符串(Raw String Literals):
换行符等空白会被原样保留,但仍不属于“自定义内容”。const char* text = R"(Line 1 Line 2)"; // 换行符是字符串的一部分
结论
空白符号在 C++ 中既不是预定义内容(如宏、关键字),也不是自定义内容(如变量名、宏名),而是语法分隔符,用于控制代码解析,但不影响程序的逻辑语义(除非在字符串/字符字面量中)。
C++语法辅助工具
在 C++ 中,类似空白符号(空格、换行符等)的语法辅助工具主要用于代码解析和格式控制,它们既不属于自定义内容(程序员定义的标识符)也不属于预定义内容(语言/编译器内置的符号),而是语法结构的一部分。以下是这类工具的详细分类和说明:
1. 语法辅助工具的分类
(1) 空白符号(Whitespace)
- 作用:分隔代码元素(如
int x;中的空格)。 - 是否可自定义/预定义:否,是语法强制要求的分隔符。
(2) 注释(Comments)
- 作用:代码说明或屏蔽,预处理阶段被替换为空格。
- 示例:
// 单行注释 /* 多行注释 */ - 是否可自定义/预定义:否,注释语法是语言固定的。
(3) 分号(;)
- 作用:终止语句(如
int x = 0;)。 - 是否可自定义/预定义:否,是语法强制要求的终结符。
(4) 花括号({})
- 作用:定义作用域或代码块(如函数体、类定义)。
- 是否可自定义/预定义:否,但可通过宏模拟(不推荐):
#define BEGIN { #define END }
(5) 逗号(,)
- 作用:分隔参数或列表项(如
int a, b;)。 - 是否可自定义/预定义:否,但可通过运算符重载自定义其在特定上下文的行为(如
operator,)。
(6) 冒号(:)
- 作用:用于类继承、初始化列表、三目运算符等。
- 是否可自定义/预定义:否,语法固定。
(7) 尖括号(<>)
- 作用:模板参数列表(如
std::vector<int>)。 - 是否可自定义/预定义:否,但可通过宏模拟(极不推荐):
#define TEMPLATE_PARAM <typename T>
(8) 圆括号(())
- 作用:函数调用、表达式分组。
- 是否可自定义/预定义:否,但可通过宏或运算符重载(如
operator())间接影响行为。
(9) 预处理器符号(#、##)
- 作用:宏展开时的字符串化或拼接。
- 示例:
#define STR(x) #x // 字符串化 #define CONCAT(a,b) a##b // 拼接 - 是否可自定义/预定义:
#和##的行为是预定义的,但可通过宏组合间接自定义逻辑。
2. 与空白符号的共性
这些工具的共同特点是:
- 非自定义:程序员无法修改其核心语法(如将分号改为其他符号)。
- 非预定义:它们不是像
__LINE__或std::cout那样的预定义符号,而是语言的基础语法单元。 - 语法强制性:编译器依赖它们解析代码结构,缺少或错误使用会导致编译失败。
3. 特殊情况分析
| 工具 | 是否可自定义 | 例外/变通方式 |
|---|---|---|
分号 ; | 否 | 宏替换(如 #define END ;) |
逗号 , | 否(但可重载 operator,) | 重载后可能改变语义(罕见使用) |
圆括号 () | 否(但可重载 operator()) | 仿函数(Functor)中自定义调用行为 |
花括号 {} | 否 | 宏模拟(如 #define BEGIN {) |
| 注释 | 否 | 无 |
4. 总结
- 严格语法工具(如分号、花括号、注释):完全不可自定义,属于语言基础语法。
- 部分可干预工具(如逗号、圆括号):通过运算符重载或宏间接影响行为,但核心语法不变。
- 与空白符号的关系:它们和空白符号一样,是代码解析的“基础设施”,既非自定义也非预定义,而是语法规则本身。
这类工具的存在体现了 C++ 的语法与语义分离原则:
- 语法层(如分号、括号)由语言严格规定,不可更改。
- 语义层(如运算符重载、宏)允许程序员在规则内自定义行为。
C++中介于自定义内容和预定义内容之间的有哪些?
在 C++ 中,介于自定义内容(程序员定义)和预定义内容(语言内置)之间的元素通常是指那些由标准库或编译器提供,但允许程序员重新定义或部分自定义的符号或机制。以下是主要的几类:
1. 宏(Macros)
- 预定义宏(如
__LINE__、__FILE__、__cplusplus)是编译器内置的,不可修改。 - 可自定义的宏(如
#define定义的宏)允许程序员覆盖或扩展行为,但可能和标准库宏冲突(如max、min)。#define max(a, b) ((a) > (b) ? (a) : (b)) // 可能覆盖标准库的 std::max
2. 标准库名称(Standard Library Names)
- 标准库类型/函数(如
std::string、std::vector、std::cout)是预定义的,但允许程序员通过命名空间或局部作用域覆盖:namespace my { int cout = 42; } // 自定义 cout,隐藏 std::cout - 注意:直接覆盖标准库名称(如全局定义
int endl;)是未定义行为(UB)。
3. 运算符(Operators)
- 预定义运算符(如
+、==、<<)的语义固定,但可通过运算符重载自定义行为:struct Point { int x, y; }; bool operator==(Point a, Point b) { return a.x == b.x && a.y == b.y; } // 自定义 ==
4. 属性(Attributes)
- 标准属性(如
[[nodiscard]]、[[deprecated]])是语言预定义的,但允许用户自定义属性(编译器可能忽略):[[my::custom_attr]] void foo(); // 自定义属性(依赖编译器支持)
5. 字面量(Literals)
- 预定义字面量(如
123、"hello")的语法固定,但可通过用户定义字面量(UDL)扩展后缀:long double operator"" _km(long double x) { return x * 1000; } // 自定义 _km auto dist = 5.0_km; // 调用 operator"" _km
6. 预处理器指令(Preprocessor Directives)
- 固定指令(如
#include、#ifdef)不可修改,但可通过宏组合实现间接自定义:#define MY_INCLUDE "header.h" #include MY_INCLUDE // 间接自定义包含文件
7. 关键字(Keywords)
- 严格预定义(如
int、if、class)不可更改,但可通过宏在预处理阶段替换(不推荐):#define INT int // 危险!仅用于演示 INT x = 42; // 替换为 int x = 42;
总结表
| 类别 | 预定义内容 | 可自定义/覆盖的部分 |
|---|---|---|
| 宏 | __LINE__, __FILE__ | #define 定义的宏 |
| 标准库名称 | std::cout, std::endl | 通过命名空间或局部作用域覆盖 |
| 运算符 | +, ==, << | 运算符重载 |
| 属性 | [[nodiscard]] | 自定义属性(编译器依赖) |
| 字面量 | 123, "hello" | 用户定义字面量(UDL) |
| 预处理器指令 | #include, #ifdef | 通过宏间接自定义 |
| 关键字 | int, class | 宏替换(不推荐) |
关键区别
- 完全预定义:语言强制规定的部分(如关键字、基本运算符语义)。
- 部分可自定义:标准库名称、运算符重载、字面量等,允许程序员扩展或覆盖,但需遵循一定规则。
- 完全自定义:用户定义的变量、函数、类等。
这些“介于之间”的内容体现了 C++ 的灵活性,但也需要谨慎使用以避免冲突或未定义行为。
C++符号与标识符全解析
1万+

被折叠的 条评论
为什么被折叠?



