






Study with the several resources on Docsity
Earn points by helping other students or get them with a premium plan
Prepare for your exams
Study with the several resources on Docsity
Earn points to download
Earn points by helping other students or get them with a premium plan
An overview of various c++ programming concepts including the use of ifstream objects, managing text with the c++ string class, understanding enums, references, const variables, and const functions, handling exceptions, and preprocessor macros.
Typology: Slides
1 / 11
This page cannot be seen from the preview
Don't miss anything!







File handling in C++ works almost identically to terminal input/output. To use files, you write #include
For example: 1 # include < fstream > 2 using namespace std ; 3 4 int main () { 5 ifstream source ( " source - file. txt " ) ; 6 ofstream destination ( " dest - file. txt " ) ; 7 int x ; 8 source >> x ; // Reads one int from source - file. txt 9 source. close () ; // close file as soon as we ’ re done using it 10 destination << x ; // Writes x to dest - file. txt 11 return 0; 12 } // close () called on destination by its destructor
As an alternative to passing the filename to the constructor, you can use an existing ifstream or ofstream object to open a file by calling the open method on it: source.open("other-file.txt");. Close your files using the close() method when you’re done using them. This is automat ically done for you in the object’s destructor, but you often want to close the file ASAP, without waiting for the destructor.
You can specify a second argument to the constructor or the open method to specify what “mode” you want to access the file in – read-only, overwrite, write by appending, etc. Check documentation online for details.
You’ll likely find that you want to read some text input from the user. We’ve so far seen only how to do ints, chars, etc.
It’s usually easiest to manage text using the C++ string class. You can read in a string from cin like other variables:
1 string mobileCarrier ; 2 cin >> mobileCarrier ;
However, this method only reads up to the first whitespace; it stops at any tab, space, newline, etc. If you want to read multiple words, you can use the getline function, which reads everything up until the user presses enter:
1 string sentence ; 2 getline ( cin , sentence ) ;
In many cases you’ll find that you’ll want to have a variable that represents one of a discrete set of values. For instance, you might be writing a card game and want to store the suit of a card, which can only be one of clubs, diamonds, hearts, and spades. One way you might do this is declaring a bunch of const ints, each of which is an ID for a particular suit. If you wanted to print the suit name for a particular ID, you might write this:
1 const int CLUBS = 0 , DIAMONDS = 1 , HEARTS = 2 , SPADES = 3; 2 void print_suit ( const int suit ) { 3 const char * names [] = { " Clubs " , " Diamonds " , 4 " Hearts " , " Spades " }; 5 return names [ suit ]; 6 }
The problem with this is that suit could be integer, not just one of the set of values we know it should be restricted to. We’d have to check in our function whether suit is too big. Also, there’s no indication in the code that these const ints are related.
Instead, C++ allows us to use enums. An enum just provides a set of named integer values which are the only legal values for some new type. For instance:
References are perfectly valid types, just like pointers. For instance, just like int * is the “pointer to an integer” type, int & is the “reference to an integer” type. References can be passed as arguments to functions, returned from functions, and otherwise manipulated just like any other type. References are just pointers internally; when you declare a reference variable, a pointer to the value being referenced is created, and it’s just dereferenced each time the reference variable is used.
The syntax for setting a reference variable to become an alias for another variable is just like regular assignment:
1 int & x = y ; // x and y are now two names for the same variable
Similarly, when we want to pass arguments to a function using references, we just call the function with the arguments as usual, and put the & in the function definiton, where the argument variables are being set to the arguments actually passed:
1 void sq ( int & x ) { // & is part of the type of x 2 // - x is an int reference 3 x *= x ; 4 } 5 sq ( y ) ;
Note that on the last line, where we specify what variable x will be a reference to, we just write the name of that variable; we don’t need to take an address with & here.
References can also be returned from functions, as in this contrived example:
1 int g ; // Global variable 2 int & getG () { // Return type is int reference 3 return g ; // As before , the value we ’ re making a 4 // reference * to * doesn ’t get an & in front of it 5 } 6 7 // ... Somewhere in main 8 int & gRef = getG () ; // gRef is now an alias for g 9 gRef = 7; // Modifies g
If you’re writing a class method that needs to return some internal object, it’s often best to return it by reference, since that avoids copying over the entire object. You could also then use your method to do something like:
1 vector < Card > & cardList
2 = deck. getList () ; // getList declared to return a reference 3 cardList. pop_back () ;
The second line here modifies the original list in deck, because cardList was declared as a reference and getList returns a reference.
2.2.1 Converting between const and non-const
You can always provide a non-const value where a const one was expected. For instance, you can pass non-const variables to a function that takes a const argument. The const-ness of the argument just means the function promises not to change it, whether or not you require that promise. The other direction can be a problem: you cannot provide a const reference or pointer where a non-const one was expected. Setting a non-const pointer/reference to a const one would violate the latter’s requirement that it not be changeable. The following, for instance, does not work:
1 int g ; // Global variable 2 const int & getG () { return g ; } 3 4 // ... Somewhere in main 5 int & gRef = getG () ;
This fails because gRef is a non-const reference, yet we are trying to set it to a const reference (the reference returned by getG).
In short, the compiler will not let you convert a const value into a non-const value unless you’re just making a copy (which leaves the original const value safe).
2.2.2 const functions
For simple values like ints, the concept of const variables is simple: a const int can’t be modified. It gets a little more complicated when we start talking about const objects. Clearly, no fields on a const object should be modifiable, but what methods should be available? It turns out that the compiler can’t always tell for itself which methods are safe to call on const objects, so it assumes by default that none are. To signal that a method is safe to call on a const object, you must put the const keyword at the end of its signature, e.g. int getX() const;. const methods that return pointers/references to internal class data should always return const pointers/references.
7 void f ( int x , int ** arrPtr ) { 8 try { 9 * arrPtr = new int [ divide (5 , x ) ]; 10 } 11 catch ( bad_alloc & error ) { // new throws exceptions of this type 12 cerr << " new failed to allocate memory " ; 13 } 14 catch ( runtime_exception & error ) { 15 // cerr is like cout but for error messages 16 cerr << " Caught error : " << error. what () ; 17 } 18 // ... 19 }
In such a case, the exception’s type is checked against each of the catch blocks’ argument types in the order specified. If line 2 causes an exception, the program will first check whether the exception is a bad alloc object. Failing that, it checks whether it was a runtime exception object. If the exception is neither, the function exits and the exception continues propagating up the call stack.
The destructors of all local variables in a function are called before the function exits due to an exception.
Exception usage notes:
Occasionally you’ll want to allow a function that is not a member of a given class to access the private fields/methods of that class. (This is particularly common in operator overloading.)
We can specify that a given external function gets full access rights by placing the signature of the function inside the class, preceded by the word friend. For example, if we want to make the fields of the USCurrency type from the previous lecture private, we can still have our stream insertion operator (the output operator, <<) overloaded: 1 class USCurrency { 2 friend ostream & operator < <( ostream &o , const USCurrency & c ) ; 3 int dollars , cents ; 4 public : 5 USCurrency ( const int d , const int c ) : dollars ( d ) , cents ( c ) {} 6 }; 7 8 ostream & operator < <( ostream &o , const USCurrency & c ) { 9 o << ’$ ’ << c. dollars << ’. ’ << c. cents ; 10 return o ; 11 }
Now the operator<< function has full access to all members of USCurrency objects.
We can do the same with classes. To say that all member functions of class A should be fully available to class B, we’d write: 1 class A { 2 friend class B ; 3 // More code ... 4 };
We’ve seen how to define constants using the preprocessor command #define. We can also define macros, small snippets of code that depend on arguments. For instance, we can write: 1 # define sum (x , y ) ( x + y )
Now, every time sum(a, b) appears in the code, for any arguments a and b, it will be replaced with (a + b).
Macros are like small functions that are not type-checked; they are implemented by simple textual substitution. Because they are not type-checked, they are considered less robust than functions.
If you are interested in learning more about these subjects or anything we’ve discussed, we encourage you to look through online tutorials, perhaps even to buy a C++ book – and most importantly, to just play around with C++ on your own!