### Using struct for the first time

Input into a file personal data of n students, thier: Surname, name, age, Place of birth and telephone number.
Output what the file contains.
Output Surnames and Telephone numbers of all the students.
Output an average age of all the students.
Output those students' surnames and names, whose surname begins with "A".
... and decided to fulfill it.

That is a good opportunity to learn how to use struct, so I did it and my code crashes immediately after an n is entered, which if I want to enter, I need to remove a piece of code with if(find_first_not_of()). Can you tell me where did I make mistakes?

 ``123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122`` ``````#include #include #include #include #include #include using namespace std; struct secure { string surname; string name; string age; string placeofbirth; string tel; secure(const string& s, const string& n, string a, const string& p, const string& t); }; secure::secure(const string& s, const string& n, string a, const string& p, const string& t) :surname{s}, name{n}, age{a}, placeofbirth{p}, tel{t} { if(!(find_if(s.cbegin(), s.cend(), [](const char z) {return !isalpha(z) && z != '-';}) == s.cend())) cout<<"You may not use characters that are not letters nor -."<0) { enterholder = replacement; cout<<"Enter data "<> s >> n >> a >> p >> t) { ensuretodisplay = true; cout << s << '\t' << n << '\t' << a << '\t' << p << '\t' << t <
> I saw a task to...
> That is a good opportunity to learn how to use struct, so I did it

Programming is much easier when we take baby steps: do one small part of the program, test it, make sure that it is working correctly; then add the next small step, test it and so on.
This is even more important when we are learning programming.

Start by writing a small program in which you define the struct to hold personal information. Write a small function which accepts an object of this kind and displays its members. Verify that that you can create objects of this kind, and that they are initialised correctly.

Once this much is working, move on to accepting a piece of user input, say the name, and validating it.

And so on.

> my code crashes immediately after an n is entered,

The code should not compile at all: what is this? `int main(secure& secure)`

Formatted input is more fundamental than unformatted input. If it is required that the user must enter an integer, read it in as an integer; not as a string. If required, check for input failure.
The reason I do this with strings is to prevent the program from reading "12fdsghmjhfdseawfghj4w56757" as an integer 12. Unfortunately, stoi is really string to double rather than a factual integer.

I've already written a different code for that before, but it really looks primitive.
 ``123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177`` ``````#include #include #include #include #include #include #include using namespace std; bool checksurname (const string& surname) { return find_if(surname.cbegin(), surname.cend(), [](const char c) {return !isalpha(c) && c != '-';}) == surname.cend(); } bool checkname (const string& name) { return find_if(name.cbegin(), name.cend(), [](const char d) {return !isalpha(d);}) == name.cend(); } bool checkinteger (const string& x) { return find_if(x.cbegin(), x.cend(), [](const char e) {return !isdigit(e);}) == x.cend(); } bool checkplace (const string& place) { return find_if(place.cbegin(), place.cend(), [](const char z) {return !isalpha(z) && int(z) != 45 & int(z) != 46 & int(z) != 44;}) == place.cend(); } bool checktelephone (const string& telephonenumber) { return find_if(telephonenumber.cbegin(), telephonenumber.cend(), [](const char f) {return !isdigit(f) && int(f) != 43 && int(f) != 45;}) == telephonenumber.cend(); } void message() { cout<<"Entry was incorrect. Try again."<0) { cout<<"Enter students' personal data "<> surname >> name >> age >> placeofbirth >> telephonenumber) { flag = true; cout<< surname << '\t' << name << '\t' << age << '\t' << placeofbirth << '\t' << telephonenumber <

If it wasn't for exit(0) in message, the average age would output an incorrect number, for example:
2 students' data is entered, the first one is all correct but the second input fails at age, so the program outputs 0.5 as an average age of students whose one's age is 1, and second's is unknown.
Write many small functions.

