用 C(和其他命令式语言)漂亮地打印二叉树

发布于 2024-10-08 17:29:15 字数 9890 浏览 5 评论 0 原文

第一次发帖,在编程方面相当新,所以请耐心等待!

我对用于打印格式化二叉树(在 CLI 环境中)的高效通用算法和 C实施。这是我自己编写的一些代码(这是原始版本的简化版本,也是支持许多 BST 操作的较大程序的一部分,但它应该可以正常编译):(

#include <stdbool.h>    // C99, boolean type support
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#define DATATYPE_IS_DOUBLE
#define NDEBUG                      // disable assertions
#include <assert.h>

#define WCHARBUF_LINES          20  // def: 20
#define WCHARBUF_COLMS          800 // def: 80 (using a huge number, like 500, is a good idea,
                                    //          in order to prevent a buffer overflow :)
#define RECOMMENDED_CONS_WIDTH  150
#define RECOMMENDED_CONS_WIDTHQ "150"   // use the same value, quoted

/* Preprocessor directives depending on DATATYPE_IS_* : */
#if defined DATATYPE_IS_INT || defined DATATYPE_IS_LONG
    #define DTYPE           long int
    #define DTYPE_STRING    "INTEGER"
    #define DTYPE_PRINTF    "%*.*ld"
    #undef DATATYPE_IS_CHAR

#elif defined DATATYPE_IS_FLOAT
    #define DTYPE           float
    #define DTYPE_STRING    "FLOAT"
    #define DTYPE_PRINTF    "%*.*f"
    #undef DATATYPE_IS_CHAR

#elif defined DATATYPE_IS_DOUBLE
    #define DTYPE           double
    #define DTYPE_STRING    "DOUBLE"
    #define DTYPE_PRINTF    "%*.*lf"
    #undef DATATYPE_IS_CHAR

#elif defined DATATYPE_IS_CHAR
    #define DTYPE           char
    #define DTYPE_STRING    "CHARACTER"
    #define DTYPE_PRINTF    "%*.*c" /* using the "precision" sub-specifier ( .* ) with a  */
                                    /* character will produce a harmless compiler warning */
#else
    #error "DATATYPE_IS_* preprocessor directive undefined!"

#endif


typedef struct node_struct {
    DTYPE data;
    struct node_struct *left;
    struct node_struct *right;
    /* int height;  // useful for AVL trees */
} node;

typedef struct {
    node *root;
    bool IsAVL;     // useful for AVL trees
    long size;
} tree;


static inline
DTYPE get_largest(node *n){
    if (n == NULL)
        return (DTYPE)0;

    for(; n->right != NULL; n=n->right);

    return n->data;
}

static
int subtreeheight(node *ST){
    if (ST == NULL)
        return -1;

    int height_left  = subtreeheight(ST->left);
    int height_right = subtreeheight(ST->right);

    return (height_left > height_right) ? (height_left + 1) : (height_right + 1);
}


void prettyprint_tree(tree *T){
    if (T == NULL)  // if T empty, abort
        return;

#ifndef DATATYPE_IS_CHAR /* then DTYPE is a numeric type */
    /* compute spaces, find width: */
    int width, i, j;
    DTYPE max = get_largest(T->root);

    width = (max < 10) ? 1 :
            (max < 100) ? 2 :
            (max < 1000) ? 3 :
            (max < 10000) ? 4 :
            (max < 100000) ? 5 :
            (max < 1000000) ? 6 :
            (max < 10000000) ? 7 :
            (max < 100000000) ? 8 :
            (max < 1000000000) ? 9 : 10;
    assert  (max < 10000000000);

    width += 2; // needed for prettier results

#if defined DATATYPE_IS_FLOAT || defined DATATYPE_IS_DOUBLE
    width += 2; // because of the decimals! (1 decimal is printed by default...)
#endif // float or double

    int spacesafter = width / 2;
    int spacesbefore = spacesafter + 1;
    //int spacesbefore = ceil(width / 2.0);

#else /* character input */
    int i, j, width = 3, spacesbefore = 2, spacesafter = 1;

#endif // #ifndef DATATYPE_IS_CHAR

    /* start wchar_t printing, using a 2D character array with swprintf() : */

    struct columninfo{  // auxiliary structure
        bool visited;
        int  col;
    };

    wchar_t wcharbuf[WCHARBUF_LINES][WCHARBUF_COLMS];
    int line=0;
    struct columninfo eachline[WCHARBUF_LINES];

    for (i=0; i<WCHARBUF_LINES; ++i){       // initialization
        for (j=0; j<WCHARBUF_COLMS; ++j)
            wcharbuf[i][j] = (wchar_t)' ';
        eachline[i].visited = false;
        eachline[i].col = 0;
    }

    int height = subtreeheight(T->root);

    void recur_swprintf(node *ST, int cur_line, const wchar_t *nullstr){ // nested function,
                                                                            // GCC extension!
        float offset = width * pow(2, height - cur_line);

        ++cur_line;

        if (eachline[cur_line].visited == false) {
            eachline[cur_line].col = (int) (offset / 2);
            eachline[cur_line].visited = true;
        }
        else{
            eachline[cur_line].col += (int) offset;
            if (eachline[cur_line].col + width > WCHARBUF_COLMS)
                swprintf(wcharbuf[cur_line], L"  BUFFER OVERFLOW DETECTED! ");
        }

        if (ST == NULL){
            swprintf(wcharbuf[cur_line] + eachline[cur_line].col, L"%*.*s", 0, width, nullstr);
            if (cur_line <= height){
                /* use spaces instead of the nullstr for all the "children" of a NULL node */
                recur_swprintf(NULL, cur_line, L"          ");
                recur_swprintf(NULL, cur_line, L"          ");
            }
            else
                return;
        }
        else{
            recur_swprintf(ST->left,  cur_line, nullstr);
            recur_swprintf(ST->right, cur_line, nullstr);

            swprintf(wcharbuf[cur_line] + eachline[cur_line].col - 1, L"("DTYPE_PRINTF"",
                     spacesbefore, 1, ST->data);

          //swprintf(wcharbuf[cur_line] + eachline[cur_line].col + spacesafter + 1, L")");
            swprintf(wcharbuf[cur_line] + eachline[cur_line].col + spacesafter + 2, L")");
        }
    }

    void call_recur(tree *tr){  // nested function, GCC extension! (wraps recur_swprintf())
        recur_swprintf(tr->root, -1, L"NULL");
    }

    call_recur(T);

    /* Omit empty columns: */
    int omit_cols(void){        // nested function, GCC extension!
        int col;

        for (col=0; col<RECOMMENDED_CONS_WIDTH; ++col)
            for (line=0; line <= height+1; ++line)
                if (wcharbuf[line][col] != ' ' && wcharbuf[line][col] != '\0')
                    return col;

        return 0;
    }

    /* Use fputwc to transfer the character array to the screen: */
    j = omit_cols() - 2;
    j = (j < 0) ? 0 : j;

    for (line=0; line <= height+1; ++line){     // assumes RECOMMENDED_CONS_WIDTH console window!
        fputwc('\n', stdout);                   // optional blanc line
        for (i=j; i<j+RECOMMENDED_CONS_WIDTH && i<WCHARBUF_COLMS; ++i)
            fputwc(wcharbuf[line][i], stdout);
        fputwc('\n', stdout);
    }
}

也上传到 一个pastebin服务,为了保留语法突出显示

它工作得很好,尽管自动宽度设置可能会更好。预处理器的魔力有点傻(甚至丑陋),与算法没有真正的关系,但它允许在树节点中使用各种数据类型(我认为这是一个尝试预处理器的机会 - 请记住,我是新手!)。

当在 cmd.exe 中运行时,主程序应该

system("mode con:cols="RECOMMENDED_CONS_WIDTHQ" lines=2000");

在调用 Prettyprint_tree() 之前调用。

示例输出:

                                                  (106.0)


                      (102.0)                                                 (109.0)


        (101.5)                      NULL                       (107.0)                     (115.0)


  NULL          NULL                                     (106.1)        NULL         (113.0)        NULL


                                                      NULL   NULL                 NULL   NULL

理想情况下,输出应如下所示(我使用 wprintf() 系列函数的原因是无论如何都能够打印 Unicode 字符):

            (107.0)
         ┌─────┴─────┐
     (106.1)        NULL
   ┌───┴───┐
  NULL   NULL

那么,我的问题:

  • 您对这段代码有何看法? (也非常欢迎编码风格的建议!)
  • 是否可以以优雅的方式扩展以包含画线字符? (不幸的是,我不这么认为。)
  • C 或其他命令式语言(或命令式伪代码)中还有其他算法吗?
  • 有些不相关:您对嵌套函数有何看法 >(不可移植的 GNU 扩展)?我认为这是一种编写函数的递归部分的优雅方法,而不必提供所有局部变量作为参数(并且作为实现隐藏技术也很有用),但这可能是我的 Pascal 过去:-)我感兴趣更有经验的编码员的意见。

