Becca
Becca

Reputation: 117

Inputting string and intergers from a text file into variable

Hey is it possible to have a text file which its contents are:

Weapon Name: Katana
Damage: 20
Weight: 6

Is it possible to assign these bits of information into member variables of a weapons class?. So that when i call getWeaponName in my main i will get Katana?

I was looking around google and i can get the whole text file input but its not assigned to any variable.

The code i have so far is:

Weapons :: Weapons()
{
    this->weaponName = "";
    this->damage = 0;
    this->weight = 0;
}

Weapons :: Weapons(string weaponName,int damage,int weight)
{
    this->weaponName = weaponName;
    this->damage = damage;
    this->weight = weight;
}

void Weapons ::  getWeapon()
{
    ifstream myfile ("Weapons\\Katana.txt");
    string line;
    if (myfile.is_open())
    {
        while (myfile.good())
        {
            getline (myfile,weaponName,'\t');//This line gets the entire text file.
            //getline (myfile,damage,'\t');
            //getline (myfile,weight,'\t');
            //myfile >> weaponName;
            //myfile >> damage;
            //myfile >> weight;
            cout << weaponName<< "\n";
        }
        myfile.close();
    }

    else
    {
        cout << "Unable to open file";
    }
}

Thanks in advance.

Upvotes: 0

Views: 139

Answers (4)

Scott Mermelstein
Scott Mermelstein

Reputation: 15397

Change

getline (myfile, weaponName, '\t');

to

getline (myfile, weaponName);

What your version is doing is telling getline to grab everything in the file, up to a tab character, and I'm guessing you don't have any tab characters. The version I'm recommending - with no delimiter specified - will get characters up to a newline. So it should read in Weapon Name: Katana.

Then you still need to extract "Katana". Assuming your input file has a very fixed format, you can simply do something like

weaponName = weaponName.substr(weaponName.find_first_of(':') + 2);

This will take the substring starting at the position 2 after the ':'.

Edit

Using weaponName is not exactly proper for your getline statement. weaponName is a string, but at that point, you're just looking for a line. You already have the proper variables in place in getWeapon(). We just need to use them:

void Weapons ::  getWeapon()
{
    ifstream myfile ("Weapons\\Katana.txt");
    string line;
    string number;
    if (myfile.is_open())
    {
        while (myfile.good())
        {
            getline (myfile,line);
            weaponName = line.substr(line.find_first_of(':') + 2);
            getline (myfile,line);
            number = line.substr(line.find_first_of(':') + 2);
            damage = atoi(number.c_str());
            getline (myfile,line);
            number = line.substr(line.find_first_of(':') + 2);
            weight = atoi(number.c_str()); 
            cout << weaponName<< "\n";
        }
        myfile.close();
    }
    else
    {
        cout << "Unable to open file";
    }
 }

Note: you'll need to #include <stdlib.h> for atoi to work.

Honestly, this still isn't very robust. Others have offered you better solutions for looking at the input to see what the data is, and reading and storing all your data, but this should show you the very basics.

Upvotes: 2

Jerry Coffin
Jerry Coffin

Reputation: 490663

First, you need/want to distinguish between "weapons" (plural) and a single weapon. To make much sense, each individual weapon has the characteristics you're reading (name, weight, damage). So, weapons will be a collection of individual weapon objects, each of which has the characteristics.

Based on that, we can attempt to write some meaningful code:

