init
This commit is contained in:
86
docs/antlr_guide.md
Normal file
86
docs/antlr_guide.md
Normal file
@ -0,0 +1,86 @@
|
||||
# ANTLR in Python Interpreter
|
||||
|
||||
## 配置教程
|
||||
|
||||
### 配置 Antlr C++ 运行环境
|
||||
|
||||
Python 解释器采用 Antlr 作为前端语法分析器,其中核心代码编译时间较长,因此我们提前编译好了 Antlr 的运行环境,这样你的程序在编译时就不需要再编译 Antlr 的运行环境了。
|
||||
|
||||
为了在你自己的电脑上也使用 Antlr 运行环境,你需要将 Antlr 编译好的运行环境安装到你的电脑上。
|
||||
|
||||
将 `antlr-runtime_4.13.1_amd64.deb` 文件下载到 WSL 中,打开文件所在目录,执行以下命令安装:
|
||||
|
||||
```shell
|
||||
sudo apt install ./antlr-runtime_4.13.1_amd64.deb
|
||||
```
|
||||
|
||||
在这个包中,含有 Antlr 4.13.1 的动态链接库、静态链接库以及头文件,如果不装这个包,
|
||||
将导致你的程序在编译时找不到 Antlr 的头文件和动态链接库,从而编译失败。
|
||||
|
||||
使用 Archlinux 的同学可以直接使用以下命令安装运行环境:
|
||||
```shell
|
||||
pacman -S antlr4-runtime
|
||||
```
|
||||
|
||||
如有在其他环境下编程的同学(比如 Windows、Mac 和除 Debian,Arch 之外的 Linux 系统),请联系助教。
|
||||
|
||||
### 生成语法树
|
||||
|
||||
#### 使用 VScode 插件(推荐)
|
||||
|
||||
首先在 Windows 环境下安装插件(注意,不要在 WSL 环境下安装):
|
||||
|
||||

|
||||
|
||||
安装后,点击卸载旁的箭头,安装 2.3.1 版本。
|
||||
|
||||

|
||||
|
||||
安装完后重新加载。打开 `Python3.g4` 文件,右边会出现对应插件的图标,点击,等待其中的 PARSER RULES 等部分加载完毕。
|
||||
|
||||