预先感谢您的回复!

附言。这个问题不是这个问题重复。


编辑: Jonathan Leffler 写了一篇

  • 上面的代码实际上是一个更大的“家庭作业”项目的一部分(在共享库中实现 BST 操作 + 使用该库的 CLI 应用程序)。然而,“prettyprint”功能不是需求的一部分;只是我决定自己添加一些东西。
  • 我还添加了一个“不旋转而转换为 AVL”函数,该函数使用“arraystr”作为中间表示;-) 我忘记了这里没有使用它。我已经编辑了代码以将其删除。此外,bool IsAVL 结构成员也没有被使用;只是没有在此特定功能中使用。我必须从各种文件中复制/粘贴代码并进行大量更改才能呈现上面引用的代码。这是一个我不知道如何解决的问题。我很乐意发布整个程序,但它太大了并且用我的母语(而不是英语!)进行评论。
  • 整个项目大约有 1600 个 LOC(包括注释),具有多个构建目标(调试/发布/静态链接),并且使用 -Wall- 编译干净 Wextra 已启用。根据构建目标自动启用/禁用断言和调试消息。另外,我认为嵌套函数不需要函数原型,毕竟嵌套函数根据定义不实现任何外部接口 - GCC 当然没有在这里抱怨。我不知道为什么 OSX 上有这么多警告:(
  • 我在 Windows 7 上使用 GCC 4.4.1。
  • 尽管在 Windows 上编写和测试了这个程序,但我实际上是一个 Linux 用户......不过,我可以'不支持 vim,我使用 nano (在 GNU 屏幕内)或 gedit (射击我)无论如何,我更喜欢 K& 。 R 大括号风格 :)
  • 可移植性并不重要,对于 Linux 用户来说,GCC 几乎事实上...事实上,它在 Windows 下也能很好地工作,这是一个很好的奖励。
  • 我没有使用 VCS,也许我应该使用。我想尝试一下,但所有这些对于我的需求来说似乎都太复杂了,我不知道如何选择一个:-)
  • 关于检查深度溢出,你绝对是对的,值得庆幸的是它很容易添加。
  • 感谢L' '的建议!
  • 我发现你的建议(封装“整个绘图代码,以便屏幕图像和相关信息位于单个结构中”非常有趣......但我不知道不太明白你所说的“封装”是什么意思。您能否提供 3 或 4 行(伪)代码来显示可能的函数声明和/或可能的函数调用?

这是我的第一个“大型”(而且不平凡)的程序,我非常感谢您的建议。


编辑#2: 这是此处提到的“快速而肮脏”方法的实现。
编辑#3:我决定将其拆分为单独的答案,因为它是对OP的有效答案。)

许多回复都提到了Graphviz。我已经知道它了(许多 Linux 应用程序都与它链接),但我认为这对于 10KB CLI 可执行文件来说有点过大了。不过,我会牢记在心,以备将来之用。看起来很棒。

(First-time poster and rather new in programming, so be patient, please!)

I'm interested in both an efficient general algorithm for printing formatted binary trees (in a CLI environment) and a C implementation. Here is some code that I wrote myself for fun (this is a much simplified version of the original and part of a larger program supporting many BST operations, but it should compile just fine):

#include <stdbool.h>    // C99, boolean type support
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#define DATATYPE_IS_DOUBLE
#define NDEBUG                      // disable assertions
#include <assert.h>

#define WCHARBUF_LINES          20  // def: 20
#define WCHARBUF_COLMS          800 // def: 80 (using a huge number, like 500, is a good idea,
                                    //          in order to prevent a buffer overflow :)
#define RECOMMENDED_CONS_WIDTH  150
#define RECOMMENDED_CONS_WIDTHQ "150"   // use the same value, quoted

/* Preprocessor directives depending on DATATYPE_IS_* : */
#if defined DATATYPE_IS_INT || defined DATATYPE_IS_LONG
    #define DTYPE           long int
    #define DTYPE_STRING    "INTEGER"
    #define DTYPE_PRINTF    "%*.*ld"
    #undef DATATYPE_IS_CHAR

#elif defined DATATYPE_IS_FLOAT
    #define DTYPE           float
    #define DTYPE_STRING    "FLOAT"
    #define DTYPE_PRINTF    "%*.*f"
    #undef DATATYPE_IS_CHAR

#elif defined DATATYPE_IS_DOUBLE
    #define DTYPE           double
    #define DTYPE_STRING    "DOUBLE"
    #define DTYPE_PRINTF    "%*.*lf"
    #undef DATATYPE_IS_CHAR

#elif defined DATATYPE_IS_CHAR
    #define DTYPE           char
    #define DTYPE_STRING    "CHARACTER"
    #define DTYPE_PRINTF    "%*.*c" /* using the "precision" sub-specifier ( .* ) with a  */
                                    /* character will produce a harmless compiler warning */
#else
    #error "DATATYPE_IS_* preprocessor directive undefined!"

#endif


typedef struct node_struct {
    DTYPE data;
    struct node_struct *left;
    struct node_struct *right;
    /* int height;  // useful for AVL trees */
} node;

typedef struct {
    node *root;
    bool IsAVL;     // useful for AVL trees
    long size;
} tree;


static inline
DTYPE get_largest(node *n){
    if (n == NULL)
        return (DTYPE)0;

    for(; n->right != NULL; n=n->right);

    return n->data;
}

static
int subtreeheight(node *ST){
    if (ST == NULL)
        return -1;

    int height_left  = subtreeheight(ST->left);
    int height_right = subtreeheight(ST->right);

    return (height_left > height_right) ? (height_left + 1) : (height_right + 1);
}


void prettyprint_tree(tree *T){
    if (T == NULL)  // if T empty, abort
        return;

#ifndef DATATYPE_IS_CHAR /* then DTYPE is a numeric type */
    /* compute spaces, find width: */
    int width, i, j;
    DTYPE max = get_largest(T->root);

    width = (max < 10) ? 1 :
            (max < 100) ? 2 :
            (max < 1000) ? 3 :
            (max < 10000) ? 4 :
            (max < 100000) ? 5 :
            (max < 1000000) ? 6 :
            (max < 10000000) ? 7 :
            (max < 100000000) ? 8 :
            (max < 1000000000) ? 9 : 10;
    assert  (max < 10000000000);

    width += 2; // needed for prettier results

#if defined DATATYPE_IS_FLOAT || defined DATATYPE_IS_DOUBLE
    width += 2; // because of the decimals! (1 decimal is printed by default...)
#endif // float or double

    int spacesafter = width / 2;
    int spacesbefore = spacesafter + 1;
    //int spacesbefore = ceil(width / 2.0);

#else /* character input */
    int i, j, width = 3, spacesbefore = 2, spacesafter = 1;

#endif // #ifndef DATATYPE_IS_CHAR

    /* start wchar_t printing, using a 2D character array with swprintf() : */

    struct columninfo{  // auxiliary structure
        bool visited;
        int  col;
    };

    wchar_t wcharbuf[WCHARBUF_LINES][WCHARBUF_COLMS];
    int line=0;
    struct columninfo eachline[WCHARBUF_LINES];

    for (i=0; i<WCHARBUF_LINES; ++i){       // initialization
        for (j=0; j<WCHARBUF_COLMS; ++j)
            wcharbuf[i][j] = (wchar_t)' ';
        eachline[i].visited = false;
        eachline[i].col = 0;
    }

    int height = subtreeheight(T->root);

    void recur_swprintf(node *ST, int cur_line, const wchar_t *nullstr){ // nested function,
                                                                            // GCC extension!
        float offset = width * pow(2, height - cur_line);

        ++cur_line;

        if (eachline[cur_line].visited == false) {
            eachline[cur_line].col = (int) (offset / 2);
            eachline[cur_line].visited = true;
        }
        else{
            eachline[cur_line].col += (int) offset;
            if (eachline[cur_line].col + width > WCHARBUF_COLMS)
                swprintf(wcharbuf[cur_line], L"  BUFFER OVERFLOW DETECTED! ");
        }

        if (ST == NULL){
            swprintf(wcharbuf[cur_line] + eachline[cur_line].col, L"%*.*s", 0, width, nullstr);
            if (cur_line <= height){
                /* use spaces instead of the nullstr for all the "children" of a NULL node */
                recur_swprintf(NULL, cur_line, L"          ");
                recur_swprintf(NULL, cur_line, L"          ");
            }
            else
                return;
        }
        else{
            recur_swprintf(ST->left,  cur_line, nullstr);
            recur_swprintf(ST->right, cur_line, nullstr);

            swprintf(wcharbuf[cur_line] + eachline[cur_line].col - 1, L"("DTYPE_PRINTF"",
                     spacesbefore, 1, ST->data);

          //swprintf(wcharbuf[cur_line] + eachline[cur_line].col + spacesafter + 1, L")");
            swprintf(wcharbuf[cur_line] + eachline[cur_line].col + spacesafter + 2, L")");
        }
    }

    void call_recur(tree *tr){  // nested function, GCC extension! (wraps recur_swprintf())
        recur_swprintf(tr->root, -1, L"NULL");
    }

    call_recur(T);

    /* Omit empty columns: */
    int omit_cols(void){        // nested function, GCC extension!
        int col;

        for (col=0; col<RECOMMENDED_CONS_WIDTH; ++col)
            for (line=0; line <= height+1; ++line)
                if (wcharbuf[line][col] != ' ' && wcharbuf[line][col] != '\0')
                    return col;

        return 0;
    }

    /* Use fputwc to transfer the character array to the screen: */
    j = omit_cols() - 2;
    j = (j < 0) ? 0 : j;

    for (line=0; line <= height+1; ++line){     // assumes RECOMMENDED_CONS_WIDTH console window!
        fputwc('\n', stdout);                   // optional blanc line
        for (i=j; i<j+RECOMMENDED_CONS_WIDTH && i<WCHARBUF_COLMS; ++i)
            fputwc(wcharbuf[line][i], stdout);
        fputwc('\n', stdout);
    }
}

(also uploaded to a pastebin service, in order to preserve syntax highlighting)

It works quite well, although the automatic width setting could be better. The preprocessor magic is a bit silly (or even ugly) and not really related to the algorithm, but it allows using various data types in the tree nodes (I saw it as a chance to experiment a bit with the preprocessor - remember, I am a newbie!).

The main program is supposed to call

system("mode con:cols="RECOMMENDED_CONS_WIDTHQ" lines=2000");

before calling prettyprint_tree(), when running inside cmd.exe .

Sample output:


                                                  (106.0)


                      (102.0)                                                 (109.0)


        (101.5)                      NULL                       (107.0)                     (115.0)


  NULL          NULL                                     (106.1)        NULL         (113.0)        NULL


                                                      NULL   NULL                 NULL   NULL

Ideally, the output would be like this (the reason I'm using the wprintf() family of functions is being able to print Unicode characters anyway):

            (107.0)
         ┌─────┴─────┐
     (106.1)        NULL
   ┌───┴───┐
  NULL   NULL

So, my questions:

  • What do you think about this code? (Coding style suggestions are also very welcome!)
  • Can it be extended in an elegant way in order to include the line-drawing characters? (Unfortunately, I don't think so.)
  • Any other algorithms in C or other imperative languages (or imperative pseudo-code)?
  • Somewhat unrelated: What's your opinion about nested functions (non-portable GNU extension)? I think it's an elegant way to write recursive parts of a function without having to provide all the local variables as arguments (and also useful as an implementation-hiding technique), but it could be my Pascal past :-) I'm interested in the opinion of more experienced coders.

Thank you in advance for your responses!

PS. The question is not a duplicate of this one.


edit:
Jonathan Leffler wrote an excellent answer that will most probably become the "accepted answer" after a few days (unless someone posts something equally awesome!). I decided to respond here instead of commenting because of the space constraints.

  • The code above is actually part of a larger "homework" project (implementing BST operations in a shared library + a CLI app using that library). However, the "prettyprint" function was not part of the requirements; just something I decided to add myself.
  • I also added a "convert to AVL without rotations" function, that used "arraystr" as an intermediate representation ;-) I forgot that it wasn't used here. I've edited the code to remove it. Also, the bool IsAVL struct member is anything but unused; just not used in this particular function. I had to copy/paste code from various files and make a lot of changes in order to present the code cited above. That's a problem that I don't know how to solve. I would gladly post the whole program, but it is too large and commented in my mother-tongue (not in English!).
  • The whole project was about 1600 LOC (including comments) with multiple build targets (debug/release/static-linking) and it compiled cleanly with -Wall and -Wextra enabled. Assertions and debug messages were enabled/disabled automatically depending on the build target. Also I thought that function prototypes weren't needed for nested functions, after all nested functions do not implement any external interface by definition - GCC certainly didn't complain here. I don't know why there are so many warnings on OSX :(
  • I'm using GCC 4.4.1 on Windows 7.
  • Despite writing and testing this program on Windows, I am actually a Linux user... Still, I can't stand vim and I use nano (inside GNU screen) or gedit instead (shoot me)! In any case, I prefer the K&R brace style :)
  • Portability doesn't really matter, for Linux users GCC is pretty much de facto... The fact that it also works well under Windows is a nice bonus.
  • I'm not using a VCS, perhaps I should. I want to try, but all of them seem too complex for my needs and I don't know how to choose one :-)
  • You are definitely right about checking for depth overflow, thankfully it is very easy to add.
  • Thanks for the L' ' advice!
  • I find your suggestion (encapsulating "the whole of the drawing code so that the screen image and related information is in a single structure") extremely interesting... but I don't really understand what you mean as "encapsulation". Could you, please, provide 3 or 4 lines of (pseudo)code showing a possible function declaration and/or a possible function call?

