跳到主要内容C++ 核心面试题总结:语法、内存与面向对象 | 极客日志C++算法
C++ 核心面试题总结:语法、内存与面向对象
综述由AI生成C++ 面试重点覆盖语法基础、标准库、内存管理及面向对象特性。掌握变量生命周期、指针与引用差异、智能指针原理及 RAII 机制至关重要。STL 容器如 vector 与 list 的底层实现及适用场景也是高频考点。此外,虚函数多态机制、进程线程通信方式以及常用开发工具命令同样需要熟悉。梳理了这些核心知识点,帮助开发者系统复习并应对技术面试。
极客零度2 浏览 一、C++ 语法基础
变量的使用和生命周期,声明和初始化
全局变量在程序的任何地方都能访问,通常在整个程序执行期间有效。局部变量定义在函数或代码块内部,仅在该范围内可访问。静态变量则通过 static 关键字声明。
| 各变量间的区别 | 全局变量 | 局部变量 | 静态全局 | 静态局部 |
| 声明方式 | 在函数外部声明,通常在文件的顶部或者在函数定义之外 | 在函数或代码块的开始部分声明 | 在变量声明时加上 static 关键字 | 在变量声明时加上 static 关键字 |
| 代码示例 | // 全局变量声明并初始化 int globalVar = 10; | void func() { // 局部变量声明并初始化 int localVar = 20; } | // 静态全局变量声明并初始化 static int globalVar = 10; | void func() { // 静态局部变量声明并初始化 static int localVar = 20; } |
| 初始化 | 在声明时初始化 | 建议在声明时初始化,避免未知错误 | 在声明时或者在第一次使用之前初始化 | 在声明时或者在第一次使用之前初始化 |
| 内存存储位置 | 存储在常量区(静态存储区) | 存储在栈区 | 存储在常量区(静态存储区) | 存储在常量区(静态存储区) |
| 作用域 | 整个文件都可访问 | 函数内部 | 整个文件都可访问 | 函数内部 |
| 生命周期 | 全局生命周期,会一直存在到程序结束 | 函数被调用期间 | 全局生命周期 | 具有全局生命周期,但作用域仍然受限于其声明的函数或代码块,保持其值在函数调用之间持久化 |
C++ 的命名空间的作用
命名空间主要用于避免命名冲突,将相关的函数、类等封装在一起,提高代码的组织性和可读性,支持模块化开发。
namespace MyNamespace {
int x;
void func();
}
标准命名空间中通常需要加上 std:: 前缀,例如 std::cout << "Hello, world!" << std::endl;,也可以使用 using namespace std; 来简化书写。
include " " 和 <> 的区别
#include <文件名> 是包含标准库头文件的方式,编译器会按照标准路径顺序搜索。
#include "文件名" 是包含用户自定义或者项目内部头文件的方式,优先在当前目录查找,然后才是按照标准路径顺序搜索。
指针是什么?
指针是一个用来存储变量地址的特殊数据类型。简单来说,指针变量存储的是内存地址,而不是常规的值。通过指针,我们可以直接访问和操作内存中的数据。
可以使用解引用操作符 * 来访问指针所指向的变量,使用地址运算符 & 来获取变量的地址。
什么是指针数组和数组指针
指针数组是一个数组,其中的每个元素都是指针。这些指针可以指向不同的内存地址,通常用于存储一组相同类型的指针。
数组指针是一个指针,它指向数组的首地址。它本身是一个指针,但指向的内容是一个数组对象。
指针数组常用于需要动态管理一组指针的场景,而数组指针则用于处理数组的整体,特别是在函数参数传递和多维数组的处理中比较常见。
引用是什么?
引用提供了一个变量的别名。它使用 & 符号来定义。
int num = 10;
int &ref = num;
- 引用必须在定义时初始化,并且一旦初始化后,它就不能再绑定到其他变量。
应用场景:引用可以用于函数参数,允许在函数内部直接修改传递的变量,而不是复制一份值。
指针和引用的区别
引用不能指向空值(null),而指针可以。引用在使用时不需要解引用操作(不需要 * 符号),而指针需要。引用在定义时必须初始化,而指针可以在后续指向不同的对象。
什么是函数指针和指针函数以及区别
函数指针 是指向一个函数的指针变量。它可以指向一个特定类型和签名(参数类型和返回类型)的函数。
typedef void (*FuncPtr)(int);
void myFunction(int x) {
}
int main() {
FuncPtr ptr = &myFunction;
ptr(10);
return 0;
}
指针函数 指的是返回类型为指向函数的指针的函数。换句话说,指针函数是一个返回类型为函数指针的函数。
int (*funcPtr)(int, int);
int add(int a, int b) { return a + b; }
int (*getAddFunctionPointer())(int, int) { return &add; }
int main() {
funcPtr = getAddFunctionPointer();
int result = funcPtr(3, 4);
return 0;
}
区别:函数指针是指向函数的指针变量,而指针函数是一个返回类型为函数指针的函数。函数指针在声明时需要指定其指向的函数的签名,而指针函数的返回类型是一个函数指针类型。
什么是常量指针和指针常量以及区别
常量指针:指的是指针本身的值(即指向的地址)可以改变,但指针指向的对象的值不能通过这个指针进行修改。
int x = 10;
int* const ptr = &x;
*ptr = 20;
指针常量:指的是指针本身的值(即指向的地址)不能改变,但可以通过这个指针修改指向对象的值。
int x = 10;
const int* ptr = &x;
x = 20;
int y = 50;
ptr = &y;
int const * p;
区别:常量指针强调指针本身是常量,指向对象的值可以改变;指针常量强调指针所指向的对象是常量,指针的指向可以改变。简单记忆:距 const 修饰右边最近的值是常量,不能被修改。
智能指针的本质是什么以及实现原理
智能指针是一种用于管理动态分配内存和自动释放资源的工具,其本质是利用了 RAII(资源获取即初始化)的设计模式。即在对象初始化的时候获取资源(比如动态分配的内存),在对象析构的时候自动释放资源。
它提供了一个封装了指针的对象,通过其析构函数来确保在对象生命周期结束时,所管理的资源能够被正确释放,避免内存泄漏和资源泄漏。
实现原理:智能指针实现是引入计数引用机制。智能指针对象本身持有一个指向动态分配资源的指针,并维护一个引用计数。每当有一个新的智能指针指向同一块内存时,引用计数增加;当智能指针超出作用域或被显式销毁时,引用计数减少。当引用计数为零时,智能指针负责释放其管理的资源。
- std::shared_ptr:允许多个指针共享同一块资源,通过引用计数来管理资源的生命周期,适用于多个所有者的情况。
- std::unique_ptr:独占所指向的对象,保证在任何时刻只有一个指针可以指向该对象,移动语义保证资源的所有权可以传递但不共享。
- std::weak_ptr:用于协助 std::shared_ptr,不会增加引用计数,避免循环引用问题,通常用于解决 shared_ptr 的环状引用问题。
智能指针通过结合 RAII 设计模式和引用计数技术,提供了一种高效、安全、方便的动态内存管理机制,是现代 C++ 开发中推荐使用的重要工具之一。
weak_ptr 是否有计数方式,在那分配空间?
std::weak_ptr 是一种观测型智能指针,用于解决 std::shared_ptr 的循环引用问题,它本身并不进行内存空间分配或引用计数,而是依赖于 std::shared_ptr 来管理资源的生命周期。
std::weak_ptr 可以通过 std::lock() 成员函数获取一个 std::shared_ptr。
调用 std::lock() 成员函数可以获取一个 std::shared_ptr 对象,该对象指向 std::weak_ptr 所观测的对象(如果它仍然存在)。如果 std::weak_ptr 过期(即其管理的对象已经被释放),std::lock() 返回一个空的 std::shared_ptr。
#include <iostream>
#include <memory>
int main() {
std::shared_ptr<int> shared = std::make_shared<int>(42);
std::weak_ptr<int> weak = shared;
std::shared_ptr<int> locked = weak.lock();
if (locked) {
std::cout << "Value pointed by shared_ptr: " << *locked << std::endl;
} else {
std::cout << "The shared_ptr is no longer available." << std::endl;
}
return 0;
}
类型强制转换有哪几种?
- 静态转换(static_cast)
- 常量转换(const_cast)
- 动态转换(dynamic_cast)
- 重新解释转换(reinterpret_cast)
函数参数传递时,指针、引用以及值传递有什么区别?
- 值传递:复制实参的值给形参,函数内部操作的是副本,不影响原始值。
- 引用传递:形参是原始实参的别名,函数内部操作影响原始值。
- 指针传递:形参是原始实参的地址,函数内部通过解引用操作影响原始值。
进程间的通信方式有那些?
管道、命名管道、消息队列、共享内存、信号量、套接字。
线程间的通信方式有那些?
简单谈谈对线程的理解,线程间共享资源时该怎么处理?
线程(Thread)是操作系统调度的基本单位,是进程内的一条执行路径,每个进程至少包含一个线程。
线程间共享资源时,需要注意数据的一致性和并发访问的安全性,避免数据竞争和不可预料的结果。一般会采用互斥锁来避免多线程访问导致的资源调用错误。
常用关键字含义及其使用
- static:关键字在不同上下文中有不同含义,主要用于静态变量(属于类或函数的静态存储区域,生命周期延续到程序结束)、静态函数(属于类的静态成员函数,与类的实例无关,可以直接通过类名访问)。
- const:用来声明常量。包括常量变量(一旦初始化就不能修改的变量)、常量成员函数(在成员函数声明或定义中的
const 关键字表示该函数不会修改对象的状态)。
- sizeof:是一个操作符,用于计算数据类型或变量的字节大小。例如
sizeof(int) 返回 int 类型的字节数,在大多数系统中是 4 个字节。
- final:用于指示某个类、虚函数或者虚继承在派生时不可被继承或重写。如
final class MyClass final { ... }; 表示该类不能被继承。
- override:是 C++11 引入的关键字,用于显式指示函数是在派生类中覆盖了基类中的虚函数。在派生类中重写基类的虚函数时,可以使用
override 关键字,以确保正确性和可读性。
- virtual:用于声明虚函数,即在基类中声明的函数,可以在派生类中被重写(override)。虚函数支持动态绑定,即在运行时根据对象的实际类型决定调用哪个版本的函数。
- volatile:用来声明一个变量是易变的,可能会被意外修改,通常用于多线程或者硬件相关的编程。如
volatile int sensorValue;,告诉编译器不要对 sensorValue 进行优化,因为它可能会在程序控制之外被改变。
- explicit:用来声明构造函数为显式构造函数,防止隐式类型转换。例如
explicit MyClass(int value);,禁止编译器根据上下文进行隐式转换,必须显式调用构造函数。
- inline:声明可以用于函数定义或者成员函数定义,建议编译器将函数的代码插入到每个调用点处,而不是通过函数调用的方式进行调用。
- extern C 的作用:用于告诉编译器按照 C 语言的方式进行链接,主要用于解决 C++ 代码和 C 代码混合编译的问题。
- define 和 typedef 的区别:define 用于定义宏,预处理阶段替换源代码中的标识符;typedef 用于给数据类型取别名。后续可以直接使用别名进行声明变量。
const 可以修饰那些内容
变量、指针和引用、函数、类对象或类指针、函数返回值。
class 和 struct 的区别?有了 class 为啥仍还保留 struct?
class 和 struct 用来定义类和结构体,主要区别在于默认的访问权限不同(class 默认私有,struct 默认公有)。
它们的区别主要体现在成员默认的访问权限和继承方式上:
- 在 class 中,默认的成员访问权限是 private,在 struct 中,默认的成员访问权限是 public。
- 当没有显式指定继承方式时,class 默认是 private 继承,而 struct 默认是 public 继承。
虽然 class 和 struct 在语法上有一些细微差别,但其实它们提供了类似的功能。保留 struct 主要是为了提供语法上的灵活性和向后兼容性。
二、标准库
什么是 STL?
STL 是 C++ 标准模板库的缩写。它是 C++ 标准库的一部分,提供了一组通用的模板类和函数,实现了常用的数据结构和算法,以及一些函数对象和迭代器概念,使得 C++ 程序员可以更加高效地进行编程。
- 容器:如 vector(动态数组)、deque(双端队列)、list(双向链表)、set(集合)、map(映射)等,这些容器提供了高效的数据存储和访问方式,各自适用于不同的需求和使用场景。
- 算法:如排序、搜索、合并等,这些算法独立于任何特定的容器类型,可以利用这些算法直接对容器进行操作。
- 迭代器:提供了一种统一的方式来遍历容器中的元素。
- 函数对象:即重载了函数调用操作符
() 的对象。比如比较、排序等。
- 适配器:用于修改容器的接口或行为,使其能够满足特定的需求。
数组和 vector 的区别?
- 大小:数组的大小在创建时是固定的,而 vector 的大小是动态可变的。
- 内存分配:数组的内存分配在栈上(静态数组)或堆上(动态数组),而 vector 的内存总是动态分配的,通常在堆上。
- 功能:vector 提供了许多内置的操作,如动态扩展、插入和删除等,而数组只能直接访问元素,不支持动态调整大小。
- 性能:vector 在需要扩展时可能会重新分配内存,而数组的内存分配则不会改变。
list 和 vector 的主要区别是什么?
- 存储方式:list 是一个双向链表,而 vector 是一个动态数组。
- 访问时间:list 不支持常数时间的随机访问,vector 支持常数时间的随机访问。
- 插入和删除:在 list 中,在任意位置插入或删除元素。而在 vector 中,不太适合在中间插入或删除元素。
在什么情况下使用 list 而不是 vector?
当你需要在容器的中间频繁插入和删除元素,而不是在末尾插入或删除时,list 更适合。当你不需要随机访问元素,而是需要频繁进行插入和删除操作时,list 是更好的选择,因为它的这些操作在常数时间内完成。
set 的底层数据结构是什么?
set 通常使用红黑树或其他自平衡的二叉搜索树实现。它确保所有元素是唯一的,并且保持排序状态,因此插入、删除和查找操作的时间复杂度是对数时间 (O(log n))。
set 和 multiset 有什么不同?
set 中的元素是唯一的,不能有重复的元素。multiset 允许有重复的元素。
map 和 unordered_map 的主要区别是什么?
- 底层数据结构:map 通常使用红黑树或其他自平衡的二叉搜索树实现,而 unordered_map 使用哈希表实现。
- 访问时间:map 提供对数时间 (O(log n)) 的查找、插入和删除操作,保持元素的排序。unordered_map 提供平均常数时间 (O(1)) 的查找、插入和删除操作,但不保持元素的排序。
- 排序:map 中的元素是按照键的顺序排序的,而 unordered_map 中的元素没有特定的顺序。
三、内存
RAII 是什么?
RAII 的核心思想是:资源的获取(如内存、文件句柄、网络连接等)与对象的初始化绑定在一起。具体来说,资源在对象的构造函数中获取,并在对象的析构函数中释放。
使用 RAII 的原因以及使用方法?
- 自动管理资源:利用对象的生命周期自动管理资源,避免忘记释放资源。
- 异常安全:即使发生异常,RAII 也能确保资源被正确释放,防止资源泄漏。
- 代码简洁:通过封装资源管理,减少代码重复和复杂性。
- 定义资源管理类:创建一个类,负责资源的分配和释放。
- 在构造函数中分配资源:例如,分配内存、打开文件等。
- 在析构函数中释放资源:确保资源在对象生命周期结束时得到释放。
new 和 malloc 的区别?delete 和 free 的区别?
- new:是 C++ 操作符,用于动态分配内存并调用构造函数。它会返回指向新分配内存的指针,并且可以用 delete 释放内存。new 会调用对象的构造函数初始化对象。
- malloc:是 C 标准库函数,用于动态分配内存,但不调用构造函数。它返回一个
void* 指针,需要强制转换为所需类型的指针。使用 malloc 分配的内存需要用 free 释放。
- delete:用于释放 new 分配的内存,并调用析构函数。对于数组使用 delete[]。
- free:用于释放 malloc 分配的内存。free 不会调用析构函数。
int* p = new int(10);
delete p;
int* q = (int*)malloc(sizeof(int));
free(q);
C++ 的内存框架分布情况
- 栈(Stack):用于存储局部变量和函数调用信息。栈内存分配速度快,但空间有限。
- 堆(Heap):用于动态分配内存。程序员可以控制分配和释放内存,但需要显式管理。内存的生命周期由程序员控制。
- 静态存储区(Static Storage Area):用于存储静态变量和全局变量。这些变量在程序运行期间一直存在。
- 常量区(Constant Data Area):用于存储常量数据,如字符串常量。
堆和栈的区别?
| 栈 | 堆 |
| 分配方式 | 自动分配和释放 | 手动分配和释放(使用 new/malloc 和 delete/free)。 |
| 生命周期 | 函数调用时分配,函数返回时释放。 | 由程序员控制。 |
| 大小 | 通常较小(受限于栈大小)。 | 通常较大(受限于系统可用内存)。 |
| 速度 | 较快。 | 较慢(需要管理和合并空闲块)。 |
| 用途 | 局部变量、函数调用信息。 | 动态内存分配。 |
什么是内存对齐,内存对齐有什么好处?
内存对齐 是将数据结构的内存地址按某个特定的对齐边界(通常是数据类型的大小)进行排列。对齐可以提高内存访问效率,因为许多处理器在对齐的数据上访问更快。
- 提高性能:对齐的数据结构能更有效地利用缓存和减少内存访问时间。
- 防止错误:某些硬件架构要求数据必须对齐,否则会引发硬件异常或性能下降。
什么是内存泄漏?写代码时应该怎么防止内存泄漏。
内存泄漏 是指程序在动态分配内存后,没有释放这些内存,导致系统资源浪费并最终可能导致程序崩溃或变慢。
- 使用 RAII:通过对象的生命周期自动管理资源。
- 智能指针:使用
std::unique_ptr 和 std::shared_ptr 管理动态内存,避免手动释放内存。
- 内存分析工具:使用工具如 Valgrind、AddressSanitizer 等检测和分析内存泄漏。
- 良好的编码实践:确保每个 new 都有对应的 delete,每个 malloc 都有对应的 free。
说说什么是深拷贝和浅拷贝?
- 定义:复制对象时,只复制对象的指针值,而不复制指针指向的内容。结果是原始对象和拷贝对象共享相同的资源。
- 问题:修改一个对象的资源会影响到另一个对象,可能导致资源冲突和错误。
- 定义:复制对象时,创建对象指针指向的资源的完整副本。这样,原始对象和拷贝对象各自拥有自己的资源。
- 好处:避免共享资源带来的问题,保证对象的独立性。
四、类相关
讲讲 C++ 的三大特性(封装、继承、多态)
- 封装:将数据(成员变量)和对数据的操作(成员函数)捆绑在一起,并对外部隐藏数据的具体实现。
- 继承:通过继承,子类可以继承父类的属性和行为,从而实现代码的重用和扩展。
- 多态:允许不同的对象以相同的接口进行操作,即同一操作可以作用于不同的对象上,表现出不同的行为。
多态是怎么实现的?
多态的实现主要依赖于虚函数和动态绑定。多态允许程序在运行时决定调用哪个具体的函数实现。
运行时多态:通过使用虚函数实现,允许基类指针或引用调用派生类中的重写函数。这是通过虚函数表(vtable)和虚指针(vptr)机制实现的。
什么是虚函数和纯虚函数?以及区别?
- 定义:在基类中声明为
virtual 的成员函数,允许派生类重写(override)该函数。虚函数实现运行时多态。
- 作用:通过基类指针或引用调用虚函数时,实际调用的是派生类中的重写函数。
- 定义:在基类中声明为
virtual 并且等于 0 的虚函数,例如 virtual void func() = 0;。
- 作用:定义接口,要求所有派生类必须实现纯虚函数。使得基类成为抽象类,不能实例化。
虚函数是怎么实现的?虚函数在那个阶段生成?
虚函数表(vtable):每个类(如果包含虚函数)会有一个虚函数表,它是一个指向虚函数实现的指针数组。每个对象会有一个指向虚函数表的指针(vptr)。
动态绑定:通过 vptr 指针,运行时可以查找虚函数表中的具体函数地址,决定调用哪个函数实现。
- 编译阶段:编译器生成虚函数表和虚指针。虚函数表在类定义时生成,虚指针在对象实例化时设置。
构造函数和析构函数的作用?
- 作用:在创建对象时初始化对象的状态。构造函数可以用于分配资源、初始化数据成员等。
- 特性:构造函数与类名相同,无返回值,可以被重载。
- 作用:在对象生命周期结束时清理对象的资源。析构函数负责释放构造函数分配的资源,如内存、文件句柄等。
- 特性:析构函数的名字前加
~,无返回值,不能被重载。
构造函数和析构函数那个可以被声明为虚函数,为什么?
析构函数可以被声明为虚函数:如果基类的析构函数是虚函数,确保派生类的析构函数在基类指针或引用被销毁时正确调用。防止资源泄漏和不完全析构。
构造函数不能被声明为虚函数:构造函数在对象创建时调用,此时对象还未完全构造,无法设置虚函数表,因此构造函数不能是虚函数。
构造函数和析构函数那个可以被重载,为什么?
析构函数不可以被重载:每个类只能有一个析构函数,负责对象生命周期结束时的清理操作。如果允许重载,会导致析构时不确定性。
构造函数可以被重载:允许创建对象时通过不同的参数进行初始化。通过重载构造函数,可以提供多种初始化方式。
谈谈什么是重载、重写、隐藏?
- 重载:同一作用域内多个函数名相同但参数列表不同。
- 覆盖:派生类重写基类的虚函数,提供特定实现。
- 重定义:派生类中定义了与基类同名但参数列表不同的函数,导致基类函数不可见。
this 指针,为什么会存在 this 指针?
定义:this 指针是指向当前对象的指针。在类的成员函数内部,this 指针指向调用该函数的对象。
- 访问对象成员:允许成员函数访问对象的属性和其他成员函数。
- 区分成员变量和参数:在成员函数中,
this 指针可以用来区分同名的成员变量和函数参数。
- 实现链式调用:可以返回
*this 实现链式调用。
五、其他必备
什么是 git? 以及 git 常用命令
Git 是一个分布式版本控制系统,用于跟踪文件的更改,特别是源代码。它允许多人协作开发,通过分支和合并功能管理项目的不同版本和特性。
git clone [url]:克隆远程仓库到本地。
git add [file]:将文件添加到暂存区。
git commit -m "message":提交更改到本地仓库,-m 用于添加提交信息。
git status:查看工作目录和暂存区的状态。
git diff:查看文件的更改内容。
git remote add [name] [url]:添加远程仓库。
git fetch [remote]:从远程仓库获取最新的更改,但不合并。
git pull:从远程仓库获取并合并更改。
git push:将本地提交推送到远程仓库。
git log:查看提交历史。
git revert [commit]:撤销指定提交,但保留历史记录。
git reset [commit]:重置当前分支到指定提交。
什么是 svn? git 和 svn 的区别
SVN 是一个集中式版本控制系统,用于跟踪文件的更改和管理版本。与 Git 不同,SVN 采用集中式存储模式,所有版本历史都存储在中央服务器上。
git 一般用于存放代码,svn 一般用于存放文档等。
Linux 系统下常用的命令会那些,举例?
ls:列出目录内容。
cd [dir]:切换目录。
pwd:显示当前工作目录。
cp [source] [destination]:复制文件或目录。
mv [source] [destination]:移动文件或目录。
rm [file]:删除文件,rm -r [dir] 删除目录及其内容。
cat [file]:显示文件内容。
vim [file]:使用 Vim 编辑器编辑文件。
chmod [permissions] [file]:改变文件权限。
chown [owner]:[group] [file]:改变文件所有者和组。
ps:显示当前进程。
top:实时显示系统进程和资源使用情况。
df -h:显示文件系统的磁盘使用情况。
du -sh [dir]:显示目录的大小。
kill [pid]:结束进程,pid 是进程 ID。
ping [host]:测试网络连接。
ifconfig 或 ip a:显示网络接口信息。
netstat:显示网络状态和端口使用情况。
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- Gemini 图片去水印
基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online