Я сделал простое управление контактами на 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 ответа
Ваш код на C ++ очень похож на C. Это один из случаев, когда класс будет очевидным выбором для структурирования вашего кода. Однако перед этим я хотел бы указать на несколько моментов в вашем текущем коде.
- В
const std::string&& Main
должно бытьconst std::string& Main
. Ссылка на const rvalue почти всегда семантически бесполезна. Еще лучше, если вы используете C ++ 17 или новее, в большинстве случаев вы можете передатьstd::string_view
вместоconst std::string&
.
- Использовать
'n'
надstd::endl
. Это может потенциально повлиять на производительность (особенно при выполнении файлового ввода-вывода). Вот такая тема.
- Перед записью или чтением проверьте, действителен ли файловый поток.
std::ifstream file("contacts.txt");
if(!file)
{
// something went wrong, handle the error!
}
- Как правило, избегайте
system
. Это может быть неэффективно, но, что более важно, непереносимо. Ваш код не будет работать, например, в Linux, посколькуpause
не является допустимой командой оболочки в Linux.
- Вам не нужно звонить вручную
File.close()
. Файл будет закрыт, когда объектfstream
объект уничтожен.
- Следовать Принцип единой ответственности. Проще говоря, это означает, что функция должна делать только одно. Например, ваш
EditContact()
функция отвечает за а) открытие файла, б) анализ данных, в) запрашивая ввод пользователя и г) запись данных в файл. Разбейте свои функции на логические части.
- Вы можете использовать
switch
заявление вместоif
внутри твоегоmain
функция. Кроме того, как упоминалось выше, вашmain
функция делает слишком много. Вы можете легко переместить множество операторов внутри их собственных функций.
- Как упоминалось выше, ваш код довольно неэффективен, поскольку он открывает, анализирует и записывает файл почти при каждой операции. Файловый ввод-вывод стоит недешево, и, поскольку вы используете 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
функция, чтобы выяснить это!