代码文档化

本章涵盖两个主题

  1. 如何将注释放入代码中,以便 Doxygen 将其合并到生成的文档中。这将在下一节中进一步详细说明。
  2. 如何构建注释块的内容,使输出看起来更好,如注释块的结构一节所述。

特殊注释块

特殊注释块是一个 C 或 C++ 风格的注释块,带有一些额外的标记,因此 Doxygen 知道它是一段需要出现在生成的文档中的结构化文本。下一节介绍了 Doxygen 支持的各种样式。

对于 Python、VHDL 和 Fortran 代码,有不同的注释约定,可以在Python 中的注释块VHDL 中的注释块Fortran 中的注释块章节中找到。

类 C 语言(C/C++/C#/Objective-C/PHP/Java)的注释块

对于代码中的每个实体,都有两种(或在某些情况下三种)类型的描述,它们共同构成该实体的文档;简要描述和详细描述,两者都是可选的。对于方法和函数,还有第三种类型的描述,即所谓的主体内描述,它由方法或函数主体中找到的所有注释块的串联组成。

允许有多个简要或详细描述(但不建议,因为描述出现的顺序未指定)。

顾名思义,简要描述是简短的一句话,而详细描述提供了更长、更详细的文档。“主体内”描述也可以作为详细描述,或者可以描述一系列实现细节。对于 HTML 输出,简要描述还用于在引用项目的地方提供工具提示。

有几种方法可以将注释块标记为详细描述

  1. 您可以使用 Javadoc 样式,它由以两个 * 开头的 C 样式注释块组成,如下所示

    /**
     * ... text ...
     */
    

  2. 或者您可以使用 Qt 样式,并在 C 样式注释块的开头添加一个感叹号 (!),如本例所示

    /*!
     * ... text ...
     */
    

    在这两种情况下,中间的 * 都是可选的,因此

    /*!
     ... text ...
    */
    

    也是有效的。

  3. 第三种选择是使用至少两行 C++ 注释行,其中每行都以附加的斜杠或感叹号开头。以下是两种情况的示例

    ///
    /// ... text ...
    ///
    

    //!
    //!... text ...
    //!
    

    请注意,在这种情况下,空行会结束文档块。

  4. 有些人喜欢使他们的注释块在文档中更加可见。为此,您可以使用以下方法

    /*******************************************//**
     *  ... text
     ***********************************************/
    

    注意:2 个斜杠用于结束普通注释块并开始特殊注释块。

    注意:使用 clang-format 之类的格式化工具时要小心,因为它可能会将这种类型的注释视为 2 个单独的注释,并在它们之间引入空格。

    /////////////////////////////////////////////////
    /// ... text ...
    /////////////////////////////////////////////////
    

    /***********************************************
     *  ... text
     ***********************************************/
    

    只要JAVADOC_BANNER设置为 YES

    /**
    * JavaDoc 样式(C 样式)注释的简史。
    *
    * 这是典型的 JavaDoc 样式 C 样式注释。它以两个
    * 星号开头。
    *
    * @param theory 即使只有一个可能的统一理论。它也只是一组
    * 规则和方程式。
    */
    void cstyle( int theory );
    /******************************************************************************
    * JavaDoc 样式(C 样式)横幅注释的简史。
    *
    * 这是典型的 JavaDoc 样式 C 样式“横幅”注释。它以
    * 斜杠开头,后跟一些数字 n 的星号,其中 n > 2。它的
    * 编写方式是为了对正在阅读
    * 源代码的开发人员更加“可见”。
    *
    * 通常,开发人员不知道这(默认情况下)不是有效的 Doxygen
    * 注释块!
    *
    * 但是,只要将 JAVADOC_BANNER = YES 添加到 Doxyfile 中,它就会
    * 按预期工作。
    *
    * 这种注释样式与 clang-format 配合良好。
    *
    * @param theory 即使只有一个可能的统一理论。它也只是一组
    * 规则和方程式。
    ******************************************************************************/
    void javadocBanner( int theory );
    /**************************************************************************//**
    * Doxygen 样式横幅注释的简史。
    *
    * 这是 Doxygen 样式的 C 样式“横幅”注释。它以“普通”
    * 注释开头,然后在第一行末尾附近转换为“特殊”注释块。它的
    * 编写方式是为了对正在阅读
    * 源代码的开发人员更加“可见”。
    * 这种注释样式与 clang-format 配合不佳。
    *
    * @param theory 即使只有一个可能的统一理论。它也只是一组
    * 规则和方程式。
    ******************************************************************************/
    void doxygenBanner( int theory );

    单击此处,查看 Doxygen 生成的相应 HTML 文档。

对于简要描述,还有几种可能性

  1. 可以使用\brief命令以及上述注释块之一。此命令在段落末尾结束,因此详细描述在空行之后。

    这是一个例子

    /*! \brief Brief description.
     *         Brief description continued.
     *
     *  Detailed description starts here.
     */
    

  2. 如果配置文件的JAVADOC_AUTOBRIEF设置为YES,则使用 Javadoc 样式注释块将自动启动一个简要描述,该描述在第一个点号、问号或感叹号后跟空格或新行时结束。这是一个例子

    /** Brief description which ends at this dot. Details follow
     *  here.
     */
    

    该选项对于多行特殊 C++ 注释具有相同的效果

    /// Brief description which ends at this dot. Details follow
    /// here.
    

  3. 第三种选择是使用不超过一行的特殊 C++ 样式注释。以下是两个例子

    /// Brief description.
    /** Detailed description. */
    

    //! Brief description.
    
    //! Detailed description
    //! starts here.
    

    请注意最后一个示例中的空行,这是将简要描述与包含详细描述的块分开所必需的。JAVADOC_AUTOBRIEF对于这种情况也应设置为NO

如您所见,Doxygen 非常灵活。如果您有多个详细描述,如下例所示

//! Brief description, which is
//! really a detailed description since it spans multiple lines.
/*! Another detailed description!
 */

它们将被连接起来。请注意,如果描述位于代码中的不同位置,也会出现这种情况!在这种情况下,顺序将取决于 Doxygen 解析代码的顺序。

与大多数其他文档系统不同,Doxygen 还允许您将成员(包括全局函数)的文档放在定义之前。这样,文档可以放置在源文件中,而不是头文件中。这使头文件保持简洁,并允许成员的实现者更直接地访问文档。作为折衷方案,简要描述可以放在声明之前,详细描述可以放在成员定义之前。

将文档放在成员之后

如果要记录文件、结构体、联合体、类或枚举的成员,有时希望将文档块放在成员之后而不是之前。为此,您必须在注释块中添加一个额外的 < 标记。请注意,这也适用于函数的参数。

以下是一些例子

int var; /*!< Detailed description after the member */

此块可用于将 Qt 样式的详细文档块放在成员之后。执行相同操作的其他方法是

int var; /**< Detailed description after the member */

int var; //!< Detailed description after the member
         //!<

int var; ///< Detailed description after the member
         ///<

大多数情况下,人们只想在成员之后放置一个简要描述。这可以通过以下方式完成

int var; //!< Brief description after the member

int var; ///< Brief description after the member

对于函数,可以使用 @param 命令来记录参数,然后使用 [in][out][in,out] 来记录方向。对于内联文档,也可以通过从方向属性开始来实现,例如

void foo(int v /**< [in] docs for input parameter v. */);

请注意,这些块的结构和含义与上一节中的特殊注释块相同,只是 < 表示成员位于块之前而不是之后。

以下是使用这些注释块的示例

/*! 一个测试类 */
class Afterdoc_Test
{
public:
/** 一个枚举类型。
* 文档块不能放在枚举之后!
*/
enum EnumType
{
int EVal1, /**< 枚举值 1 */
int EVal2 /**< 枚举值 2 */
};
void member(); //!< 一个成员函数。
protected:
int value; /*!< 一个整数值 */
};

单击此处,查看 Doxygen 生成的相应 HTML 文档。

警告
这些块只能用于记录成员参数。它们不能用于记录文件、类、联合体、结构体、组、命名空间、宏和枚举本身。此外,下一节中提到的结构命令(如 \class)不允许在这些注释块内使用。
小心使用此构造作为宏定义的一部分,因为当MACRO_EXPANSION设置为 YES 时,在应用宏的地方,注释也会被替换,并且此注释随后被用作最后遇到的项的文档,而不是宏定义本身!

示例

以下是使用 Qt 风格的 C++ 代码文档示例

//! 一个测试类。
/*!
更详细的类描述。
*/
class QTstyle_Test
{
public:
//! 一个枚举。
/*! 更详细的枚举描述。 */
enum TEnum {
TVal1, /*!< 枚举值 TVal1。 */
TVal2, /*!< 枚举值 TVal2。 */
TVal3 /*!< 枚举值 TVal3。 */
}
//! 枚举指针。
/*! 详细信息。 */
*enumPtr,
//! 枚举变量。
/*! 详细信息。 */
enumVar;
//! 一个构造函数。
/*!
构造函数的更详细描述。
*/
QTstyle_Test();
//! 一个析构函数。
/*!
析构函数的更详细描述。
*/
~QTstyle_Test();
//! 一个普通成员,它接受两个参数并返回一个整数值。
/*!
\param a 一个整数参数。
\param s 一个常量字符指针。
\return 测试结果
\sa QTstyle_Test(), ~QTstyle_Test(), testMeToo() 和 publicVar()
*/
int testMe(int a,const char *s);
//! 一个纯虚成员。
/*!
\sa testMe()
\param c1 第一个参数。
\param c2 第二个参数。
*/
virtual void testMeToo(char c1,char c2) = 0;
//! 一个公共变量。
/*!
详情。
*/
int publicVar;
//! 一个函数变量。
/*!
详情。
*/
int (*handler)(int a,int b);
};

点击这里查看 Doxygen 生成的相应 HTML 文档。

简要描述包含在类、命名空间或文件的成员概述中,并使用小号斜体字体打印(可以通过在配置文件中将 BRIEF_MEMBER_DESC 设置为 NO 来隐藏此描述)。默认情况下,简要描述会成为详细描述的第一句话(但可以通过将 REPEAT_BRIEF 标签设置为 NO 来更改)。对于 Qt 样式,简要描述和详细描述都是可选的。

默认情况下,Javadoc 样式文档块的行为方式与 Qt 样式文档块相同。然而,这不符合 Javadoc 规范,其中文档块的第一句话会自动被视为简要描述。要启用此行为,您应该在配置文件中将 JAVADOC_AUTOBRIEF 设置为 YES。如果您启用此选项并且想要在句子中间放置一个点而不结束它,则应该在其后放置一个反斜杠和一个空格。这是一个例子

  /** Brief description (e.g.\ using only a few words). Details follow. */

这里是上面显示的代码片段,这次使用 Javadoc 样式进行文档记录,并将 JAVADOC_AUTOBRIEF 设置为 YES

/**
* 一个测试类。更详细的类描述。
*/
class Javadoc_Test
{
public:
/**
* 一个枚举。
* 更详细的枚举描述。
*/
enum TEnum {
TVal1, /**< 枚举值 TVal1。 */
TVal2, /**< 枚举值 TVal2。 */
TVal3 /**< 枚举值 TVal3。 */
}
*enumPtr, /**< 枚举指针。详情。 */
enumVar; /**< 枚举变量。详情。 */
/**
* 一个构造函数。
* 构造函数的更详细描述。
*/
Javadoc_Test();
/**
* 一个析构函数。
* 析构函数的更详细描述。
*/
~Javadoc_Test();
/**
* 一个接受两个参数并返回整数值的普通成员。
* @param a 一个整数参数。
* @param s 一个常量字符指针。
* @see Javadoc_Test()
* @see ~Javadoc_Test()
* @see testMeToo()
* @see publicVar()
* @return 测试结果
*/
int testMe(int a,const char *s);
/**
* 一个纯虚成员。
* @see testMe()
* @param c1 第一个参数。
* @param c2 第二个参数。
*/
virtual void testMeToo(char c1,char c2) = 0;
/**
* 一个公共变量。
* 详情。
*/
int publicVar;
/**
* 一个函数变量。
* 详情。
*/
int (*handler)(int a,int b);
};

点击这里查看 Doxygen 生成的相应 HTML 文档。

类似地,如果希望将 Qt 样式文档块的第一句话自动视为简要描述,则可以在配置文件中将 QT_AUTOBRIEF 设置为 YES。

其他位置的文档

在上一节的示例中,注释块始终位于文件、类或命名空间的声明或定义前面,或者位于其成员的前面后面。虽然这通常很方便,但有时可能需要将文档放在其他地方。对于文档化文件,这是必需的,因为不存在“在文件前面”这样的东西。

Doxygen 允许您将文档块放在几乎任何地方(例外情况是在函数体内部或普通的 C 样式注释块内部)。

对于不将文档块直接放在项目之前(或之后)的代价是需要在文档块内放置一个结构命令,这会导致一些信息重复。因此,在实践中,您应该避免使用结构命令,除非其他要求迫使您这样做。

结构命令(如所有其他命令)以反斜杠 (\) 或 at 符号 (@) 开头,如果您更喜欢 Javadoc 样式,则后跟命令名称和一个或多个参数。例如,如果您想记录上面示例中的类 Test,您也可以将以下文档块放在 Doxygen 读取的输入中的某处

/*! \class Test
    \brief A test class.

    A more detailed class description.
*/

这里使用特殊命令 \class 来指示注释块包含类 Test 的文档。其他结构命令包括

  • \struct 用于记录 C 结构体。
  • \union 用于记录联合体。
  • \enum 用于记录枚举类型。
  • \fn 用于记录函数。
  • \var 用于记录变量或 typedef 或枚举值。
  • \def 用于记录 #define。
  • \typedef 用于记录类型定义。
  • \file 用于记录文件。
  • \namespace 用于记录命名空间。
  • \package 用于记录 Java 包。
  • \interface 用于记录 IDL 接口。

有关这些命令和许多其他命令的详细信息,请参阅特殊命令部分。

要记录 C++ 类的成员,您还必须记录类本身。命名空间也是如此。要记录全局 C 函数、typedef、枚举或预处理器定义,您必须首先记录包含它的文件(通常这将是头文件,因为该文件包含导出到其他源文件的信息)。

注意
让我们重复一遍,因为它经常被忽略:要记录全局对象(函数、typedef、枚举、宏等),您必须记录定义它们的 文件。换句话说,此文件中必须至少有一个
/*! \file */ 
或一个
/** @file */ 
行。

这是一个名为 structcmd.h 的 C 头文件的示例,该文件使用结构命令进行文档记录

/*! \file structcmd.h
\brief 一个已文档化的文件。
详情。
*/
/*! \def MAX(a,b)
\brief 一个宏,返回 \a a 和 \a b 的最大值。
详情。
*/
/*! \var typedef unsigned int UINT32
\brief 一个类型定义,用于 。
详情。
*/
/*! \var int errno
\brief 包含上一个错误代码。
\warning 不是线程安全的!
*/
/*! \fn int open(const char *pathname,int flags)
\brief 打开一个文件描述符。
\param pathname 描述符的名称。
\param flags 打开标志。
*/
/*! \fn int close(int fd)
\brief 关闭文件描述符 \a fd。
\param fd 要关闭的描述符。
*/
/*! \fn size_t write(int fd,const char *buf, size_t count)
\brief 将 \a buf 中的 \a count 个字节写入文件描述符 \a fd。
\param fd 要写入的描述符。
\param buf 要写入的数据缓冲区。
\param count 要写入的字节数。
*/
/*! \fn int read(int fd,char *buf,size_t count)
\brief 从文件描述符读取字节。
\param fd 要从中读取的描述符。
\param buf 要读取到其中的缓冲区。
\param count 要读取的字节数。
*/
#define MAX(a,b) (((a)>(b))?(a):(b))
typedef unsigned int UINT32;
int errno;
int open(const char *,int);
int close(int);
size_t write(int,const char *, size_t);
int read(int,char *,size_t);

点击这里查看 Doxygen 生成的相应 HTML 文档。

由于上面示例中的每个注释块都包含一个结构命令,因此所有注释块都可以移动到另一个位置或输入文件(例如源文件),而不会影响生成的文档。这种方法的缺点是原型会重复,因此所有更改都必须进行两次!因此,您应该首先考虑这是否真的需要,并尽可能避免使用结构命令。我经常收到在函数前面的注释块中包含 \fn 命令的示例。这显然是 \fn 命令是多余的并且只会导致问题的情况。

当您将注释块放在扩展名为 .dox.txt.doc.md.markdown 的文件中,或者当扩展名通过 EXTENSION_MAPPING 映射到 md 时,Doxygen 将从文件列表中隐藏此文件。

如果您有一个 Doxygen 无法解析但仍然想记录的文件,您可以使用 \verbinclude 以原样显示它,例如

/*! \file myscript.sh
 *  Look at this nice script:
 *  \verbinclude myscript.sh
 */

确保脚本明确列在 INPUT 中,或者 FILE_PATTERNS 包含 .sh 扩展名,并且可以在通过 EXAMPLE_PATH 设置的路径中找到该脚本。

Python 中的注释块

对于 Python,有一种使用所谓的文档字符串 (""") 来记录代码的标准方法。此类字符串存储在 __doc__ 中,并且可以在运行时检索。Doxygen 将提取此类注释,并假设它们必须以预格式化的方式表示。

"""@package docstring
此模块的文档。
更多详细信息。
"""
def func()
"""一个函数的文档。
更多详细信息。
"""
pass
class PyClass
"""一个类的文档。
更多详细信息。
"""
def __init__(self)
"""构造函数。"""
self._memVar = 0;
def PyMethod(self)
"""一个方法的文档。"""
pass

点击这里查看 Doxygen 生成的相应 HTML 文档。

注意
当使用 """ 时,不支持 Doxygen 的任何特殊命令,并且文本以逐字文本显示,请参阅 \verbatim。要使用 Doxygen 的特殊命令并将文本作为常规文档而不是 """,请使用 """! 或在配置文件中将 PYTHON_DOCSTRING 设置为 NO
除了 """,还可以使用 '''

还有另一种方法可以使用以 "##" 或 "##<" 开头的注释来记录 Python 代码。这些类型的注释块更符合 Doxygen 支持的其他语言的文档块的工作方式,并且这也允许使用特殊命令。

这是相同的示例,但现在使用 Doxygen 样式的注释

## @package pyexample
# 此模块的文档。
#
# 更多详细信息。
## 一个函数的文档。
#
# 更多详细信息。
def func()
pass
## 一个类的文档。
#
# 更多详细信息。
class PyClass
## 构造函数。
def __init__(self)
self._memVar = 0;
## 一个方法的文档。
# @param self 对象指针。
def PyMethod(self)
pass
## 一个类变量。
classVar = 0;
## @var _memVar
# 一个成员变量

点击这里查看 Doxygen 生成的相应 HTML 文档。

由于 Python 看起来更像 Java 而不是 C 或 C++,您应该在配置文件中将 OPTIMIZE_OUTPUT_JAVA 设置为 YES

VHDL 中的注释块

对于 VHDL,注释通常以“--”开头。Doxygen 将提取以“--!”开头的注释。VHDL 中只有两种类型的注释块;单行“--!”注释表示简要描述,多行“--!”注释(其中每行都重复使用“--!”前缀)表示详细描述。

注释始终位于要记录的项的前面,但有一个例外:对于端口,注释也可以位于项的后面,此时被视为该端口的简要描述。

以下是一个带有 Doxygen 注释的 VHDL 文件示例

-------------------------------------------------------
--! @file
--! @brief 使用 with-select 的 2:1 多路复用器
-------------------------------------------------------
--! 使用标准库
library ieee;
--! 使用逻辑元素
use ieee.std_logic_1164.all;
--! 多路复用器实体简要描述
--! 此多路复用器设计元素的详细描述。
--! mux design element.
entity mux_using_with is
port (
din_0 : in std_logic; --! 多路复用器的第一个输入
din_1 : in std_logic; --! 多路复用器的第二个输入
sel : in std_logic; --! 选择输入
mux_out : out std_logic --! 多路复用器的输出
);
end entity;
--! @brief MUX 的架构定义
--! @details 关于此多路复用器元素的更多详细信息。
architecture behavior of mux_using_with is
begin
with (sel) select
mux_out <= din_0 when '0',
din_1 when others;
end architecture;

点击此处查看 Doxygen 生成的相应 HTML 文档。

从 VHDL 2008 开始,也可以使用 /* 样式注释。
Doxygen 将 /* ... */ 处理为普通注释,并将 /*! ... */ 样式注释处理为 Doxygen 要解析的特殊注释。

要获得外观正确的输出,您需要在配置文件中将 OPTIMIZE_OUTPUT_VHDL 设置为 YES。这也会影响许多其他设置。如果它们尚未正确设置,Doxygen 将发出警告,告知哪些设置被覆盖。

Fortran 中的注释块

当将 Doxygen 用于 Fortran 代码时,您应该将 OPTIMIZE_FOR_FORTRAN 设置为 YES

解析器会尝试猜测源代码是固定格式的 Fortran 代码还是自由格式的 Fortran 代码。这可能并不总是正确。如果不是,则应使用 EXTENSION_MAPPING 来纠正此问题。通过设置 EXTENSION_MAPPING = f=FortranFixed f90=FortranFree,扩展名为 f 的文件被解释为固定格式的 Fortran 代码,扩展名为 f90 的文件被解释为自由格式的 Fortran 代码。

对于 Fortran,“!>”或“!<”表示注释开始,“!!”或“!>”可用于将单行注释延续为多行注释。

以下是一个已记录的 Fortran 子例程的示例

!> 构建聚合的约束矩阵
!! 方法。
!! @param aggr 关于聚合的信息
!! @todo 处理特殊情况
subroutine intrestbuild(A,aggr,Restrict,A_ghost)
implicit none
Type(SpMtx), intent(in) :: A !< 我们的精细级别矩阵
Type(Aggrs), intent(in) :: aggr
Type(SpMtx), intent(out) :: Restrict !< 我们的约束矩阵
!...
end subroutine

作为替代方案,您也可以在固定格式代码中使用注释

C> 函数注释
C> 注释的另一行
function a(i)
C> 输入参数
integer i
end function A

注释块的结构

上一节重点介绍了如何使代码中的注释为 Doxygen 所知,它解释了简要描述和详细描述之间的区别,以及结构命令的使用。

在本节中,我们将查看注释块本身的内容。

Doxygen 支持多种格式化注释的样式。

最简单的形式是使用纯文本。这将在输出中按原样显示,非常适合简短描述。

对于较长的描述,您通常会需要更多结构,例如逐字文本块、列表或简单表格。为此,Doxygen 支持 Markdown 语法,包括 Markdown Extra 扩展的一部分。

Markdown 被设计成非常易于阅读和书写。其格式受纯文本邮件的启发。Markdown 非常适合简单的通用格式,例如项目的介绍页面。Doxygen 还支持直接读取 Markdown 文件。有关更多详细信息,请参阅Markdown 支持章节。

对于特定于编程语言的格式,Doxygen 在 Markdown 格式的基础上提供了两种附加标记形式。

  1. Javadoc 样式的标记。有关 Doxygen 支持的所有命令的完整概述,请参阅特殊命令
  2. XML 标记,如 C# 标准中所指定。有关 Doxygen 支持的 XML 命令,请参阅XML 命令

如果这仍然不够,Doxygen 还支持 一部分 HTML 标记语言。

转到下一节或返回到索引