This is my first "large-ish" (and non-trivial) program and I'm really thankful for your advice.


edit #2:
Here is an implementation of the "quick and dirty" method mentioned here.
(edit #3: I decided to split it to a separate answer, since it is a valid answer to the OP.)

Many responses mentioned Graphviz. I already knew about it (many Linux apps are linked against it) but I thought it would be overkill for a 10KB CLI executable. However, I'll keep it in mind for the future. It seems great.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

℉絮湮 2024-10-15 17:29:16

也许你可以看看 Bresenham 线算法 它可能适合你

Maybe you can take a look at the Bresenham's line algorithm that it could be suitable for you

冰魂雪魄 2024-10-15 17:29:16

这是此处提到的“快速而肮脏”方法的 C 实现。它并没有变得更快和/或更脏:

void shittyprint_tree(tree *T){ // Supposed to be quick'n'dirty!
                                // When DTYPE is "char", width is a bit larger than needed.
    if (T == NULL)
        return;

    const int width = ceil(log10(get_largest(T->root)+0.01)) + 2;
    const wchar_t* sp64 = L"                                                                ";

    void nested(node *ST, int spaces){  // GCC extension
        if (ST == NULL){
            wprintf(L"\n");             // Can be commented to disable the extra blanc line.
            return;
        }
        nested(ST->right, spaces + width);
        wprintf(L"%*.*s("DTYPE_PRINTF")\n", 0, spaces, sp64, 1, 1, ST->data);
        nested(ST->left, spaces + width);
    }

    nested(T->root, 2);
}

示例输出(使用与以前相同的树):

            (115.0)

                 (113.0)

       (109.0)

            (107.0)

                 (106.1)

  (106.0)

       (102.0)

            (101.5)

不过,我不能说它符合我最初的要求......

Here is a C implementation of the "quick and dirty" method mentioned here. It doesn't get much quicker and/or dirtier:

void shittyprint_tree(tree *T){ // Supposed to be quick'n'dirty!
                                // When DTYPE is "char", width is a bit larger than needed.
    if (T == NULL)
        return;

    const int width = ceil(log10(get_largest(T->root)+0.01)) + 2;
    const wchar_t* sp64 = L"                                                                ";

    void nested(node *ST, int spaces){  // GCC extension
        if (ST == NULL){
            wprintf(L"\n");             // Can be commented to disable the extra blanc line.
            return;
        }
        nested(ST->right, spaces + width);
        wprintf(L"%*.*s("DTYPE_PRINTF")\n", 0, spaces, sp64, 1, 1, ST->data);
        nested(ST->left, spaces + width);
    }

    nested(T->root, 2);
}

Sample output (using the same tree as before):

            (115.0)

                 (113.0)

       (109.0)

            (107.0)

                 (106.1)

  (106.0)

       (102.0)

            (101.5)

I can't say, though, that it fits my original requirements...

沩ん囻菔务 2024-10-15 17:29:15

您需要决定您的代码是否需要可移植。如果您可能需要使用 GCC 以外的编译器,那么嵌套函数对于您的可移植性目标来说是致命的。我不会使用它们 - 但我的可移植性目标可能与您的不同。

您的代码缺少 ;如果没有它,它的编译相当干净 - GCC 抱怨缺少非静态函数以及 swprintf() 和 fputwc() 的原型,但添加了 < wchar.h> 生成许多与 swprintf() 相关的严重警告;他们实际上是在诊断错误。

gcc -O -I/Users/jleffler/inc -std=c99 -Wall -Wextra -Wmissing-prototypes \
    -Wstrict-prototypes -Wold-style-definition -c tree.c
tree.c:88:6: warning: no previous prototype for ‘prettyprint_tree’
tree.c: In function ‘prettyprint_tree’:
tree.c:143:10: warning: no previous prototype for ‘recur_swprintf’
tree.c: In function ‘recur_swprintf’:
tree.c:156:17: warning: passing argument 2 of ‘swprintf’ makes integer from pointer without a cast
/usr/include/wchar.h:135:5: note: expected ‘size_t’ but argument is of type ‘int *’
tree.c:156:17: error: too few arguments to function ‘swprintf’
/usr/include/wchar.h:135:5: note: declared here
tree.c:160:13: warning: passing argument 2 of ‘swprintf’ makes integer from pointer without a cast
/usr/include/wchar.h:135:5: note: expected ‘size_t’ but argument is of type ‘int *’
tree.c:174:22: warning: passing argument 2 of ‘swprintf’ makes integer from pointer without a cast
/usr/include/wchar.h:135:5: note: expected ‘size_t’ but argument is of type ‘int *’
tree.c:174:22: warning: passing argument 3 of ‘swprintf’ makes pointer from integer without a cast
/usr/include/wchar.h:135:5: note: expected ‘const wchar_t * restrict’ but argument is of type ‘int’
tree.c:177:13: warning: passing argument 2 of ‘swprintf’ makes integer from pointer without a cast
/usr/include/wchar.h:135:5: note: expected ‘size_t’ but argument is of type ‘int *’
tree.c:177:13: error: too few arguments to function ‘swprintf’
/usr/include/wchar.h:135:5: note: declared here
tree.c: In function ‘prettyprint_tree’:
tree.c:181:10: warning: no previous prototype for ‘call_recur’
tree.c:188:9: warning: no previous prototype for ‘omit_cols’

(这是 MacOS X 10.6.5 上的 GCC 4.5.2。)

  • 请查找 swprintf() 的接口;它更像是 snprintf() 而不是 sprintf()(这是一件好事™!)。

整体想法很有趣。我建议在提交代码进行分析时选择一种表示形式,并清理与代码分析无关的任何内容。例如,arraystr 类型已定义但未使用 - 您不想让像我这样的人对您的代码进行廉价攻击。与未使用的结构成员类似;甚至不要将它们保留为注释,即使您可能希望将它们保留在 VCS 的代码中(为什么?)。您正在使用版本控制系统(VCS),不是吗?这是一个反问句 - 如果您没有使用 VCS,请立即开始使用,以免失去您所珍视的东西。

在设计方面,您希望避免执行诸如要求主程序运行晦涩的 system() 命令之类的事情 - 您的代码应该处理此类问题(可能使用初始化函数,也可能使用终结器)功能撤消对终端设置所做的更改)。

