跳到主要内容
C++ 模板编程基础:泛型编程入门与实践 | 极客日志
C++ 算法
C++ 模板编程基础:泛型编程入门与实践 C++ 模板编程涵盖函数模板与类模板的核心语法、实例化机制及特化技术。文章通过加法函数、容器类及链表实战案例,演示了泛型编程的代码复用方法与类型安全优势。重点解析了类型推导规则、编译期常量限制、声明定义分离导致的链接错误等常见问题,并结合 STL 设计思想总结模板在通用软件开发中的关键作用。
监控大屏 发布于 2026/2/13 更新于 2026/4/23 2 浏览C++ 模板编程基础:泛型编程入门与实践
一、学习目标与重点
掌握模板的核心概念、分类(函数模板、类模板)及基本语法
理解泛型编程的思想,能够独立编写函数模板和类模板
掌握模板的实例化、特化、偏特化等关键技术
解决模板使用中的常见问题(类型推导失败、编译错误等)
结合实际场景运用模板提升代码复用性和灵活性
了解模板与 STL 的关联,为后续 STL 学习奠定基础
核心重点:模板的语法规则、类型参数与非类型参数的使用、模板特化的应用场景、泛型编程的核心价值
二、模板与泛型编程概述
2.1 什么是泛型编程
泛型编程(Generic Programming)是一种代码复用技术,核心思想是'编写与类型无关的通用代码,在使用时再指定具体类型',实现'一次编写,多次复用'。
生活中的泛型类比:
快递盒:同一个快递盒(通用容器)可装手机、书籍、衣物(不同类型数据),无需为每种物品单独设计盒子
模板工具:如 3D 打印机模板,同一模板可打印不同材质(塑料、金属)的同一形状零件
2.2 为什么需要模板
在 C++ 中,若未使用模板,针对不同类型的相同逻辑需重复编写代码,导致冗余且难以维护:
int add_int (int a, int b) {
return a + b;
}
double add_double (double a, double b) {
return a + b;
}
float add_float (float a, float b) {
return a + b;
}
模板的价值:用一套代码适配所有兼容逻辑的类型,减少冗余、提升可维护性,同时保证类型安全(编译时类型检查)。
2.3 模板的分类
C++ 模板主要分为两类:
函数模板:用于创建通用函数,支持不同类型的参数输入
类模板:用于创建通用类(如容器、算法类),支持不同类型的成员变量和成员函数参数
核心优势:模板是 C++ 泛型编程的基础,STL(标准模板库)的容器(vector、map)、算法(sort、find)均基于模板实现。
三、函数模板:通用函数的实现
3.1 函数模板的基本语法
函数模板的声明需使用 template 关键字,指定类型参数(或非类型参数),语法格式如下:
< T>
返回值类型 函数名(T 参数 , T 参数 ,...){
}
template
typename
1
2
template <typename T>:模板声明,typename 表示'后面的标识符是类型参数',T 是类型占位符(可自定义名称,如 Type、ElemType)
函数参数列表中使用 T 作为类型,表明参数类型由调用时指定或推导
函数体逻辑需与类型无关(如使用 + 运算符需确保传入类型支持该运算符)
3.2 函数模板的定义与调用 #include <iostream>
using namespace std;
template <typename T>
T add (T a, T b) {
cout << "模板函数调用,类型为:" << typeid (T).name () << endl;
return a + b;
}
int main () {
int num1 = add <int >(10 , 20 );
cout << "int 类型加法:10 + 20 = " << num1 << endl;
double num2 = add <double >(3.14 , 2.86 );
cout << "double 类型加法:3.14 + 2.86 = " << num2 << endl;
float num3 = add (5.5f , 3.5f );
cout << "float 类型加法:5.5 + 3.5 = " << num3 << endl;
string str1 = "Hello, " , str2 = "C++!" ;
string str3 = add (str1, str2);
cout << "string 类型加法:" << str3 << endl;
return 0 ;
}
模板函数调用,类型为:int int 类型加法:10 + 20 = 30
模板函数调用,类型为:double double 类型加法:3.14 + 2.86 = 6
模板函数调用,类型为:float float 类型加法:5.5 + 3.5 = 9
模板函数调用,类型为:std ::basic_string<char ,struct std ::char_traits<char >,class std ::allocator<char > > string 类型加法:Hello, C++!
3.3 函数模板的类型推导规则 编译器会根据实参类型自动推导模板的类型参数 T,推导规则如下:
实参类型必须一致(若函数参数均为 T 类型),否则推导失败
引用/const 修饰会被保留或忽略(需注意类型匹配)
数组、函数会退化为指针类型(除非显式指定为引用)
template <typename T>
void print_type (T param) {
cout << "参数类型:" << typeid (T).name () << endl;
}
int main () {
int a = 10 ;
const int b = 20 ;
int & c = a;
int arr[5 ] = {1 , 2 , 3 , 4 , 5 };
void func (int ) {}
print_type (a);
print_type (b);
print_type (c);
print_type (arr);
print_type (func);
template <typename T>
void print_ref_type (T& param) {}
print_ref_type (b);
print_ref_type (c);
return 0 ;
}
警告:若实参类型不一致且未显式指定类型,会导致编译错误:
add (10 , 3.14 );
add <double >(10 , 3.14 );
3.4 函数模板的重载 函数模板支持重载,可与普通函数或其他函数模板构成重载关系,调用时遵循'最匹配原则':
普通函数优先于模板函数(若参数完全匹配)
模板函数的显式特化优先于通用模板
更具体的模板(如多参数模板)优先于更通用的模板
#include <iostream>
using namespace std;
int add (int a, int b) {
cout << "普通函数(int)调用:" ;
return a + b;
}
template <typename T> T add (T a, T b) {
cout << "模板函数(" << typeid (T).name () << ")调用:" ;
return a + b;
}
template <typename T1, typename T2>
auto add (T1 a, T2 b) -> decltype (a + b) {
cout << "模板函数(" << typeid (T1).name () << "," << typeid (T2).name () << ")调用:" ;
return a + b;
}
int main () {
cout << add (10 , 20 ) << endl;
cout << add (3.14 , 2.86 ) << endl;
cout << add (10 , 3.14 ) << endl;
return 0 ;
}
普通函数(int )调用:30
模板函数(double )调用:6
模板函数(int ,double )调用:13.14
3.5 函数模板的特化 当通用模板无法满足特定类型的需求(如特殊逻辑、运算符不支持)时,可对模板进行特化(Specialization),为特定类型提供专属实现。
特化的语法格式: template <>
返回值类型 函数名<特化类型>(特化类型 参数 1 , 特化类型 参数 2 ,...){
}
示例:函数模板特化(处理 string 类型的特殊加法)
#include <iostream>
#include <string>
using namespace std;
template <typename T> T add (T a, T b) {
cout << "通用模板:" ;
return a + b;
}
template <> string add <string>(string a, string b) {
cout << "string 特化模板:" ;
return a + " " + b;
}
int main () {
cout << add (10 , 20 ) << endl;
cout << add (3.14 , 2.86 ) << endl;
cout << add (string ("Hello" ), string ("World" )) << endl;
return 0 ;
}
通用模板:30
通用模板:6
string 特化模板:Hello World
特化模板必须与通用模板在同一作用域,且函数名、参数列表必须与通用模板一致
特化模板的返回值类型需与通用模板兼容
特化后,调用该类型时优先使用特化版本
四、类模板:通用类的实现
4.1 类模板的基本语法 类模板用于创建通用类,支持不同类型的成员变量和成员函数参数,语法格式如下:
template <typename T>
class 类名 {
public :
T 成员变量;
类名 (T 参数): 成员变量 (参数) {}
T get_value () const {
return 成员变量;
}
void set_value (T value) {
成员变量 = value;
}
};
类模板声明需在 class 关键字前加 template <模板参数列表>
类内部可使用类型参数 T 定义成员变量、成员函数参数或返回值
类模板的成员函数若在类外定义,需再次声明模板参数列表
4.2 类模板的定义与实例化 类模板不能直接使用,需实例化(指定具体类型)后才能创建对象,实例化分为显式实例化和隐式实例化。
#include <iostream>
using namespace std;
template <typename T>
class Container {
private :
T data;
public :
Container (T value): data (value) {}
T get_data () const {
return data;
}
void set_data (T value) ;
};
template <typename T>
void Container<T>::set_data (T value) {
data = value;
}
int main () {
Container<int > int_container (100 ) ;
cout << "int 容器初始值:" << int_container.get_data () << endl;
int_container.set_data (200 );
cout << "int 容器修改后:" << int_container.get_data () << endl;
Container<double > double_container (3.14 ) ;
cout << "double 容器值:" << double_container.get_data () << endl;
Container<string> str_container ("Hello C++" ) ;
cout << "string 容器值:" << str_container.get_data () << endl;
return 0 ;
}
int 容器初始值:100 int 容器修改后:200
double 容器值:3.14
string 容器值:Hello C++
4.3 类模板的多参数模板 类模板支持多个类型参数(或非类型参数),参数之间用逗号分隔:
template <typename T, typename Alloc = void >
class MyVector {
};
template <typename T, int N>
class Array {
private :
T data[N];
public :
int size () const {
return N;
}
T& operator [](int index) {
return data[index];
}
};
#include <iostream>
using namespace std;
template <typename T, int N>
class FixedArray {
private :
T data[N];
public :
FixedArray () {
for (int i = 0 ; i < N; ++i) {
data[i] = T ();
}
}
void set (int index, T value) {
if (index >= 0 && index < N) {
data[index] = value;
}
}
T get (int index) const {
if (index >= 0 && index < N) {
return data[index];
}
return T ();
}
int size () const {
return N;
}
void print () const {
for (int i = 0 ; i < N; ++i) {
cout << data[i] << " " ;
}
cout << endl;
}
};
int main () {
FixedArray<int , 5 > int_arr;
int_arr.set (0 , 10 );
int_arr.set (1 , 20 );
int_arr.set (2 , 30 );
cout << "int 数组(大小" << int_arr.size () << "):" ;
int_arr.print ();
FixedArray<double , 3 > double_arr;
double_arr.set (0 , 1.1 );
double_arr.set (1 , 2.2 );
double_arr.set (2 , 3.3 );
cout << "double 数组(大小" << double_arr.size () << "):" ;
double_arr.print ();
FixedArray<string, 4 > str_arr;
str_arr.set (0 , "Apple" );
str_arr.set (1 , "Banana" );
cout << "string 数组(大小" << str_arr.size () << "):" ;
str_arr.print ();
return 0 ;
}
int 数组(大小 5 ):10 20 30 0 0
double 数组(大小 3 ):1.1 2.2 3.3
string 数组(大小 4 ):Apple Banana
非类型参数必须是编译期可确定的常量(如字面量、const 变量、enum 值)
支持的类型:整数类型(int、long)、枚举类型、指针类型、引用类型
不支持浮点数(double、float)、类类型(如 string)作为非类型参数
4.4 类模板的特化与偏特化 类模板同样支持特化(为特定类型提供专属实现),且支持偏特化(为部分模板参数指定类型,保留其他参数的通用性)。
4.4.1 类模板的全特化 template <>
class 类名<特化类型>{
};
#include <iostream>
#include <string>
using namespace std;
template <typename T>
class Printer {
public :
void print (T value) {
cout << "通用模板 - 类型:" << typeid (T).name () << ",值:" << value << endl;
}
};
template <>
class Printer <string> {
public :
void print (string value) {
cout << "string 特化模板 - 字符串:\"" << value << "\"" << endl;
}
};
template <>
class Printer <int > {
public :
void print (int value) {
cout << "int 特化模板 - 整数:" << value << "(单位:个)" << endl;
}
};
int main () {
Printer<double > double_printer;
double_printer.print (3.14 );
Printer<string> str_printer;
str_printer.print ("Hello C++" );
Printer<int > int_printer;
int_printer.print (100 );
return 0 ;
}
通用模板 - 类型:double ,值:3.14
string 特化模板 - 字符串:"Hello C++"
int 特化模板 - 整数:100 (单位:个)
4.4.2 类模板的偏特化 偏特化是为部分模板参数指定类型,保留其他参数的通用性,适用于多参数模板。语法格式:
template <保留的模板参数>
class 类名<保留参数,指定的参数>{
};
#include <iostream>
using namespace std;
template <typename T, typename U>
class Pair {
public :
Pair (T first, U second): first_val (first), second_val (second) {}
void display () {
cout << "通用模板 - 第一个值(" << typeid (T).name () << "):" << first_val << ",第二个值(" << typeid (U).name () << "):" << second_val << endl;
}
private :
T first_val;
U second_val;
};
template <typename T>
class Pair <T, int > {
public :
Pair (T first, int second): first_val (first), second_val (second) {}
void display () {
cout << "偏特化(U=int) - 第一个值:" << first_val << ",第二个值(整数):" << second_val << "(已乘以 2:" << second_val * 2 << ")" << endl;
}
private :
T first_val;
int second_val;
};
template <typename T, typename U>
class Pair <T*, U*> {
public :
Pair (T* first, U* second): first_ptr (first), second_ptr (second) {}
void display () {
cout << "偏特化(T*+U*) - 第一个指针地址:" << first_ptr << ",值:" << *first_ptr << ";第二个指针地址:" << second_ptr << ",值:" << *second_ptr << endl;
}
private :
T* first_ptr;
U* second_ptr;
};
int main () {
Pair<string, double > p1 ("PI" , 3.14 ) ;
p1. display ();
Pair<string, int > p2 ("计数" , 5 ) ;
p2. display ();
int a = 10 ;
double b = 2.5 ;
Pair<int *, double *> p3 (&a, &b) ;
p3. display ();
return 0 ;
}
通用模板 - 第一个值(std ::basic_string<char ,struct std : :char_traits<char >,class std : :allocator<char > >):PI,第二个值(double ):3.14
偏特化(U=int ) - 第一个值:计数,第二个值(整数):5 (已乘以 2 :10 )
偏特化(T*+U*) - 第一个指针地址:0x7ffee4b7e7ac ,值:10 ;第二个指针地址:0x7ffee4b7e7a0 ,值:2.5
偏特化不能改变模板参数的数量,只能为部分参数指定具体类型
偏特化版本的模板参数列表需与通用模板兼容
若存在多个偏特化版本,编译器会选择最匹配的版本
五、模板的编译机制与常见问题
5.1 模板的编译机制 C++ 模板采用'实例化时编译'(Compile on Instantiation)机制,核心特点:
模板定义本身不会生成可执行代码,仅当实例化(指定具体类型)时,编译器才会生成对应类型的函数/类代码
模板的声明和定义需在同一翻译单元(.cpp 文件)中可见,否则会导致链接错误('未定义的引用')
编译模板定义文件时,编译器仅检查模板语法是否正确(如括号匹配、关键字使用),不生成代码
当其他文件调用模板(如 add<int>(10,20)),编译器会生成 int 类型的 add 函数代码
若模板定义在.h 文件中,包含该.h 文件的.cpp 文件均可实例化模板;若模板定义在.cpp 文件中,需显式实例化才能被其他文件使用
template <typename T> T add (T a, T b) ;
template <typename T> T add (T a, T b) {
return a + b;
}
#include "add.h"
int main () {
add <int >(10 , 20 );
}
将模板的声明和定义放在同一.h 文件中(推荐,简单高效)
在模板定义文件中显式实例化所需类型:
return a + b;
}
template int add <int >(int , int );
template double add <double >(double , double );
5.2 模板使用的常见错误与规避
错误 1:类型推导失败 原因:实参类型不一致、模板参数无法从实参推导、隐式转换不支持。
规避方案:
确保实参类型一致,或显式指定模板类型参数
避免依赖隐式转换,手动转换实参类型
add (10 , 3.14 );
add <double >(10 , 3.14 );
add (static_cast <double >(10 ), 3.14 );
错误 2:模板参数不支持特定操作 原因:传入的类型不支持模板中的运算符或函数(如对自定义类使用 + 运算符)。
规避方案:
为自定义类重载所需运算符
使用模板特化为该类型提供专属实现
在模板中添加类型约束(C++20 后支持 concept)
class Point {
public :
int x, y;
Point (int x = 0 , int y = 0 ): x (x), y (y) {}
Point operator +(const Point& other) {
return Point (x + other.x, y + other.y);
}
};
Point p1 (1 , 2 ) , p2 (3 , 4 ) ;
Point p3 = add (p1, p2);
错误 3:非类型参数不是编译期常量 原因:非类型参数使用了运行时才能确定的值(如普通变量)。
规避方案:
非类型参数使用字面量、const 变量、enum 值等编译期常量
若需运行时确定大小,改用动态内存分配(如 vector)
int n = 5 ;
FixedArray<int , n> arr;
const int N = 5 ;
FixedArray<int , N> arr;
错误 4:模板特化与通用模板不匹配 原因:特化模板的函数名、参数列表与通用模板不一致。
规避方案:
特化模板的参数列表、返回值类型需与通用模板严格一致
确保特化模板与通用模板在同一作用域
template <typename T> T add (T a, T b) {
return a + b;
}
template <> int add <int >(int a, int b, int c) {
return a + b + c;
}
六、实战案例:基于模板实现通用链表
6.1 问题描述 实现一个通用单向链表类,支持任意类型的元素存储,提供插入、删除、查找、遍历等基础操作,通过模板实现类型无关性。
6.2 实现思路
定义链表节点类模板 ListNode<T>,存储 T 类型的元素和下一个节点的指针
定义链表类模板 LinkedList<T>,包含头节点指针、链表长度等成员,实现核心操作
支持的操作:头插法、尾插法、按索引插入、按值删除、按索引查找、遍历打印等
为 string 类型提供特化的遍历打印(美化输出)
6.3 代码实现 #include <iostream>
#include <string>
using namespace std;
template <typename T>
class ListNode {
public :
T data;
ListNode<T>* next;
ListNode (T value): data (value), next (nullptr ) {}
};
template <typename T>
class LinkedList {
private :
ListNode<T>* head;
int length;
public :
LinkedList (): head (nullptr ), length (0 ) {}
~LinkedList () {
ListNode<T>* curr = head;
while (curr != nullptr ) {
ListNode<T>* temp = curr;
curr = curr->next;
delete temp;
}
head = nullptr ;
length = 0 ;
}
void push_front (T value) {
ListNode<T>* new_node = new ListNode <T>(value);
new_node->next = head;
head = new_node;
length++;
}
void push_back (T value) {
ListNode<T>* new_node = new ListNode <T>(value);
if (head == nullptr ) {
head = new_node;
} else {
ListNode<T>* curr = head;
while (curr->next != nullptr ) {
curr = curr->next;
}
curr->next = new_node;
}
length++;
}
bool insert (int index, T value) {
if (index < 0 || index > length) {
cout << "插入失败:索引" << index << "非法!" << endl;
return false ;
}
if (index == 0 ) {
push_front (value);
return true ;
}
ListNode<T>* curr = head;
for (int i = 0 ; i < index - 1 ; ++i) {
curr = curr->next;
}
ListNode<T>* new_node = new ListNode <T>(value);
new_node->next = curr->next;
curr->next = new_node;
length++;
return true ;
}
bool remove (T value) {
if (head == nullptr ) {
cout << "删除失败:链表为空!" << endl;
return false ;
}
ListNode<T>* curr = head;
ListNode<T>* prev = nullptr ;
while (curr != nullptr && curr->data != value) {
prev = curr;
curr = curr->next;
}
if (curr == nullptr ) {
cout << "删除失败:未找到值" << value << "!" << endl;
return false ;
}
if (prev == nullptr ) {
head = curr->next;
} else {
prev->next = curr->next;
}
delete curr;
length--;
return true ;
}
T get (int index) const {
if (index < 0 || index >= length) {
cout << "查找失败:索引" << index << "非法!" << endl;
return T ();
}
ListNode<T>* curr = head;
for (int i = 0 ; i < index; ++i) {
curr = curr->next;
}
return curr->data;
}
int size () const {
return length;
}
void print () const {
if (head == nullptr ) {
cout << "链表为空!" << endl;
return ;
}
cout << "链表元素(长度" << length << "):" ;
ListNode<T>* curr = head;
while (curr != nullptr ) {
cout << curr->data << " -> " ;
curr = curr->next;
}
cout << "nullptr" << endl;
}
};
template <>
void LinkedList<string>::print () const {
if (head == nullptr ) {
cout << "链表为空!" << endl;
return ;
}
cout << "字符串链表(长度" << length << "):" ;
ListNode<string>* curr = head;
while (curr != nullptr ) {
cout << "\"" << curr->data << "\" -> " ;
curr = curr->next;
}
cout << "nullptr" << endl;
}
int main () {
cout << "===== 测试 int 类型链表 =====" << endl;
LinkedList<int > int_list;
int_list.push_back (10 );
int_list.push_back (20 );
int_list.push_front (5 );
int_list.insert (2 , 15 );
int_list.print ();
cout << "索引 2 的元素:" << int_list.get (2 ) << endl;
int_list.remove (10 );
int_list.print ();
cout << "\n===== 测试 string 类型链表 =====" << endl;
LinkedList<string> str_list;
str_list.push_back ("Apple" );
str_list.push_back ("Banana" );
str_list.push_front ("Orange" );
str_list.insert (1 , "Grape" );
str_list.print ();
str_list.remove ("Grape" );
str_list.print ();
cout << "\n===== 测试 double 类型链表 =====" << endl;
LinkedList<double > double_list;
double_list.push_back (1.1 );
double_list.push_back (2.2 );
double_list.push_front (0.5 );
double_list.print ();
return 0 ;
}
6.4 运行结果 ===== 测试 int 类型链表 =====
链表元素(长度 4 ):5 -> 10 -> 15 -> 20 -> nullptr
索引 2 的元素:15
链表元素(长度 3 ):5 -> 15 -> 20 -> nullptr
===== 测试 string 类型链表 =====
字符串链表(长度 4 ):"Orange" -> "Grape" -> "Apple" -> "Banana" -> nullptr
字符串链表(长度 3 ):"Orange" -> "Apple" -> "Banana" -> nullptr
===== 测试 double 类型链表 =====
链表元素(长度 3 ):0.5 -> 1.1 -> 2.2 -> nullptr
结论:通过模板实现的通用链表支持 int、string、double 等多种类型,代码复用率高,且通过特化为 string 类型提供了美化输出,兼顾了通用性和灵活性。实际开发中,类似 STL 的 vector、list 等容器均采用类似的模板设计。
七、模板与 STL 的关联 STL(Standard Template Library,标准模板库)是 C++ 泛型编程的典范,其核心组件(容器、算法、迭代器)均基于模板实现:
容器:vector、list、map<K,V> 等均为类模板,支持任意兼容类型的元素存储
算法:sort、find、reverse 等均为函数模板,可作用于不同类型的容器
迭代器:作为容器与算法的桥梁,也是模板类型,适配不同容器的遍历逻辑
示例:STL 模板的使用(vector 容器+sort 算法)
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main () {
vector<int > nums = {5 , 2 , 9 , 1 , 5 , 6 };
sort (nums.begin (), nums.end ());
cout << "排序后的 vector:" ;
for (int num : nums) {
cout << num << " " ;
}
cout << endl;
vector<string> fruits = {"Apple" , "Banana" , "Orange" , "Grape" };
sort (fruits.begin (), fruits.end ());
cout << "排序后的 string vector:" ;
for (const string& fruit : fruits) {
cout << fruit << " " ;
}
cout << endl;
return 0 ;
}
核心启示:模板是 STL 的基础,掌握模板编程后,能更深入理解 STL 的设计思想,甚至自定义适配 STL 的容器或算法。
八、总结
模板是 C++ 泛型编程的核心,分为函数模板和类模板,核心价值是'一次编写,多次复用',兼顾类型安全和灵活性。
函数模板支持显式/隐式实例化、重载、特化,适用于创建通用函数(如加法、排序)。
类模板支持多参数、非类型参数、全特化、偏特化,适用于创建通用容器(如链表、数组)。
模板的编译机制为'实例化时编译',需注意声明与定义的一致性,避免链接错误。
模板是 STL 的底层实现基础,掌握模板编程是深入学习 STL 和 C++ 高级特性的关键。
通过本文学习,你应能独立编写函数模板和类模板,解决实际开发中的代码复用问题,并理解模板特化、偏特化等高级技术的应用场景。下一篇将深入探讨 C++ 的异常处理机制,提升代码的健壮性和容错能力!
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,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
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online