I am doing a project in my class that requires the program to stores teams into a vector. So I would have something like this:
string bballteam;
vector<string> teamVector;
and then I want the user to input team names. So I can just prompt
cin >> bballTeam;
teamVector.push_back(bballTeam);
to store the user input into the vector. However, I would like for the user input to be an array so that I can store players (like a roster). What are some ways I can implement this?
I acknowledge that you can't have arrays in a vector.
What you need to know is the following:
If you have many different attributes for one Object, then a struct or class must be used. If you have for example a "Player", then you would put the attributes of "Player" in a struct. Like this:
struct Player {
std::string firstName{};
std::string name {};
unsigned int age{};
double score{};
};
Here you have one Type with many attributes or properties.
If you need many "Objects" of same Type, then you would store those in an "array" type, like a C-Style array, or an std::array, or, if you need some variable size, a std::vector.
So, again
Many attributes of different Type will be stored in a struct
Many obejects with the same Type will be stored in n array.
Of course all these structures or arrays can be nested. You may have an array of struct or a struct containing arrays.
This depends on how you want to abstract the reality. It is about "relations".
For the input: Objects of the same Type are entered in "loops", e.g. a for or a while loop.
Object attributes of different Type will be entered one after the other.
If you have a struct or class and want to enter all its attributes with a simple extraction (>>) operation, then you will observe that the existing extract operator >> does not know, how to work with your Type. That, because you defined a new Type, for example "Player". So we need to tell the compiler how to "Get" the data. And for that, we need to define the input functionality for the new Type. This is done with so called operator overloading. Sorry, you need to read about that. But, it is simple for input and for output. Within a struct you can normally use the following function signature:
struct Player {
std::string firstName{};
std::string name {};
unsigned int age{};
double score{};
// Extraction (input)
friend std::istream& operator >> (std::istream& is, Player& p);
// Insertion (output)
friend std::ostream& operator << (std::ostream& os, const Player& p);
};
You need to program the details of these functions.
So, next, the Team. Here you need many objects of the same Type, so many "Players".
You could write std::vector<Player> team{}; to define a team having many players.
And a complete example for the stuff learned so far:
#include <iostream>
#include <string>
#include <vector>
#include <iomanip>
struct Player {
std::string firstName{};
std::string name{};
unsigned int age{};
double score{};
// Extraction (input)
friend std::istream& operator >> (std::istream& is, Player& p) {
std::getline(is >> std::ws, p.firstName);
std::getline(is, p.name);
return is >> p.age >> p.score;
}
// Insertion (output)
friend std::ostream& operator << (std::ostream& os, const Player& p) {
return os << p.firstName << ' ' << p.name << ' ' << p.age << ' ' << p.score;
}
};
int main() {
// Define the team
std::vector<Player> team{};
// Give user instructions and get number of players for input
std::cout << "How many players do you want to enter?\n";
int numberOfPlayers{}; std::cin >> numberOfPlayers;
// Get the player data and add to team
for (int i = 0; i < numberOfPlayers; ++i) {
std::cout << "\n\nEnter player data << " << i + 1 << " One at a line : First name, name, age, score:\n";
Player player;
std::cin >> player;
team.push_back(player);
}
// Show complete team:
std::cout << "\n\nTeam:\n";
for (const Player& p : team)
std::cout << p << '\n';
}
Since you did not state your requirements very clearly, I cannot answer further. But you should have understood the basics now and can add further structs and arrays as needed.
Happy coding
Related
Apologies in advance if this is below the community's paygrade. I just started learning about OOP and English is not my native language.
I just learned about using getters and setters, and the importance of data encapsulation. However, All the examples I've seen online using getters and setters all deal with static values such as the one below:
#include <iostream>
using namespace std;
class Employee {
private:
// Private attribute
int salary;
public:
// Setter
void setSalary(int s) {
salary = s;
}
// Getter
int getSalary() {
return salary;
}
};
int main() {
Employee myObj;
myObj.setSalary(50000);
cout << myObj.getSalary();
return 0;
}
I've managed to allow user input by declaring another set of variables and feeding them as parameters into the setters like below:
int main() {
Employee myObj;
int salary;
cout<<"Enter salary: " << endl;
cin>>salary;
myObj.setSalary(salary);
cout << myObj.getSalary();
return 0;
}
Is this the only way to allow user input? Is there a more elegant way of accomplishing this other than declaring another set of local variables?
Sorry if this is a beginner question. I'm new to the C++ game and am still getting my feet wet.
Is this the only way to allow user input? Is there a more elegant way of accomplishing this other than declaring another set of local variables?
(and from comment)
I was hoping I could do some variation of myObj.setSalary(cin) so cin would feed the value directly into the getter without me having to pass it into temp variables
I would not mix input from stdin with setters, better keep them as seperate methods:
class foo {
int x;
int other_member;
public:
void set_x(int a) { x = a; }
void set_other_member(int a) { other_member = a; }
void read_x(std::istream& in) {
in >> x;
}
};
This lets you write
f.read_x(std::cin);
to read x from stdin.
Note that there are many ways to accomplish the same and what is "elegant" is rather subjective. Typically you would provide an overload for operator>> to read a foo from a stream:
std::istream& operator>>(std::istream& in, foo& f) {
int a,b;
in >> a >> b;
f.set_x(a);
f.set_other_member(b);
}
Either this (using the setters) or you make the operator a friend of foo (to access privates directly) or you use foo::read_x to implement it. Then you can write
foo f;
std::cin >> f;
Note that C++ is not a "pure" OOP language, and idiomatic C++ will happily eschew certain OOP principles in favor of simpler and more effective code where applicable.
It is perfectly acceptable, if you want a datatype to just "hold" data, to use a plain old struct (with public data fields). Don't confuse the benefits of encapsulation (...of non-trivial constructs, so you can change their implementation internally) with any kind of "security". Getters/Setters or not, anyone can change your object's data if he wants to. Incidentially, this makes reading data easier.
cin >> e.salary;
If you don't want "just a struct", it is good style to have a constructor that sets meaningful initial values, so that you don't have "uninitialized" Employee objects flying around (RAII). This means that neither your client nor your destructor has to deal with the possibility that Employee might not actually be one (hold null pointers, or other kinds of uninitialized resources).
Employee e( "Mark Smith", 10000 );
Once you are at that point, Employee should be responsible for anything that happens to / with an Employee object. That implies that it should not be somebody else that's reading user input and writes it to Employee data fields (via setters or not), it should be a message sent to the Employee object that it should update its fields, from input or otherwise, as appropriate.
(Consider the possibility of someone setting a pointer member of yours to nullptr, or worse, some random value. Your destructor would throw a fit trying to free the resource, and then how you'd debug where that invalid pointer came from?)
At which point we arrive at the operator>>(), which can be overloaded for istreams and your class.
class Employee
{
public:
// ...
friend istream & operator>>( istream &, Employee & );
// ...
};
istream & operator>>( istream & in, Employee & e )
{
getline( e.name, in );
in >> e.salary;
return in;
}
int main()
{
Employee e; // For the sake of example, a default constructed employee
cout << "Enter new employee's name (on its own line), and salary:\n";
cin >> e;
}
And once there, you get an idea of what data encapsulation actually means: Employee is an object, not a data container. You should not poke around in it's innards, you should send messages.
Employee e( "Mark Smith", Role::INTERNEE, 10000 );
cout << e; // Output: "Mark Smith, Internee, with an income of 10.000 sicles."
e.promote( Role::TEAM_LEAD, 10000 ); // Exception: "Wrong pay scale for Team Leads."
e.promote( Role::TEAM_LEAD, 30000 );
cout << e; // Output: "Mark Smith, Team Lead, with an income of 30.000 sicles and a free car."
Note how the Employee class sanity-checked the new salary, and knew that a promotion to Team Lead automatically included a free car, and did that without you having to set that yourself. That is what this encapsulation is about...
As for your question "as asked", whether your implementation could do read a salary more elegantly, you could of course...
void setSalary(istream & in) {
in >> salary;
}
...and...
cout << "Enter salary:" << endl;
e.setSalary( cin );
And because that's properly encapsuled, you could add sanity checks, change the type of salary to BigNum, or start support reading in hieratic.
THIS QUESTION IS A GENERAL ANSWER WILL COVER YOU QUESTION
So in OOP, making and using only the copy or the OBJECT is a core concept, the way you are making a single copy OBJECT of a class is a standard one, but have you ever thought about MAKING AN ARRAY OF TYPE "YOUR CLASS's Objects"? because in your case the obj will only hold only one copy of Employee but if you use a data structure e.g "Array" for storing a number of objects then it will be a lot easier let me clarify for you
EXAMPLE
int a, b, c, d, e, f, g ... z;
assigning all of these alphabets a value will be a huge pain but wait you can have a data structure for it.
int arrayOfAlphabets[26];
now you want to assign values? (you can assign it dynamically but this is just an example to show you that you can just control the assigning of a variable with just a counter)
for(int x = 0 ; x < 26; x++){
arryOfAlphabets[x] = someNumber
}
Want to get Values? you can get values dynamically but this is just an example to show you that you can just control the setting & getting of a variable with just a counter)
for(int x = 0; x < 26; x++){
cout << "Value of Alphabet" + x + " is: " + arrayOfAlphabets[x] << endl;
}
now you have seen the example that making a data structure specific data type can remove a lot of pain of yours now keep in mind for the sake of understanding,
YOUR CLASS IS DATATYPE THAT YOU CREATED
class Employee {
private:
// Private attribute
int salary;
public:
// Setter
void setSalary(int s) {
salary = s;
}
// Getter
int getSalary() {
return salary;
}
};
and in main what you can do is
int main(){
Employee emp[5];
int tempSalery;
for( int i=0; i<5; i++ )
{
cin >> tempSalery;
emp[i].setSalery(tempSalery);
}
}
now to print out your result
for( int i=0; i<5; i++ ){
cout << "Employee " + x + "Salery is: " + emp[i].getSalery() << endl;
}
A few hours ago I asked how I could read from a file with a specific format so I could program operator >>
The format of the file was:
Salad;Tomatoe 50;Fresh lettuce 100;Potatoe 60;Onion 10
Macaroni;Macaroni 250;Tomatoe 60;Oil 10
Fish and chips;fish 30;potatoe 30;Oil 40
And I have the following class:
...
#include <list> //I'm using list of the STL
....
class recipe{
private:
list<pair<string,unsigned int>> ing; //A list of the ingredients of one recipe. String for the name of the ingredient and unsigned int for the quantity of each ingredient
public:
....
//My solution for operator >>
istream & operator >> (istream &i, recipe &other){
string line, data, name_ing;
string code, nombre;
unsigned int plate, quantity;
list<pair<string,unsigned int>> ings;
getline(i,line);
stringstream s (line);
getline(s,data,';');
code = data;
getline(s,data,';');
plate = atoi(data.c_str());
getline(s,data,';');
name = data;
while(getline(s,data,' ')){
name_ing = data;
getline(s,data,';');
quantity = atoi(data.c_str());
pair<string,unsigned int> ingredient;
ingredient.first = name_ing;
ingredient.second = quantity;
ings.push_back(ingredient);
}
recipe a_recipe(code,plate,name,0,0,0,0,0,ings);
oher = a_recipe;
return i;
}
So now I have another problem, I don't know how to read those ingredients that are composed by two words, for example: "fresh lettuce 50", because the output would be:
Salad;Tomatoe 50;Fresh 0;Potatoe 60;Onion 10
It doesn't read Lettuce and the quantity. Any help?
As already written:
To solve the problem at hand there is a more or less standard approach. You want to read csv data.
In your case, it is a little bit more difficult, because you do have nested csv data. So first a ";" separated list and then a space separated list. The 2nd one is a little bit unprecise, because our ingredients coud have 2 spaces before the quantity, like in "Red pepper 2"
Now, how could this to be done? C++ is an object oriented language. You can create objects, consisting of data and member functions that operate on this data. We will define a class "Recipe" and overwrite the inserter and extractor operator. Because the class and only the class should know how this works. Having done that, input and output becomes easy.
The extractor, and that is the core of the question is, as said, a little bit more tricky. How can this be done?
In the extractor we will first read a complete line from an std::istream using the function std::getline. After having the line, we see a std::string containing "data-fields", delimited by a semicolon. The std::string needs to be split up and the "data-fields"-contents shall be stored. Additionally you need to split the ingredients.
The process of splitting up strings is also called tokenizing. The "data-fields"-content is also called "token". C++ has a standard function for this purpose: std::sregex_token_iterator.
And because we have something that has been designed for such purpose, we should use it.
This thing is an iterator. For iterating over a string, hence sregex. The begin part defines, on what range of input we shall operate, then there is a std::regex for what should be matched / or what should not be matched in the input string. The type of matching strategy is given with last parameter.
1 --> give me the stuff that I defined in the regex and
-1 --> give me that what is NOT matched based on the regex.
We can use this iterator for storing the tokens in a std::vector. The std::vector has a range constructor, which takes 2 iterators a parameter, and copies the data between the first iterator and 2nd iterator to the std::vector.
The statement
std::vector token(std::sregex_token_iterator(line.begin(), line.end(), separator, -1), {});
defines a variable "token" of type std::vector<std::string>, splits up the std::string and puts the tokens into the std::vector. After having the data in the std::vector, we will copy it to the data members of our class.
For the 2nd split we create 2 simple lambdas and copy the data into the ingredients list.
Very simple.
Next step. We want to read from a file. The file conatins also some kind of same data. The same data are rows.
And as for above, we can iterate over similar data. If it is the file input or whatever. For this purpose C++ has the std::istream_iterator. This is a template and as a template parameter it gets the type of data that it should read and, as a constructor parameter, it gets a reference to an input stream. It doesnt't matter, if the input stream is a std::cin, or a std::ifstream or a std::istringstream. The behaviour is identical for all kinds of streams.
And since we do not have files an SO, I use (in the below example) a std::istringstream to store the input csv file. But of course you can open a file, by defining a std::ifstream csvFile(filename). No problem.
We can now read the complete csv-file and split it into tokens and get all data, by simply defining a new variable and use again the range constructor.
std::vector cookBook(std::istream_iterator<Recipe>(sourceFile), {});
This very simple one-liner will read the complete csv-file and do all the expected work.
Please note: I am using C++17 and can define the std::vector without template argument. The compiler can deduce the argument from the given function parameters. This feature is called CTAD ("class template argument deduction").
Additionally, you can see that I do not use the "end()"-iterator explicitely.
This iterator will be constructed from the empty brace-enclosed initializer list with the correct type, because it will be deduced to be the same as the type of the first argument due to the std::vector constructor requiring that.
Ì hope I could answer your basic question. Please see the full blown C++ example below:
#include <iostream>
#include <regex>
#include <string>
#include <list>
#include <vector>
#include <iterator>
#include <sstream>
// Data types for ingredients and quantity
using Ingredients = std::pair<std::string, int>;
// Some helper functions
auto trim = [](const std::string & s) { return std::regex_replace(s, std::regex("^ +| +$"), "$1"); };
auto split = [](const std::string & s) {size_t pos{ s.rfind(' ') }; return Ingredients(s.substr(0, pos), std::stoi(s.substr(pos))); };
std::regex separator{ ";" };
// Our recipe class
struct Recipe {
// data
std::string title{};
std::list<Ingredients> ingredients{};
// Overwrite extractor
friend std::istream& operator >> (std::istream& is, Recipe& r) {
// We will read one line into this temproary
std::string line{};
if (std::getline(is, line)) {
// Tokenize the base string
std::vector token(std::sregex_token_iterator(line.begin(), line.end(), separator, -1), {});
// get the recipe title
r.title = token[0];
// And, get the ingredients
r.ingredients.clear();
std::transform(std::next(token.begin()), token.end(), std::back_inserter(r.ingredients),
[](const std::string& s) { return split(trim(s)); });
}
return is;
}
// Overwrite inserter
friend std::ostream& operator << (std::ostream& os, const Recipe& r) {
// Print one recipe
os << "---- Recipe: " << r.title << "\n-- Ingredients:\n\n";
for (const auto& [ingredient, quantity] : r.ingredients)
os << ingredient << " --> " << quantity << "\n";
return os;
}
};
// Source file with CSV data. I added "Red Pepper 2" to Salad
std::istringstream sourceFile{ R"(Salad;Tomatoe 50;Lettuce 100;Potatoe 60;Red Pepper 2;Onion 10
Macaroni;Macaroni 250;Tomatoe 60;Oil 10
Fish and chips;fish 30;potatoe 30;Oil 40)" };
int main() {
// Read all data from the file with the following one-liner
std::vector cookBook(std::istream_iterator<Recipe>(sourceFile), {});
// Show some debug output
std::copy(cookBook.begin(), cookBook.end(), std::ostream_iterator<Recipe>(std::cout, "\n"));
return 0;
}
Again:
What a pity that nobody will read this . . .
I suggest that you make a type out of the ingredient and amount part instead of using std::pair<std::string, unsigned>. With that you can add stream operators for that type too (and not risk it being used by a different std::pair<std::string, unsigned> than the one you want to support). It breaks the problem down somewhat and makes it simpler to implement / understand.
That being that said, I suggest that you use something else than a space as a delimiter between the ingredient name and the amount since that complicates the parsing (as you can see in the code).
Here's an example with comments:
#include <cstdlib>
#include <iostream>
#include <list>
#include <sstream>
#include <string>
#include <tuple>
// a simple ingredient type
struct ingredient {
std::string name{};
unsigned amount{};
};
// read an ingredient "<name> <amount>"
std::istream& operator>>(std::istream& is, ingredient& i) {
std::string entry;
if(std::getline(is, entry, ';')) { // read until ; or EOL
// find the last space in "entry"
if(size_t pos = entry.rfind(' '); pos != std::string::npos) {
// extract the trailing amount
if(unsigned am = static_cast<unsigned>(
// Create a substring from the last space+1 and convert it to an
// unsigned (long). The static_cast<unsigned> silences a warning about
// the possibility to get the wrong value if it happens to be larger
// than an unsigned can hold.
std::strtoul(entry.substr(pos + 1).c_str(), nullptr, 10));
// and check that we extracted something else than zero
am != 0)
{ // extracted the amount successfully
i.name = entry.substr(0, pos); // put the name part in i.name
i.amount = am; // and the amount part in i.amount
} else { // extracting the amount resulted in 0
// set failbit state on is
is.setstate(std::ios::failbit);
}
} else { // no space found, set failbit
is.setstate(std::ios::failbit);
}
}
return is;
}
// output an ingredient
std::ostream& operator<<(std::ostream& os, const ingredient& i) {
return os << i.name << " " << i.amount;
}
class recipe {
public:
std::string const& name() const { return rname; }
// convenience iterators to iterate over ingreidiences, const
auto begin() const { return ing.cbegin(); }
auto end() const { return ing.cend(); }
// non-const if you'd like to be able to change an ingredient property while iterating
auto begin() { return ing.begin(); }
auto end() { return ing.end(); }
private:
std::list<ingredient> ing{}; // the new type in use
std::string rname{}; // recipe name
friend std::istream& operator>>(std::istream&, recipe&);
};
std::istream& operator>>(std::istream& i, recipe& other) {
std::string line;
if(std::getline(i, line)) {
std::istringstream ss(line);
if(std::getline(ss, other.rname, ';')) {
// only read the recipe's name here and delegate reading each ingredient
// to a temporary object of your new ingredient type
other.ing.clear(); // remove any prior ingrediences from other
ingredient tmp;
while(ss >> tmp) { // extract as normal
other.ing.push_back(tmp); // and put in ing if successful
}
}
}
return i;
}
// output one recipe in the same format as it can be read
std::ostream& operator<<(std::ostream& os, const recipe& other) {
os << other.name();
for(auto& i : other) {
os << ';' << i;
}
return os << '\n';
}
int main() {
std::istringstream is(
"Salad;Tomatoe 50;Fresh lettuce 100;Potatoe 60;Onion 10\n"
"Macaroni;Macaroni 250;Tomatoe 60;Oil 10\n"
"Fish and chips;fish 30;potatoe 30;Oil 40\n");
recipe r;
while(is >> r) {
std::cout << r;
}
}
I want to overload the operator >>, so I can read some data from a file to save it in my class. My problem is that I don't know how to read one single word. Does a function like get() or getline() exist for this purpose?
For example, I have this class:
class Person{
private:
char * name;
int id;
public:
....
I have this file with some info:
James 23994
Anne 23030
Mary 300392
And what I want is to read the name and the id of these people to save them in my class.
Things are easier if you use std::string instead of a bare pointer that requires you to allocate memory, keep track of the size (or rely on null-termination), etc...
struct Person {
std::string name;
int id;
};
Now you can use the already existing operator<< for std::string and int :
std::istream& operator>>(std::istream& in, Person& p) {
in >> p.name >> p.id;
return in;
}
Note that operator>> does read input until it finds a ' ' by default, hence to "read a word" you dont have to do anything extra.
If you insist on having private fields, you should declare the operator as friend of the class.
I have a class "Employee"
I want to create a array of pointers for that.
Will this work out?
Employee *employeeArr[size];
In my "for loop" something like this
employeeArr[i] = new Employee(surname , firstname , gender); // constructor implemented Employee( para1, para2, para3)
OR should I write
Employee *employeeArr = new Employee[size];
And fill everything with "dots" like
employeeArr[i].setSurname(surname);
Can you explain the reason as well, I'm really new to pointers. The second one was told to me by someone else but I couldn't get an answer as to why I can't use the first one.
Also if possible, do not mention std::array or std::vector, I'm still too new
Sorry to surprise you:
None of the examples you give should be considered as as the "correct way" to handle collections of classes in c++.
Also if possible, do not mention std::array or std::vector, I'm still too new
No, that's the wrong path hauling up the mare. The proper usage of raw pointers and raw arrays is certainly beyond your capabilities, if you can't grasp how to deal with std::array or std::vector primarily.
Supposed your Employee class looks like
struct Employee {
std::string surname_;
std::string firstname_;
enum Gender {
Female = 'F' ,
Male = 'M' ,
Unxpecified = 'X'
} gender_;
};
and you have an overload for the std::operator>>()
std::istream& operator>>(std::istream& is, Employee& employee) {
char genderSymbol;
is >> employee.surname_ >> employee.firstname_ >> genderSymbol;
switch(genderSymbol) {
case 'F':
case 'M':
case 'X':
employee.gender_ = (Employee::Gender)genderSymbol;
break;
default:
is.setstate(std::ios_base::failbit);
break;
}
}
One good and idiomatic way to represent that Employee array would be to use a
std::vector<Employee> employeeArr;
and fill it in a loop:
Employee employee;
while(std::cin >> employee) {
employeeArr.emplace_back(employee);
}
If you really need pointers (references) you may consider to use smart pointers as provided with the Dynamic Memory Management utility classes.
For instance you may decide to have a
std::vector<std::unique_ptr<Employee>> employeeArr;
and initialize it like
while(std::cin >> surname >> firstname >> gender) {
employeeArr.emplace_back(std::make_unique<Employee>(surname , firstname , gender));
}
This comes into consideration if you want to manage pools of hierarchically organized class instances like:
struct Employee {
virtual ~Employee() {}
std::string surname_;
std::string firstname_;
enum Gender {
Female = 'F' ,
Male = 'M' ,
Unxpecified = 'X'
} gender_;
};
struct IForeman : Employee {
virtual std::vector<const Employee const*> TeamMembers() const = 0;
virtual void AddTeamMember(const Employee const* member) = 0;
};
class Foreman : public IForeman {
str::vector<const Employee const*> teamMembers_;
public:
std::vector<const Employee const*> TeamMembers() const {
return teamMembers_;
}
void AddTeamMember(const Employee const* member) {
teamMembers_.push_back(member);
}
};
Consider to hand out owned or shared pointers to related connections using plain const pointers.
Also if possible, do not mention std::array or std::vector, I'm still too new
You got it backwards. If you are too new, then you should be using std::array and std::vector. Do not use built-in arrays and do not do manual memory management if you're new.
What you should be using, is:
#include <array>
// ...
std::array<Employee, size> employeeArr;
if size is known at compile-time and will never change. If it's not known at compile-time, or if the array needs to grow dynamically, then use vector:
#include <vector>
// ...
std::vector<Employee> employeeArr;
and then add Employee objects to it using push_back():
employeeArr.push_back(Employee(/* ... */));
And there's no pointers involved here either. Just values.
Once you get more familiar with containers, then you can delve deeper into C++ and learn about pointers and memory management.
The first example creates an array of pointers to Employee objects, while the second one creates a dynamically allocated array of Employees. These are completely different things.
If you don't understand vectors, you should not be messing with pointers because they are easy to misuse. I would recommend learning C++ step by step with a good book if you don't already have one.
As far as I know, in most situations, the size of a static array must be a constant expression (able to be computed at compile time). That's why your first example does not work. If you want arrays with an unknown size then you should use vectors or dynamic arrays.
Once you understand vectors you will realize that they are much more convenient than dynamic arrays since they handle things like freeing memory automatically for you. Another nice thing about vectors is that you can resize them after they are created.
C++ is a very flexible language, and the decision is completely yours.
You can make an array of pointers (well, if you did so, memory handling will be completely manual, and error-prone).
You can make a vector of pointers (vector is better than array in that it is flexible in adding and removing without a lot of manual code writing)
You can use "smart-pointers", which means array (or vector as a better practice, (and maybe a smart-pointer of a vector)) of smart-pointers, this smart-pointers handle the memory for you.
For example:
vector<shared_ptr<Employee> > employees;
employees.push_back(make_shared<Employee>(surname, firstname, gender));
make_shared makes a (shared pointer) of the newly created employee.
Shared pointer is an automatically reference-counted pointer, which is great in threading and in sharing object (pointer to object) between different objects.
shared_ptr in CPP Reference
unique_ptr in CPP Reference
You can see this question for an example of using unique_ptr
Employee *employeeArr[size]; is an array of Employee pointers so the size is static which is known at compile-time. Whereas Employee *employeeArr = new Employee[size]; is a pointer to a dynamic array of Employee objects; not pointers.
Make difference and use each cautiously.
You can use class vector where you don't matter of allocatio-de-allocation of dynamic memory.
Here is an example showing the 3 possible usages:
#include <iostream>
#include <vector>
#include <string>
class Employee{
public:
Employee(){} // Imporatant for a dynamic array of objects
Employee(const std::string, const std::string, const bool);
void set(const std::string, const std::string, const bool);
// some other methods here
void print()const;
private:
std::string surName, firstName;
bool gender;
};
Employee::Employee(const std::string sur, const std::string first, const bool gend) :
surName(sur),
firstName(first),
gender(gend){
}
void Employee::set(const std::string sur, const std::string first, const bool gend){
surName = sur;
firstName = first;
gender = gend;
}
void Employee::print()const{
std::cout << "surName: " << surName << std::endl;
std::cout << "firsName: " << firstName << std::endl;
gender ? std::cout << "Male" : std::cout << "Female" ;
std::cout << std::endl;
}
int main(){
// 1: An array of pointers:
Employee* empl[3];
std::string surName, firstName;
bool gender;
for(auto i(0); i != 3; ++i){
std::cout << "surName: ";
std::cin >> surName;
std::cout << "firstName: ";
std::cin >> firstName;
std::cout << "gender: ";
std::cin >> gender;
empl[i] = new Employee(surName, firstName, gender);
}
for(auto i(0); i != 3; ++i)
empl[i]->print();
std::cout << "_________________________" << std::endl;
// 2: A pointer to a dynamic array:
Employee* empl2 = new Employee[3]; // default constructor is imortant here
for(auto i(0); i != 3; i++){
std::cout << "surName: ";
std::cin >> surName;
std::cout << "firstName: ";
std::cin >> firstName;
std::cout << "gender: ";
std::cin >> gender;
empl2[i].set(surName, firstName, gender);
}
for(auto i(0); i != 3; ++i)
empl2[i].print();
delete[] empl2;
std::cout << "_________________________" << std::endl;
// 3: with vectors:
std::vector<Employee> vecEmpl; // default ctor is not important here
for(auto i(0); i != 3; ++i){
std::cout << "surName: ";
std::cin >> surName;
std::cout << "firstName: ";
std::cin >> firstName;
std::cout << "gender: ";
std::cin >> gender;
Employee emp(surName, firstName, gender);
vecEmpl.push_back(emp);
}
for(auto i(0); i != 3; ++i)
vecEmpl[i].print();
std::cout << std::endl << std::endl;
return 0;
}
I want to serialize an object of type Person. I want to use it later on for data saving or even game saving. I know how to do it for primitives like int, char, bool, and even c-strings like char[].
The problem is, I want the string to be as big as it needs to rather than declaring a char array of size 256 and hoping no one enters something too big. I read that serializing a class with std::string as a member doesn't work because it has an internal pointer, but is there a way to serialize my class which has a char* as a member?
I realize Boost has a serialization library, but I'd like to do this without the need of external libraries, it seems like a good activity to try.
Here's my Person class:
class Person
{
private:
char* _fname;
char* _lname;
public:
Person();
Person(const char* fname, const char* lname);
Person(const string& fname, const string& lname);
string fname() const;
void fname(const char* fname);
void fname(const string& fname);
string lname() const;
void lname(const char* lname);
void lname(const string& lname);
};
First: Use std::string in your class it will make your life so much easier in the long run.
But this advice works for both std::string and char* (with minor tweaks that should be obvious).
Basically you want to serialize data of unknown size (at compile time). This means when you de-serialize the data you must either have a technique that tells you how long the data is (prefix the object with a size) or a way to find the end of the data (a termination marker).
A termination marker is easier for serialization. But harder for de-serialization (as you must seek forward to find the end). Also you must escape any occurrences of the termination marker within your object and the de-serialization must know about the escaping and remove it.
Thus because of this complications I prefer not to use a termination marker. As a result I prefix the object with a size. The cost of this is that I must encode the size of the object in a way that will not break.
So if we prefix an object with its size you can do this:
// Place a ':' between the string and the size.
// There must be a marker as >> will continue reading if
// fname contains a digit as its first character.
// I don;t like using a space as >> skips spaces if you are not carefull
// and it is hard to tell the start of the string if the first characters in fname
// are the space character.
std::cout << strlen(fname) << ":" << fname;
Then you can de-serialize like this:
size_t size;
char mark;
std::cint >> size >> mark;
if (!std::cin || mark != ':')
{ throw BadDataException;
}
result = new char[size+1](); // Note the () to zero fill the array.
std::cin.read(result, size)
Edit 1 (based on comments) Update: to use with string:
size_t size;
char mark;
std::cint >> size >> mark;
if (!std::cin || mark != ':')
{ throw BadDataException;
}
std::string result(' ', size); // Initialize string with enough space.
std::cin.read(&result[0], size) // Just read directly into the string
Edit 2 (based on commented)
Helper function to serialize a string
struct StringSerializer
{
std::string& value;
StringSerializer(std::string const& v):value(const_cast<std::string&>(v)){}
friend std::ostream& operator<<(std::ostream& stream, StringSerializer const& data)
{
stream << data.value.size() << ':' << data.value;
}
friend std::istream& operator>>(std::istream& stream, StringSerializer const& data)
{
std::size_t size;
char mark(' ');
stream >> size >> mark;
if (!stream || mark != ':')
{ stream.setstate(std::ios::badbit);
return stream;
}
data.value.resize(size);
stream.read(&data.value[0], size);
}
};
Serialize a Person
std::ostream& operator<<(std::ostream& stream, Person const& data)
{
return stream << StringSerializer(data.fname) << " "
<< StringSerializer(data.lname) << " "
<< data.age << "\n";
}
std::istream& operator>>(std::istream& stream, Person& data)
{
stream >> StringSerializer(data.fname)
>> StringSerializer(data.lname)
>> data.age;
std::string line;
std::getline(stream, line);
if (!line.empty())
{ stream.setstate(std::ios::badbit);
}
return stream;
}
Usage:
int main()
{
Person p;
std::cin >> p;
std::cout << p;
std::ofstream f("data");
f << p;
}
You can't serialize pointer, you need to serialize data pointer points to.
You'll need to serialize whole web of objects, starting from Person (or Game) and looking into each object, which is reachable from your start object.
When deserializing, you reading data from your storage, allocate memory for that data and use address of this freshly allocated memory as a member of Person/Game object
Pointer fields make it bit harder, but not impossible to serialize. If you don't want to use any of the serialization libraries, here is how you can do it.
You should determine the size of what is pointed to at the time of serialization (e.g. it may be of fixed size or it may be a C-string with null character at the end), then you can save a mark indicating that you're serializing an indirect object together with size and the actual content of the area pointed to.
When you stumble upon that mark during deserialization, you can allocate the right amount of memory, copy the object into it and store the pointer to the area in the deserialized object.
I recommend using a vector to encapsulate strings for
serialization.
#include <vector>
using namespace std;
map vector<unsigned char> cbuff;
inline cbuff vchFromString(const std::string &str) {
unsigned char *strbeg = (unsigned char*) str.c_str();
return cbuff(strbeg, strbeg + str.size());
}
inline std::string stringFromVch(const cbuff &vch) {
std::string res;
std::vector<unsigned char>::const_iterator vi = vch.begin();
while (vi != vch.end()) {
res += (char) (*vi);
vi++;
}
return res;
}
class Example
{
cbuff label;
Example(string labelIn)
{
SetLabel(labelIn);
}
IMPLEMENT_SERIALIZE
(
READWRITE(label);
)
void SetLabel(string labelIn)
{
label = vchFromString(labelIn);
}
string GetLabel()
{
return (stringFromVch(label));
}
};