如何使用多个源文件和头文件

时间:2012-03-17 13:21:53

标签: c header typedef extern

我最近学会了如何使用带头文件的多个源文件来使代码可移植和分层。为了做到这一点,我尝试使用这个原理创建我的树程序。这是我的文件

b_tree_ds.h - 这将包含树的节点的数据结构的声明,可以调用实现树的不同功能的各种功能(可以在不同的源文件中)< / p>

typedef struct node {
    struct node* left;
    struct node* right;
    int key;    // contains value
}NODE;

当我尝试在typedef extern struct node中添加extern时,它会出现多个存储类的错误,但如果我错过它,我会得到多个定义的错误。

以下是我的其他源文件

traverse.h - 包含遍历函数的声明

void traverse_print (NODE* p);

这里我也收到了未知标识符NODE的错误

traverse.c - 包含该函数的定义

#include <stdio.h>
#include "b_tree_ds.h"
#include "traverse.h"

void traverse_print(NODE* p)
{
    if(p->left != NULL)
    {
        traverse_print(p->left);
    }

    if (p->right != NULL)
    {
        traverse_print(p->right);
    }

    printf ("\n%d",p->key);
}

最后是main.c

#include <stdio.h>
#include "traverse.h"

void main()
{
    // input
    NODE p;

    printf("\nInput the tree");
    input_tree (&p);

    printf("\n\nThe tree is traversing ...\n")
    traverse_print(&p);
}

void input_tree (NODE *p)
{
    int in;
    int c;
    NODE *temp;

    printf("\n Enter the key value for p: ");
    scanf("%d", &in);
    p->key  =in;
    printf ("\n\nIn relation to node with value %d",in);
    printf ("Does it have left child (Y/N): ")
    if ((c = getchar()) == Y);
    {
        //assign new memory to it.
        temp = (NODE *)malloc(sizeof(NODE));
        input_tree(temp);
    }
    printf ("\n\nIn relation to node with value %d",p->key);

    printf ("\nDoes it have right child (Y/N): ")
    if ((c = getchar()) == Y);
    {
        //assign new memory to it.
        temp = (NODE *)malloc(sizeof(NODE));
        input_tree(temp);
    }
}

这是我第一次尝试这种做法,请建议我的程序结构良好,还是应该尝试其他方法。

3 个答案:

答案 0 :(得分:1)

你可能遇到了问题,因为你还没有充分的理由将事情分开。一个很好的理由可以帮助您确定哪些部分属于一起,哪些部分是分开的。所以从一个更简单的方法开始。

将程序拆分为三个文件main.c,其中包含main(),node.h,该头确保声明在所有程序中是通用的,因此编译器和node.c,操纵NODE结构的函数。

typedef ... NODE;和操作NODE的函数的所有声明放入node.h头文件中。因此,您可以将现有的头文件合并为一个,并将其命名为node.h。

正如Joop Eggen建议的那样,将#ifndef _NODE_H_ ... #endif放在node.h内容周围,以防止意外被#include两次。

使用包含以下内容的最小main.c文件测试该文件是否正确

#include "node.h"

int main() { return 0; }

并编译它。这应该不会出现编译错误。如果它包含错误,则错误在头文件中。

将操作NODE的函数放入名为node.c的文件中,该文件最初为:

#include "node.h"

使用main.c(gcc main.c node.c)编译并链接它,并且应该没有错误。

构建程序是阶段,将代码添加到main.c文件,node.c文件,并将node.c文件中的函数声明添加到node.h中。添加少量代码,并经常编译(打开警告,例如gcc -Wall main.c node.c)并进行测试以确保它正在按预期执行。

该计划最终将完成。

答案 1 :(得分:1)

我建议查看What are extern variables in C?

您可以添加<stdio.h>等系统标头,而不必担心是否需要其他标头才能使用其服务。您应该以相同的方式设计自己的标题。如果您的文件被多次包含(无论是意外还是故意),您还应该防止错误。

你有:

  • b_tree_ds.h

    typedef struct node {
        struct node* left;
        struct node* right;
        int key;    // contains value
    } NODE;
    

    到目前为止,这很好;你只需要将其包裹在标题保护中,这样重新加入就不会造成伤害。

    #ifndef B_TREE_DS_H_INCLUDED
    #define B_TREE_DS_H_INCLUDED
    
    typedef struct node {
        struct node* left;
        struct node* right;
        int key;    // contains value
    } NODE;
    
    #endif /* B_TREE_DS_H_INCLUDED */
    

    你注意到:

      

    当我尝试在extern中添加typedef extern struct node时,它会出现多个存储类的错误,但如果我错过它,我会收到多个定义的错误。

    从语法上讲,externstaticautoregistertypedef都是存储类,并且您只能在给定的存储类中拥有一个存储类宣言。这就是为什么你得到多个存储类错误。在C2011普遍存在之前,“多重定义”错误将继续成为一个问题,并且标题保护可以防止出现问题。我认为即使在广泛使用C2011之后,头部保护仍然有价值。

  • traverse.h

    void traverse_print (NODE* p);
    

    按照目前的情况,你不能简单地写#include "traverse.h"来使用它的设施。尽可能避免这种情况。 (见:Self-sufficent header files in C and C++What is a good reference documenting patterns of use of h files in CShould I use #include in headers。)因此,这应包括b_tree_ds.h

    #ifndef TRAVERSE_H_INCLUDED
    #define TRAVERSE_H_INCLUDED
    
    #include "b_tree_ds.h"
    
    extern void traverse_print(NODE *p);
    
    #endif /* TRAVERSE_H_INCLUDED */
    

    你可以在这个标题上省略标题包含警戒(假设b_tree_ds.h是自我保护的),但在所有标题中自我保持更简单。

    还有另外一种可以提及的技术:

    #ifndef TRAVERSE_H_INCLUDED
    #define TRAVERSE_H_INCLUDED
    
    typedef struct node NODE;
    
    extern void traverse_print(NODE *p);
    
    #endif /* TRAVERSE_H_INCLUDED */
    

    这使NODE成为一个不透明的类型;标题traverse.h的用户对NODE中的内容一无所知。有一些协调问题需要解决,这使得这种技术不常用。

对标题进行了这些更改,然后:

  • traverse.c只需要包含traverse.h(并且应该可以在任何其他标题之前包含它以提供自我控制的自动测试),但是
  • 如果traverse.c包含两个标题,则无论其包含的顺序如何都没有问题(如果重复是直接或间接的,则无关紧要。)
  • 您的main.c可以只包含traverse.h,如图所示,也可以。使用原始代码时,由于main.c仅包含traverse.htraverse.h未包含b_tree_ds.h,因此代码无法正确编译。

答案 2 :(得分:0)

忘了外面。在traverse.h中,您应该包含b_tree_ds.h。有些编译器有一个包含一次的编译指示,但用b_tree_ds.h的内容包围它并没有什么坏处。

#ifndef B_TREE_DS_H
#define B_TREE_DS_H

...

#endif // B_TREE_DS_H

查找此案例的编译器信息,以及预编译头文件。

以上是第二次排除内容的独立于平台的方式。