2017-02-11 16 views
0

Я новичок в C++, и до сих пор у меня был весь код в одном и том же файле. Теперь, когда я прогрессирую, мне нужно разделить мой код на исходные и заголовочные файлы, с которыми я не очень хорошо знаком.C++ Отдельный заголовок и исходный файл

Я могу заставить его работать с простыми задачами, но в этой программе, которую я сейчас пытаюсь разделить на отдельные файлы, возникает ошибка, а когда у меня есть все в одном файле, я могу ее скомпилировать.

я застрял на сообщение об ошибке

main.cpp:10:1: error: unknown type name 'textEditor' 
textEditor siEditor; 

Если кто-то может объяснить, почему я бегу в эту ошибку и как предотвратить это будет оценено. Я читал, что это может быть связано с дублированными объявлениями, но я не понимаю, откуда.

Это как мой main.cpp выглядит:

#include <iostream> 
#include <fstream> 

using namespace std; 

#include "textData.h" 
#include "textEditor.h" 

textData siData; 
textEditor siEditor; 

int main() 
{ 
    cout << "\nWelcome to siEdit!" << endl; 
    while (true) 
    { 
     cout << "\nWhat would you like to do? \nNew file = n, Append = a, View = v, Quit = q: "; 
     string toDo; 
     cin >> toDo; 

     if (toDo == "n") 
     { 
      siEditor.openText(); 
      cout << "Now editing the file: " << siData.fileName.c_str() << endl; 
      cout << "Type '=!' to stop editing and save. \n " << endl; 
      siEditor.writeText(); 
     } 

     else if (toDo == "a") 
     { 
      siEditor.appendTextOpen(); 
      cout << "Now appending text: " << siData.appendTextfileName.c_str() << endl; 
      cout << "Type '=!' to stop editing and save changes. \n " << endl; 
      siEditor.appendText(); 
     } 

     else if (toDo == "v") 
     { 
      siEditor.readText(); 
      cout << "\n"; 
     } 

     else if (toDo == "q") 
     { 
      return 0; 
     } 

     else 
     { 
      cout << "Invalid input." << endl; 
     } 
    } 
} 

siEdit.cpp:

#include <iostream> 
#include <fstream> 

using namespace std; 

#include "textData.h" 
#include "textEditor.h" 

textData siData; 

class textEditor 
{ 
    public: 
    void openText() 
    { 
     //when associated file is open. 
     while (siData.siFile.is_open()) 
     { 
      siData.siFile.close(); 
     } 
     cout << "\nWhat do you want to call your file? "; 
     cin >> siData.fileName; 

     //Creates/Opens fileEditor 
     const char* path = siData.fileName.c_str(); 
     siData.siFile.open(path); 
    } 

    void writeText() 
    { 
     bool editing = true; 
     bool hasEditing = false; 

     while (editing == true) 
     { 
      //Get user input 
      string input = " "; 
      getline(cin, input); 
      string yesNo; 

      if (input == "=!") 
       { 
        cout << "Would you like to save the file? Y/N" << endl; 
        cin >> yesNo; 

        if (yesNo == "Y") 
        { 
         cout << "Filed saved: " << siData.fileName.c_str(); 
         editing = false; 
        } 

        else if (yesNo == "N") 
        { 
         cout << "No changes have been saved. Exiting." << endl; 
         hasEditing = false; 
         editing = false; 
         siData.siFile.clear(); 
        } 

        else 
        { 
         cout << "Invalid input. Type '=! to exit." << endl; 

        } 
       }  

      else 
      { 
       siData.siFile << input; 
       siData.siFile << endl; 
       hasEditing = true; 
      } 
     } 
    } 


    void readText() 
    { 
     string line; 
     cout << "\nEnter the name of your file: "; 
     cin >> siData.fileName; 
     cout << "\n"; 
     const char* path = siData.fileName.c_str(); 

     // input file stream 
     //Internal stream buffer which performes I/O on file. 
     ifstream siFileRead(path); 
     if(siFileRead.is_open()) 
     { 
      while(getline(siFileRead,line)) 
      { 
       cout << line << endl; 
       siData.siFile << line; 
      } 
     } 

     else 
     { 
      cout << "Unable to open file. Confirm name and file location."; 
     } 
    } 

