Программа управления контактами

Я сделал простое управление контактами на C ++.

Это мой второй проект; Я почти уверен, что это можно сделать лучше, и я мог бы сократить время его выполнения, но я только что присоединился к сообществу C ++ в этом году. Можете ли вы просмотреть мой код?

Это система управления контактами на основе CUI, с помощью которой вы можете добавлять, удалять, редактировать или искать контакты.

#include <iostream>
#include <fstream>
#include <vector>
#include <sstream>
#include <conio.h>

//Used For Replacing The Line
void ReplaceLine(const std::string& ToReplace,const std::string&& Main){
    std::vector<std::string>Content;
    std::string s;
    std::ifstream File("contacts.txt");
    while(getline(File,s)){
        if(s==ToReplace){
            s=Main;
        }
        Content.push_back(s);
    }
    std::ofstream writer("contacts.txt",std::ostream::trunc);
    for(std::string& s:Content){
        writer<<s<<std::endl;
    }
}

//Editing The Contact
void EditContact(const std::string& ID){
    std::string NewName,NewNumber;
    std::cout<<"Enter A New Name : ";
    std::cin>>NewName;
    std::cout<<"Enter A New Number : ";
    std::cin>>NewNumber;
    NewName+=" ";
    ReplaceLine(ID,NewName+NewNumber);
}

//Adding New Contact
void AddContact(const std::string&& ID){
    std::ofstream File("contacts.txt",std::ios_base::app);
    std::string s;
    std::ifstream reader("contacts.txt");
    char Confirmation;
    while(getline(reader,s)){
            if(ID==s){
            std::cout<<"Contact Already Exist!n"<<"Do You Want To OverWrite?[Y/N]"<<std::endl<<">>";
            std::cin>>Confirmation;
            if(Confirmation=='Y'){
                system("clear");
                EditContact(ID); 
                std::cout<<"Contacts Overriden"<<std::endl;
                system("pause");
                File.close();
                return;
            }
            else{
                std::cout<<"Contacts Aren't Touched"<<std::endl;
                system("pause");
                File.close();
                return;
            }
        }
    }
    File<<ID<<std::endl;
    std::cout<<"New Contact Has Been Added"<<std::endl;
    system("pause");
    File.close();
}

//Deleting A Contact
void DeleteContact(const std::string&& ToDlt){
    std::vector<std::string>Contact;
    std::string s;
    bool Deleted=false;
    std::ifstream Reader("contacts.txt");
    while(getline(Reader,s)){
        if(s==ToDlt){
            Deleted=true;
            continue;
        }
        Contact.push_back(s);
    }
    std::ofstream Writer("contacts.txt",std::ios_base::trunc);
    for(std::string&k : Contact){
        Writer<<k<<std::endl;
    }
    if(!Deleted){
        std::cout<<"Contact Didn't Existed!"<<std::endl;
    }
    else{
        std::cout<<"Contact Has Been Deleted"<<std::endl;
    }
}

//Searching A Contact This Wont Be Called Directly
int Search(const std::string& Query){
    int Count=0;
    bool IsNum=false;
    if(isdigit(Query[0])){
        IsNum=true;
    }
    std::ifstream reader("contacts.txt");
    std::string Name,Number;
    std::string s;
    while(getline(reader,s)){
        std::stringstream Extractor(s);
        Extractor>>Name>>Number;
        if(IsNum==true){
            if(Number.find(Query)!=std::string::npos){
                Count++;
                std::cout<<"NAME : "<<Name<<"   "<<"NUMBER : "<<Number<<std::endl;
            }
        }
        else{
            if(Name.find(Query)!=std::string::npos){
                Count++;
                std::cout<<"NAME : "<<Name<<"  "<<"NUMBER : "<<Number<<std::endl;
            }
        }
    }
    return Count;
}

