Как использовать несколько исходных файлов и файлов заголовков

Недавно я узнал, как мы можем использовать несколько исходных файлов с файлами заголовков, чтобы сделать код переносимым и иерархическим. Для этого я попытался создать свою древовидную программу, используя этот принцип. Вот мои файлы

b_tree_ds.h - он будет содержать объявление структуры данных узла дерева, которое может быть вызвано различными функциями, реализующими различные функции дерева (которые могут находиться в разных исходных файлах)

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

Когда я пытаюсь добавить extern, как в typedef extern struct node, он выдает ошибку с несколькими классами хранения, но если я пропущу ее, я получаю ошибку для нескольких определений.

Вот мои другие исходные файлы

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);
    }
}

Это моя первая попытка такой практики, пожалуйста, подскажите, хорошо ли структурирована моя программа или мне стоит попробовать что-нибудь еще.


person simar    schedule 17.03.2012    source источник
comment
Вместо публикации всего кода создайте минимальный тестовый пример.   -  person Oliver Charlesworth    schedule 17.03.2012
comment
Вы разместили слишком много нерелевантного кода и недостаточно кода, который имеет значение. Проблема, вероятно, заключается в вашем заголовочном файле, который определяет структуру. Также, пожалуйста, начните принимать тот факт, что void main() неверно и что вы должны сказать int main().   -  person Kerrek SB    schedule 17.03.2012


Ответы (3)


У вас могут быть проблемы, потому что у вас еще нет веских причин для разделения. Хорошая причина поможет вам определить, какие части принадлежат друг другу, а какие - отдельные. Так что начните с более простого подхода.

Разделите программу на три файла: main.c, который содержит main (), node.h, заголовок, который гарантирует, что объявления являются общими для всей программы и, следовательно, понятны компилятору, и node.c, функции, которые управляют структура NODE.

Поместите typedef ... NODE; и все объявления функций, которые управляют NODE, в один заголовочный файл node.h. Таким образом, вы можете объединить существующие файлы заголовков в один и назвать его node.h.

Как рекомендует Юп Эгген, поместите #ifndef _NODE_H_ ... #endif вокруг содержимого node.h, чтобы защитить его от случайного включения дважды.

Проверьте правильность этого файла с помощью минимального файла 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) и тестируйте, чтобы убедиться, что он делает то, что вы ожидаете.

В конечном итоге программа будет завершена.

person gbulmer    schedule 17.03.2012

Я рекомендую посмотреть Что такое внешние переменные в 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, выдается ошибка нескольких классов хранения, но если я пропущу ее, я получаю ошибку для нескольких определений.

    Синтаксически extern, static, auto, register и typedef - все классы хранения, и вы можете иметь только один класс хранения в данном объявлении. Вот почему вы получаете ошибку нескольких классов хранения. Ошибка «множественного определения» будет оставаться проблемой до тех пор, пока C2011 не станет широко распространенным, и защита заголовка не позволит этому стать проблемой. Я думаю, что ограждения жатки останутся ценными даже после того, как C2011 станет широко доступным.

  • traverse.h

    void traverse_print (NODE* p);
    

    В его нынешнем виде вы не можете просто написать #include "traverse.h", чтобы использовать его возможности. По возможности этого следует избегать. (См .: Самодостаточные файлы заголовков на C и C ++, Какая хорошая ссылка документирование шаблонов использования h-файлов в C и Следует ли мне использовать # включить в заголовки.) Следовательно, это должно включать 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.h, а traverse.h не включал b_tree_ds.h, код не мог правильно компилироваться.
person Jonathan Leffler    schedule 17.03.2012

Забудьте о внешнем. В traverse.h вы должны включить b_tree_ds.h. В некоторых компиляторах есть прагма include once, но не помешает окружить содержимое b_tree_ds.h:

#ifndef B_TREE_DS_H
#define B_TREE_DS_H

...

#endif // B_TREE_DS_H

Ищите информацию о компиляторе в этом случае, а также о предварительно скомпилированных заголовках.

Вышеупомянутое является независимым от платформы способом исключения контента во второй раз.

person Joop Eggen    schedule 17.03.2012