不喜欢嵌套函数的另一个原因是:我不知道如何获得函数的声明。看似合理的替代方案并没有起作用——但我没有去阅读有关它们的 GCC 手册。

  • 您检查列宽是否溢出;您不检查深度溢出。如果您创建的树太深,您的代码将会崩溃并烧毁。

小问题:您可以告诉那些不使用“vi”或“vim”进行编辑的人 - 他们不会将函数的左大括号放在第 1 列中。在“vi”中,第 1 列中的左大括号为您提供了从函数内部的任何位置开始函数的简单方法(“[[”向后跳转;“]]”跳转到下一个函数的开头)。

不要禁用断言。

一定要包含主程序和相关的测试数据 - 这意味着人们可以测试您的代码,而不仅仅是编译它。

使用宽字符常量而不是强制转换:

wcharbuf[i][j] = (wchar_t)' ';

wcharbuf[i][j] = L' ';

您的代码创建一个大屏幕图像(代码中为 20 行 x 800 列)并填充要打印的数据。这是一个合理的方法。小心地,你可以安排处理画线字符。但是,我认为您需要重新考虑核心绘图算法。您可能希望封装整个绘图代码,以便屏幕图像和相关信息位于单个结构中,该结构可以通过引用(指针)传递给函数。您将拥有一组函数来在树搜索代码指定的位置绘制各种位。您将有一个函数在适当的位置绘制数据值;您将有一个在适当位置画线的功能。您可能不会有嵌套函数 - 在我看来,当一个函数嵌套在另一个函数中时,阅读代码要困难得多。使函数静态是好的;使嵌套函数成为静态(非嵌套)函数。为他们提供所需的上下文 - 从而封装屏幕图像。

  • 总体一个良好的开端;很多好主意。还有很多事情要做。

