为代码编写文档

本章涵盖两个主题

  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
     ***********************************************/
    

    注意:使用两个斜杠来结束普通注释块并开始一个特殊注释块。

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

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

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

    只要将 JAVADOC_BANNER 设置为 YES

    /**
    * JavaDoc 风格(C 风格)注释的简要历史。
    *
    * 这是典型的 JavaDoc 风格 C 风格注释。它以两个
    * 星号开头。
    *
    * @param theory 即使只有一个可能的统一理论。它也只是一个
    * 规则和方程的集合。
    */
    void cstyle( int theory );
    /******************************************************************************
    * JavaDoc 风格(C 风格)横幅注释的简要历史。
    *
    * 这是典型的 JavaDoc 风格 C 风格“横幅”注释。它以
    * 一个正斜杠后跟若干个星号(n > 2)开头。这样写是为了
    * 让阅读源代码的开发者更容易“看到”。
    * 源代码。
    *
    * 通常,开发者没有意识到这(默认情况下)不是一个有效的 Doxygen
    * 注释块!
    *
    * 但是,只要在 Doxyfile 中添加 JAVADOC_BANNER = YES,它就可以
    * 正常工作。
    *
    * 这种注释风格与 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 规范中文档块的第一句话会自动被视为简要描述。要启用此行为,您应该在配置文件中将 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 风格注释块内部)。

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

结构命令(像所有其他命令一样)以反斜杠(\)开头,如果您更喜欢 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;
--! 多路选择器实体简要描述
--! 此多路选择器设计元素的详细描述。
--! 多路选择器设计元素。
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 多路选择器的架构定义
--! @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# 标准中所指定。请参阅 XML 命令 以获取 Doxygen 支持的 XML 命令。

如果这些仍然不够,Doxygen 还支持 HTML 标记语言的子集

前往下一节或返回索引