    // open the existing text file 
    void appendTextOpen() 
    { 
     while (siData.siFileAppend.is_open()) 
     { 
      // erase previous text 
      siData.siFileAppend.clear(); 
      // close the input text file 
      siData.siFileAppend.close(); 
     } 

     cout << "\nEnter the name of the file: "; 
     //find file name. 
     cin >> siData.appendTextfileName; 

     //Makes/Opens file 
     const char* path = siData.appendTextfileName.c_str(); 
     siData.siFileAppend.open(path, fstream::app); 
    } 

    //add text together with previous input. 
    void appendText() 
    { 
     bool editing = true; 
     bool hasEditing = false; 

     while (editing == true) 
     { 
      //Gets user input 
      string input = " "; 
      getline(cin, input); 

      if (input == "=!") 
      { 
       if (hasEditing == true) 
       { 
        cout << "File saved: " << siData.appendTextfileName.c_str() << endl; 
        editing = false; 
       } 
      } 

      else 
      { 
       siData.siFileAppend << input; 
       siData.siFileAppend << endl; 
       hasEditing = true; 
      } 
     } 
    } 
}; 

textData.h:

#ifndef SIEDITOR_H 
#define SIEDITOR_H 

class textData 
{ 
    public: 
     string fileName; 
     string appendTextfileName; 
     ofstream siFile; 
     ofstream siFileAppend; 
}; 

#endif 

textEditor.h:

#ifndef SIEDITOR_H 
#define SIEDITOR_H 

class textEditor 
{ 
    public: 
    void openText() 
    void writeText() 
    void readText() 
    void appendTextOpen() 
    void appendText() 
}; 


#endif 
+0

Вы не должны переопределять свои классы в своих файлах cpp; вы должны внедрять методы. В идеале вы должны иметь 'textData.cpp' и' textEditor.cpp', иначе я не думаю, что он будет включен правильно. – byxor

+0

Я думаю, что могут быть рабочие примеры кода, разбитого на файлы заголовков и реализации там, на interwebs. – juanchopanza

ответ

1

Класс должен быть определен только один раз.

Переместить определение класса для отдельного файла заголовка (присоединиться к Снегурочка как содержание вашего же класса имя: поля и методы):

// textEditor.h 
#pragma once 
class textEditor { 
    void appendText(); 
private: 
    string fileName; 
} 

методы класса Move для разделения исходного файла:

// textEditor.cpp 
#include "textEditor.h" 
void textEditor::appendText() { 
    // ... impl 
} 

И в main.cpp:

// main.cpp 
#include "textEditor.h" 

textEditor siEditor; 

int main() 
{ 
    siEditor.appendText(); 
} 
+0

* «Класс должен быть объявлен только один раз». * - Нет, они могут быть объявлены так часто, как вы хотите. Они должны быть определены ** только один раз. 'class textEditor {/ * ... */ };' это определение, а не объявление. 'class textEditor;' будет объявлением. –

+0

Да, конечно. Спасибо, исправлено. – oklas

2

Вы используете тот же самый защитный элемент в обоих заголовках f iles, а именно SIEDITOR_H. Это предотвращает включение содержимого второго заголовка. Используйте #pragma once вместо символов охраны.

#pragma oncede facto стандарт это supported by all compilers of practical interest.

В вашем файле реализации не повторяется определение класса. Просто определите объявленные функции-члены. И static данных, если они есть.

0

Рассмотрите, что делает препроцессор. Он запускается отдельно для каждого файла *.cpp и обрабатывает все ваши заявления #include, #ifndef, #define и #endif.

Это начало из вашей main.cpp:

#include <iostream> 
#include <fstream> 

using namespace std; 

#include "textData.h" 
#include "textEditor.h" 

textData siData; 
textEditor siEditor; 

Как бы вы предобработки это, если вы были препроцессор?[*] Вы, вероятно, начинаете с утверждений #include. Промежуточный результат будет:

// contents of <iostream>... 
// contents of <fstream>... 

using namespace std; 

#ifndef SIEDITOR_H 
#define SIEDITOR_H 

class textData 
{ 
    public: 
     string fileName; 
     string appendTextfileName; 
     ofstream siFile; 
     ofstream siFileAppend; 
}; 

#endif 

