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

Наскоро научих как можем да използваме множество изходни файлове със заглавни файлове, за да направим кода преносим и йерархичен. За да направя това, се опитах да създам моята дървовидна програма, използвайки този принцип. Ето моите файлове

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 - съдържа декларация на функцията traverse

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.

Както Joop Eggen препоръчва, поставете #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

Забравете за extern. В 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

Потърсете информация за компилатора за този случай, а също и за предварително компилирани заглавки.

Горното е независим от платформата начин за изключване на съдържание втори път.

person Joop Eggen    schedule 17.03.2012