For example (caveat: untested)
 ``123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136`` ``````#include #include #include #include // parse error (throw) if the entire string does not form a valid integer int to_int( std::string str ) { // trim trailing white space while( !str.empty() && std::isspace( str.back() ) ) str.pop_back() ; // try to parse it as an integer (default base 10) std::size_t parsed_till = 0 ; cont int value = std::stoi( str, std::addressof(parsed_till) ) ; // may throw if( parsed_till == str.size() ) return value ; // fully parsed else throw std::invalid_argument( "bad trailing characters" ) ; } // not empty, all alpha, no spaces bool valid_name( const std::string& str ) { if( str.empty() ) return false ; for( char c : str ) if( !std::isalpha(c) ) return false ; return true ; } // not empty, all alpha, may contain embedded '-', no spaces bool valid_surname( const std::string& str ) { if( str.empty() ) return false ; if( str.front() == '-' || str.back() == '-' ) return false ; for( char c : str ) if( !std::isalpha(c) && c != '-' ) return false ; // note: check for two consecutive '-' elided for brevity return true ; } // not empty, all alpha, may contain embedded '-', ',' or '.', no spaces bool valid_place( const std::string& str ) { static const auto is_special = [] ( char c ) { return c == '-' || c == ',' || c == '.' ; }; if( str.empty() ) return false ; if( is_special( str.front() ) ) return false ; if( is_special( str.back() ) ) return false ; for( char c : str ) if( !std::isalpha(c) && !is_special(c) ) return false ; // note: check for two consecutive special characters elided for brevity return true ; } struct person_info { person_info() = default ; person_info( std::string surname, std::string name, std::string place_of_birth, int age, int tel = 0 ) ; // constructor std::string surname; std::string name; std::string place_of_birth; int tel = 0 ; // default member initialiser int age = 0 ; bool valid() const // class invariant ; bool empty() const ; // true if all fields are empty/zero person_info& clear() ; // clear all fields (make empty) }; bool person_info::empty() const { return name.empty() && surname.empty() && place_of_birth.empty() && age==0 && tel==0 ; } // note: to clear, we simply assign an empty (default constructed) object person_info& person_info::clear() { return *this = {} ; } bool person_info::valid() const { if( empty() ) return true ; // default constructed person_info is valid // if any field is present, all fields must be present and valid return valid_name(name) && valid_surname(surname) && valid_place(place_of_birth) && age >= 0 && age < 200 && tel >= 0 ; } person_info::person_info( std::string surname, std::string name, std::string place_of_birth, int age, int tel = 0 ) : name(name), surname(surname), place_of_birth(place_of_birth), age(age), tel(tel) { if( !valid() ) throw std::invalid_argument( "bad constructor arg" ) ; } // read in person info from an input stream. ( operator>> ? ) // the stream would be placed into a failed state on input failure std::istream& get_person_info( std::istream& stm, person_info& pinfo ) { std::string name, surname, place_of_birth ; int age, tel ; if( stm >> name >> surname >> place_of_birth >> age >> tel ) { try { person_info temp( name, surname, place_of_birth, age, tel ) ; pinfo = temp ; // valid temp, assign to pinfo (ideally move with std::move) return stm ; } catch( const std::exception& ) // invalid constructor arg { stm.clear( std::ios::failbit ) ; // put the stream into a failed state } } // we reach here if input failed or there were invalid values in input pinfo.clear() ; return stm ; } // write person info to an output stream. ( operator<< ? ) std::ostream& put_person_info( std::ostream& stm, const person_info& pinfo ) { return stm << pinfo.name << ' ' << pinfo.surname << ' ' << pinfo.place_of_birth << ' ' << pinfo.age << ' ' << pinfo.tel ; } int main() { std::ifstream file( "personaldata.txt" ) ; // open the file for input person_info pinfo ; // default constructed while( get_person_info( file, pinfo ) ) // for each person read from the file { // do what ever. for example, print out the info to stdout std::cout << pinfo << '\n' ; } }``````

Take it up from there.
Well, that's a nice code that you've provided, but can you explain me what should I put in the main() if that's not secure& secure?

I saw somewhere that I could put a name between } and ; at the end of struct, so that I would later put that_name.string in getlines, though it doesn't work either. I am aware I might not have enough arguments in main's bracelets, but I don't know what should I put there if it requires more.

Also, what's wrong with find_last_not_of?
> what should I put in the main()