#ifndef SIEDITOR_H 
#define SIEDITOR_H 

class textEditor 
{ 
    public: 
    void openText() 
    void writeText() 
    void readText() 
    void appendTextOpen() 
    void appendText() 
}; 


#endif 

textData siData; 
textEditor siEditor; 

Теперь давайте проверяла этот промежуточный результат:

// contents of <iostream>... 
// contents of <fstream>... 

using namespace std; 

#ifndef SIEDITOR_H // <--- true, SIEDITOR_H is not defined, don't skip until #endif 
#define SIEDITOR_H // <--- OK, SIEDITOR_H is now defined 

class textData 
{ 
    public: 
     string fileName; 
     string appendTextfileName; 
     ofstream siFile; 
     ofstream siFileAppend; 
}; 

#endif // <--- end of block started by #ifndef SIEDITOR_H 

#ifndef SIEDITOR_H // <--- false, SIEDITOR_H is defined, skip until #endif 
#define SIEDITOR_H 

class textEditor 
{ 
    public: 
    void openText() 
    void writeText() 
    void readText() 
    void appendTextOpen() 
    void appendText() 
}; 


#endif // <--- end of block started by #ifndef SIEDITOR_H 

textData siData; 

В результате предварительной обработки оказывается быть:

// contents of <iostream>... 
// contents of <fstream>... 

using namespace std; 

class textData 
{ 
    public: 
     string fileName; 
     string appendTextfileName; 
     ofstream siFile; 
     ofstream siFileAppend; 
}; 

textData siData; 
textEditor siEditor; // error, unknown type textEditor 

Это объясняет которое вы спрашивали. Решение состоит в том, чтобы использовать каждый другой заголовок в каждом файле заголовка. Вы должны выбрать полностью уникальные включают имена охранников. В крупных проектах это может стать затруднительным. Вот некоторые материалы для чтения:


Тем не менее, есть несколько ошибок в коде:

Прежде всего, файлов заголовки предполагают много. Они предполагают, что кто-то еще уже включил необходимые стандартные заголовки, чтобы получить std::string и std::ofstream. Они также предполагают, что кто-то еще уже использовал using namespace std; или using std::string; using std::ofstream;.

Это очень плохая практика. Ваш файл заголовка должен включать стандартные заголовки, которые ему нужны, и только изложить полные имена (no using namespace std;).

В дополнение к этому, он должен использовать стандартные заголовки, которые гарантированно содержать то, что вам нужно. Если вам нужно std::string, тогда включите <string>.

Стандартные заголовки могут включать другие стандартные заголовки, но только очень немногие гарантируемые косвенные включения (я слишком ленив, чтобы посмотреть в стандарте, если <iostream> подразумевает <string>, но я предполагаю, что это не так.)

Вот пример:

textEditor.h:

#ifndef SI_TEXT_DATA_H 
#define SI_TEXT_DATA_H 

#include <string> 
#include <fstream> 

class textData 
{ 
    public: 
     std::string fileName; 
     std::string appendTextfileName; 
     std::ofstream siFile; 
     std::ofstream siFileAppend; 
}; 

#endif 

Наконец, вы переопределите textEditor класс в siEdit.cpp. Это не разрешено. Вместо этого вы должны объявить функции-члены в файле *.h внутри определения класса и определить функции-члены в файле *.cpp.

textEditor.h должен выглядеть следующим образом:

#ifndef SI_TEXT_EDITOR_H 
#define SI_TEXT_EDITOR_H 

class textEditor // class definition begins 
{ 
    public: 
    void openText();  // declaration of member function 
    void writeText();  // declaration of member function 
    void readText();  // declaration of member function 
    void appendTextOpen(); // declaration of member function 
    void appendText();  // declaration of member function 

}; // class definition ends 

#endif 

И siEdit.cpp должен выглядеть следующим образом:

#include "textData.h" 
#include "textEditor.h" 

textData siData; 

void textEditor::openText() // definition of a member function begins 
{ 
    // ... 

} // definition of a member function ends 

// other definitions 

ваших глобальных переменных (например, textData siData;) не очень хорошая идея, либо, особенно учитывая, как они не завернуты в анонимные пространства имен.


[*] Фактический препроцессор не может технически работать, как это, но вы можете представить это таким образом.