//This Is Used To Take Inputs For Searching
void Query(){
    std::string Query="";
    std::cout<<"Contact Search"<<std::endl;
    std::cout<<">>";
    while(true){
        char s=getche();
        //If User Presses Enter Case It Worked Atleast for mine
        if(int(s)==10){
            return;
        }
        //Handling BackSpace Case Worked In Mine :)
        if(int(s)==127){
           if(Query.length()==0){
              Query=" ";
              std::cout<<"b"<<"b";
           }
           Query.erase(Query.end()-1);
        }
        //Else  Adding Character To Query
        else{
           Query.push_back(s);
        }
        system("clear");
        //The Contacts Get Printed In Search Itself
        int Searched=0;
        Searched=Search(Query);
        if(Searched==0){
            std::cout<<"No Results Found!!"<<std::endl;
        }
        std::cout<<">>"<<Query;
    }
    system("pause");
    system("clear");
}
void EditContact(const std::string&& ID){
    system("clear");
    std::ifstream Reader("contacts.txt");
    std::string S;
    bool Exist=false;
    std::vector<std::string>NewBlocks;
    while(getline(Reader,S)){
        if(S==ID){
            std::string Name,Num;
            std::cout<<"Enter The New Name : ";
            std::cin>>Name;
            std::cin.ignore();
            std::cout<<"Enter The New Number : ";
            std::cin>>Num;
            Name+=" ";
            NewBlocks.push_back(Name+Num);
            Exist=true;
            continue;
        }
        NewBlocks.push_back(S);
    }
    if(!Exist){
        std::cout<<"The Contact You Want To Edit Didn't Exist!!"<<std::endl;
        system("pause");
        return;
    }
    std::ofstream Writer("contacts.txt",std::ios_base::trunc);
    for(std::string& Val:NewBlocks){
        Writer<<Val<<std::endl;
    }
    std::cout<<"Contacts Has Been Edited!!"<<std::endl;
    system("pause");
}

int main()
{
    char Cmnd;
    while(true){
        std::cout<<"~Add A Conatct [1]n~Delete A Contact [2]n~Edit A Contact [3]n~Search A Contact [4]n>>";
        std::cin>>Cmnd;
        std::cin.ignore();
        if(Cmnd=='1'){
            system("clear");
            std::string Name,Number;
            std::cout<<"Enter Name : ";
            std::cin>>Name;
            std::cin.ignore();
            std::cout<<"Enter Number : ";
            std::cin>>Number;
            Name+=" ";
            AddContact(Name+Number);
            system("clear");
        }
        else if(Cmnd=='2'){
            system("clear");
            std::string Name,Num;
            std::cout<<"Enter The Number : ";
            std::cin>>Num;
            std::cin.ignore();
            std::cout<<"Enter Users Name : ";
            std::cin>>Name;
            Name+=" ";
            DeleteContact(Name+Num);
            system("clear");
        }
        else if(Cmnd=='3'){
            system("clear");
            std::string Name,Num;
            std::cout<<"Enter The Name : ";
            std::cin>>Name;
            std::cin.ignore();
            std::cout<<"Enter The Number : ";
            std::cin>>Num;
            Name+=" ";
            EditContact(Name+Num);
            system("clear");
        }
        else if(Cmnd=='4'){
            system("clear");
            Query();
            system("clear");
        }
        else{
            std::cout<<"Invalid Option!!";
            system("pause");
            system("clear");
        }
    }
}

Пожалуйста, дайте мне знать, что я могу улучшить.

4 ответа
4

Ваш код на C ++ очень похож на C. Это один из случаев, когда класс будет очевидным выбором для структурирования вашего кода. Однако перед этим я хотел бы указать на несколько моментов в вашем текущем коде.


  1. В const std::string&& Main должно быть const std::string& Main. Ссылка на const rvalue почти всегда семантически бесполезна. Еще лучше, если вы используете C ++ 17 или новее, в большинстве случаев вы можете передать std::string_view вместо const std::string&.

  1. Использовать 'n' над std::endl. Это может потенциально повлиять на производительность (особенно при выполнении файлового ввода-вывода). Вот такая тема.

  1. Перед записью или чтением проверьте, действителен ли файловый поток.
std::ifstream file("contacts.txt");
if(!file)
{
  // something went wrong, handle the error!
}

  1. Как правило, избегайте system. Это может быть неэффективно, но, что более важно, непереносимо. Ваш код не будет работать, например, в Linux, поскольку pause не является допустимой командой оболочки в Linux.

  1. Вам не нужно звонить вручную File.close(). Файл будет закрыт, когда объект fstream объект уничтожен.

  1. Следовать Принцип единой ответственности. Проще говоря, это означает, что функция должна делать только одно. Например, ваш EditContact() функция отвечает за а) открытие файла, б) анализ данных, в) запрашивая ввод пользователя и г) запись данных в файл. Разбейте свои функции на логические части.

  1. Вы можете использовать switch заявление вместо if внутри твоего main функция. Кроме того, как упоминалось выше, ваш main функция делает слишком много. Вы можете легко переместить множество операторов внутри их собственных функций.

  1. Как упоминалось выше, ваш код довольно неэффективен, поскольку он открывает, анализирует и записывает файл почти при каждой операции. Файловый ввод-вывод стоит недешево, и, поскольку вы используете C ++, вы, очевидно, заботитесь о производительности.

Итак, какие у вас есть варианты?

