std::vector<class> fills on debug but not on release - c++

I have a class A which has several data types defined within it. Within the program, I define a vector of type A and then read from a text file into this vector.
When I am debugging, the vector "fills up" and I can read values from it - everything works as planned. However, when I build the release version and run the .exe, the vector is empty. The rest of the program works fine, it's just not pushing the values.
I'm fairly new to C++, so I am assuming it is something to do with my constructor function, or possibly how I handle the enum?. Here is my MCVE:
#include "stdafx.h"
#include <iostream>
#include <string>
#include <vector>
#include <fstream>
enum class Type
{
Type1
};
Type convertStringToType(std::string input)
{
return Type::Type1;
}
class A
{
public:
int num;
std::string str;
Type typ;
A(int refNumber, std::string name, Type type)
{
num = refNumber;
str = name;
typ = type;
}
};
std::vector<A> readFileIntoVector(std::string filename)
{
std::ifstream readFile(filename);
std::vector<A> tempVector;
std::string tempNum = "";
std::string tempStr = "";
std::string tempTyp = "";
std::getline(readFile, tempNum, ',');
std::getline(readFile, tempStr, ',');
std::getline(readFile, tempTyp, ',');
while (readFile)
{
tempVector.push_back(A(std::stoi(tempNum), tempStr, convertStringToType(tempTyp)));
std::getline(readFile, tempNum, ',');
std::getline(readFile, tempStr, ',');
std::getline(readFile, tempTyp, ',');
}
return tempVector;
}
int main()
{
std::vector<A> exampleVector = readFileIntoVector("Text.txt");
if (exampleVector.empty() == true)
{
std::cout << "Vector is empty.";
system("PAUSE");
}
else
{
int a = 1;
do
{
std::cin >> a;
if (a == 0 || a == 1)
{
std::cout << exampleVector.at(a).num << "\n";
std::cout << exampleVector.at(a).str << "\n";
}
} while (a == 0 || a == 1);
return 0;
}
}
This is Text.txt:
1, String1, Type1,
2, String2, Type1,

As I mentioned in the comments, the most likely problem is that when you created a release build for the project, the text file which the program depends on was not included in the respective folder.
To fix it, you must include this file in that folder yourself, or find a way to indicate to VS that the program depends on that text file, that way it is copied to the release folder automatically for you. I don't use VS, so I don't know how possible that last part is, but I hope you get the idea.

There are two possibilities:
As you mentioned that you are able to get correct value while debugging in debug mode, there is a chance that you are able to read correctly from the .txt file and getting correct values in your vector, but as you are debugging in release mode you are not able to see the correct value set in the watchlist, as you may find watchlist containing garbage value in release mode even if your vector is getting the correct value.
You are not getting .txt file, but in that case you should not get velue even in debug version.

Related

Writing a vector of objects to file and then reading it

I'm new to C++ and stackoverflow so forgive me any mistakes in my post ;). I need to create a code, which allows me to fill new objects with data from std::cin and export these objects to binary file later. Also, I need to import objects exported to file at some point. Objects represent users with standard user information like username, ID, lvl etc.
#include <vector>
#include <string>
#include <iostream>
class User {
std::string username;
unsigned int ID, lvl;
public:
User(std::string un, int uID, int ulvl) {
username = un;
ID = uID;
lvl = ulvl;
}
};
int main() {
std::string u_name;
int u_ID,u_lvl;
bool finish = false;
char choice;
std::vector<User> test_user_vec;
do {
std::cout << "Enter username: ";
std::cin >> u_name;
std::cout << "Enter ID: ";
std::cin >> u_ID;
std::cout << "Enter lvl: ";
std::cin >> u_lvl;
test_user_vec.push_back(User(u_name, u_ID, u_lvl));
std::cout << "Do you want to add another user? (y/n)?";
choice = getch();
if (choice == 'y') finish = true;
} while (!finish);
return 0;
}
I assume that test_user_vec stores every object I created while my program is running. My problem occurs when I want to export that vector to file. The purpose of this action is to store objects' data even after my program terminates and import all the data when I run my program again.
I was trying to solve this problem on my own, nothing really came to my mind. While I was looking for some info i found something like this:
#include <fstream>
#include <vector>
#include <string>
int main()
{
std::vector<std::string> v{ "one", "two", "three" };
std::ofstream outFile("my_file.txt");
// the important part
for (const auto &e : v) outFile << e << "\n";
}
I've tested it with <string> and <int> vectors and my variables. It's good until I try to export <object>vector.
Also i found another solution and tried to do something with it on another test code:
class Test {
public:
int number;
float number2;
};
int main(){
Test test1;
test1.number = 122;
test1.number2=12;
std::fstream testfile("test1.bin", std::ios::out | std::ios::binary);
testfile.write((char*)&test1, sizeof(test1));
testfile.close();
//ater writing an object with variables i commented this section
//then uncommented this section and run the program again
std::fstream testfile2("test1.bin", std::ios::in);
testfile2.read((char*)&test1, sizeof(test1));
std::cout << test1.number;
testfile2.close();
return 0;
}
Again, it works, i can read test1.number until I want to use vector of objects, not a single object. With vector of objects my cout printed some random values like 11314123e-03.
I was trying to somehow combine these 2 solutions, but nothing worked out. I would like to have a binary file, because i heard it's faster and has any data protection (i can't just open it in notepad and read the data) I'm new to c++, there is a great chance of me trying to do it reeeeeealy inefficient way, so pls help :D.
Data member getter functions can be added to the User class and used in fstream output operations. This should provide the general idea:
std::string userName;
for (const auto &u : v)
{
outFile.write(u.GetID(), sizeof(int));
outFile.write(u.GetLvl(), sizeof(int));
userName = u.GetName();
outFile.write(username.length(), sizeof(size_t));
outFile.write(userName.data(), username.length());
}
For userName, the length is written to precede the userName string data in the file so that the file can be parsed when read. The binary encoding/convention is designer's decision as there are several options. Another option would be to encode the entire object as a null-terminated string, although this would generally be less size efficient except for the userName string itself.
Note: test_user_vec.push_back(User(u_name, u_ID, u_lvl)); is creating temporary User objects on the stack. As #drescherjm and #RaymondChen pointed out, that is OK, but this is a better alternative: test_user_vec.emplace_back(...);