|
||||
|
||||
接下来配置运行文件。点击左侧的运行和调试,创建 `launch.json` 文件,并写入
|
||||
|
||||
```javascript
|
||||
{
|
||||
"version": "2.3.1",
|
||||
"configurations": [
|
||||
|
||||
{
|
||||
"name": "Debug ANTLR4 grammar",
|
||||
"type": "antlr-debug",
|
||||
"request": "launch",
|
||||
"input": "./a.txt", // 输入文件(即你要运行的 Python 代码文件)
|
||||
"grammar": "./resources/Python3Parser.g4", // 语法文件
|
||||
"startRule": "file_input", // 语法入口规则,我们的公式语法入口规则是 file_input
|
||||
"printParseTree": true, // 是否 打印/可视化 parse tree
|
||||
"visualParseTree": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
最后打开要运行的文件,在左侧的运行和调试中,点击运行即可生成,如下图所示。
|
||||
|
||||

|
||||
|
||||
#### 使用 Clion 插件
|
||||
|
||||
由于本次 `.g4` 文件的特性,目前 ANTLR 插件只能支持不带 `INDENT` 和 `DEDENT` 规则的解释。
|
||||
|
||||
首先在插件市场中找到插件:
|
||||
|
||||

|
||||
|
||||
安装后,右键 `.g4` 中的 `return_stmt` 或任何不包含 `INDENT` 和 `DEDENT` 的规则,点击 `test rule`:
|
||||
|
||||

|
||||
|
||||
之后在屏幕下方的 `antlr-preview` 中,左侧是待测试的代码,右侧是依据代码生成的语法树结构图。
|
||||
|
||||
## ANTLR 是什么
|
||||
|
||||
ANTLR(全名:ANother Tool for Language Recognition)是基于 LL(\*)算法实现的语法解析器生成器(parser generator),用 Java 语言编写,使用自上而下(top-down)的递归下降 LL 剖析器方法。
|
||||
|
||||
ANTLR 可以将输入的代码转化成与之对应的**树形结构**,即语法树,以便后续程序操作。按照上面的配置操作,即可得到一份 `Python` 代码对应的语法树。
|
239
docs/grammar.md
Normal file
239
docs/grammar.md
Normal file
@ -0,0 +1,239 @@
|
||||
## Specific Python
|
||||
|
||||
### I. 程序结构
|
||||
|
||||
按照控制流逐行执行。
|
||||
|
||||
### II. 语法规则
|
||||
|
||||
#### 1. 字符集合
|
||||
|
||||
ASCII 编码,区分大小写,中文字符是**未定义**的,合法字符集如下:
|
||||
|
||||
- **标识符**(包括变量标识符、函数标识符):26 个小写英语字母,26 个大写英语字母,数码 `0` 到 `9`,下划线 `_`
|
||||
- **标准运算符**:加号 `+`,减号 `-`,乘号 `*`,浮点除 `/`,整除`//`,取模 `%`
|
||||
- **关系运算符**:大于 `>`,小于 `<`,大于等于 `>=`,小于等于 `<=`,不等于 `!=`,等于 `==`
|
||||
- **增量运算符**:加法 `+=`,减法 `-=`,乘法 `*=`,浮点除 `/=`,整除 `//=`,取模`%=`
|
||||
- **赋值运算符**:赋值 `=`
|
||||
- **下标运算符**:取下标对象 `[]`
|
||||
- **优先级运算符**:括号 `()`
|
||||
- **分隔符**:逗号 `,`
|
||||
- **特殊符号**:空格 ` `,换行符 `\n`,制表符 `\t`,注释标识符 `#`,字符串标识符 `"` 或 `'`。
|
||||
|
||||
#### 2. 关键字
|
||||
|
||||
```python
|
||||
None
|
||||
True
|
||||
False
|
||||
def
|
||||
return
|
||||
break
|
||||
continue
|
||||
if
|
||||
elif
|
||||
else
|
||||
while
|
||||
or
|
||||
and
|
||||
not
|
||||
```
|
||||
|
||||
关键字不可作为变量名或函数名。
|
||||
|
||||
#### 3. 空白字符
|
||||
|
||||
**空格**、**制表符**在源文件中可以区分词素 (Token),同时在一行的开头可以表示缩进。
|
||||
|
||||
**换行符**表示新的语句的开始。
|
||||
|
||||
缩进在 Python 用于识别代码块,相关问题将在 Parser 中使用 Antlr4 解决,无需考虑相关的实现。
|
||||
|
||||
#### 4. 注释
|
||||
|
||||
从 `#` 开始到本行结束的内容都会被作为注释。
|
||||
|
||||
#### 5. 标识符
|
||||
|
||||
标识符的第一个字符必须是英文字母,第二个字符开始可以是英文字母、数字或者下划线。
|
||||
|
||||
标识符区分大小写。长度超过 $64$ 个字符的标识符是**未定义**的。
|
||||
|
||||
#### 6. 常量
|
||||
|
||||
- 逻辑常量:`True` 为真,`False` 为假
|
||||
|
||||
- 整数常量:
|
||||
|
||||
- 整数常量以十进制表示,非十进制的表示为**未定义**行为
|
||||
- 整数常量不设负数,负数可以由正数取负号得到
|
||||
|
||||
- Python 中,整数的范围是没有限制的,此处同理,这意味着你必须实现高精度整数
|
||||
|
||||
- 首位为 $0$ 的整数常量是**未定义**的
|
||||
|
||||
- 浮点数常量:
|
||||
|
||||
- 浮点数常量以十进制表示
|
||||
- 浮点数常量不设负数,负数可以由正数取负号得到
|
||||
- 拥有前导 $0$ 的浮点数为**未定义**的,如`0001.1`
|
||||
- 含有 e 的浮点数为**未定义**的,如 `1e5`
|
||||
- 以 . 开头的浮点数为**未定义**的,如 `.123`
|
||||
|
||||
- 字符串常量
|
||||
- 字符串常量是由双引号 `"` 或单引号 `'` 括起来的字符串
|
||||
- 可以由两个字符串拼接而形成新的字符串常量。如 `"123""456"` 相当于 `"123456"`
|
||||
- 字符串中的所有字符必须是可示字符 (printable character) 或空格中的一种
|
||||
- 空值常量:`None` 用来表示变量没有指向任何值
|
||||
|
||||
### 7. 数据类型
|
||||
|
||||
- `bool`:只有 `True` 和 `False`
|
||||
|
||||
- `int`:高精度整数
|
||||
|
||||
- `float`: 与 C++ 中的 `double` 一致
|
||||
|
||||
- `str`:字符串,**不可修改!**
|
||||
|
||||
- 元组: 具体表现请参考 python (待完善)
|
||||
|
||||
### 8. 表达式
|
||||
|
||||
#### 8.1. 基础表达式
|
||||
|
||||
基础表达式包括单独出现的常量、变量、函数调用和数组访问。
|
||||
|
||||
#### 8.2. 算术表达式
|
||||
|
||||
合法的算术运算符包括:`+`, `-`, `*`, `/`, `//`, `%`,具体行为可以参照 C++,特殊行为有:
|
||||
|
||||
- 字符串运算符
|
||||
- `str + str` 表示字符串的拼接
|
||||
- `str * int` 表示字符串的重复
|
||||
- `str <= str` 表示字符串的比较,比较规则同 C++ `std::striing`,`>=`, `==`等同理
|
||||
- 除法
|
||||
- `/` 表示浮点除,即计算结果为浮点数
|
||||
- `//` 表示整除,即计算结果为整数。注意无论正负皆**向下取整**,例如 `-5 // 3 = -2`
|
||||
- `%` 表示模运算,无论模数的正负,皆定义为:`a % b = a - (a // b) * b`
|
||||
- 连续比较
|
||||
- 存在 `1<2>3` 这样连续的关系运算符,处理方法是将其拆为相邻的比较并用 `and` 连接但**每个值最多只计算一次**
|
||||
- 如 `a()<b()<c()`,等价于先翻译成 `a()<b() and b()<c()` 再进行计算, `a(),b(),c()` 最多只调用一次(因为短路运算符)
|
||||
- 逻辑运算符
|
||||
- `and` 等价于 C++ 中的 `&&`,`or` 等价于 C++ 中的 `||`, `not` 等价于 C++ 中的 `!`
|
||||
- `and` 和 `or` 为**短路**运算符
|
||||
- **隐式转化**
|
||||
- 运算时如果两个运算数类型不一致,需转化成相同类型之后再进行运算
|
||||
- 具体转化规则参考 C++
|
||||
|
||||
不包含在特殊行为和 C++ 标准中的行为均为**未定义**行为。
|
||||
|
||||
#### 8.3. 赋值表达式
|
||||
|
||||
- 语法与具体行为均可以参照 C++,并无特殊差异
|
||||
- 给一个变量赋值的意义是将这个变量指向右值,右值不管类型
|
||||
- **与标准 Python 不同,全局变量的生效范围是全部范围**(不用 global 关键字即可访问)
|
||||
- 局部变量的生效范围是在当前语句块(被缩进和取消缩进包起来的部分),具体局部变量和全局变量划分规则和 C++ 一样
|
||||
- **只有函数的定义会产生新的作用域**,作用域的产生请参考 Python
|
||||
- 如 `a=1,a=”123”,a=1.1` 这三条语句依次执行,再输出 `a`,结果是 `1.1`,允许变量遮蔽
|
||||
- 可以给多变量赋值,如 `a,b=1,2` 意思是 `a=1,b=2` 依次执行,此处或与标准 Python **不同**
|
||||
- 增量赋值即为普通算术运算的简化表达,具体行为参考 C++
|
||||
|
||||
#### 8.4. 运算符优先级
|
||||
|
||||
从低到高依次为(其中处在相同行的运算符优先级相同)
|
||||
|
||||
```python
|
||||
=
|
||||
or
|
||||
and
|
||||
not
|
||||
< > <= >= == !=
|
||||
+ -
|
||||
* / // %
|
||||
()
|
||||
```
|
||||
|
||||
### 9. 语句
|
||||
|
||||
#### 9.1. 变量定义/赋值语句
|
||||
|
||||
- 单变量赋值:`var = value`
|
||||
- 连续赋值:`var_1 = var_2 = ... = value`
|
||||
|
||||
参考[赋值表达式](####8.3. 赋值表达式)。
|
||||
|
||||
#### 9.2. 表达式语句
|
||||
|
||||
参考[表达式](###8. 表达式)
|
||||
|
||||
#### 9.3. 条件语句
|
||||
|
||||
```python
|
||||
if expression_1:
|
||||
# code block
|
||||
elif expression_2:
|
||||
# code block
|
||||
else expression_3:
|
||||
# code block
|
||||
```
|
||||
|
||||
`elif` 相当于 `else if`,`else` 可以没有。
|
||||
|
||||
#### 9.4. 循环语句
|
||||
|
||||
```python
|
||||
while expression:
|
||||
# code block
|
||||
```
|
||||
|
||||
#### 9.5. 跳转语句
|
||||
|
||||
- 包括 `return`,`break`,和 `continue`
|
||||
- 具体行为参考 C++
|
||||
|
||||
### 10. 函数
|
||||
|
||||
#### 10.1. 函数定义
|
||||
|
||||
```python
|
||||
def func(parameters):
|
||||
# code block
|
||||
```
|
||||
|
||||
- 参数列表如 `a, b, c`,变量名之间用逗号分隔,可以为空
|
||||
|
||||
- 有些变量可以有默认值,但是都必须出现在无默认值的变量后面
|
||||
- 函数可以有返回值,也可以没有返回值,无需显示声明。返回值可以为多变量,如 `return a, b`
|
||||
|
||||
#### 10.2. 函数调用
|
||||
|
||||
```python
|
||||
func(parameters)
|
||||
```
|
||||
|
||||
- 函数调用必须出现在该函数定义后。
|
||||
|
||||
- 参数有两种形式:keyword 和 positional
|
||||
|
||||
- Keyword argument 比如 `foo(a=1,b=2)` 表示传入参数 `a` 的值为 `1`,`b` 的值 `2`
|
||||
|
||||
- Positional argument 是指 `foo(1,2)` 这样按照出现顺序指代参数
|
||||
|
||||
- 若一个参数列表中同时有两种参数形式,那么 positional argument 必须出现在 keyword argument 之前。如 `foo(1,b=2)`
|
||||
|
||||
- 数据保证递归层数不超过 $2000$ 层
|
||||
|
||||
### 11. 内建函数
|
||||
|
||||
- `print`:输出,可以有任意个参数,逐个输出,中间用空格分隔。输出后换行。输出 `float` 保留 $6$ 位小数。输出字符串不要输出前后面的引号。如 `print("123",1.0)` 请输出 `123 1.000000`。
|
||||
|
||||
- `int`:将 `float` 或 `bool` 或 `str` 转成 `int`。
|
||||
|
||||
- `float`: 将 `int` 或 `bool` 或 `str` 转成 `float`。
|
||||
|
||||
- `str`: 将 `int` 或 `float` 或 `bool` 转成 `str`。
|
||||
|
||||
- `bool`: 将 `int` 或 `float` 或 `str` 转成 `bool`。对于 `str`,如果是 `""` 则为 `False`,否则为 `True`。
|
||||
|
||||
- 转型类函数都只有一个参数。
|
2
docs/implementation_details.md
Normal file
2
docs/implementation_details.md
Normal file
@ -0,0 +1,2 @@
|
||||
# 实现细节
|
||||
|
BIN
docs/plugin-market.png
Normal file
BIN
docs/plugin-market.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 679 KiB |
BIN
docs/right-click.png
Normal file
BIN
docs/right-click.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 191 KiB |
14
docs/suggestions.md
Normal file
14
docs/suggestions.md
Normal file
@ -0,0 +1,14 @@
|
||||
### 一些小建议
|
||||
|
||||
0. 有搞不明白的问题找助教啦~
|
||||
|
||||
1. git仓库中放置测试点可能使你的仓库克隆时间增长,导致评测变慢。
|
||||
推荐将本仓库下载到本地后,只在你的仓库中放置必要的文件。
|
||||
|
||||
2. 建议使用多文件编程。将整个解释器分成多个模块,分别写在不同的文件中。
|
||||
|
||||
3. 清楚调用的函数的复杂度。
|
||||
|
||||
4. 你可以先 `using int2048 = long long;` 来方便前期编写。
|
||||
|
||||
TO_BE_CONTINUED
|
BIN
docs/vscode-antlr-result.png
Normal file
BIN
docs/vscode-antlr-result.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 86 KiB |
BIN
docs/vscode-antlr.png
Normal file
BIN
docs/vscode-antlr.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
BIN
docs/vscode-install.png
Normal file
BIN
docs/vscode-install.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 40 KiB |
BIN
docs/vscode-plugin.png
Normal file
BIN
docs/vscode-plugin.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 141 KiB |
182
docs/workflow_details.md
Normal file
182
docs/workflow_details.md
Normal file
@ -0,0 +1,182 @@
|
||||
# 解释器作业的完成流程
|
||||
|
||||
## Step 1. 配置环境
|
||||
|
||||
见文档 [antlr_guide.md](antlr_guide.md)。
|
||||
|
||||
## Step 2. 阅读 "./resources/Python3Parser.g4"
|
||||
|
||||
**阅读 `.g4` 文件需要一定的正则表达式基础。**
|
||||
如果你不会正则表达式,可以参考 [正则表达式 - 维基百科](https://zh.wikipedia.org/wiki/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F)。
|
||||
|
||||
我们假设有这样的语法规则(并不是我们这次作业的一部分):
|
||||
|
||||
```
|
||||
plus: NUMBER '+' NUMBER;
|
||||
NUMBER:[0-9]+;
|
||||
ADD:'+';
|
||||
```
|
||||
|
||||
## Step 3. 阅读 "./generated/Python3Parser.h"
|
||||
|
||||
你可以看到以下代码(对应着上面的语法规则):
|
||||
|
||||
```c++
|
||||
//...............
|
||||
class PlusContext : public antlr4::ParserRuleContext {
|
||||
public:
|
||||
PlusContext(antlr4::ParserRuleContext *parent, size_t invokingState);
|
||||
virtual size_t getRuleIndex() const override;
|
||||
std::vector<antlr4::tree::TerminalNode *> NUMBER();
|
||||
antlr4::tree::TerminalNode* NUMBER(size_t i);
|
||||
antlr4::tree::TerminalNode* ADD();
|
||||
|
||||
virtual void enterRule(antlr4::tree::ParseTreeListener *listener) override;
|
||||
virtual void exitRule(antlr4::tree::ParseTreeListener *listener) override;
|
||||
|
||||
virtual std::any accept(antlr4::tree::ParseTreeVisitor *visitor) override;
|
||||
}
|
||||
//...............
|
||||
```
|
||||
|
||||
因为在上述的 `plus` 语法中,`NUMBER` 一定要出现至少一次,所以 `PlusContext` 有以下两个函数:
|
||||
|
||||
```c++
|
||||
std::vector<antlr4::tree::TerminalNode *> NUMBER();
|
||||
antlr4::tree::TerminalNode* NUMBER(size_t i);
|
||||
```
|
||||
|
||||
第一个函数返回一个 `vector`,包含了指向所有 `NUMBER` 的指针,第二个函数返回指向第 i 个 `NUMBER` 的指针,从 0 开始。
|
||||
|
||||
因为 `ADD` 只在 `plus` 中出现了一次,所以它只有以下函数,返回指向唯一的 `ADD` 的指针:
|
||||
|
||||
```c++
|
||||
antlr4::tree::TerminalNode* ADD()
|
||||
```
|
||||
|
||||
对于一个 `temrinal node`,有以下方法:
|
||||
|
||||
```c++
|
||||
//...............
|
||||
std::string toString()
|
||||
Token* TerminalNodeImpl::getSymbol()
|
||||
/*
|
||||
* for example, consider:
|
||||
* antlr4::tree::TerminalNode *it;
|
||||
* it->toString() returns the string, for example, "123456" or "a"
|
||||
* (so you need to converse (std::string)"123456" to (int)123456)
|
||||
* it->getSymbol()->getTokenIndex() returns where this word is in the whole input.
|
||||
*/
|
||||
//...............
|
||||
```
|
||||
|
||||
## Step 4. 完成 "./src/Evalvisitor.h"
|
||||
|
||||
在这一步中,所要做的就是补全相关代码:
|
||||
|
||||
```c++
|
||||
//...............
|
||||
std::any visitPlus(Python3Parser::PlusContext *ctx)
|
||||
{
|
||||
/*
|
||||
* TODO
|
||||
* the pseudo-code is:
|
||||
* return visit(ctx->NUMBER(0))+visit(ctx->NUMBER(1));
|
||||
*/
|
||||
}
|
||||
//...............
|
||||
```
|
||||
|
||||
当写:
|
||||
|
||||
```c++
|
||||
visit(ctx->NUMBER(0))
|
||||
```
|
||||
|
||||
等价于写:
|
||||
|
||||
```c++
|
||||
visitAtom(ctx->NUMBER(0))
|
||||
```
|
||||
|
||||
所以我们只需要用 `visit` 函数来访问各种结点,而不是用 `visitBalabala`,想想为什么?
|
||||
|
||||
## Step 5. 编译程序
|
||||
|
||||
输入以下代码即可:
|
||||
|
||||
```sh
|
||||
cmake -B build
|
||||
cmake --build build
|
||||
```
|
||||
|
||||
如果你不会使用 `cmake`,你可以借助于 `Clion` 来实现,如果你不知道这步如何操作,请询问助教。
|
||||
|
||||
## 神奇的 std::any
|
||||
|
||||
关于这个类,你只需要会一个方法:`std::any_cast`。
|
||||
|
||||
譬如说,如果有以下的语法规则:
|
||||
|
||||
```
|
||||
plus: atom '+' atom;
|
||||
atom: NUMBER | STRING+;
|
||||
NUMBER:[0-9]+;
|
||||
STRING:[A-Z]+;
|
||||
ADD:'+';
|
||||
```
|
||||
|
||||
在 Parser.h 中的 `Context` 长这样:
|
||||
|
||||
```c++
|
||||
//...............
|
||||
class PlusContext : public antlr4::ParserRuleContext {
|
||||
public:
|
||||
PlusContext(antlr4::ParserRuleContext *parent, size_t invokingState);
|
||||
virtual size_t getRuleIndex() const override;
|
||||
std::vector<AtomContext*> atom();
|
||||
AtomContext* atom(size_t i);
|
||||
antlr4::tree::TerminalNode* ADD()
|
||||
|
||||
virtual void enterRule(antlr4::tree::ParseTreeListener *listener) override;
|
||||
virtual void exitRule(antlr4::tree::ParseTreeListener *listener) override;
|
||||
|
||||
virtual std::any accept(antlr4::tree::ParseTreeVisitor *visitor) override;
|
||||
}
|
||||
//notice that the atom is AtomContext* type rather than a TerminalNode* type.
|
||||
//an easy way to tell the difference is: capital letter-TerminalNode; xxxContext otherwise
|
||||
//...............
|
||||
```
|
||||
|
||||
那么相应的代码就长这样:
|
||||
|
||||
```c++
|
||||
//...............
|
||||
std::any visitPlus(Python3Parser::PlusContext *ctx)
|
||||
{
|
||||
auto ret1 = visit(ctx->NUMBER());
|
||||
auto ret2 = visit(ctx->NUMBER());
|
||||
if (auto v1int = std::any_cast<int*>(ret1), v2int = std::any_cast<int*>(ret2);
|
||||
v1int && v2int)
|
||||
return *v1int + *v2int;
|
||||
else if (auto v1str = std::any_cast<string*>(ret1), v2str = std::any_cast<string*>(ret2);
|
||||
v1str && v2str)
|
||||
return *v1str + *v2str;
|
||||
else
|
||||
throw(std::string("unsupported operand type(s) for +: ") + ret1.type().name() + " + " + ret2.type().name());//no need
|
||||
}
|
||||
//...............
|
||||
```
|
||||
|
||||
我们保证测试文件的语法都是正确的,所以后两行实则是不需要的。
|
||||
|
||||
`any_cast` 模板函数可以直接将 `any` 类型转换为你想要的类型,但是如果转换失败,它会直接抛出异常。
|
||||
而在上文的代码中,我们向模板填入的类型是目标类型的引用,所以如果类型不匹配、转换失败,它会返回 `nullptr`。
|
||||
|
||||
在 OOP 课程中,你们将会学习构造函数和析构函数。所以你们最好是在理解 `std::any` 是如何构造与析构的基础上,进行编程。通过阅读[cppreference](https://zh.cppreference.com/w/cpp/utility/any)来理解。如果这对于你们来说太过困难,请求助助教。
|
||||
|
||||
https://www.cnblogs.com/mangoyuan/p/6446046.html
|
||||
|
||||
https://www.cnblogs.com/xiaoshiwang/p/9590029.html
|
||||
|
||||
搜索 "C++11 traits" 来获得更多信息。
|
Reference in New Issue
Block a user