Как использовать несколько исходных и заголовочных файлов - PullRequest
3 голосов
/ 17 марта 2012

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

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

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

Ответы [ 3 ]

1 голос
/ 17 марта 2012

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

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

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

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

Проверьте правильность этого файла с помощью минимального файла main.c, содержащего:

#include "node.h"

int main() { return 0; }

, и скомпилируйте его.Это не должно давать никаких ошибок компиляции.Если он содержит ошибки, ошибка находится в файле заголовка.

Поместите функции, которые управляют NODE, в файл с именем node.c, который первоначально будет:

#include "node.h"

compile and linkчто с main.c (gcc main.c node.c), и не должно быть ошибок.

Сборка программы происходит поэтапно, добавляя код в файл main.c, файл node.c,и добавить объявления функций в файле node.c в node.h.Добавьте небольшие объемы кода и часто компилируйте (с включенными предупреждениями, например, gcc -Wall main.c node.c) и тестируйте, чтобы убедиться, что он выполняет то, что вы ожидаете.

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

1 голос
/ 17 марта 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 , и Должен ли я использовать #include в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.h, а traverse.h не включал b_tree_ds.h, код не будет компилироваться должным образом.
0 голосов
/ 17 марта 2012

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

#ifndef B_TREE_DS_H
#define B_TREE_DS_H

...

#endif // B_TREE_DS_H

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

...