class weapon {
    std::string name;
    int damage;
    int weight;
public:
    std::string get_name() { return name; }

Now, we want a weapon to be able to "reconstitute" itself from data stored in a file. Note, however, that right now we're writing a weapon class, so we're only going to deal with one weapon, not a whole collection of them:

friend std::istream &operator>>(std::istream &is, weapon &w) { 
    std::ignore(is, 1024, ':'); // ignore the "Weapon Name:" header
    std::getline(is, w.name);
    std::ignore(is, 1024, ':'); // ignore the "Damage:" header
    is >> w.damage;
    std::ignore(is, 1024, ':');  // ignore the "Weight:" header
    is >> w.weight;
    return is;
}

Though we don't need it just yet, let's create a matching function to write out a weapon in the correct format as well:

std::ostream &operator<<(std::ostream &os, weapon const &w) { 
   return os << "Weapon Name: " << w.name << "\n"
             << "Damage: " << w.damage << "\n"
             << "Weight: " << w.weight << "\n";
}

With that, we can read the data for a single weapon. Then we need some way to store multiple weapons. Lacking a reason to do otherwise, our first choice for that is normally an std::vector. If we want to fill that with the data from a file, we can do it something like this:

// open a file of all the weapons data:
std::ifstream in("weapons.txt");

// initialize the vector from the data in the file:
std::vector<weapon> weapons((std::istream_iterator<weapon>(in)),
                             std::istream_iterator<weapon>());

With this in place we can (for example) list all the weapons (here we're going to use the "operator<<" we defined above):

 std::copy(weapons.begin(), weapons.end(), 
           std::ostream_iterator<weapon>(std::cout, "\n"));

If we want an abbreviated list with just the name of each weapon, we can do something like this:

for (auto const &w : weapons)
    std::cout << w.get_name() << "\n";

Upvotes: 1

James Kanze
James Kanze

Reputation: 154047

Your format looks like a variant of a typical .ini file. There are lots of parsers around for that, if you can modify the format to make it conform. That would be by far the easiest solution. Otherwise: how are the various weapons separated in the file? Is it by an empty line, or is it because the first entry is always "Weapon Name"? In the first case, I would use something like the following to read the file (in a free function, not as a member):

std::auto_ptr<Weapon> currentWeapon;
Line line;
int lineNumber = 0;
while ( source >> line ) {
    ++ lineNumber;
    if ( line.empty() ) {
        if ( currentWeapon.get() != NULL ) {
            weaponCollection.insert( currentWeapon );
        }
        currentWeapon.release();
    } else {
        Line::const_iterator pivot 
            = std::find( line.begin(), line.end(), ':' );
        if ( pivot == line.end() ) {
            //  Output error message, using lineNumber...
        } else {
            if ( currentWeapon.get() == NULL ) {
                currentWeapon.reset( new Weapon );
            }
            std::string key( strip( std::string( line.begin(), pivot ) ) );
            std::string value( strip( std::string( pivot + 1, line.end() ) ) );
            if ( key == "WeaponName" ) {
                currentWeapon->weaponName = value;
            } else if ( key == "Damage" ) {
                currentWeapon->damage = asInt( value );
            } else if ( key == "Weight" ) {
                currentWeapon->weight = asInt( value );
            } // ...
            else {
                // output error message...
            }
        }
    }
}

Line is a class in my toolbox, that I use a lot for this sort of thing. It's basically:

class Line : private std::string
{
public:
    typedef std::string::iterator iterator;
    //  ...
    using std::string::empty;
    using std::string::begin;
    //  ...
};

The only difference between it and std::string is that its operator>> calls std::getline, then "cleans up" the results, by removing any trailing white space (including a possible '\r', because the file was written under Windows, but I'm reading it under Unix); in this case, it might be useful to make it also remove any leading white space. (Mine also has a manipulator which sets a comment character; if this is set, it removes any text from this character to the end of the line, before trimming trailing whitespace. (From experience: do provide some sort of facility for commenting in the file. You'll regret it if you don't.)

And asInt is, of course:

int
asInt( std::string const& original )
{
    std::istringstream s( original );
    int results;
    s >> results >> std::ws;
    if ( !s || s.get() != EOF ) {
        //  Error...
    }
    return results;
}

Again, something that you should have in your toolbox already.

If the key to a new weapon is the "Weapon Name" attribute, skip the empty line business (or treat empty lines as comments), and store any existing Weapon and create the new one in the handling for "Weapon Name".

If you throw on error, above, you'll need to use a try...catch block to output the error and continue. You might also want to mark that there was an error somewhere, and abort the game if so.

Upvotes: 0

Amadeus
Amadeus

Reputation: 10685

You will need to parse each line of your file. So, change the function

getline(myfile, weaponName, '\t');

to

getline(myfile, weaponName);

and parse result.

Do something like that:

#include <cstdio>
#include <string>

using namespace std;

int main() 
{
  string line = "Weapon Name: Katana";

  int pos = line.find(':');
  string weaponName;
  if ( line.substr(0, pos) == "Weapon Name")
    weaponName = line.substr(pos+1, line.npos);

  printf("%s\n", weaponName.c_str());
}

Upvotes: 2

Related Questions