I have no idea about C++, but I've been assigned to edit this piece of code:
// Setup path information for output file from environmental variables
char * path = new char[100];
path = getenv("MODEL_MERGE");
char * templatePath = new char[100];
char * outputPath = new char[100];
strcpy(templatePath, path);
strcat(templatePath, "infile location");
strcpy(outputPath, path);
strcat(outputPath,"outfile location");
cout << "temp: " << templatePath << endl;
cout << "out: " << outputPath << endl;
//input output file streams for reading/writing to files
ifstream readFile(templatePath);
ofstream outFile(outputPath);
My goal is to replace the "infile location" and "outfile location", which currently point to specific files. I want the user to be able to enter the file names when running from command prompt. Sorry if this is something as simple as <<cin, but I couldn't get that to work, and I have zero experience with this language.
Got it! Everything above was replaced by:
//User inputs paths
string input;
string output;
cout<<"Input path?"<<endl;
cin>> input;
cout<<"output path?"<<endl;
cin>> output;
//input output file streams for reading/writing to files
ifstream readFile(input.c_str());
ofstream outFile(output.c_str());`
Thanks everyone for the help!
There is enough wrong with the code supplied to OP to be worth a quick going over in addition to pointing the OP in a useful direction.
First, no test for NULL on the call to getenv. If MODEL_MERGE doesn't exist, NULL is returned and then used in string copies. BOOM!
Second, newing all those arrays. Dynamically allocate only as a last resort. new must be pared with at least one delete, depending on the code's flow, to return the allocated memory for reuse when no longer needed. Since there seems to no need to dynamically allocate and the sizes of the arrays are known, they should have been defined as char templatePath[100];. Less memory management to be dealt with and effectively no possibility of leakage.
Third renders point two obsolete. Rather than using char arrays, use strings where possible. Not only do they handle all of the memory management, including resizing as needed rather than trampling out of bounds, for you, they also perform routine tasks like copying and appending with much less fuss. This bit I'll demonstrate below.
Proper use of cin and cout is well detailed on a number of sites so I won't go over it here.
Also note I've removed the need for using namespace std; by explicitly stating the namespace at use. Read why using namespace std; is often a bad idea.
#include <fstream>
#include <iostream>
int main()
{
char * Model_MergePath = getenv("MODEL_MERGE");
if (Model_MergePath != NULL)
{ //MODEL_MERGE is defined
std::string path(Model_MergePath); //replace icky and fault-prone char array
std::string templatePath = path; // copy strings with =
std::string outputPath; // not assigning path here so I can demonstrate
//something else later
std::string inFileLoc; // new throw away variables for user input.
std::string outFileLoc; // could use the same var for both. I didn't for clarity
std::cin >> inFileLoc; // get input
templatePath += inFileLoc; // append to strings with +=
std::cin >> outFileLoc;
outputPath = path + outFileLoc; // concatenate strings with +
// validate paths for correctness and possible intrusion attempts here
// this, I'm afraid, is up to the OP as the security requirements are unknown
std::cout << "temp: " << templatePath << std::endl;
std::cout << "out: " << outputPath << std::endl;
//input output file streams for reading/writing to files
std::ifstream readFile(templatePath);
// older C++ compilers may require a c-style string as the file path
std::ofstream outFile(outputPath.c_str());
// do stuff with readFile and outFile
// remove the deletes that should have corresponded to the replaced `new`s
return 0;
}
else
{ //MODEL_MERGE is NOT defined
std::cerr << "Cannot find environment variable MODEL_MERGE. Exiting." << std::endl;
return -1;
}
}
Related
I am currently writing a program that automates a game I play. I am doing this as a side project and I am currently stuck on a file input/output issue. All the text information stored inside the json file gets fully deleted once I finish running the program. I am trying to find certain phrase in a json file and then replace it with a different one. I made multiple functions to help assist me in doing this. I am utilizing one function to find the line specified within the file, and then another line to replace the string I located with a string I made, based on splicing using a mixture of hard-coded text and a user inputted string. I am aware that I am using cin >> rather than getline and using strings in c++ but I promise you that is not the issue at hand here. The problem is that my entire file's content is being deleted and I am not quite sure why. I will link my code below with a picture of the output and hopefully someone can help me out here because I am genuinely confused, especially since this seems like such a rudimentary task. I took out segments of the code because the entire project is pretty extensive, if there is anything missing down below or anything is unclear please let me know in the comments.
string FindFullLine(std::ifstream& file, unsigned int num){
std::string s;
for (int i = 1; i <= num; i++)
std::getline(file, s);
return s;
}
bool replace(std::string& str, const std::string& from, const std::string& to) {
size_t start_pos = str.find(from);
if(start_pos == std::string::npos)
return false;
str.replace(start_pos, from.length(), to);
return true;
}
^^ separate functions that are being used in this small piece of code below
string champo = " \"mainChamp\": ";
string apo = "\",";
string champ, ban, gamemode, newchamp;
cout << "What champion would you like to pick? " << endl;
cin >> champ; //getline doesnt work; cin is not causing the issue
cout << "\nWhat champion would you like to ban? " << endl;
cin >> ban;
cout << "\nWhat gamemode would you like to pick? " << endl;
cin >> gamemode;
newchamp = " \"mainChamp\": \"";
newchamp += champ;
newchamp.append(apo);
ifstream yuumi ("C:\\forfuncoding\\bot\\Yuumi Bot Extension Fix\\bot\\config.json"); //opening file stream for input
ofstream yuumi1 ("C:\\forfuncoding\\bot\\Yuumi Bot Extension Fix\\bot\\config.json"); //opening file stream for output
if(!yuumi || !yuumi1){
cout << "Error opening up files! " << endl;
exit(0);
}
string s = FindFullLine(yuumi, 2);
string temps;
string strTemp;
temps = s; //temporary val to store s
cout << s;
replace(s, s , newchamp); //first parameter is where new string is stored, second param is the old string and third param is what old string is being replaced by new string
cout << s;
while(yuumi >> strTemp)
{
if(strTemp == temps){
cout << s;
strTemp = s;
}
yuumi1 << strTemp;
}
yuumi.close(); //closes file for output
yuumi1.close(); //closes file for input
exit(0); //just for testing purposes
I have tried changing many values by passing them by reference/not by reference, I have tried using fstream with ios::out and ios::in, I have tried using getline, I have tried moving the logic around; making sure all the variables are scoped correctly. At first glance I thought the issue was in the replace function since it returns a bool but after further inspection it also works as is. Originally, the FindFullLine function was made for fstream, but I changed it to ifstream in the parameters, I don't believe that would change much however. I also tried getting rid of the ifstream and ofstream.close(); functions. I have tried practically everything I can think of.Down below is a screenshot of the format I was attempting to copy.
format of text in json file i was copying
I believe I formatted everything correctly in my code in order to match exactly what is happening in the text file. But when I run the code, everything disappears and the text file is blank. Linked down below is the output that I get, there is a cout << statement after the replace function is ran to ensure that everything is working as intended, and from the output's point of view, everything is working correctly.
output from console
The program is supposed to open a text file whose path is user-input. Next, it counts the lines contained in the file and outputs them. Here's what I tried:
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main()
{
string path = NULL;
string garbage = NULL;
int cnt = 0;
cout << "Enter file path: ";
cin >> path;
ifstream inFile(path.c_str());
if (inFile)
{
while (!inFile.eof())
{
getline(inFile, garbage);
++cnt;
}
}
inFile.close();
cout << endl;
cout << path << " has " << cnt << " lines";
cin.ignore();
cin.get();
return 0;
}
This is what I get:
Program: C:\Windows\SYSTEM32\MSVCP120D.dll
File: c:\program files (x86)\microsoft visual studio 12.0\vc\include\xstring
Line: 1168
Expression: invalid null pointer
Note: The course I'm following has only shown me the basics of the methods used by ifstream and ofstream, like open, close and eof. So I would appreciate a solution with only these, as I'm sure you know many ways of doing this.
The class std::string is a reasonable container class, like std::vector but with an API that has a number of extra string-oriented functions.
In a particular, its use does not resemble old-fashioned C-style string handling in terms of char*, which is where I assume you got the idea of trying to use NULL as an initializer. (in modern C++ you should use the C++ keyword nullptr to create a null pointer, rather than the old C style macro NULL)
what string path = NULL; actually does is interpret NULL as a const char*, and then tries to read the C-style string at the location NULL points to so as to copy it into path. Since NULL is null rather than actually pointing to a string, you get the error message you cite.
What you really want to do is to simply use the default constructor via string path; which initializes path to be an empty string.
Don't use the meaningless NULL they're not pointers:
string path;
string garbage;
#include <iostream>
#include <fstream>
#include <cstring>
#include <map>
using namespace std;
int main()
{
cout << "Hello world!" << endl;
//define a bool to determine if you're still in the file's header section or not
bool header = true;
//define the map as a multidimensional string array that stores up to 100 z-levels
//and 50x50 tiles per z level
char* worldmap[100][50][50];
int zLevel=0;
//header declaration map
map<string, char*> declarations;
//create an input file stream object
ifstream file;
//open file
file.open("map1.dmm");
//validate file
if(!file.good()){return 1;}
//begin reading the file
while(!file.eof()){
//create a character array to write to
char line[512];
//read the file line by line; write each line to the character array defined above
file.getline(line, 512);
//header check
if(header){
if(!line[0]){
header = false;
break;
}else{
bool declaringKey=true;
char* key={};
char* element={};
char* token[20]={};
token[0] = strtok(line, "\"()");
for(unsigned int n = 0;n<20;n++){
if(n>0)token[n] = strtok(NULL, "\"()");
//cout << token[0] << endl;
if(!token[n] || (token[n])[1] == '=')continue;
if(declaringKey){
key = token[n];
declaringKey=false;
}else{
//cout << "pow" <<endl;
element = token[n];
cout<<"declarations[" << key << "] = " << element << endl;
declarations.emplace(key,element); //<-------------- problem line, i think
cout << declarations[key] << endl;
}
}declaringKey=true;
}
}else{
if(!line[0]) {
zLevel++;
continue;
}
}
}
//string test = "aa";
return 0;
}
I'm trying to create a map loader that loads a simple map from a text file. I'm aware that there are other map loaders available but most of them do far more than I need them to. So far, this code only reads the header, which basically defines what each set of characters represents as a tile, for example: "aa" = "sand tile"
The problem is, when I'm emplacing the key/element into the declarations map, it seems to use the same element for all keys. I'm assuming that this is because by defining a character pointer it always points to the same data, and only serves the purpose of changing the value contained by that pointer, rather than allocating new data and keeping them separate.
But that raises another question, why does it emplace a different key with the same element, even though both are pointers..? Anyways,
How can I make it so that all keys/elements are independent character arrays, rather than pointers to the exact same space carved out by the array..?
EDIT: You can just assume the code works other than the fact that it stores the same element to all keys. Also, the element it stores is always the last one that's parsed out of the file's header section.
Just use a std::string for the value, too. This should solve your immediate problem.
That said, do not use stream.eof() to control a loop reading values! It does not work. Instead, always try to read from a stream and then verify if the read was successful, e.g.:
while (file.getline(line, sizeof(line))) {
// ...
}
Personally, I wouldn't read into a fixed size buffer and use a std::string instead:
for (std::string line; std::getline(file, line); ) {
// ...
}
From this point I would also not use strtok() but rather either the members of std::string or suitable algorithms. This way I also wouldn't let astray, considering it a good idea to store pointers (not to mention that I can't deal with pointers and, thus, my programs don't use them).
I have the following block of code that i am using to read a text file of the following format:
firstname lastname id mark
firstname lastname id mark
Following is the block of code.
void DBManager::ReadFile(void){
fstream myfile; /*fstream object that will be used for file input and output operations*/
char* fn; /*pointer to the storage which will hold firstname*/
char* ln; /*pointer to the storage which will hold lastname*/
int id; /*integer var to hold the id*/
float mark; /*float var to hold the mark*/
/*read in the filename*/
g_FileName = new char[1024]; /*allocate memory on the heap to store filename*/
cout << "Please enter the filename:";
cin >> g_FileName;
/*open file*/
myfile.open(g_FileName, ios::in | ios::out);
if(myfile.is_open()){ /*check if the file opening is successful*/
cout << "File reading successful !\n";
/*read information from the file into temporary variables before passing them onto the heap*/
while (!myfile.eof()) {
fn=(char*) new char[1024];
ln=(char*) new char[1024];
myfile >> fn >> ln >> id >> mark;
cout << fn << " " << ln << " " << id << " " << mark << " " << endl;
}
myfile.close();
}
else{ /*else print error and return*/
perror("");
return;
}
}
The above block of code works ! :)
But I am surprised as to how myfile knows it is supposed to hold one line at a time and how its being smart enough about setting the four variables.
I am new to C++ , and hence this might be covered in some sort of documentation. But i would be happy to have some insight from you'll or a link to somewhere i can understand fstream objects better.
In C++, std::fstream is a type of stream which works specifically for files. When reading from a file, the interface for std::fstream is almost identical to std::cin. Input streams are programmed to read the next word or number when asked with the >> operator. They know where words and numbers are because they are separated by white space. In the default locale, spaces, tabs and newlines are considered to be white space. You can change the locale to include other characters, like commas, and have those be skipped while reading from a file. Basically, when reading with input streams, newlines and spaces are treated the same.
Some nice explanation for learning about streams is here: http://www.cprogramming.com/tutorial/c++-iostreams.html
I'm not sure what the question is. However, the code has several problems:
You should always check input after having tried to read.
Testing for eof() to determine if there is more to read doesn't work.
You have a memory leak, allocating memory in every iterator.
Reading without a constraint into a char array is unsafe, i.e., it is prone to buffer overrides (one of the major attack vectors).
You want to use a loop looking something like this:
std::string fn, ln;
while (myfile >> fn >> ln >> id >> mark) {
...
}
This is a very strange issue. I'm trying to print a large text file, it's a Wikipedia entry. It happens to be the page on Velocity. So, when I tell it to print the file, it prints "In", when it should print "In physics, velocity is etc, etc etc".
Here's the code I'm using to print out:
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
ifstream wiki;
wiki.open("./wiki/velocity.txt");
char* wikiRead;
wiki >> wikiRead;
cout << wikiRead << endl;
wiki.close();
}
Please help.
wiki >> wikiRead;
The default delimiter for stream is space, so when the stream encounters a space, it simply stops reading, that is why it reads only one word.
If you want the stream to read all words, the you've to use a loop as:
char* wikiRead = new char[1024]; //must allocate some memory!
while(wiki >> wikiRead)
{
cout << wikiRead << endl;
}
wiki.close();
delete []wikiRead; //must deallocate the memory
This will print all the words in the file, each on a new line. Note if any of the word in the file is more than 1024 character long, then this program would invoke undefined behavior, and the program might crash. In that case, you've to allocate a bigger chunk of memory.
But why use char* in the first place? In C++, you've better choice: Use std::string.
#include<string>
std::string word;
while(wiki >> word)
{
cout << word << endl;
}
wiki.close();
Its better now.
If you want to read line-by-line, instead of word-by-word, then use std::getline as:
std::string line;
while(std::getline(wiki, line))
{
cout << line << endl;
}
wiki.close();
This will read a complete line, even if the line contains spaces between the words, and will print each line a newline.
You ask the stream to read the (binary) value of a pointer (probably 4 bytes, depending on your machine architecture), then you ask it to print the text pointed to by those 4 bytes!
I wonder why you ignored the compiler warning (most of the modern compiler warns you about using uninitialized variables). How about this?
ifstream wiki;
wiki.open("./wiki/velocity.txt");
char wikiRead[255];
wiki >> wikiRead;
cout << wikiRead << endl;
wiki.close();
Alternatively I'd suggest you to use string object with getline to get a single line of text.
string str;
getline(wiki, str);
The >> operator applied to a char * reads only one word. Moreover, you're reading into an uninitialized pointer, which is not valid. Usually std::string, not char *, is used for string processing in C++.
If you only want to print the file's contents, you can hook the file's buffer directly to std::cout:
int main() {
std::ifstream wiki("./wiki/velocity.txt");
std::cout << wiki.rdbuf() << '\n';
}
If you want to put the contents into an automatically-allocated string, use std::getline with the delimiter disabled.
int main() {
std::ifstream wiki("./wiki/velocity.txt");
std::string wiki_contents;
getline( wiki, wiki_contents, '\0' /* do not stop at newline */ );
std::cout << wiki_contents << '\n'; // do something with the string
}
Since you want to read a large file, reading it block by block is a better way.
ifstream wiki;
wiki.open("./wiki/velocity.txt");
const int buf_size = 1024;
char* wikiRead = 0;
int cnt = 1;
do
{
wikiRead = realloc( wikiRead, bufsize*cnt );
wiki.Read( wikiRead + (bufSize*(cnt-1)), buf_size ); //appends to reallocated memory
cnt++;
}while( !wiki.eof())
wikiRead[(bufSize*(cnt-2)) + wiki.gcount() + 1] = '\0'; // null termination.
wiki.Close();
cout << wikiRead;
delete[] wikiRead;
The operator>> is designed to only read one word at a time. If you want to read lines, use getline.
#include <iostream>
#include <fstream>
#include<string>
using namespace std;
int main()
{
ifstream wiki;
wiki.open("./wiki/velocity.txt");
string wikiRead;
while (getline(wiki, wikiRead))
{
cout << wikiRead << endl;
}
wiki.close();
}