请求有关封装的信息...

您可以使用如下结构:

typedef struct columninfo Colinfo;

typedef struct Image
{
    wchar_t    image[WCHARBUF_LINES][WCHARBUF_COLUMNS];
    Colinfo    eachline[WCHARBUF_LINES];
} Image;

Image image;

您可能会发现添加一些额外的成员很方便和/或明智;这将在实施过程中显现出来。然后,您可以创建一个函数:

void format_node(Image *image, int line, int column, DTYPE value)
{
    ...
}

您还可以将一些常量(例如 spaceafter)设置为枚举值:

enum { spacesafter = 2 };

然后这些常量可以由任何函数使用。

You need to decide on whether your code needs to be portable. If you might ever need to use a compiler other than GCC, the nested functions are lethal to your portability goal. I would not use them - but my portability goals may not be the same as yours.

Your code is missing <wchar.h>; it compiles fairly cleanly without it - GCC complained about missing prototypes for your non-static functions and for swprintf() and fputwc()), but adding <wchar.h> generates a lot of serious warnings related to swprintf(); they are actually diagnosing a bug.

gcc -O -I/Users/jleffler/inc -std=c99 -Wall -Wextra -Wmissing-prototypes \
    -Wstrict-prototypes -Wold-style-definition -c tree.c
