Simple read from text file algorithm doesn't work - c++

I have a text file that contains keys and values like this:
keyOne=1
keyTwo=734
keyThree=22.3
keyFour=5
The keys are just lower-case and upper-case letters like in my example. The values are either integers or floats. Each key and value is separated by an equals sign (=). Now I want to read the values into variables I have in my program.
This is the code I have tried to read the values:
(I omitted the part where I store the values in my program's variables, and just print them out now for demonstration.)
std::fstream file(optionsFile, std::fstream::in);
if (file.good()) {
int begin;
int end;
std::string line;
while(std::getline(file, line)) {
// find the position of the value in the line
for (unsigned int i = 0; i < line.length(); i++) {
if (line.at(i) == '=') {
begin = i + 1;
end = line.length();
break;
}
}
// build the string... it starts at <begin> and ends at <end>
const char *string = "";
for (int i = begin; i < end; i++) {
string += line.at(i);
}
// only gibberish is printed in the following line :(
std::cout << "string=" << string << std::endl;
}
}
I don't understand why it won't print the value.. instead only weird stuff or even nothing is printed
Please help this broke my spirit so hard :(

You are using C-style strings (char arrays) without properly allocated memory, and you are just manipulating with the pointer, so you are not appending characters into your string:
// build the string... it starts at <begin> and ends at <end>
const char *string = "";
for (int i = begin; i < end; i++) {
string += line.at(i);
}
Use std::string instead:
/// build the string... it starts at <begin> and ends at <end>
std::string str;
for (int i = begin; i < end; i++) {
str += line.at(i);
}
Or allocate memory by hand, use the proper indexing, terminate the string with '\0' character and don't forget to delete the string after you don't need it anymore:
char *string = new char[end - begin + 1];
int j = 0;
for (int i = begin; i < end; i++) {
string[j++] = line.at(i);
}
// Don't forget to end the string!
string[j] = '\0';
// Don't forget to delete string afterwards!
delete [] string;
So, just use std::string.
Edit Why did you mix C strings and std::string in the first place?

As was already mentioned, native string types in c/c++ do not support straightforward concatenation since they are essentially pointers to some preallocated memory. You should always use std::string when a string is supposed to be mutable.
Btw, think about the following refactoring:
void process_option (const std::string& a_key, const std::string& a_value)
{
std::cout << a_key << " <-- " << a_value << std::endl;
}
void read_options (std::istream& a_in, const char* a_source)
{
int line_n = 0;
std::string line;
while (std::getline(a_in, line))
{
++ line_n;
std::string::size_type p = line. find('=');
if (p == line. npos)
{
// invalid_entry(a_source, line_n);
continue;
}
process_option(
line. substr(0, p), // key
line. substr(p + 1, line. find_first_of("\t\r\n", p + 1)) // value
);
}
}
void read_options (const char* a_filename)
{
std::ifstream file(a_filename);
if (! file)
{
// read_error(a_filename);
return;
}
read_options(file, a_filename);
file. close();
}
void read_options (const std::string& a_filename)
{
read_options(a_filename. c_str());
}

Related

Calling clear() on a vector does not actually delete data in data()?

Summary:
It seems like just calling clear() on a vector is not enough to clear it.
vector_full_of_stuff.clear();
I had to call clear() and then shrink_to_fit() to completely delete all data inside of it.
vector_full_of_stuff.clear();
// AND THEN
vector_full_of_stuff.shrink_to_fit();
What gives? This became a problem because when I would call data() on a vector, it would include stuff that I thought should have been cleared when I called clear() earlier in the code.
Additional Details:
I am doing an computer networking assignment where I have to parse a PASV command result into an IP and Port Number. While parsing a fictitious PASV command result separated by commas, I noticed that if I parse a three digit followed by a two digit I get that third digit from the previous parse when calling data() even though I shouldn't (?) because I called clear() before it.
ex.
PASV Command Result = 209,202,252,54,19,15
The "2" from "252" carries over into "19" when parsing.
Code:
// this one actually deletes data
void splitString(string str, char delimiter, vector<string> * out) {
vector<char> word_buffer;
for (int i = 0; i < str.length(); ++i) {
if (str[i] == delimiter) {
out->push_back(word_buffer.data());
word_buffer.clear();
word_buffer.shrink_to_fit();
} else {
word_buffer.push_back(str[i]);
}
}
out->push_back(word_buffer.data());
word_buffer.clear();
}
//
// this one doesn't
// the only thing that's different about this one
// is that its missing shrink_to_fit()
void splitString(string str, char delimiter, vector<string> * out) {
vector<char> word_buffer;
for (int i = 0; i < str.length(); ++i) {
if (str[i] == delimiter) {
out->push_back(word_buffer.data());
word_buffer.clear();
// word_buffer.shrink_to_fit(); // need this to delete data
} else {
word_buffer.push_back(str[i]);
}
}
out->push_back(word_buffer.data());
word_buffer.clear();
}
//
// main driver code
int main() {
vector<string> user_input_tokens;
string port = "209,202,252,54,19,15";
splitString(port, ',', &user_input_tokens);
for (string str : user_input_tokens) {
cout << str << ".";
}
}
//
Expected Output:
209.202.252.54.19.15.
Actual Output:
209.202.252.542.192.152.
The vector's data() method returns a raw pointer to the vector's allocated array in memory. clear() destroys the contents of that array if needed and sets the vector's size() to 0, but does not reallocate the array itself, and thus does not change the vector's capacity(). Calling the vector's shrink_to_fit() method reallocates the array so its capacity() matches its size(), if possible (shrink_to_fit() is advisory only and not guaranteed to actually do anything).
Also, when constructing a std::string from a char* pointer by itself, the char data needs to be null-terminated, but your data is not. You need to push a null terminator into the vector before using data():
void splitString(const string &str, char delimiter, vector<string> * out) {
vector<char> word_buffer;
for (int i = 0; i < str.length(); ++i) {
if (str[i] == delimiter) {
word_buffer.push_back('\0');
out->push_back(word_buffer.data());
word_buffer.clear();
} else {
word_buffer.push_back(str[i]);
}
}
if (!word_buffer.empty()) {
word_buffer.push_back('\0')
out->push_back(word_buffer.data());
}
}
Otherwise, you can simply take the vector's size() into account when constructing the strings, no null terminators needed:
void splitString(const string &str, char delimiter, vector<string> * out) {
vector<char> word_buffer;
for (int i = 0; i < str.length(); ++i) {
if (str[i] == delimiter) {
out->push_back(string(word_buffer.data(), word_buffer.size()));
// alternatively:
// out->emplace_back(word_buffer.data(), word_buffer.size());
word_buffer.clear();
}
else {
word_buffer.push_back(str[i]);
}
}
if (!word_buffer.empty()) {
out->push_back(string(word_buffer.data(), word_buffer.size()));
// alternatively:
// out->emplace_back(word_buffer.data(), word_buffer.size());
}
}
That being said, there are other ways to implement a splitString() function without needing the word_buffer vector at all, eg:
void splitString(const string &str, char delimiter, vector<string> * out) {
string::size_type start = 0, pos = str.find(delimiter);
while (pos != string::npos) {
out->push_back(str.substr(start, pos-start));
start = pos + 1;
pos = str.find(delimiter, start);
}
if (start < str.size()) {
if (start > 0) {
out->push_back(str.substr(start));
} else {
out->push_back(str);
}
}
}
Live Demo
void splitString(const string &str, char delimiter, vector<string> * out) {
istringstream iss(str);
string word;
while (getline(iss, word, delimiter))
out->push_back(std::move(word));
}
Live Demo
But, even if you wanted to buffer the words manually, a std::string would have made more sense than a std::vector<char>, especially since you are outputting std::string values:
void splitString(const string &str, char delimiter, vector<string> * out) {
string word_buffer;
for (string::size_type i = 0; i < str.length(); ++i) {
if (str[i] == delimiter) {
out->push_back(std::move(word_buffer));
word_buffer.clear();
} else {
word_buffer.push_back(str[i]);
}
}
if (!word_buffer.empty()) {
out->push_back(std::move(word_buffer));
}
}
Live Demo

String pointer manipulation

I have this assignment at school. A string pointer is passed to the function and returns 2 const strings to a different functions.
The 2 new strings divide the original string into 2 parts based on a space.
Example:
Input
str = 05/12 Hello
Desired output
key = 05/12
satData = Hello
This is the code I wrote but its giving me errors. Please help
void RBapp::processInsert(string &str)
{
string *key = new string();
string *satData = new string();
int i = 0, j =0;
while(str[i]!=" ")
{
key[j] = str[i];
i++;
j++;
}
j = 0;
while(str[i]!='\0')
{
satData[j] = str[i];
i++;
j++;
}
myRBT.rbInsert(key, satData);
}
Using stringstream
void RBapp::processInsert(const std::string &str)
{
std::stringstream ss(str);
std::string key;
std::string satData;
ss >> key;
ss >> satData;
myRBT.rbInsert(key, satData);
}
Your program is subject to undefined behavior since you are accessing memory that is not valid.
When you use:
string *key = new string();
string *satData = new string();
You have two pointers that point to empty strings.
key[j] = str[i];
is wrong if j > 0 since that points to invalid memory.
Based on the description of what you are trying to do, what you need is something along the lines of:
void RBapp::processInsert(string &str)
{
// There is no need to use new string.
// Just use two string objects.
string key;
string satData;
int i = 0;
while(str[i]!=" ")
{
// Add the character to key
key.push_back(str[i]);
i++;
}
// Increment i here if you want the space to be excluded from
// satData. If you want to include the space character in satData,
// then, there is no need to increment i
++i;
while(str[i]!='\0')
{
// Add the character to satData
satData.push_back(str[i]);
i++;
}
myRBT.rbInsert(key, satData);
}
You say you receive a string pointer - what I see is you receive a string. In C++ you would try to avoid hand-written loops as much as possible - std::string has a lot of stuff you need.
void process(const string &str) {
auto firstSpace = str.find_first_of(' ');
auto key = str.substr(0, firstSpace);
auto value = str.substr(firstSpace, string::npos);
myRBT.rbInsert(key, value);
}

How to split char pointer array into two new char pointer array with delimiters?

I currently have an char *command[SIZE] array in main that is filled by taking in user input. An example of what can be filled in is, {"ls", "-1", "|" "sort"}. I want to take in this as a parameter for a function and split it in two arrays (char *command1[SIZE], char *command2[SIZE]) using the delimiter "|". So char *command1[SIZE] contains {"ls", and "-l"}, and char *command2[SIZE] contains {"sort"}. Command1 and command2 should not contain the delimiter.
Here is part of my code below...
**
void executePipeCommand(char *command) {
char *command1[SIZE];
char *command2[SIZE];
//split command array between the delimiter for further processing. (the delimiter
is not needed in the two new array)
}
int main(void) {
char *command[SIZE];
//take in user input...
executePipeCommand(command);
}
**
Works for any number of split tokens, and you can pick a split token.
std::vector<std::vector<std::string>> SplitCommands(const std::vector<std::string>& commands, const std::string& split_token)
{
std::vector<std::vector<std::string>> ret;
if (! commands.empty())
{
ret.push_back(std::vector<std::string>());
for (std::vector<std::string>::const_iterator it = commands.begin(), end_it = commands.end(); it != end_it; ++it)
{
if (*it == split_token)
{
ret.push_back(std::vector<std::string>());
}
else
{
ret.back().push_back(*it);
}
}
}
return ret;
}
To convert to required format
std::vector<std::string> commands;
char ** commands_unix;
commands_unix = new char*[commands.size() + 1]; // execvp requires last pointer to be null
commands_unix[commands.size()] = NULL;
for (std::vector<std::string>::const_iterator begin_it = commands.begin(), it = begin_it, end_it = commands.end(); it != end_it; ++it)
{
commands_unix[it - begin_it] = new char[it->size() + 1]; // +1 for null terminator
strcpy(commands_unix[it - begin_it], it->c_str());
}
// code to delete (I don't know when you should delete it as I've never used execvp)
for (size_t i = 0; i < commands_unix_size; i++)
{
delete[] commands_unix[i];
}
delete[] commands_unix;

c++: I am trying to reverse the order of words in string (not the whole string)

#include <iostream>
#include <vector>
using namespace std;
void RevStr (char *str)
{
if(*str !=0)
{
vector<char> v1;
while((*str != ' ')&&(*str !=0))
v1.push_back(*str++);
// trying to not add space in the last word of string
if(*str !=0)
{
v1.push_back(' ');
str++;
}
RevStr(str);
cout<<*str;
}
}
int main()
{
RevStr("hello world!");
cout<<endl;
}
I want to change the order of words in the string for example " how are you" => "you are how"
I am having some problem, its not printing correctly (print only w), please help me and tell me what i did wrong. However i know that I should not call "cout<<*str;
" since i am inserting the "array of char" in stack (recurssion) but i dont know what i need to do.
C++ makes it simple:
#include <algorithm>
#include <iterator>
#include <vector>
#include <string>
#include <iostream>
#include <sstream>
std::string reverse(std::string const& text)
{
std::stringstream inStream(text);
std::stringstream outStream;
std::vector<std::string> words;
std::copy(std::istream_iterator<std::string>(inStream), std::istream_iterator<std::string>(), std::back_inserter(words));
std::copy(words.rbegin(), words.rend(), std::ostream_iterator<std::string>(outStream, " "));
return outStream.str();
}
int main()
{
std::cout << reverse("Hello World") << "\n";
}
A common approach to do this is to reverse the entire string first, then for each word, reverse the letters in the word. So no recursion is necessary. You might find it easier to give this a try (yes, I know this isn't exactly an answer to your question :) ).
Use cout << str, not cout << *str to print a string. There's an operator<< overload for char *. But maybe that's not what you're trying to do; I can't quite follow your logic, in any event.
You're losing the "hello" part.
The algorithm you seem to go for does this:
each call to RevStr isolates the first word in the string it is passed as a parameter
calls RevStr with the remaining of the string
prints the word it isolated at step 1 as the stack unwinds
Basically, you should be printing the v1 data.
I would strongly advise making using some of the functionality exposed via std::string as a place to start.
One way you might do this would look like this:
std::string ReverseString(std::string s)
{
std::stack<std::string > stack;
std::string tmpstr = "";
std::string newstr = "";
size_t strsize = s.size();
size_t pos = 0; size_t tmppos = 0;
size_t i = 0; size_t stacksize = 0;
while( pos < strsize )
{
tmppos = s.find(" ", pos, 1); // starting as pos, look for " "
if (tmppos == std::string::npos) // std::string::npos => reached end
{
tmppos = strsize; // don't forget the last item.
}
tmpstr = s.substr(pos, tmppos-pos); // split the string.
stack.push(tmpstr); // push said string onto the stack
pos = tmppos+1;
}
stacksize = stack.size();
for ( i = 0; i < stacksize; i++ )
{
tmpstr = stack.top(); // grab string from top of the stack
stack.pop(); // stacks being LIFO, we're getting
if ( i != 0 ) // everything backwards.
{
newstr.append(" "); // add preceding whitespace.
}
newstr.append(tmpstr); // append word.
}
return newstr;
}
It's by no means the best or fastest way to achieve this; there are many other ways you could do it (Jerry Coffin mentions using std::vector with an iterator, for example), but as you have the power of C++ there, to me it would make sense to use it.
I've done it this way so you could use a different delimiter if you wanted to.
In case you're interested, you can now use this with:
int main(int argc, char** argv)
{
std::string s = "In Soviet Russia String Format You";
std::string t = ReverseString(s);
std::cout << t << std::endl;
}
given that its a char*, this reverses it inplace (ie, doesn't require more memory proportional to the incoming 'str'). This avoids converting it to a std::string ( not that its a bad idea to, just because it's a char* to start with.)
void reverse_words(char* str)
{
char* last = strlen(str) + str;
char *s, *e;
std::reverse(str,last);
for(s=e=str; e != last; e++)
{
if(*e == ' ')
{
std::reverse(s,e);
s = e+1;
}
}
std::reverse(s,e);
}
void Reverse(const string& text)
{
list<string> words;
string temp;
for ( auto cur = text.begin(); cur != text.end(); ++cur)
{
if (*cur == ' ')
{
words.push_front(temp);
temp.clear();
}
else
{
temp += *cur;
}
}
if (! temp.empty())
{
words.push_front(temp);
}
for_each(words.begin(), words.end(), [](const string& word) { cout << word << " "; });
cout << endl;
}
void swap(char* c1, char* c2) {
char tmp = *c1;
*c1 = *c2;
*c2 = tmp;
}
void reverse(char* s, char* e) {
if (s == NULL || e == NULL)
return;
while(s < e)
swap(s++, e--);
}
void reverse_words(char* line) {
if (line == NULL)
return;
reverse(line, line+strlen(line)-1);
char *s = line;
char *e;
while (*s != '\0') {
e = s;
while (*e != ' ' && *e != '\0') ++e;
--e;
reverse(s,e);
s = e+2;
}
}

How to put two backslash in C++

i need to create a function that will accept a directory path. But in order for the compiler to read backslash in i need to create a function that will make a one backslash into 2 backslash.. so far this are my codes:
string stripPath(string path)
{
char newpath[99999];
//char *pathlong;
char temp;
strcpy_s(newpath, path.c_str());
//pathlong = newpath;
int arrlength = sizeof(newpath);
for (int i = 0; i <= arrlength ;i++)
{
if(newpath[i] == '\\')
{
newpath[i] += '\\';
i++;
}
}
path = newpath;
return path;
}
this code receives an input from a user which is a directory path with single backslash.
the problem is it gives a dirty text output;
int arrlength = sizeof(newpath); causes the size of your entire array (in chars) to be assigned to arrlength. This means you are iterating over 99999 characters in the array, even if the path is shorter (which it probably is).
Your loop condition also allows goes one past the bounds of the array (since the last (99999th) element is actually at index 99998, not 99999 -- arrays are zero-based):
for (int i = 0; newpath[i]] != '\0'; i++)
Also, there is no reason to copy the string into a character array first, when you can loop over the string object directly.
In any case, there is no need to escape backslashes from user input. The backslash is a single character like any other; it is only special when embedded in string literals in your code.
In this line:
if(newpath[i] = '\\')
replace = with ==.
In this line:
newpath[i] += '\\';
This is supposed to add a \ into the string (I think that's what you want), but it actually does some funky char math on the current character. So instead of inserting a character, you are corrupting the data.
Try this instead:
#include <iostream>
#include <string>
#include <sstream>
int main(int argc, char ** argv) {
std::string a("hello\\ world");
std::stringstream ss;
for (int i = 0; i < a.length(); ++i) {
if (a[i] == '\\') {
ss << "\\\\";
}
else {
ss << a[i];
}
}
std::cout << ss.str() << std::endl;
return 0;
}
lots wrong. did not test this but it will get you closer
http://www.cplusplus.com/reference/string/string/
string stripPath(string path)
{
string newpath;
for (int i = 0; i <= path.length() ;i++)
{
if(path.at(i) == '\\')
{
newpath.append(path.at(i));
newpath.append(path.at(i));
}
else
newpath.append(path.at(i));
}
return newpath;
}
But in order for the compiler to read
backslash in i need to create a
function that will make a one
backslash into 2 backslash
The compiler only reads string when you compile, and in that case you will need two as the first back slash will be an escape character. So if you were to have a static path string in code you would have to do something like this:
std::string path = "C:\\SomeFolder\\SomeTextFile.txt";
The compiler will never actually call your function only compile it. So writing a function like this so the compiler can read a string is not going to solve your problem.
The condition if (newpath[i] = '\\') should be if (newpath[i] == '\\').
The statement newpath[i] += '\\'; will not give the intended result of concatenation. It will instead add the integral value of '\\' to newpath[i].
Moreover why are you using a char newpath[99999]; array inside the function. newpath could be std::string newpath.
int main()
{
std::string path = "c:\\test\\test2\\test3\\test4";
std::cout << "orignal path: " << path << std::endl;
size_t found = 0, next = 0;
while( (found = path.find('\\', next)) != std::string::npos )
{
path.insert(found, "\\");
next = found+4;
}
std::cout << "path with double slash: " << path << std::endl;
return 0;
}