а) Открывайте, анализируйте, обновляйте и закрывайте файл каждый раз, когда вы хотите выполнить операцию с файлом (это то, что вы сейчас делаете). Как упоминалось выше, это не очень эффективное решение.

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

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

Хорошо, мы переходим к варианту (c). Как мы храним данные в памяти? Вы уже выполнили одну из своих функций. Храните это внутри std::vector. Теперь любая операция, которую вы хотите выполнить, выполняется со строками в векторе.

Мы можем создать класс под названием CustomerManagementSystem.

(в псевдокоде)

class CustomerManagementSystem
{
   CustomerManagementSystem(const std::string& str)
   {
         std::ifstream file(str)
         if(file is invalid)
         {
            // handle error
         }
         parseFile(file);
   }
}

Мы можем предоставить конструктор, который принимает имя файла, открывает его, анализирует файл и сохраняет данные в std::vector<std::string>. Фактически, лучшим подходом было бы определение такой структуры, как

struct CustomerInfo
{
   std::string name;
   int number;
};

и создать std::vector<CustomerInfo> для хранения данных.

Когда конструктор заканчивается, file уничтожается, и файл автоматически закрывается.

Теперь каждая операция, которую вы хотите выполнить, может быть функцией-членом класса.

void AddContact(const std::string& ID)
{
    auto id = FindIdInVector(ID);
    if(id already exists)
    {
        UpdateID();
    }
    else
    {
       data.push_back(ID);
    }
}


Итак, как нам в итоге обновить файл? Мы используем мощную функцию C ++, которая называется Приобретение ресурсов — это инициализация. Это пугающее имя, но очень простыми словами: ваш ресурс (например, дескрипторы файлов, память, сокеты и т. Д.) Должен быть создан (или «приобретен») в конструкторе, а ресурс будет удален (или «освобожден») ( например, для звонка delete на некоторой памяти, выделенной с помощью newили закрытие файлового объекта) внутри деструктора.

Это гарантирует, что ресурс «приобретается» при создании объекта и «освобождается», когда объект выходит за пределы области видимости. Более того, это также обеспечивает очистку всех ваших ресурсов, если когда-либо ваша программа выдает исключение.

Итак, как нам использовать RAII внутри нашего класса?

Мы уже выполнили первую половину контракта; в нашем конструкторе мы открыли файл и сохранили данные в памяти.

Итак, вторая половина контракта: в деструкторе мы открываем файл и записываем в него сохраненные данные.

Итак, деструктор будет выглядеть так:

~CustomerManagementSystem()
{
    std::ofstream file(filename);
    if(!file)
    {
       // file wasn't opened! handle error here
    } 
    writeToFile(file, data);
}

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

    Если бы вы собирались это сделать, когда вы читали его в память, вы могли бы сохранить данные в std::map<std::string, std::string>. Это позволит быстро и легко найти контакт по имени (вероятно, самый распространенный случай).

    std::map также сортирует все содержимое, поэтому было бы тривиально добавить несколько вещей, например «перечислить все мои контакты в алфавитном порядке».

    Еще одна проблема, которая кажется мне проблемой, заключается в том, что практически все ваш код знает все обо всем. Даже код в основном знает все детали, например, как именно форматируются данные в файле.

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

      Похоже, это могла бы быть отличная программа, но она совершенно не зависит от платформы:

      #include <conio.h>
      

      Если бы вы могли заменить это чем-то более портативным, было бы намного лучше. Я рекомендую научиться использовать библиотеку Curses, если вы хотите создавать программы CUI; видеть Что эквивалентно getch() & getche() в линуксе?. Это также позволит вам заменить (также непереносимый) system() звонки с более надежными функциями.

      Отмечу что system даже не был объявлен — в программах на C ++ вы должны включать <cstdlib> и назовите это как std::system.


          if(Cmnd=='1'){
              ⋮
          }
          else if(Cmnd=='2'){
              ⋮
          }
          else if(Cmnd=='3'){
              ⋮
          }
          else if(Cmnd=='4'){
              ⋮
          }
          else{
              ⋮
          }
      

      Проще написать это с помощью switch утверждение:

          switch (command) {
          case '1':
              ⋮
              break;
          case '2':
              ⋮
              break;
          case '3':
              ⋮
              break;
          case '4':
              ⋮
              break;
          default:
              ⋮
              break;
          }
      

      У вас есть много дублированного кода для открытия, чтения и (для изменения операций) перезаписи файла. Что произойдет, если вы обновите формат файла? Вам придется изменить практически все!

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

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

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

        Добавить комментарий

        Ваш адрес email не будет опубликован. Обязательные поля помечены *