tree.c:88:6: warning: no previous prototype for ‘prettyprint_tree’
tree.c: In function ‘prettyprint_tree’:
tree.c:143:10: warning: no previous prototype for ‘recur_swprintf’
tree.c: In function ‘recur_swprintf’:
tree.c:156:17: warning: passing argument 2 of ‘swprintf’ makes integer from pointer without a cast
/usr/include/wchar.h:135:5: note: expected ‘size_t’ but argument is of type ‘int *’
tree.c:156:17: error: too few arguments to function ‘swprintf’
/usr/include/wchar.h:135:5: note: declared here
tree.c:160:13: warning: passing argument 2 of ‘swprintf’ makes integer from pointer without a cast
/usr/include/wchar.h:135:5: note: expected ‘size_t’ but argument is of type ‘int *’
tree.c:174:22: warning: passing argument 2 of ‘swprintf’ makes integer from pointer without a cast
/usr/include/wchar.h:135:5: note: expected ‘size_t’ but argument is of type ‘int *’
tree.c:174:22: warning: passing argument 3 of ‘swprintf’ makes pointer from integer without a cast
/usr/include/wchar.h:135:5: note: expected ‘const wchar_t * restrict’ but argument is of type ‘int’
tree.c:177:13: warning: passing argument 2 of ‘swprintf’ makes integer from pointer without a cast
/usr/include/wchar.h:135:5: note: expected ‘size_t’ but argument is of type ‘int *’
tree.c:177:13: error: too few arguments to function ‘swprintf’
/usr/include/wchar.h:135:5: note: declared here
tree.c: In function ‘prettyprint_tree’:
tree.c:181:10: warning: no previous prototype for ‘call_recur’
tree.c:188:9: warning: no previous prototype for ‘omit_cols’

(This is GCC 4.5.2 on MacOS X 10.6.5.)

  • Do look up the interface to swprintf(); it is more like snprintf() than sprintf() (which is A Good Thing™!).

The overall idea is interesting. I suggest choosing one representation when submitting your code for analysis, and cleaning up anything that is not relevant to the code analysis. For example, the arraystr type is defined but unused - you don't want to let people like me get cheap shots at your code. Similarly with the unused structure members; don't even leave them as comments, even if you might want to keep them in the code in your VCS (though why?). You are using a version control system (VCS), aren't you? And that's a rhetorical question - if you aren't using a VCS, start using one now, before you lose something you value.

Design-wise, you want to avoid doing things like requiring the main program to run an obscure system() command - your code should take care of such issues (maybe with an initializer function, and perhaps a finalizer function to undo the changes made to the terminal settings).

One more reason not to like nested functions: I can't work out how to get a declaration of the function in place. What seemed like plausible alternatives did not work - but I didn't go and read the GCC manual on them.

  • You check for column-width overflow; you do not check for depth overflow. Your code will crash and burn if you create a tree that is too deep.

Minor nit: you can tell people who do not use 'vi' or 'vim' to edit - they don't put the opening brace of a function in column 1. In 'vi', the opening brace in column 1 gives you an easy way to the start of a function from anywhere inside it ('[[' to jump backwards; ']]' to jump to the start of the next function).

Don't disable assertions.

Do include a main program and the relevant test data - it means people can test your code, instead of just compiling it.

Use wide-character constants instead of casts:

wcharbuf[i][j] = (wchar_t)' ';

wcharbuf[i][j] = L' ';

Your code creates a big screen image (20 lines x 800 columns in the code) and fills in the data to be printed. That's a reasonable way to do it. With care, you could arrange to handle the line-drawing characters. However, I think you would need to rethink the core drawing algorithms. You would probably want to encapsulate the whole of the drawing code so that the screen image and related information is in a single structure, which can be passed by reference (pointer) to functions. You'd have a set of functions to draw various bits at positions your tree-searching code designates. You would have a function to draw the data value at an appropriate position; you would have a function to draw lines at appropriate positions. You would probably not have nested functions - it is, to my eyes, far harder to read the code when there's a function nested inside another. Making functions static is good; make the nested functions into static (non-nested) functions. Give them the context they need - hence the encapsulation of the screen image.

  • Overall a good start; lots of good ideas. Lots still to do.

Request for information on encapsulation...

You could use a structure such as:

typedef struct columninfo Colinfo;

typedef struct Image
{
    wchar_t    image[WCHARBUF_LINES][WCHARBUF_COLUMNS];
    Colinfo    eachline[WCHARBUF_LINES];
} Image;

Image image;

You might find it convenient and/or sensible to add some extra members; that would show up during the implementation. You might then create a function:

void format_node(Image *image, int line, int column, DTYPE value)
{
    ...
}

You could also make some of the constants, such as spacesafter into enum values:

enum { spacesafter = 2 };

These can then be used by any of the functions.

空‖城人不在 2024-10-15 17:29:15

编码风格prettyprint_tree() 函数兼顾了太多的计算和数据,难以轻松阅读。例如,图像缓冲区的初始化和打印可以放置在单独的函数中,并且宽度计算也可以放置在单独的函数中。我相信你可以用log编写一个公式来代替

width = (max < 10) ? 1 :
        (max < 100) ? 2 :
        (max < 1000) ? 3 :
        ...

计算。

我不习惯阅读嵌套函数和 C,这让我更难扫描你的代码。除非您不与其他人共享您的代码,或者出于意识形态原因将代码与 GCC 绑定在一起,否则我不会使用这些扩展。

算法:对于一个用 C 编写的快速而肮脏的漂亮打印机,我永远不会使用你的布局风格。与您的算法相比,编写有序遍历来打印是很容易的

   a
  / \
 b   c

     c
 a
     b

而且我不介意必须倾斜我的头。对于比这更漂亮的东西,我宁愿发出

digraph g { a -> b; a -> c; }

并将其留给 dot 来进行格式化。