Either int main () { ...
or int main ( int argc, char* argv[] ) { ... if we want to access command line arguments.
See: http://en.cppreference.com/w/cpp/language/main_function

> I saw somewhere that I could put a name between } and ;

In `struct my_struct { /* .... */ } my_var ;` my_struct is the type and my_var is a variable of that type.

Usually, we would write something like:
 ``123456789`` ``````struct my_struct { /* .... */ }; // the type my_struct int main() { my_struct my_var ; // variable of type my_struct // use my_var // ... }``````

> what's wrong with find_last_not_of?

There is nothing wrong with find_last_not_of.
I wrote the code in a different way which did not require using it; that is all.
For some reason a declaration of my_var in my code fails when I do it as it gives me an error "No matching function for call to secure::secure()".

What I did is I made a void(secure &secure) function and then attempted to call it from main().

 ``1234567`` ``````int main() { struct secure &exotic; perfection(exotic); cin.get(); return 0; }``````

Now it says I did not initialize anything, how can that be?
Last edited on
> For some reason a declaration of my_var in my code fails when I do it
> as it gives me an error "No matching function for call to secure::secure()".

The type secure is not default constructible. Provide a default constructor.

For an example, see:
 ``123456789`` ``````struct person_info { person_info() = default ; // default constructor person_info( std::string surname, std::string name, std::string place_of_birth, int age, int tel = 0 ) ; // constructor with args // ... };``````

> Now it says I did not initialize anything, how can that be?

A reference must be initialised; it must be bound to an object. There are no uninitialised references.
 ``123456789`` ``````struct secure { string surname; string name; string age; string placeofbirth; string tel; secure(const string& s, const string& n, string a, const string& p, const string& t); };``````

From what you've written it looks as if I already had a constructor.
You did, but not a default constructor that takes no arguments. That is the one that is trying to be called.
How am I supposed to do this then?

Please, forgive me, but I literally have no idea what should I put there. Things I've tried so far failed.
 ``1234567891011121314151617`` ``````struct secure { string surname; string name; string age; string placeofbirth; string tel; secure(const string& s, const string& n, string a, const string& p, const string& t); secure() {} // *** added: default constructor }; int main() { secure exotic ; // default-constructed // ... }``````
Contrary to what I want, you constructed me a default. I want to refer to those variables considering the statements I made in secure::secure().
 ``12345678910111213141516171819202122232425262728`` ``````struct secure { string surname; string name; string age; string placeofbirth; string tel; secure(const string& s, const string& n, string a, const string& p, const string& t); }; secure::secure(const string& s, const string& n, string a, const string& p, const string& t) :surname{s}, name{n}, age{a}, placeofbirth{p}, tel{t} { if(!(find_if(s.cbegin(), s.cend(), [](const char z) {return !isalpha(z) && z != '-';}) == s.cend())) cout<<"You may not use characters that are not letters nor -."<

A default one of course skips all of that.
What should I put in main so that I may get errors whenever surname consists of digits etc.?
 ``123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130`` ``````#include #include #include #include // not empty, all alpha, no spaces bool valid_name( const std::string& str ) { if( str.empty() ) return false ; for( char c : str ) if( !std::isalpha(c) ) return false ; return true ; } // not empty, all alpha, may contain embedded '-', no spaces bool valid_surname( const std::string& str ) { if( str.empty() ) return false ; if( str.front() == '-' || str.back() == '-' ) return false ; for( char c : str ) if( !std::isalpha(c) && c != '-' ) return false ; // note: check for two consecutive '-' elided for brevity return true ; } // not empty, all alpha, may contain embedded '-', ',' or '.', no spaces bool valid_place( const std::string& str ) { static const auto is_special = [] ( char c ) { return c == '-' || c == ',' || c == '.' ; }; if( str.empty() ) return false ; if( is_special( str.front() ) ) return false ; if( is_special( str.back() ) ) return false ; for( char c : str ) if( !std::isalpha(c) && !is_special(c) ) return false ; // note: check for two consecutive special characters elided for brevity return true ; } struct person_info // not default constructible { person_info( std::string surname, std::string name, std::string place_of_birth, int age, int tel = 0 ) ; // constructor std::string surname; std::string name; std::string place_of_birth; int tel = 0 ; // default member initialiser int age = 0 ; bool valid() const ; // class invariant ; }; bool person_info::valid() const { // if any field is present, all fields must be present and valid return valid_name(name) && valid_surname(surname) && valid_place(place_of_birth) && age >= 0 && age < 200 && tel >= 0 ; } person_info::person_info( std::string surname, std::string name, std::string place_of_birth, int age, int tel ) : surname(surname), name(name), place_of_birth(place_of_birth), tel(tel), age(age) { if( !valid() ) throw std::invalid_argument( "bad constructor arg" ) ; } // write person info to an output stream. ( operator<< ? ) std::ostream& put_person_info( std::ostream& stm, const person_info& pinfo ) { return stm << pinfo.name << ' ' << pinfo.surname << ' ' << pinfo.place_of_birth << ' ' << pinfo.age << ' ' << pinfo.tel << '\n' ; } int main() { // either: { std::string surname, name, place_of_birth ; int age, tel ; std::cout << "surname? " && std::cin >> surname ; std::cout << "name? " && std::cin >> name ; std::cout << "place_of_birth? " && std::cin >> place_of_birth ; std::cout << "age? " && std::cin >> age ; std::cout << "tel? " && std::cin >> tel ; try { person_info pinfo( surname, name, place_of_birth, age, tel ) ; put_person_info( std::cout << "ok: ", pinfo ) ; } catch( const std::invalid_argument& ) { std::cerr << "*** error: invalid input\n" ; } } // or, with more accurate error reporting and recovery: { std::string surname, name, place_of_birth ; int age, tel ; while( std::cout << "surname? " && std::cin >> surname && !valid_surname(surname) ) std::cout << "inavid surname. try again\n" ; while( std::cout << "name? " && std::cin >> name && !valid_name(name) ) std::cout << "inavid name. try again\n" ; while( std::cout << "place_of_birth? " && std::cin >> place_of_birth && !valid_place(place_of_birth) ) std::cout << "inavid place_of_birth. try again\n" ; while( std::cout << "age? " && std::cin >> age && ( age < 0 || age > 199 ) ) { std::cout << "inavid age. try again\n" ; std::cin.clear() ; std::cin.ignore( 1000, '\n' ) ; } while( std::cout << "phone? " && std::cin >> tel && tel < 0 ) { std::cout << "inavid phone number. try again\n" ; std::cin.clear() ; std::cin.ignore( 1000, '\n' ) ; } person_info pinfo( surname, name, place_of_birth, age, tel ) ; put_person_info( std::cout << "ok: ", pinfo ) ; } }``````
Again, that's a good code you wrote, but it doesn't solve problems with mine.

I asked several people about what is the issue with my code, and it turns out that I somehow overwrite the declarations made in secure::secure(). I also previously thought it calls for a wrong default but it doesn't seem to be the case.

How do I write stuff into a new string that will follow the rules of secure::secure()?

 ``123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126`` ``````#include #include #include #include #include #include using namespace std; struct secure { string surname; string name; string age; string placeofbirth; string tel; secure(const string& s, const string& n, string& a, const string& p, const string& t); }; secure::secure(const string& s, const string& n, string& a, const string& p, const string& t) :surname{s}, name{n}, age{a}, placeofbirth{p}, tel{t} { if(!(find_if(s.cbegin(), s.cend(), [](const char z) {return !isalpha(z) && z != '-';}) == s.cend())) cout<<"You may not use characters that are not letters nor -."<0) { enterholder = replacement; cout<<"Enter data "<> s >> n >> a >> p >> t) { ensuretodisplay = true; cout << s << '\t' << n << '\t' << a << '\t' << p << '\t' << t <

 How do I write stuff into a new string that will follow the rules of secure::secure()?

Is usually said a class should serve for one purpose only (single responsibility principle).
What do you want your struct/class do? Do you want it just to contain data in an organised way or do you want it also to ask for input, write on file, display its data...?
In the first case, in my opinion you should check data *before* inserting them into your struct. As far as I can see, JLBorges’ code performs that task quite well.
Otherwise, if you want your class to be the only manager of its data, you should consider to make them private and write member methods to ask for them and guarantee their consistency (again: IMHO).

I’m just a beginner, but that’s what I’ve understood about class design. If you (or whoever reads this post) have different information, I’d be grateful if you want to share them with me.
What I want to do is to write into a file info of n students. That being said, I want it to follow certain rules of struct, such as disallow digits in a string of surname.

 ``12345678910111213141516171819202122232425262728`` ``````struct secure { string surname; string name; string age; string placeofbirth; string tel; secure(const string& s, const string& n, string& a, const string& p, const string& t); }; secure::secure(const string& s, const string& n, string& a, const string& p, const string& t) :surname{s}, name{n}, age{a}, placeofbirth{p}, tel{t} { if(!(find_if(s.cbegin(), s.cend(), [](const char z) {return !isalpha(z) && z != '-';}) == s.cend())) cout<<"You may not use characters that are not letters nor -."<

If an input fails, I want it to output an error message and ask for a string again. I specifically want to make use of struct as the task suggests me. That said, what my code is doing is it simply overwrites the rules of secure::secure() and I need to know how do I write stuff into a new string following the set rules.
IMO, you're trying to do too much in your constructor. Instead it might be better to default construct the structure and then use setter functions to change the values of the member variables to the proper values. These setters can be used to enforce your constraints and return a value to indicate success or failure to meet the constraint. If you try to force the constructor to meet the constraints the only way it can signal failure is to insert "known bad" values into the variables or throw an exception when the constraint is not met. IMO, constructors should usually be kept as simple as possible and avoid constraint testing if possible.

You also need to get rid of the goto statements in your code, learn to use the more accepted types of loops like for(;;), while(), do{}while().

Enoizat wrote:
What do you want your struct/class do?
Gieniusz Krab wrote:
What I want to do is to write into a file info of n students.

That would definitely be the proper answer to the question “What do you want to do?”.

 I asked several people about what is the issue with my code, and it turns out that I somehow overwrite the declarations made in secure::secure(). ... what my code is doing is it simply overwrites the rules of secure::secure() and I need to know how do I write stuff into a new string following the set rules.

What about inviting those people to join this conversation? Because that overwriting problem is not clear to me.

I agree with what jlb said about your constructor, anyway, with just a little tweaking, it more or less works:
 ``1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950`` ``````#include #include #include #include struct secure { std::string surname; std::string name; std::string age; std::string placeofbirth; std::string tel; secure(const std::string& s, const std::string& n, const std::string& a, const std::string& p, const std::string& t); }; secure::secure(const std::string& s, const std::string& n, const std::string& a, const std::string& p, const std::string& t) : surname{s}, name{n}, age{a}, placeofbirth{p}, tel{t} { if(!(std::find_if(s.cbegin(), s.cend(), [](const char z) {return !isalpha(z) && z != '-';}) == s.cend())) std::cout<<"You may not use characters that are not letters nor -.\n"; if(!(std::find_if(n.cbegin(), n.cend(), [](const char x) {return !isalpha(x);}) == n.cend())) std::cout<<"You are allowed to only use letters.\n"; if(a.find_last_not_of("0123456789")) std::cout<<"Age must be a positive integer.\n"; if((std::find_if(p.cbegin(), p.cend(), [](const char c) {return !isspace(c);}) == p.cend())) std::cout<<"Spaces are not allowed.\n"; if(t.find_last_not_of("1234567890+-")) std::cout<<"Telephone numbers consists only of digits, + and -.\n"; std::cout << "\nInserted data are: " << surname << "; " << name << "; " << age << "; " << placeofbirth << "; " << tel << '\n'; } void waitForEnter(); int main() { secure exotic("Van Helsing 13", "Smiling :) John", "-6.66", "8th September 20017", "john@vanhelsing13"); waitForEnter(); return 0; } void waitForEnter() { std::cout << "\nPress ENTER to continue...\n"; std::cin.ignore(std::numeric_limits::max(), '\n'); }``````

Output:
 ```You may not use characters that are not letters nor -. You are allowed to only use letters. Age must be a positive integer. Telephone numbers consists only of digits, + and -. Inserted data are: Van Helsing 13; Smiling :) John; -6.66; 8th September 20017; john@vanhelsing13 Press ENTER to continue...```

It works, but I can’t appreciate its logic.
What you constructor performs are these operations in order:
- at the beginning it assigns to each property a value from the argument list
- then it checks the arguments (not the properties!) for errors.
It means the parameter values are copied into the properties even if they are (later discovered to be) wrong.

The conclusion is there’s no way you can know if the data inside your struct are consistent or not. What I mean is if you checked the parameters *before* assigning them, you could later perform at least some basic check, like:
 ``1234`` ``````if(exotic.tel.empty()) { std::cout << "Error in current data.\n"; // do something to manage error }``````

But once you have accepted the data, the only thing you can do is checking their consistency again by the same operations you performed inside the constructor. So you need a function to perform that check. But, once you have that function, it will be a duplicate of the constructor... And, anyway, it wouldn’t allow you to correct the data. Unless it asked the user new data. But then it would be a duplicate of the function which asks the user for data...

Are you sure you need to stuck to the code you’ve written? Perhaps you could consider writing a new code based on all the good pieces of advice you received so far.
Topic archived. No new replies allowed.