Is it possible to print specific lines out of this code?

I am trying to print out whatever is necessary from my program. What it does is it takes a long list from a text file and sort it based on first choice and GPA and put it into a vector.
I manage to sort by First choice and GPA however how can I remove whatever output that isn't necessary?
I know I asked this before but I think didn't ask correctly previously and I already edited some of it.
This is an example of my Txt File (The sequence of each line is 1st choice, 2nd choice, 3rd choice, GPA, Name):
CC,DR,TP,3.8,AlexKong
SN,SM,TP,4,MarcusTan
DR,TP,SC,3.6,AstaGoodwin
SC,TP,DR,2.8,MalcumYeo
SN,SM,TP,3.7,DavidLim
SN,SM,TP,3.2,SebastianHo
SC,TP,DR,4,PranjitSingh
DR,TP,SC,3.7,JacobMa
and so on...
This is my output now (it is a long vector):
TP,DR,SC,4,SitiZakariah
TP,DR,SC,3.9,MuttuSami
TP,DR,SC,3.5,SabrinaEster
TP,DR,SC,3,KarimIlham
TP,DR,SC,3,AndryHritik
SN,SM,TP,4,JasonTan
SN,SM,TP,3.8,MarcusOng
SN,SM,TP,3.7,DavidLim
SN,SM,TP,3.4,MollyLau
SN,SM,TP,3.2,SebastianHo
SN,SM,TP,3.2,NurAfiqah
SN,SM,TP,2.4,TanXiWei
SC,TP,DR,4,SallyYeo
SC,TP,DR,4,PranjitSingh
SC,TP,DR,3.6,RanjitSing
SC,TP,DR,2.8,MalcumYeo
SC,TP,DR,2.8,AbdulHalim
SC,TP,DR,2.7,AlifAziz
DR,TP,SC,3.9,SitiAliyah
DR,TP,SC,3.9,LindaChan
DR,TP,SC,3.8,SohLeeHoon
DR,TP,SC,3.7,PrithikaSari
DR,TP,SC,3.7,NurAzizah
DR,TP,SC,3.7,JacobMa
DR,TP,SC,3.6,AstaGoodwin
CC,DR,TP,3.9,MuruArun
CC,DR,TP,3.7,DamianKoh
CC,DR,TP,3.3,MattWiliiams
CC,DR,TP,3.3,IrfanMuhaimin
And this is the output that I need (Basically students with CC as their 1st choice without displaying the 3 options. I don't want the other options without CC as their first option. I already manage to print the output without the 3 choices as follow.):
3.9,MuruArun
3.8,AlexKong
3.7,DamianKoh
3.3,MattWiliiams
3.3,IrfanMuhaimin
This is my program:
#include <iostream>
#include <vector>
#include <fstream>
#include <string>
#include <algorithm>
using namespace std;
struct greater
{
template<class T>
bool operator()(T const &a, T const &b) const { return a > b; }
};
void main()
{
vector<string> v;
ifstream File;
File.open("DSA.txt");
if (!File.is_open()) return;
string line;
string Name;
string GPA;
string First;
string Second;
string Third;
getline(File, First, ',');
getline(File, Second, ',');
getline(File, Third, ',');
getline(File, Name, ',');
getline(File, GPA, '\n');
cout << "Round 1:\n";
if (First == "CC")
while (File>>line)
{
v.push_back(line);
}
sort(v.begin(), v.end(), greater());
for (int i = 0; i < v.size(); i++)
{
cout << v[i].substr(9) << endl; //remove first 3 choices from output
}
}
This is my attempt to filter out my output:
if (First == "CC")
while (File>>line)
{
v.push_back(line);
}
sort(v.begin(), v.end(), greater());
for (int i = 0; i < v.size(); i++)
{
cout << v[i].substr(9) << endl;
}
I thought that if I getline and make an if condition to separate CC (if the first choice is CC, then condition is true) then I only print the ones with CC as first choice and ignore the rest. so basically I try to search for CC as the first choice.
But obviously I was very wrong. So I was hoping if anyone knows how to filter the output
Previous point:
As was noted in the comment section using namespace std; is a bad choice and your code has an example of one of the reasons why that is, the redefinition of greater which is already present in the namespace.
The provided link has further explanation and alternatives.
As for you code, if the goal is to output the lines, starting with CC without the options, ordered by GPA, as I understand it, there are simpler ways of doing it, for instance, you can use std::find to parse only lines with "CC" at its beginning and work from there.
You could also use std::string::starts_with however it's only available with C++20, so I'll go with the first option.
Live demo
int main()
{
std::vector<std::string> v;
std::ifstream File;
File.open("DSA.txt");
if (!File.is_open())
return EXIT_FAILURE;
std::string line;
while (File >> line)
{
if (line.find("CC") == 0) // find lines that start with CC
v.push_back(&line[9]); // add lines without the options
} // or v.push_back(line.substr(9));
sort(v.begin(), v.end(), std::greater<std::string>()); //sort the lines
std::cout << "GPA" << "\t" << "Name" <<"\n\n"; // Title for the table
for (auto& str : v) //print the lines
{
std::replace(str.begin(), str.end(), ',', '\t'); //let's replace de comma
std::cout << str << "\n";
}
return EXIT_SUCCESS;
}
Taking your sample, this will output:
GPA Name
3.9 MuruArun
3.7 DamianKoh
3.3 MattWiliiams
3.3 IrfanMuhaimin
Lines with "CC" in second or third options will not be parsed, as is our goal.
Note:
This sorting method by string is possible and works in this case because the GPA values are lower than 10, otherwise we would have to convert the GPA field and sort the lines by its value, e.g.: 10 is larger than 9 but as a string it would be sorted first because lexicographicaly ordered, 9 would be considered larger, the character 9 is larger than the character 1.
As you can see I used the default greater template, in this case you don't need to make your own, you can just use this one.
One more thing, main must have int return type.
Note that sorting and filtering records of data is a classical task for a DBMS.
So instead of writing a program, consider loading your CSV into a DBMS of your choice (MonetDB is a nice FOSS DBMS for analytics), say into a table named people then issuing an appropriate query, e.g.
SELECT * FROM people WHERE first_choice = 'CC' ORDER BY gpa;
(that is an SQL query) to get the output you want.
Some DBMSes even work natively with CSV files, in which case you won't need to load anything, just point your DBMS at the CSV file.
Finally, and sorry for suggesting something crude, but - if you are willing to be more "manual" about this - a spreadsheet application like LibreOffice Calc or MS Excel can import the CSV; and you can use the AutoFilter functionality to only display people with CC as the first option, and sort of descending GPA using the autofilter drop-down menu on the GPA column.
PS - This is not to detract from other valid answers of course.
Obviously you are using the wrong approach. This must be changed.
At first, we do need to analyze What the problem is about. So, we have a file with many lines. Each line of the many lines contains infos / values for one student. The values are delimited by a comma.
Such data is usually refered to as CSV --> comma separated values.
There are tons of posts here on SO to explain, how to read CSV files.
Anyway. After having done the initial analysis, we must start to think now, How we could solve that problem. Looking at the data in one line, we notice that it is always structured on the same way. For that reason, we define a structure, which will contain the values for one student. We call this new structure "Student". It will be defined like this:
// Data for one student
struct Student {
std::string first{};
std::string second{};
std::string third{};
double GPA{};
std::string name{};
};
Please note that the GPA will be stored as a double value and not as a string, because maybe we want to do some mathematical calculations.
The next requirement is that we have many lines with student data. So, we will store the many students in a container. And here we select the std::vector, because it can grow dynamically.So all data for all studnets can be stored in
// Here we will store all student swith all data
std::vector<Student> student{};
Next, we want to read all data from a file. So, then let us define a filestream variable and give the filename as a constructor parameter. This will try to open the file automatically. And, if this variable will not be used any longer and falls out of scope, then the file will be closed automatically.
We check, if the file is open. Because the bool operator and the ! operator for streams is overwritten to return the status of the file, we can simply write:
if (fileStream) {
Next we want to read many lines of the file, containing student data. We will use the std::getlinefunction to read a complete line. And we will use this function in a while loop. The function will return a reference to the ifstream again. And, as written above, this has a bool operator. So, the reading will be stopped at EOF (End-Of_file).
while (std::getline(fileStream, line))
Then, we have a complete line in our variable "line". This needs to be split into its sub components. "SN,SM,TP,3.7,DavidLim" needs to be split into "SN", "SM","TP", 3.7, "DavidLim".
There are many many possible solutions for splitting a CSV-string. I will use an approach with std::getline. At the bottom of this post, I show some further examples.
So, in order to use iostream facilities to extract data from a string, we can use the std::istringstream. We will put the line into it and can then extract data as from any other stream:
std::istringstream iss{ line };
Then we will use again the std::getline function to extract our needed data from the stringstream. And, after having extracted everything, we will add a complete Student record to our target vector:
student.push_back(tempStudent);
Now, we have all student data in our vector and we can use all function and algorithms of C++ to do all kind of operations on the data.
For the filtering, we will iterate over all data in the vector and then use an if statement to find out, if the current student record fullfills the condition. Then we will print it.
See the following example program:
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <algorithm>
// Data for one student
struct Student {
std::string first{};
std::string second{};
std::string third{};
double GPA{};
std::string name{};
};
const std::string fileName{ "r:\\DSA.txt" };
int main() {
// Here we will store all student swith all data
std::vector<Student> student{};
// Open the source file
std::ifstream fileStream{ fileName };
// Check, if file could be opened
if (fileStream) {
// One complete line of the source file
std::string line{};
// Now read all lines of the source file
while (std::getline(fileStream, line)) {
// Now we have a complete line like "SN,SM,TP,4,MarcusTan\n" in the variable line
// In order to extract from this line, we will put it in a std::istringstream
std::istringstream iss{ line };
// Now we can extract from this string stream all our needed strings
Student tempStudent{};
// Extract all data
std::getline(iss, tempStudent.first,',');
std::getline(iss, tempStudent.second,',');
std::getline(iss, tempStudent.third, ',');
std::string tempGPA{}; std::getline(iss, tempGPA, ','); tempStudent.GPA = std::stod(tempGPA);
std::getline(iss, tempStudent.name);
// Add this data for one student to the vector with all students
student.push_back(tempStudent);
}
// Now, all Students are available
// If you want to sort, then do it know. We can sort for any field.
// As an example, we sort by name. Anything else also possible
std::sort(student.begin(), student.end(), [](const Student& s1, const Student& s2) { return s1.name < s2.name; });
// Now, we make a filtered output
// Iterate over all students
for (const Student& s : student) {
// Check, if condition is fullfilled
if (s.first == "CC") {
std::cout << s.GPA << ", " << s.name << '\n';
}
}
}
else {
// There was a problem with opening the input source file. Show error message.
std::cerr << "\n\nError: Could not open file '" << fileName << "'\n\n";
}
}
But this is very C-Style. In modern C++ we would go a different way. The object oriented appoach keeps data and methods (operating on that data) together in one class or struct.
So, basically we would define an extractor and inserter operator for the struct, because only this Object should know, how to read and write its data.
Then things will be really simple and compact.
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <algorithm>
#include <iterator>
// Data for one student
struct Student {
std::string first{};
std::string second{};
std::string third{};
double GPA{};
std::string name{};
friend std::istream& operator >> (std::istream& is, Student& s) {
char comma{};
return std::getline(std::getline(std::getline(std::getline(is, s.first,','), s.second,','), s.third,',') >> s.GPA >> comma, s.name);
}
friend std::ostream& operator << (std::ostream& os, const Student& s) {
return os << s.first << '\t' << s.second << '\t' << s.third << '\t' << s.GPA << '\t' << s.name;
}
};
const std::string fileName{ "r:\\DSA.txt" };
int main() {
// Open the source file and check, if it could be opened
if (std::ifstream fileStream{ fileName }; fileStream) {
// Read the complet CSV file and parse it
std::vector student(std::istream_iterator<Student>(fileStream), {});
// Show all recors with first==CC
std::copy_if(student.begin(), student.end(), std::ostream_iterator<Student>(std::cout, "\n"), [](const Student& s) { return s.first == "CC"; });
}
return 0;
}
So, you have a one-liner for reading all student data. And then you can apply all kind of algorithms from the standard library.
That's the way to go.
Splitting a string
Splitting a string into tokens is a very old task. There are many many solutions available. All have different properties. Some are difficult to understand, some are hard to develop, some are more complex, slower or faster or more flexible or not.
Alternatives
Handcrafted, many variants, using pointers or iterators, maybe hard to develop and error prone.
Using old style std::strtok function. Maybe unsafe. Maybe should not be used any longer
std::getline. Most used implementation. But actually a "misuse" and not so flexible
Using dedicated modern function, specifically developed for this purpose, most flexible and good fitting into the STL environment and algortithm landscape. But slower.
Please see 4 examples in one piece of code.
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <regex>
#include <algorithm>
#include <iterator>
#include <cstring>
#include <forward_list>
#include <deque>
using Container = std::vector<std::string>;
std::regex delimiter{ "," };
int main() {
// Some function to print the contents of an STL container
auto print = [](const auto& container) -> void { std::copy(container.begin(), container.end(),
std::ostream_iterator<std::decay<decltype(*container.begin())>::type>(std::cout, " ")); std::cout << '\n'; };
// Example 1: Handcrafted -------------------------------------------------------------------------
{
// Our string that we want to split
std::string stringToSplit{ "aaa,bbb,ccc,ddd" };
Container c{};
// Search for comma, then take the part and add to the result
for (size_t i{ 0U }, startpos{ 0U }; i <= stringToSplit.size(); ++i) {
// So, if there is a comma or the end of the string
if ((stringToSplit[i] == ',') || (i == (stringToSplit.size()))) {
// Copy substring
c.push_back(stringToSplit.substr(startpos, i - startpos));
startpos = i + 1;
}
}
print(c);
}
// Example 2: Using very old strtok function ----------------------------------------------------------
{
// Our string that we want to split
std::string stringToSplit{ "aaa,bbb,ccc,ddd" };
Container c{};
// Split string into parts in a simple for loop
#pragma warning(suppress : 4996)
for (char* token = std::strtok(const_cast<char*>(stringToSplit.data()), ","); token != nullptr; token = std::strtok(nullptr, ",")) {
c.push_back(token);
}
print(c);
}
// Example 3: Very often used std::getline with additional istringstream ------------------------------------------------
{
// Our string that we want to split
std::string stringToSplit{ "aaa,bbb,ccc,ddd" };
Container c{};
// Put string in an std::istringstream
std::istringstream iss{ stringToSplit };
// Extract string parts in simple for loop
for (std::string part{}; std::getline(iss, part, ','); c.push_back(part))
;
print(c);
}
// Example 4: Most flexible iterator solution ------------------------------------------------
{
// Our string that we want to split
std::string stringToSplit{ "aaa,bbb,ccc,ddd" };
Container c(std::sregex_token_iterator(stringToSplit.begin(), stringToSplit.end(), delimiter, -1), {});
//
// Everything done already with range constructor. No additional code needed.
//
print(c);
// Works also with other containers in the same way
std::forward_list<std::string> c2(std::sregex_token_iterator(stringToSplit.begin(), stringToSplit.end(), delimiter, -1), {});
print(c2);
// And works with algorithms
std::deque<std::string> c3{};
std::copy(std::sregex_token_iterator(stringToSplit.begin(), stringToSplit.end(), delimiter, -1), {}, std::back_inserter(c3));
print(c3);
}
return 0;
}
Please compile with C++17 enabled.
What a pity that nobody will read that.

c++ how to read a cfg file more complex?

I recently changed from c# to c++.
Ive been watching some tutorials on how to read a config file. I am probably asking it wrong, what I mean is:
I am making a program, but it's for multiple users. Every user will have his prefence on what will be in a textfile.
Example:
In my textfile ("items.txt"), i have default "ints" and configurable "ints".
10=5
90=2
50=9
In c#, if i remember correctly, i read all lines and if the line started with for example "10=", i splitted the text so my line would only be the configurable int left and i can use that easly in my program, it was something like:
string[] lines = File.ReadAllLines(path);
string str;
foreach (string line in lines)
{
if (line.StartsWith("10"))
{
str = line.Split('=')[1];
//I have what i need (str);
}
}
I did that for everything that i needed. So, what's the best way to do this in c++?
ALSO: I need to get a specific line for them, so i can use them all later on in my program.
Thanks a lot in advance!
You have 3 things in your files:
lines, which we can read with getline
keys (the part of the line before =)
values (the part after the =).
We can split the key and value by finding the = character.
So, you get something like:
std::ifstream file(path);
if (!file) {
std::cerr << "unable to open " << path << std::endl;
return false;
}
for (std::string line; getline(file, line); ) {
auto pos = line.find('=');
if (pos == line.end()) {
std::cerr << "skipping invalid line '" << line << "'\n";
continue;
}
auto key = line.substr(0, pos);
auto val = line.substr(pos);
// do something with key,val
}
If you want to convert the key and value strings above into int, you can use a std::stringstream to do the conversion, or use std::strtol.
This code is a C++ equivalent of the C# code you posted. This code by itself will compile, of course you have to fill something in for path instead of an empty string. If you need this somewhere in a method instead of as a program on itself, abandon the int main() and return 0 of course. Reply to this if something is unclear. I tried to set it up the same way your C# code is structured, so you would understand everything.
#include <fstream>
#include <vector>
#include <string>
int main()
{
std::string path = "";
std::ifstream file(path); //open the file from path, might want to check file.fail()
std::vector<std::string> lines;
std::string line;
while (file >> line)
lines.push_back(line);
std::string str;
for (const std::string& line : lines)
{
if (line.substr(0, 2).compare("10") == 0)
{
size_t foundIdx = line.find('=');
str = line.substr(foundIdx + 1);
}
}
file.close();
return 0;
}
Well in C++ we now use std::regex
For your case - I'd use the following:
#include <iostream>
#include <string>
#include <regex>
#include <iterator>
int main()
{
// Simple config file
std::string rules = "10=5\n 90=2\n50=9";
std::regex single_rule_regex("[0-9]+\\=[0-9]+");
std::regex single_rule_part_regex("[0-9]+");
auto rules_begin = std::sregex_iterator(rules.begin(),
rules.end(),
single_rule_regex);
auto rules_end = std::sregex_iterator();
for (auto it = rules_begin; it != rules_end; ++it)
{
std::smatch rule_match = *it;
std::string rule_str = rule_match.str();
std::cout << rule_str << std::endl;
// key=value
auto parts = std::sregex_iterator(rule_str.begin(),
rule_str.end(),
single_rule_part_regex);
std::string key = parts->str();
std::string value = (++parts)->str();
std::cout << key << " has " << value << std::endl;
}
}
Output for the gives rules is:
10=5
10 has 5
90=2
90 has 2
50=9
50 has 9

Read file into array and return it from a function C++

In Lua, I have such a function to read a file into an array:
function readFile(file)
local output = {}
local f = io.open(file)
for each in f:lines() do
output[#output+1] = each
end
f:close()
return output
end
Now in C++, I tried to write that like this:
string * readFile(file) {
string line;
static string output[] = {};
ifstream stream(file);
while(getline(stream, line)) {
output[sizeof(output)+1] = line;
}
stream.close();
return output;
}
I know you can't return arrays from functions, only pointers. So I did this:
string *lines = readFile("stuff.txt");
And it threw me the error cannot convert 'std::string {aka std::basic_string<char>} to' std::string* {aka std::basic_string<char>*}' in intialization string *lines = readFile("stuff.txt");
Can anyone tell me what is wrong here, and is there a better way to read files into arrays?
EDIT:
I'm going to be using the returned array to do value matching using a for loop. In Lua this would be written as:
for _, each in ipairs(output) do
if each == (some condition here) then
--Do Something
end
end
How can this be done in C++, using vectors (according to the answer by Jerry Coffin)?
EDIT 2:
I can't match the vectors correctly for some reason. I wrote the code in a separate test file.
int main() {
vector<string> stuff = read_pass();
cout << stuff.size() << endl;
cout << stuff[0] << endl;
if (stuff[0] == "admin") {
cout << "true";
}
else {
cout << "false";
}
return 0;
}
read_pass() looks like this:
vector<string> read_pass() {
ifstream stream("stuff.txt");
string line;
vector<string> lines;
while(getline(stream, line)) {
lines.push_back(line);
}
stream.close();
return lines;
}
And stuff.txt looks like this:
admin
why?
ksfndj
I just put it some random lines to test the code. Every time I compile and run main.cpp the output I get is
3
admin
false
So why isn't the code being matched properly?
EDIT 3:
So instead of forcing myself down the vectors method of doing things, I decided to try this instead:
#include <cstdlib>
#include <iostream>
#include <fstream>
#include <vector>
#include "basefunc.h"
using namespace std;
int main() {
string storedUsrnm;
string storedPw;
string pw = "admin";
string usrnm = "admin";
ifstream usernames("usrnm.accts");
ifstream passwords("usrpw.accts");
while(getline(usernames, storedUsrnm)) {
getline(passwords, storedPw);
print("StoredUsrnm " + storedUsrnm);
print("StoredPw: " + storedPw);
if (storedUsrnm == usrnm && storedPw == pw) {
print("True!");
return 0;
}
}
print("False!");
return 0;
}
Where print() is
void print(string str) {
cout << str << endl;
}
This still prints false, at the end, and it leads me to believe that for some reason, the "admin" read by the ifstream is different from the "admin" string. Any explanations for how this is so? Or does this code not work either?
Doesn't look to me like your current code should even compile. Anyway, I'd probably do something like this:
std::vector<std::string> read_file(std::istream &infile) {
std:string line;
std::vector<std::string> lines;
while (std::getline(infile, line))
lines.push_back(line);
return lines;
}
So the basic idea here is to read a line from the file, and if that succeeded, add that line (with push_back) to the vector of results. Repeat until reading a line from the file fails. Then return the vector of all the lines to the caller.
A few notes: especially at first, it's fairly safe to presume that any use of pointers is probably a mistake. That shouldn't be taken as an indication that pointers are terribly difficult to work with, or anything like that--just that they're almost never necessary for the kinds of things most relative beginners do in C++.
Likewise with arrays--at first, assume that what you might think of as an array in some other language translates to a std::vector in C++. C++ does also have arrays, but using them can wait a while (a long while, IMO--I've been writing C++ for decades now, and virtually never use raw pointers or arrays at all).
In the interest of simplicity, I've consolidated the data into the program, so it reads the data from the stringstream, like this:
#include <vector>
#include <string>
#include <fstream>
#include <iostream>
#include <sstream>
using namespace std;
vector<string> read_pass(istream &is) {
string line;
vector<string> lines;
while (getline(is, line)) {
lines.push_back(line);
}
return lines;
}
int main() {
istringstream input{ "admin\nwhy?\nksfndj" };
// To read from an external file, change the preceding line to:
// ifstream input{ "stuff.txt" };
vector<string> stuff = read_pass(input);
cout << stuff.size() << endl;
cout << stuff[0] << endl;
if (stuff[0] == "admin") {
cout << "true";
}
else {
cout << "false";
}
return 0;
}
At least for me, this produces:
3
admin
true
...indicating that it has worked as expected. I get the same with an external file. If you're not getting the same with an external file, my immediate guess would be that (at least the first line of) the file contains some data you're not expecting. If the problem continues, you might consider writing out the individual characters of the strings you read in numeric format, to give a more explicit idea of what you're really reading.
After a long time, I finally came up with the answer
#include <fstream>
#include <iostream>
#include <cstdlib>
#include <map>
using namespace std;
typedef map<int, string> strArr;
strArr readFile(string file) {
ifstream stream(file);
string line;
strArr output;
while(getline(stream, line)) {
output[output.size()+1] = line;
}
stream.close();
return output;
}
It doesn't read the file into an array, but it does return a map that does basically the same thing

std::istringstream.good() returns true once more than expected

I am making a toy program to create classes from a string (so that e.g. I feed it "test1 test2" and it makes test1.cpp, test1.h, test2.cpp, test2.h)
The action happens in this function:
bool makeClassesFromString(std::string classNames){
bool returnValue = true;
if (classNames == ""){
returnValue = false;
}
else{
std::istringstream issClassNames (classNames);
std::string sClassName;
while(issClassNames.good()){
issClassNames >> sClassName;
std::string sourceName = sClassName;
sourceName += ".cpp";
std::string headerName = sClassName;
headerName += ".h";
std::ofstream source(sourceName.c_str()), std::ios::app);
std::ofstream header(headerName.c_str()), std::ios::app);
//source << "#include \"" << headerName << "\"";
source.close();
header.close();
}
}
return returnValue;
}
The files are opened in append mode to ensure that any classes that already exist aren't accidentally overwritten.
I recently returned to this program to include some extras - in particular, the old version just created two empty files and I wanted to modify it to create the files with the necessary defines and includes so I wouldn't have to manually go in and add them each time. This revealed unexpected behavior - the last class to be made gets any text added twice.
In the code sample above, I've commented out the line where I started working on this but when I don't comment it out, I get behavior like this:
input:
classmaker.exe test1 test2
output:
test1.h
test2.h
test1.cpp //(Which contains: #include "test1.h")
test2.cpp //(Which contains: #include "test2.h"#include "test2.h"
My guess is that my while(issClassNames.good()) returns an extra time with the last classname (and so appends the string to the source file again) but I am not certain what behavior drives this.
Proposed solutions so far:
Test whether file already exists attempting to open in ifstream mode before opening in ofstream mode. (Problem: Doesn't solve issue of running while once mode than necessary)
Remove append mode. (Problem: Risks overwriting already existing class, still doesn't solve the while-loop issue)
Use conditional test in while-loop, don't open files on last run by e.g. comparing current class name with last class name and aborting if equal. Problem: Not sure how to check whether this is last run except cludgy "is this the same class name as last time?" test which might also risk aborting too early if the input string is "test1 test1 test2"
EDIT
I have realized: It's not really that the istringstream.good() returns true once more than expected, it is rather that it evaluates like so:
input: "test1 test2"
evaluation:
good = true; extract next to string //(succeeds, overwrites empty string with "test1")
good = true; extract next to string //(succeeds, overwrites "test1" with "test2")
good = true; extract next to string //(fails, Doesn't overwrite "test2")
good = false; break loop.
EDIT 2 + 3
Well that was easily solved. Thank you, all of you. Unfortunately I can only mark one answer as accepted so I picked the first posted answer but I appreciate the help.
The final program in its entirety:
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
void makeClassesFromStdInput();
void makeClassesFromParameter(int argc, const char** argv);
bool makeClassesFromString(std::string classNames);
int main(int argc, const char** argv){
switch (argc) {
case 0: //This shouldn’t happen
break;
case 1:
//This will keep making classes from the CLI until
//the user breaks the process.
makeClassesFromStdInput();
break;
default:
//This will make classes from the parameters passed
//to the program except the first parameter which is
//assumed to be the program name.
makeClassesFromParameter(argc, argv);
break;
}
return 0;
}
void makeClassesFromStdInput(){
std::cout << "Input class name and press enter.\nWhitespace delimits class names.\nA blank line ends the process.\n>";
bool running = true;
std::string classNames;
while(running){
std::getline(std::cin, classNames);
running = makeClassesFromString(classNames);
}
}
void makeClassesFromParameter(int argc, const char** argv){
std::string classNames = "";
for(int i = 1; i<argc; i++){
classNames += argv[i];
classNames += " ";
}
makeClassesFromString(classNames);
}
bool makeClassesFromString(std::string classNames){
bool returnValue = true;
if (classNames == ""){
returnValue = false;
}
else{
std::istringstream issClassNames (classNames);
std::string sClassName;
while(issClassNames >> sClassName){
std::string sourceName = sClassName;
sourceName += ".cpp";
std::string headerName = sClassName;
headerName += ".h";
std::ofstream source(sourceName.c_str(), std::ios::app);
std::ofstream header(headerName.c_str(), std::ios::app);
source << "#include \"" << headerName << "\"";
//Header is slightly more involved because we want all capital letters
for (std::string::size_type i=0; i<headerName.length(); ++i){
if(headerName[i] == '.'){
headerName[i] = '_';
}
else{
headerName[i] = toupper(headerName[i]);
}
}
header << "#ifndef __" << headerName << "\n"
<< "#define __" << headerName << "\n"
<< "\n"
<< "\n"
<< "#endif";
source.close();
header.close();
}
}
return returnValue;
}
The stream can't know what you are about to read. You always need to check after you tried to read if your read attempt was successful:
while(issClassNames >> sClassName) {
// do whatever processing of sClassName you want to do
}
Try changing:
while(issClassNames.good()){
to:
while(issClassNames >> sClassName){
And take that line out of the top of the loop.
Stream operator >> returns a stream, which can be cast to a bool with the result being the result of good(). Since the returned stream is in the state after the call of >>, it will return false if the eof bit was set by the operation.
You should replace
while(issClassNames.good()){
issClassNames >> sClassName;
with
while(issClassNames >> sClassName) {
WHY Assume your input is test1 test2. You first check stream is good. As there are no errors, it is good so you read test1 in sClassName. Now still the stream is good, so you go ahead and read test2 in sClassName (without an error in this step). Stream is still good so you go ahead and try to read again, but operator >> fails and sClassName remains unchanged and thus you see the old value test2.
operator >> returns handle to stream itself (which is convertible to bool) and can we used in condition. So you would enter the loop only if the read was succesful.