Coding style: The prettyprint_tree() function juggles too much computation and data to be comfortable to read. Initialization and printing of the image buffer can for example be placed in separate functions and the width computation also. I am sure you can write a formula with log to replace the

width = (max < 10) ? 1 :
        (max < 100) ? 2 :
        (max < 1000) ? 3 :
        ...

computation.

I am not used to reading nested functions and C, which makes it much harder for me to scan your code. Unless you don't share your code with others or have ideological reasons for tying the code to GCC, I wouldn't use those extensions.

Algorithm: For a quick and dirty pretty-printer, written in C, I would never use your style of layout. In comparison to your algorithm, it is a no-brainer to write an in-order traversal to print

   a
  / \
 b   c

as

     c
 a
     b

and I don't mind having to tilt my head. For anything prettier than that I would much rather emit

digraph g { a -> b; a -> c; }

and leave it to dot to do the formatting.

二手情话 2024-10-15 17:29:15

该代码应该从以下位置开始工作:http://www.ihas1337code.com/2010/09/how-to-pretty-print-binary-tree.html

    #include <fstream>
#include <iostream>
#include <deque>
#include <iomanip>
#include <sstream>
#include <string>
#include <cmath>
using namespace std;

struct BinaryTree {
  BinaryTree *left, *right;
  int data;
  BinaryTree(int val) : left(NULL), right(NULL), data(val) { }
};

// Find the maximum height of the binary tree
int maxHeight(BinaryTree *p) {
  if (!p) return 0;
  int leftHeight = maxHeight(p->left);
  int rightHeight = maxHeight(p->right);
  return (leftHeight > rightHeight) ? leftHeight + 1: rightHeight + 1;
}

// Convert an integer value to string
string intToString(int val) {
  ostringstream ss;
  ss << val;
  return ss.str();
}

// Print the arm branches (eg, /    \ ) on a line
void printBranches(int branchLen, int nodeSpaceLen, int startLen, int nodesInThisLevel, const deque<BinaryTree*>& nodesQueue, ostream& out) {
  deque<BinaryTree*>::const_iterator iter = nodesQueue.begin();
  for (int i = 0; i < nodesInThisLevel / 2; i++) {
    out << ((i == 0) ? setw(startLen-1) : setw(nodeSpaceLen-2)) << "" << ((*iter++) ? "/" : " ");
    out << setw(2*branchLen+2) << "" << ((*iter++) ? "\\" : " ");
  }
  out << endl;
}

// Print the branches and node (eg, ___10___ )
void printNodes(int branchLen, int nodeSpaceLen, int startLen, int nodesInThisLevel, const deque<BinaryTree*>& nodesQueue, ostream& out) {
  deque<BinaryTree*>::const_iterator iter = nodesQueue.begin();
  for (int i = 0; i < nodesInThisLevel; i++, iter++) {
    out << ((i == 0) ? setw(startLen) : setw(nodeSpaceLen)) << "" << ((*iter && (*iter)->left) ? setfill('_') : setfill(' '));
    out << setw(branchLen+2) << ((*iter) ? intToString((*iter)->data) : "");
    out << ((*iter && (*iter)->right) ? setfill('_') : setfill(' ')) << setw(branchLen) << "" << setfill(' ');
  }
  out << endl;
}

// Print the leaves only (just for the bottom row)
void printLeaves(int indentSpace, int level, int nodesInThisLevel, const deque<BinaryTree*>& nodesQueue, ostream& out) {
  deque<BinaryTree*>::const_iterator iter = nodesQueue.begin();
  for (int i = 0; i < nodesInThisLevel; i++, iter++) {
    out << ((i == 0) ? setw(indentSpace+2) : setw(2*level+2)) << ((*iter) ? intToString((*iter)->data) : "");
  }
  out << endl;
}

// Pretty formatting of a binary tree to the output stream
// @ param
// level  Control how wide you want the tree to sparse (eg, level 1 has the minimum space between nodes, while level 2 has a larger space between nodes)
// indentSpace  Change this to add some indent space to the left (eg, indentSpace of 0 means the lowest level of the left node will stick to the left margin)
void printPretty(BinaryTree *root, int level, int indentSpace, ostream& out) {
  int h = maxHeight(root);
  int nodesInThisLevel = 1;

  int branchLen = 2*((int)pow(2.0,h)-1) - (3-level)*(int)pow(2.0,h-1);  // eq of the length of branch for each node of each level
  int nodeSpaceLen = 2 + (level+1)*(int)pow(2.0,h);  // distance between left neighbor node's right arm and right neighbor node's left arm
  int startLen = branchLen + (3-level) + indentSpace;  // starting space to the first node to print of each level (for the left most node of each level only)

  deque<BinaryTree*> nodesQueue;
  nodesQueue.push_back(root);
  for (int r = 1; r < h; r++) {
    printBranches(branchLen, nodeSpaceLen, startLen, nodesInThisLevel, nodesQueue, out);
    branchLen = branchLen/2 - 1;
    nodeSpaceLen = nodeSpaceLen/2 + 1;
    startLen = branchLen + (3-level) + indentSpace;
    printNodes(branchLen, nodeSpaceLen, startLen, nodesInThisLevel, nodesQueue, out);

    for (int i = 0; i < nodesInThisLevel; i++) {
      BinaryTree *currNode = nodesQueue.front();
      nodesQueue.pop_front();
      if (currNode) {
          nodesQueue.push_back(currNode->left);
          nodesQueue.push_back(currNode->right);
      } else {
        nodesQueue.push_back(NULL);
        nodesQueue.push_back(NULL);
      }
    }
    nodesInThisLevel *= 2;
  }
  printBranches(branchLen, nodeSpaceLen, startLen, nodesInThisLevel, nodesQueue, out);
  printLeaves(indentSpace, level, nodesInThisLevel, nodesQueue, out);
}

int main() {
  BinaryTree *root = new BinaryTree(30);
  root->left = new BinaryTree(20);
  root->right = new BinaryTree(40);
  root->left->left = new BinaryTree(10);
  root->left->right = new BinaryTree(25);
  root->right->left = new BinaryTree(35);
  root->right->right = new BinaryTree(50);
  root->left->left->left = new BinaryTree(5);
  root->left->left->right = new BinaryTree(15);
  root->left->right->right = new BinaryTree(28);
  root->right->right->left = new BinaryTree(41);

  cout << "Tree pretty print with level=1 and indentSpace=0\n\n";
  // Output to console
  printPretty(root, 1, 0, cout);

  cout << "\n\nTree pretty print with level=5 and indentSpace=3,\noutput to file \"tree_pretty.txt\".\n\n";
  // Create a file and output to that file
  ofstream fout("tree_pretty.txt");
  // Now print a tree that's more spread out to the file
  printPretty(root, 5, 0, fout);

  return 0;
}

