Here I have a file that reading in to a vector
typedef struct contacts
{
string name; //{jhonathan , anderson , felicia}
string nickName; //{jhonny , andy , felic}
string phoneNumber; // {13453514 ,148039 , 328490}
string carrier; // {atandt , coolmobiles , atandt }
string address; // {1bcd , gfhs ,jhtd }
} contactDetails;
vector <contactDetails> proContactFile;
I want to let user to delete a contact record from the file.For this I wrote a code.But from the code that i've written it deletes all the contact details from the file.But what I want here is when a user types a name then the program should delete only the name and the relevent nickname,carrier,phone number and address belongs to that name.Here is the code that i've written
string readString, selectContact;
cout << "Enter the name you want to delete" << endl;
cin >> selectContact;
ifstream fin;
fin.open(contactsFile);
if (!fin.is_open())
{
cout << "Unable to open Contacts.csv, please make sure file exists!" << endl;
}
ofstream fout;
fout.open("temp.csv" , ios::out);
while (getline(fin, readString))
{
if (((readString = readString.find(selectContact), 0)) == 0)
{
fout << readString <<',' << "\n";
}
cout << "Deleted Successfully" << endl;
showTableContacts();
}
if (((readString = readString.find(selectContact), 0) != 0))
{
cout << "\n" << selectContact << " not found" << endl;
}
fout.close();
fin.close();
remove("Contact.csv");//Deletes contacts.csv file
rename("temp.csv" , "Contact.csv");//Rename temp file as Contacts.csv
}
if (((readString = readString.find(selectContact), 0)) == 0)
This line has several issues.
readString.find(selectContact) returns the index of the match, or string::npos if it's not found. Checking that it is an exact match at position 0 is ok, technically, however, in your case if your string is "abcdefg" and you search for "abc" it will also match at position 0 when it shouldn't. You need to ensure the entire field matches, not just the first characters of it. (Find the first ',' in your line then ensure that all the bytes from the beginning up to the comma match your contact.)
Next consider this part:
readString = readString.find(selectContact)
You are assigning the result of the find to your string. That is, if find() returns 3, you will assign 3 to your string, which is interpreted as an ASCII character, and now your readString contains a single byte with junk in it. (In ASCII, 'A' is 65, etc)
So now your string is obliterated, and the index of your match is the contents, in the first byte (if the value is in range, or undefined behavior if it overflows.)
Code of this form:
if (xxx, 0)
is utilizing the comma operator, which sequences expressions by evaluating them from left to right, applying side effects (but throwing away the results) and the full expression evaluates to the value of the rightmost expression. Whatever xxx does (in your case, xxx is the code in step 2 above, obliterating your string) its return value is discarded and this whole expression evaluates to 0, which converts to a bool as false. Every time. (1,2,3) evaluates to 3, etc.
Therefore:
(readString = readString.find(selectContact), 0)
always evaluates to 0. And
if (((readString = readString.find(selectContact), 0)) == 0)
always obliterates your string, then tests if 0==0 which is always true.
Also, you almost never want to delete your input files. Rewriting them is dangerous enough, but you must be absolutely sure that everything succeeded before doing so; otherwise your program can end up causing a lot of pain if it deletes the input file and nothing else.
Related
This is a part of my code. I don't know why the string was partially overwrite by another string.
for(int xd = 0 ; xd < 10; xd++)
{
if(booklist[xd].length() != 0)
{
string d = string(booklist[xd]);
string e = "1,2,3,4";
string f = d + e;
cout << d.length() << endl;
cout << d << endl;
cout << f.length() << endl;
cout << f << endl;
}
}
The result of this code is:
16
brave new world
23
1,2,3,4ew world
28
nineteen eighty-four (1984)
35
1,2,3,4n eighty-four (1984)
I don't know why i got this wrong result.
Could someone help me?
Are you populating booklist by pulling from a file that you copied from Windows to a linux machine?
Windows will add a carriage return '\r' to the end of each line in addition to a newline. If you're reading from a windows file and using getline, it'll pull the carriage return into the string.
When a carriage return is output in the terminal, it resets the cursor to the beginning of the line, which would result in the behavior you're seeing.
To fix this, see this question on trimming whitespace from a string. The function you're looking for from that answer is rtrim (or "right trim"):
// trim from end (in place)
static inline void rtrim(std::string &s) {
s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) {
return !std::isspace(ch);
}).base(), s.end());
}
It's likely that your booklist entries have trailing carriage returns, causing line 4 (for instance) to print brave new world, return to column 1, and print 1,2,3,4 over it. (That's why the character count is greater on line 3 than line 1, despite the two lines having the same apparent length.)
Strip the trailing whitespace from booklist entries (or figure out why it's getting in there in the first place, and deal with that) and things should be fine.
Yes i got the reason why it shows wrong.
Just like what scohe001 said: when i use getline to put the file in to an array, it will add \r after each element, so i use substr to remove the \r .Now it get work. Thanks every one.
I have a function here that checks a text file for the existence of a string. The problem I have right now is that it's checking for substrings, so if the file has the word "IronMan" and any part of that string will pass the check.
Here's my code for the function:
void UIFunctions::checkIfExists()
{
while (!fin.eof())
{
getline(fin, line);
if (line.compare(getFriendName()) == string::npos)
{
setExists(false);
cout << getExistsErrorPrompt() << endl << endl;
if (listAfterCheck == 1)
{
listUsers();
}
cout << endl;
cout << endl;
cout << getErrorPrompt();
}
}
}
I searched for solutions and read that line.compare (changed from line.find) is what I need to match entire strings but instead of accepting substrings (like line.find did) now it doesn't accept anything at all to pass the check.
Edit: Also the file that this function uses would look something like this:
Ironman Captain Thor
The problem is that you are assuming that compare returns the same value as find would. The compare function returns an integer with value:
0 if the strings are equal
>0 if the compared string (in your case line) is lexicographically first
<0 if the comparing string (in your case getFriendName()) is lexicographically first
Therefore to check for an exact match, you would want:
if(line.compare(getFriendName()) == 0) {
setExists(false);
// ...
}
Comparing the result of compare with string::npos will never trigger since the magnitude of compare is the first differing character between your two strings.
std::basic_string::compare returns a value less than 0, 0 or more than 0. Since
if (line.compare(getFriendName()) == string::npos)
Is seeing if the return is the maximum value of the type of string::npos you more than likely will never trigger the if statement. If you only want to match a string exactly then you can use
if (line == getFriendName())
If instead you want to find any line that starts with getFriendName() then you want
if (line.find(getFriendName()) == 0)
In the above find will only return 0 if the string starts with what you want.
Using fstreams I have a file opened that contains numerous lines. Each contiguos set of 4 lines are such that: the first line is an int, the second and third are strings and fourth is a double. This sequence continues till EOF.
I'm attempting to load these lines into a struct array:
struct Library {
int id;
string title;
string artist;
double price;
};
and the code I'm trying to implement to load data into the struct is this:
const int LIMIT = 10
Library database[LIMIT];
ifstream file;
file.open("list.txt");
if(file) {
while(!(file.eof()) && counter < LIMIT) {
file >> database[counter].id;
getline(file, database[counter].title;
getline(file, database[counter].artist;
file >> database[counter].price;
}
} else {
...
}
// Using the following to debug output
for(int i = 0; i < counter; i++) {
cout << "ID: " << database[i].id << endl
<< "Title: " << database[i].title << endl
<< "Artist: " << database[i].artist << endl
<< "Price: " << database[i].price << endl
<< "-----------------------" << endl;
}
The file I'm trying to throw at this thing is
1234
Never Gonna Give You Up
Rick Astley
4.5
42
Thriller
Michael Jackson
32.1
The problem I'm having here is that between reading the id and title using file >> ... and getline(...) is that somewhere a newline bite is being introduced screwing up the output, which displays this monstrosity...
ID: 1234
Title:
Artist: Never Gonna Give You Up
Price: 0
--------------------
ID: 0
Title:
Artist:
Price: 0
--------------------
The solution is probably the most basic of solutions, but mainly because I can't figure out exactly what is going on with the newline bite I can't combobulate a phrase to shove into google and do my stuff there, and I'm at the stage where I've been looking at a problem so long, basic knowledge isn't working properly - such as how to handle basic input streams.
Any form of help would be much appreciated! Thanks in advance :)
This happens because the >> operator for the input stream only grabs part of a line, and does not always grab the newline character at the end of the line. When followed by a call to getline, the getline will grab the rest of the line previously parsed, not the line after it. There are a few ways to solve this: you can clear the buffer from the input stream after each read, or you can simply get all your input from getline and just parse the resulting strings into an integer or a double when you need to with calls to stoi or stod.
As a side note, you don't want to detect the end of your file the way you presently are. See why is eof considered wrong inside a loop condition?
You can solve this problem by adding:
fflush(file);
everytime before you use getline(file, ...). Basically this will clear the input buffer before you use the getline() function. And fflush() is declared in the cstdio library.
file >> database[counter].id;
will read, in this case, a whitespace separated sequence of characters that is interpreted as an int. The newline is considered whitespace. You should now be sitting on that newline character, thus the getline() will read nothing -- successfully -- and increment the file position just past that.
You may be better off using getline() for each line and then separately interpreting the lines from the reading. For example, the first line read could be interpreted with a subsequent std::stoi() to get the integer representation from the string.
so this user input all in one line string , i need to parse the input into two categories: user age and user name.
for example , the user input -->> [23:Frank] [15:Jack] [45:] [33:sofia] []
in this case i have more than one argument (delimiter, total of 3 ) which are [:] , in addition i need to get user input and stop looping once i encounter the [] at the end.
this is what i was thinking :
string input;
vector<string> age;
vector<string> name;
cin >> input;
while (input != "[]")
{
get age between first [ and :
assign to age variable
get name between : ]
assign to user name variable
................
}
also - what if one of the brackets is missing a name , how can assign a blank name and skip that part in order to process the rest (meaning i will output age with no name next to it).
any suggestions regarding how to get and process the data.
i saw some advance stuff like Toknizing and booster which are advance for my course, that's why i was thinking about straight forward getline and parse functions.
Thank you.
Read in token like you are currently doing with cin
test for [] as you are doing with the while loop
For the inside the loop, here are a few things to help you out:
std::string's front and back functions are perfect for ensuring that input starts with [ and ends with ]
std::string's substr function is perfect for trimming off the [] so you can easily ignore them for the rest of the parsing
std::stringstream allows you to call make a stream that only contains your trimmed input.
std::getline(stream, string, char) will read all characters it finds up to the char parameter or the end of the stream and stuff the results in the string parameter and then discard the char it found so you won't trip over it parsing the rest of the input.
strtoul will convert a string into a number and tell you if it failed. It will not accept negative numbers so you can catch people trying to trick your program.
getline(stream, string) will read the stream until it hits an end of line marker. Perfect for reading the rest of a stream that contains no end of lines.
Using strtoul:
char * endp;
unsigned long agenum strtoul(agestr.c_str(), // turn string into old c-style string
&endp, // will be updated with the end of the char after the last number
10); // base ten numbers
if (endp != '\0') // old c-strings always end on a null (numerical zero). If strtoul
// didn't end on a null, the string wasn't a valid number.
{
//not a number
}
Ok , so THANKS for people who helped or at least tried to help!!
what i end up doing for this part was as follows:
read in each string at once
use the find function in order to locate my delimiters ( which are in this case [ : ] )
return positions per each perimeter based on my argument (each pair will hold either the beginning and the end for age || name)
pass those arguments results to truncate the string by using substr function, then assign into each variables.
while (true)
{
string myInput;
cin >> myInput;
while (myInput != "[]")
{
int age_beg = myInput.find('[') + 1 ;
int age_end = myInput.find(':', age_beg);
string age = myInput.substr(age_beg, (age_end - age_beg));
int name_beg = age_end + 1;
int name_end = myInput.find(']', name_beg);
string name = myInput.substr(name_beg, (name_end - name_beg));
cout << "NAME : " << name << " AGE : " << age << endl;
cin >> myInput;
}
}
Hope this will help others with the same question in the future !!
In C++, Ubunt 12.04, I have a file named config.txt which contains user name and password. I have 2 public static string variables: USER and PASSWORD. This is my code:
// Read file from config
string text[2];
int count = 0;
while(!fin.eof()){
getline(fin,text[count]);
count++;
if(count == 2){
break;
}
}
CONNECTOR::USER = text[0];
CONNECTOR::PASSWORD = text[1];
string userAndPassword = CONNECTOR::USER + ":" + CONNECTOR::PASSWORD;
cout << CONNECTOR::USER << endl; // It's fine, right user: qsleader
cout << CONNECTOR::PASSWORD << endl; // ok, right password: 123456
cout <<"user and password: " << userAndPassword << endl; // It's weird text! Problem here!
The weird text result is: :123456d password: qsleader!! This is not what I expected! But I don't know why this happen? Can anyone give me an suggestion? (If i print: cout << "user and password: qsleader:123456", the result is good!!)!
The problem is created when you read the values. Indeed, I guess that your file has the two items on two different lines. Furthermore, I guess this file uses Windows line endings. Therefore, when you read the first item, it reads qsleader\r and then stops as the next extracted character is \n, extracted but not appended to the string.
When you create the userAndPassword string, it is in fact qsleader\r:123456. This special character \r is a return carriage. It makes the cursor go to the beginning of the line. Therefore, on the last line, you first output user and password: qsleader, then go back to the first column, and write :123456, resulting in :123456d password: qsleader.
You are setting userAndPassword to a hellish expression involving assignments. I guess your intent was:
string userAndPassword = CONNECTOR::USER + ":" + CONNECTOR::PASSWORD