接上一篇#pragma编译指令大全(上)
inline_depth
语法
#pragma inline_depth( [n] )
作用
指定函数的内敛深度,超过深度n的内敛扩展均转为函数调用。
备注
- inline_depth的控制范围是用inline,__inline标记或在/Ob2编译选项下能够自动内敛的函数。
- n的取值范围是[0,255], 0代表禁用内敛,255表示不限制内敛深度。未指定n值情况下默认为254。
- inline_depth可以控制一些列函数调用的内敛深度。举例,假设内敛深度是4,如果A调用B然后调用C,所有的3次调用都将做内敛扩展。然而,如果设置的最近一次内敛深度是2,则只有A和B被扩展,而C仍然作为函数调用。
- 要使用该指令,必须设置编译选项为/Ob1或/Ob2,并且在该指令设定内敛深度后的第一个函数生效。
- 嵌套的内敛深度设置只能递减,不能递增。如果内敛深度为6,同时后续通过inline_depth编译指令设置为8,则内敛深度仍保持为6。
- inline_depth对使用__forceinline标记的函数无效。
递归函数的内敛深度为16。
inline_recursion
语法
#pragma inline_recursion( [{on | off}] )
作用
控制直接或间接递归调用函数的内敛展开。
备注
- inline_recursion的控制范围是用inline,__inline标记或在/Ob2编译选项下能够自动内敛的函数。
- 要使用该指令,必须设置编译选项为/Ob1或/Ob2,并且在该指令设定开关后的第一个函数生效。
- 默认情况下开关为off。
- 如果开关为off,且内敛函数调用自身,则该函数只展开一次。
- 如果开关为on,则该函数将展开多次,直至达到使用inline_depth设定的内敛深度。
intrinsic
语法
#pragma intrinsic( function1 [, function2, ...] )
作用
指定参数列表中的函数为内建函数。
备注
- intrinsic告诉编译器函数行为明确,如果性能更好的情况下可以直接调用函数而无需采用内敛展开。
- 在intrinsic指令出现之后的第一个包含内建函数的函数定义开始生效。作用效果持续到源文件末尾或者遇到funtion编译指令指定相同的函数。
- intrinsic只能在函数定义外使用-在全局层次。
- 下面是具有内建形式的库函数。
row1 | row2 | row3 |
---|---|---|
_disable | _outp | fabs |
_enable | _outpw | labs |
_inp | _rotl | memcmp |
_inpw | _rotr | memcpy |
_lrotl | _strset | memset |
_lrotr | abs | strcat |
strcmp | strcpy | strlen |
*5. 使用intrinsic函数的程序之所以运行速度更快,是因为它没有函数调用的开销,但是由于插入了额外的代码,所以程序会更大。
*6. 下列的浮点数函数不具有真正的intrinsic形式,它们可以直接将浮点参数传到浮点芯片中,而不是将参数压入程序堆栈。
row1 | row2 | row3 | row4 |
---|---|---|---|
acos | cosh | pow | tanh |
asin | fmod | sinh |
*7. 当指定编译选项/Oi、/Og和/fp:fast(或任何包含/Og的选项:/Ox、/O1和/O2)时,下面列出的浮点函数将具有真正的intrinsic形式。
row1 | row2 | row3 | row4 |
---|---|---|---|
atan | exp | log10 | sqrt |
atan2 | log | sin | tan |
cos |
示例代码
// pragma_directive_intrinsic.cpp
// processor: x86
#include <dos.h> // definitions for _disable, _enable
#pragma intrinsic(_disable)
#pragma intrinsic(_enable)
void f1(void) {
_disable();
// do some work here that should not be interrupted
_enable();
}
int main() {
}
loop
语法
#pragma loop( hint_parallel(n) )
#pragma loop( no_vector )
#pragma loop( ivdep )
作用
控制对循环代码的自动并行化和自动向量化。
备注
- hint_parallel(n): 提示编译器对循环代码进行n线程并行。n为0时采用最大数量的线程。该指令只是一个提示,并不是命令,编译器不能保证将循环并行化。
- 如果循环具有数据依赖或结构化问题,比如循环存储了循环体外的标量,将不会并行化。
- 只有指定/Qpar编译选项时,hint_parallel才会生效。
- no_vector:默认情况下,自动向量化时打开的。
- idevp:提示编译器忽略该循环的向量依赖。
- loop编译指令需要放在紧挨着循环体之前,不能放在循环体内,并且对接下来的循环生效。
- 可以对同一个循环体指定多个loop选项,但是每个选项都要使用单独一条指令。
make_public
语法
#pragma make_public(type)
作用
指示本机类型应具有公共程序集可访问性。
备注
- type: 需要具有公共程序集可访问性的类型名称。
- 如果要引用的本机类型来自无法更改的.h文件,则 make_public 会很有用。
- 若要在带有公共程序集可见性的类型中使用公共函数签名中的本机类型,则本机类型还必须具有公共程序集可访问性,否则编译器将发出警告。
- make_public必须在全局范围内使用,并且仅在编译指令指定处到源文件末尾有效。
- 可以隐式或显式将本机类型设为私有;详细信息请参阅类型可见性。
示例代码
// make_public_pragma.h
struct Native_Struct_1 { int i; };
struct Native_Struct_2 { int i; };
// make_public_pragma.cpp
// compile with: /c /clr /W1
#pragma warning (default : 4692)
#include "make_public_pragma.h"
#pragma make_public(Native_Struct_1)
public ref struct A {
void Test(Native_Struct_1 u) {u.i = 0;} // OK
void Test(Native_Struct_2 u) {u.i = 0;} // C4692
};
注意上述代码示例指定的编译选项:/c /clr /W1
managed, unmanaged
语法
#pragma managed
#pragma unmanaged
#pragma managed([push,] on | off)
#pragma managed(pop)
作用
启用函数级控制以将函数编译为托管或未托管函数。
备注
- /clr编译器选项提供了用于将函数编译为托管或非托管函数的模块级控制。
- 非托管函数将在本机平台编译成机器码,并且由CLR传给本机平台运行。
- 启用/clr编译选项后,默认情况下将函数编译为托管函数。
- 该编译指令需要放在函数体之前,不能放在函数体内。
- 该编译指令必须放在#include指令之后。
- 如果未设定/clr编译选项,编译器将忽略managed和unmanaged编译指令。
- 该编译指令可以在模板函数实例化之后确定该函数是否为托管函数。
- 更多详细信息请参阅混合程序集的初始化;
示例代码
// pragma_directives_managed_unmanaged.cpp
// compile with: /clr
#include <stdio.h>
// func1 is managed
void func1() {
System::Console::WriteLine("In managed function.");
}
// #pragma unmanaged
// push managed state on to stack and set unmanaged state
#pragma managed(push, off)
// func2 is unmanaged
void func2() {
printf("In unmanaged function.\n");
}
// #pragma managed
#pragma managed(pop)
// main is managed
int main() {
func1();
func2();
}
In managed function.
In unmanaged function.
message
语法
#pragma message( messagestring )
作用
在不中断编译的情况下将字符串输出到标准输出窗口。
备注
- 该编译指令的典型用法是输出信息性消息。
- messagestring可以是宏,也可以是字符串和宏的拼接。
- 在该编译指令中使用的宏应该返回字符串类型,否则需要主动将宏返回信息转换成字符串类型。
示例代码
// pragma_directives_message1.cpp
// compile with: /LD
#if _M_IX86 >= 500
#pragma message("_M_IX86 >= 500")
#endif
#pragma message("")
#pragma message( "Compiling " __FILE__ )
#pragma message( "Last modified on " __TIMESTAMP__ )
#pragma message("")
// with line number
#define STRING2(x) #x
#define STRING(x) STRING2(x)
#pragma message (__FILE__ "[" STRING(__LINE__) "]: test")
#pragma message("")
omp
语法
#pragma omp directive
作用
将一个或多个OpenMP指令与任何可选指令子集一起使用。
once
语法
#pragma once
作用
指定该文件在编译源文件时仅被编译器包含(打开)一次。
备注
- 该编译指令可以减少编译次数,通常被称为多次包含优化(multiple-include optimization)。
- 效果类似于
#include guard idiom
,后者通常采用预处理宏来防止多次包含同一文件。 - 该编译指令还有助于避免违反单一定义原则(要求所有模板、类型、函数和对象在代码中的定义不得超过一个)。
- 如果要将代码移植到不支持该编译指令的编译器平台,则建议使用
#include guard idiom
。
示例代码
// header.h
#pragma once
// Code placed here is included only once per translation unit
// header.h
// Demonstration of the #include guard idiom.
// Note that the defined symbol can be arbitrary.
#ifndef HEADER_H_ // equivalently, #if !defined HEADER_H_
#define HEADER_H_
// Code placed here is included only once per translation unit
#endif // HEADER_H_
optimize
语法
#pragma optimize( "[optimization-list]", {on | off} )
作用
指定编译器对函数的优化类型。
备注
- 该编译指令必须放在函数体外部,并且对该指令设定后的函数生效。
- on和off用于打开或关闭optimization-list指定的优化类型。
- optimization-list可以是下表中显示的0个或多个参数,与/O编译选项参数一样。
Parameter(s) | Type of optimization |
---|---|
g | Enable global optimizations. |
s or t | Specify short or fast sequences of machine code. |
y | Generate frame pointers on the program stack. |
- 指令特殊形式:optimization-list为""空字符串。
示例代码
// 等价于/Os编译选项
#pragma optimize( "ts", on )
// 关闭前面指定的优化选项
#pragma optimize( "", off )
.
.
.
// 重新打开编译优化选项
#pragma optimize( "", on )
pack
语法
#pragma pack( [ show ] | [ push | pop ] [, identifier ] , n )
作用
指定结构、联合和类成员的对齐方式。
备注
- 紧凑压缩一个类就是将类成员变量在内存中紧挨着存放,也就是说某些成员变量的对齐边界比该变量类型的默认情况要小。比如UINT64类型默认情况是8字节对齐的,通过pack指令可以指定为4字节对齐。
- pack指令提供了数据声明级别的控制,而/Zp编译提供了模块级别的控制。
- pack指令对设定之后的第一个struct,union或者class声明生效,该指令对定义无效。
- 不带参数的pack指令默认将/Zp设置的对齐字节数作为n参数。当/Zp未设定时,默认8字节对齐。
- 修改数据结构的对齐方式之后,可以减少内存的使用,但可能会降低性能或由于未对齐访问引起硬件异常,可以通过SetErrorMode来修改异常行为模式。
- show: 可选参数,通过警告消息显示当前的对齐方式。
- push: 可选参数,将当前对齐方式压入编译堆栈,设置后续对齐方式为n,n未指定则采用当前对齐方式。
- pop: 可选参数,弹出编译堆栈顶部的元素,如果n未设定,则将弹出的顶部元素作为新的对齐方式,否则对齐方式为n。例如:
#pragma pack(pop, 16)
。如果pop指令指定了identifer(如#pragma pack(pop, r1)
),则弹出r1之前包括r1的所有元素,并将新的栈顶元素作为对齐方式。如果未找到identifer标示符,则忽略该指令。 - identifer: 可选参数,指定压入堆栈或弹出堆栈的标示符。
- n: 可选参数,指定用于对齐的值,单位为字节。有效值是1、2、4、8和16。
- 更多对齐信息请参阅__alignof,align,__unaligned,Examples of Structure Alignment。
示例代码
// pragma_directives_pack.cpp
#include <stddef.h>
#include <stdio.h>
struct S {
int i; // size 4
short j; // size 2
double k; // size 8
};
#pragma pack(2)
struct T {
int i;
short j;
double k;
};
int main() {
printf("%zu ", offsetof(S, i));
printf("%zu ", offsetof(S, j));
printf("%zu\n", offsetof(S, k));
printf("%zu ", offsetof(T, i));
printf("%zu ", offsetof(T, j));
printf("%zu\n", offsetof(T, k));
}
0 4 8
0 4 6
// pragma_directives_pack_2.cpp
// compile with: /W1 /c
#pragma pack() // n defaults to 8; equivalent to /Zp8
#pragma pack(show) // warning C4810: value of pragma pack(show) == 8
#pragma pack(4) // n = 4
#pragma pack(show) // warning C4810: value of pragma pack(show) == 4
#pragma pack(push, r1, 16) // n = 16, pushed to stack
#pragma pack(show) // warning C4810: value of pragma pack(show) == 16
#pragma pack(pop, r1, 2) // n = 2 , stack popped
#pragma pack(show) // warning C4810: value of pragma pack(show) == 2
pointers_to_members
语法
#pragma pointers_to_members( pointer-declaration, [most-general-representation] )
作用
指定能否在类定义之前声明指向类成员的指针,以及类成员是否控制指针大小以及如何解析类成员指针。
备注
参数 | 注释 |
---|---|
full_generality | 生成安全(有时并非最佳)代码。如果在关联的类定义之前声明指向成员的任何指针,请使用full_generality。此参数始终使用most-general-representation参数所指定的指针表示形式。 等价于/vmg。 |
best_case | 对指向成员的所有指针使用best-case representation参数生成安全的最佳代码。 要求在声明指向类成员的指针之前定义此类。 系统默认为best_case。 |
*3. most-general-representation: 指定了最小指针表示形式,用于编译器可以安全地引用指向编译单元中的类成员的任何指针。参数如下:
参数 | 注释 |
---|---|
single_inheritance | 最常见的表示形式为单一继承(指向成员函数的指针)。如果类定义(已为其声明指向成员的指针)的继承模型为多重继承或虚拟继承,则会导致出现错误。 |
multiple_inheritance | 最常见的表示形式为多重继承(指向成员函数的指针)。如果类定义(已为其声明指向成员的指针)的继承模型为虚拟继承,则会导致出现错误。 |
virtual_inheritance | 最常见的表示形式为虚拟继承(指向成员函数的指针)。不会导致错误。这是#pragma pointers_to_members(full_generality) 的默认参数。 |
注意:建议将该编译指令放在要使用的源文件中,并且放置在所有
#include
之后。避免影响其它源文件。
示例代码
// Specify single-inheritance only
#pragma pointers_to_members( full_generality, single_inheritance )
pop_macro
语法
#pragma pop_macro( "macro_name" )
作用
将macro_name宏的值设置为堆栈顶部的存放该宏值。
备注
- 使用pop_macro之前必须先使用push_macro。
示例代码
// pragma_directives_pop_macro.cpp
// compile with: /W1
#include <stdio.h>
#define X 1
#define Y 2
int main() {
printf("%d",X);
printf("\n%d",Y);
#define Y 3 // C4005
#pragma push_macro("Y")
#pragma push_macro("X")
printf("\n%d",X);
#define X 2 // C4005
printf("\n%d",X);
#pragma pop_macro("X")
printf("\n%d",X);
#pragma pop_macro("Y")
printf("\n%d",Y);
}
1
2
1
2
1
3
push_macro
语法
#pragma push_macro(" macro_name ")
作用
将macro_name宏的值压入堆栈顶端。
备注
- 可以通过pop_macro恢复macro_name宏的值。
示例代码
请参阅上述pop_macro。
region, endregion
语法
#pragma region name
#pragma endregion comment
作用
利用#pragma region
指定在使用Visual Studio的大纲功能时可展开或折叠的代码块。
备注
- comment: (可选参数),在代码编辑器中显示的注释。
- name: (可选参数),区域名称,此名称将在代码编辑器中显示。
-
#pragma endregion
标记#pragma region
块的结尾。两者必须一起配合使用。
示例代码
// pragma_directives_region.cpp
#pragma region Region_1
void Test() {}
void Test2() {}
void Test3() {}
#pragma endregion Region_1
int main() {}
runtime_checks
语法
#pragma runtime_checks( "[runtime_checks]", {restore | off} )
作用
禁用或还原/RTC设置。
备注
- 在/RTC编译选项未启用时不能使用该编译指令还原运行时检查。举例:如果未指定/RTCs编译选项,则指定
#pragma runtime_checks( "s", restore)
也不会启用堆栈帧校验。 - 该编译指令必须放在函数体外部,而且在该指令之后的第一个函数定义开始生效。
- retore和off分别代表打开和关闭指定的runtime_checks。参数取值如下所示:
参数 | 运行时检查的类型 |
---|---|
s | 启用堆栈(帧)校验。 |
c | 提示某个数值向较小数据类型赋值时会导致数据丢失。 |
u | 提示某个变量在定义之前被使用。 |
*4. 上表中的参数与/RTC编译选项的参数相同,例如,#pragma runtime_checks( "sc", restore )
。
*5. 特殊形式:参数runtime_checks为空字符串(""),对应功能如下:
a. off: 禁用上表中的运行时错误检查。
b. restore: 重置运行时错误检查为/RTC编译选项中指定的类型。
示例代码
#pragma runtime_checks( "", off )
.
.
.
#pragma runtime_checks( "", restore )
更多信息请参阅:RTC Sample
section
语法
#pragma section( "section-name" [, attributes] )
作用
在obj文件中创建一个段。
备注
- 此处segment和section的概念是等价的。
- 一旦定义了一个段,将在编译的剩余部分生效,但是必须通过__declspec(allocate)来指定段信息,否则将没有数据存放在该段。
- section-name: 必须参数,指定段名。该名不能与标准段名冲突。参见/SECTION查看不能使用的段名。
- attributes: 可选参数,指定段属性,各个属性间用逗号分隔。可选属性如下所示:
属性 | 注释 |
---|---|
read | 可读 |
write | 可写 |
execute | 可执行 |
shared | 共享 |
nopage | 不可分页,对于win32设备驱动程序很有用 |
nocahe | 不可分页,对于win32设备驱动程序很有用 |
discard | 可丢弃,对于win32设备驱动程序很有用 |
remove | 非常驻内存,仅适用于虚拟设备驱动程序(VxD)。 |
示例代码
// pragma_section.cpp
#pragma section("mysec",read,write)
int j = 0; // 存放在数据段,因为没有使用__declspec(allocate)声明
__declspec(allocate("mysec"))
int i = 0; // 存放在mysec段
int main(){}
setlocale
语法
#pragma setlocale( "[locale-string]" )
作用
定义在转换宽字符常量和字符串时使用的区域设置(国家/地区和语言)。
备注
- 由于编译可能在不同的区域设置环境下进行,而不同区域将多字节转换为宽字符的算法可能存在差异,因此可以使用setlocale来指定编译时的区域设置。这样可以保证宽字符能够以正确的格式保存。
- locale-string的默认值是
""
。 - "C"区域设置会将字符串中的每个字符作为wchar_t(unsigned short)映射到"C"区域设置的值。
- 其它有效的参数是语言字符串列表中的区域选项。
示例代码
#pragma setlocale("dutch")
能否处理指定的语言字符串取决于计算机是否支持对应的代码页(code page)和语言ID。
strict_gs_check
语法
#pragma strict_gs_check([push,] on )
#pragma strict_gs_check([push,] off )
#pragma strict_gs_check(pop)
作用
提供加强型的安全检测。
备注
- 指示编译器在函数堆栈中插入随机Cookie以便于检测某些类别的基于堆栈的缓冲区溢出。
- 默认情况下,/GS(缓冲区安全检查)编译选项不会为所有函数插入Cookie进行检测。更多详细信息请参阅/GS(缓冲区安全检查)
- 必须启动/GS编译选项后才能使用strict_gs_check编译指令。
- 该编译指令一般用于存在潜在危害的数据的代码模块。
- 该编译指令的攻击性较强,可以应用于可能不需要保护的函数,但是为了尽可能降低对应用程序的影响,它通常会进行优化。
- 即使使用了该编译指令,也要尽可能编写安全的代码,确保代码中不存在缓冲区溢出。
示例代码
// pragma_strict_gs_check.cpp
// compile with: /c
#pragma strict_gs_check(on)
void ** ReverseArray(void **pData,
size_t cData)
{
// *** This buffer is subject to being overrun!! ***
void *pReversed[20];
// Reverse the array into a temporary buffer
for (size_t j = 0, i = cData; i ; --i, ++j)
// *** Possible buffer overrun!! ***
pReversed[j] = pData[i];
// Copy temporary buffer back into input/output buffer
for (size_t i = 0; i < cData ; ++i)
pData[i] = pReversed[i];
return pData;
}
vtordisp
语法
#pragma vtordisp([push,] n)
#pragma vtordisp(pop)
#pragma vtordisp()
#pragma vtordisp([push,] {on | off})
作用
控制构造/析构偏移成员是否包含vtordisp隐藏字段。
备注
- push: 将当前的vtordisp设置值压入内部编译堆栈,并将新的vtordisp设置为n。如果未指定n的值,则不改变vtordisp的设置。
- pop: 将内部编译堆栈顶的元素弹出,并将其设置为新的vtordisp设置。
- n: 指定vtordisp设置的值。可以是0,1或2,分别对应于/vd0,/vd1和/vd2三个编译选项。更多信息请参阅/vd(禁用构造偏移)。
- on: 等价于
#pragma vtordisp(1)
。 - off: 等价于
#pragma vtordisp(0)
。 - 该编译指令只能用于具有虚拟基类的代码。产生vtordisp的条件如下:
a. 虚继承中派生类重写了基类的虚函数。
b. 派生类在构造函数或析构函数中通过虚基类的指针调用了重写的基类函数。 - 该编译指令会影响它之后类的对象模型。功能等价于/vd0,/vd1和/vd2编译选项,但是作用范围不同。编译选项作用于整个模块,编译指令的作用范围根据设置的打开与关闭相关。
- 默认设置为on(1),在必要时启用vtordisp字段。
- 设置为2时将对所有具有虚函数的虚基类启用vtordisp字段。
- vtordisp(2)可以确保dynamic_cast在部分构造的对象上正常工作。更多信息请参阅编译器警告(等级 1)C4436。
#pragma vtordisp(push, 2)
class GetReal : virtual public VBase { ... };
#pragma vtordisp(pop)
warning
语法
#pragma warning(
warning-specifier
:
warning-number-list [; warning-specifier : warning-number-list...] )
#pragma warning( push[ ,n ] )
#pragma warning( pop )
作用
选择性修改编译警告消息行为。
备注
- warning-specifier: 警告描述符,同一编译指令中可以指定多个,有效参数如下所示。
警告描述符 | 注释 |
---|---|
1, 2, 3, 4 | 指定警告级别。 同时会启用默认情况下处于关闭状态的指定警告。 |
default | 重置警告级别为默认值。 同时会启用默认情况下处于关闭状态的指定警告。 警告将在其默认存档级别生成。有关详细信息,请参阅默认关闭的编译器警告。 |
disable | 禁用指定的警告。 |
error | 将指定警告报告为错误。 |
once | 只显示一次指定的警告消息。 |
suppress | 将当前警告设置压入堆栈,禁用下一行的指定警告,然后弹出警告堆栈,重置警告设置。 |
*2. warning-number-list: 参数可包含多个警告编号。
// 简写
#pragma warning( disable : 4507 34; once : 4385; error : 164 )
// 等价于下面指令
// Disable warning messages 4507 and 4034.
#pragma warning( disable : 4507 34 )
// Issue warning 4385 only once.
#pragma warning( once : 4385 )
// Report warning 4164 as an error.
#pragma warning( error : 164 )
*3. 编译器会将0-999区间的告警编号默认加上4000,变成4000-4999。
*4. 在函数左大括号已经生效的警告状态在函数体其余部分也生效。
*5. 4700-4900区间内的警告与代码生成相关,使用warning编译指令修改大于4699的警告状态会在函数体末尾之后生效。
// pragma_warning.cpp
// compile with: /W1
#pragma warning(disable:4700)
void Test() {
int x;
int y = x; // no C4700 here
#pragma warning(default:4700) // C4700 enabled after Test ends
}
int main() {
int x;
int y = x; // C4700
}
函数体中只有最后一个warning编译指令会在函数体末尾生效。
*6. push: 保存每个警告的当前警告状态并压入编译栈,设置警告级别为n。
*7. pop:弹出压入编译栈的最后一个警告状态,同时撤销push和pop之间的所有修改,还原每个警告的状态。
#pragma warning( push )
#pragma warning( disable : 4705 )
#pragma warning( disable : 4706 )
#pragma warning( disable : 4707 )
// Some code
#pragma warning( pop )
pop会将每个警告(包括 4705、4706 和 4707)的状态还原为其在代码开头的状态。
*8. 编写头文件时可以使用push和pop来确保外部对警告状态的修改不会影响到当前文件。
#pragma warning( push, 3 )
// Declarations/definitions
#pragma warning( pop )
注意:要在文件开头使用push,在文件末尾使用pop。
后记
终于总结完了,原来计划是花几天的晚上时间来整理就够了,但是真正开始整理之后发现工作量远比预估的要大,断断续续差不多用了一周多时间,分析了一下主要的耗时有下面几部分:
- 有些指令从来没用过,需要查阅其它资料来熟悉和理解它。
- 理解MSDN的英文讲解相对较容易,但是要把一些英文专业术语用中文表述出来却需要反复斟酌一下,有种只可意会,不可言传的感觉,哈哈~
- 各个指令提供的示例代码需要一些时间来调试和理解。
最后吐槽一下markdown的语法,一旦插入表格,后续的有序列表就会重新编号,在网上看到好多人都遇到一样的情况,最后用了*加数字的方式进行处理,但是看着感觉还是有点怪怪的。