This code should work its from:http://www.ihas1337code.com/2010/09/how-to-pretty-print-binary-tree.html

    #include <fstream>
#include <iostream>
#include <deque>
#include <iomanip>
#include <sstream>
#include <string>
#include <cmath>
using namespace std;

struct BinaryTree {
  BinaryTree *left, *right;
  int data;
  BinaryTree(int val) : left(NULL), right(NULL), data(val) { }
};

// Find the maximum height of the binary tree
int maxHeight(BinaryTree *p) {
  if (!p) return 0;
  int leftHeight = maxHeight(p->left);
  int rightHeight = maxHeight(p->right);
  return (leftHeight > rightHeight) ? leftHeight + 1: rightHeight + 1;
}

// Convert an integer value to string
string intToString(int val) {
  ostringstream ss;
  ss << val;
  return ss.str();
}

// Print the arm branches (eg, /    \ ) on a line
void printBranches(int branchLen, int nodeSpaceLen, int startLen, int nodesInThisLevel, const deque<BinaryTree*>& nodesQueue, ostream& out) {
  deque<BinaryTree*>::const_iterator iter = nodesQueue.begin();
  for (int i = 0; i < nodesInThisLevel / 2; i++) {
    out << ((i == 0) ? setw(startLen-1) : setw(nodeSpaceLen-2)) << "" << ((*iter++) ? "/" : " ");
    out << setw(2*branchLen+2) << "" << ((*iter++) ? "\\" : " ");
  }
  out << endl;
}

// Print the branches and node (eg, ___10___ )
void printNodes(int branchLen, int nodeSpaceLen, int startLen, int nodesInThisLevel, const deque<BinaryTree*>& nodesQueue, ostream& out) {
  deque<BinaryTree*>::const_iterator iter = nodesQueue.begin();
  for (int i = 0; i < nodesInThisLevel; i++, iter++) {
    out << ((i == 0) ? setw(startLen) : setw(nodeSpaceLen)) << "" << ((*iter && (*iter)->left) ? setfill('_') : setfill(' '));
    out << setw(branchLen+2) << ((*iter) ? intToString((*iter)->data) : "");
    out << ((*iter && (*iter)->right) ? setfill('_') : setfill(' ')) << setw(branchLen) << "" << setfill(' ');
  }
  out << endl;
}

// Print the leaves only (just for the bottom row)
void printLeaves(int indentSpace, int level, int nodesInThisLevel, const deque<BinaryTree*>& nodesQueue, ostream& out) {
  deque<BinaryTree*>::const_iterator iter = nodesQueue.begin();
  for (int i = 0; i < nodesInThisLevel; i++, iter++) {
    out << ((i == 0) ? setw(indentSpace+2) : setw(2*level+2)) << ((*iter) ? intToString((*iter)->data) : "");
  }
  out << endl;
}

// Pretty formatting of a binary tree to the output stream
// @ param
// level  Control how wide you want the tree to sparse (eg, level 1 has the minimum space between nodes, while level 2 has a larger space between nodes)
// indentSpace  Change this to add some indent space to the left (eg, indentSpace of 0 means the lowest level of the left node will stick to the left margin)
void printPretty(BinaryTree *root, int level, int indentSpace, ostream& out) {
  int h = maxHeight(root);
  int nodesInThisLevel = 1;

  int branchLen = 2*((int)pow(2.0,h)-1) - (3-level)*(int)pow(2.0,h-1);  // eq of the length of branch for each node of each level
  int nodeSpaceLen = 2 + (level+1)*(int)pow(2.0,h);  // distance between left neighbor node's right arm and right neighbor node's left arm
  int startLen = branchLen + (3-level) + indentSpace;  // starting space to the first node to print of each level (for the left most node of each level only)

  deque<BinaryTree*> nodesQueue;
  nodesQueue.push_back(root);
  for (int r = 1; r < h; r++) {
    printBranches(branchLen, nodeSpaceLen, startLen, nodesInThisLevel, nodesQueue, out);
    branchLen = branchLen/2 - 1;
    nodeSpaceLen = nodeSpaceLen/2 + 1;
    startLen = branchLen + (3-level) + indentSpace;
    printNodes(branchLen, nodeSpaceLen, startLen, nodesInThisLevel, nodesQueue, out);

    for (int i = 0; i < nodesInThisLevel; i++) {
      BinaryTree *currNode = nodesQueue.front();
      nodesQueue.pop_front();
      if (currNode) {
          nodesQueue.push_back(currNode->left);
          nodesQueue.push_back(currNode->right);
      } else {
        nodesQueue.push_back(NULL);
        nodesQueue.push_back(NULL);
      }
    }
    nodesInThisLevel *= 2;
  }
  printBranches(branchLen, nodeSpaceLen, startLen, nodesInThisLevel, nodesQueue, out);
  printLeaves(indentSpace, level, nodesInThisLevel, nodesQueue, out);
}

int main() {
  BinaryTree *root = new BinaryTree(30);
  root->left = new BinaryTree(20);
  root->right = new BinaryTree(40);
  root->left->left = new BinaryTree(10);
  root->left->right = new BinaryTree(25);
  root->right->left = new BinaryTree(35);
  root->right->right = new BinaryTree(50);
  root->left->left->left = new BinaryTree(5);
  root->left->left->right = new BinaryTree(15);
  root->left->right->right = new BinaryTree(28);
  root->right->right->left = new BinaryTree(41);

  cout << "Tree pretty print with level=1 and indentSpace=0\n\n";
  // Output to console
  printPretty(root, 1, 0, cout);

  cout << "\n\nTree pretty print with level=5 and indentSpace=3,\noutput to file \"tree_pretty.txt\".\n\n";
  // Create a file and output to that file
  ofstream fout("tree_pretty.txt");
  // Now print a tree that's more spread out to the file
  printPretty(root, 5, 0, fout);

  return 0;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文