About C++

L2 - Variables
- C++ has the notion of lvalues  and rvalues  associated with variables and constants. The rvalue is the data value of the variable, that is, what 
information it contains. The "r" in rvalue can be thought of as "read" value. The "l" in lvalue can be though of as location, meaning that a variable 
has a location that data or information can be put into. A constant has some data value (rvalue) but does not have an lvalue (it cannot be written to).
Another view of these terms is that objects with an rvalue, namely a variable or a constant can appear on the right hand side of a statement. 
They have some data value that can be manipulated. Only objects with an lvalue, such as variable, can appear on the left hand side of a statement. 
An object must be addressable to store a value.

L3 - Constants
- There are three techniques used to define constants in C++. The first technique comes from the C programming language. Constants may be defined 
using the preprocessor directive, #define . The preprocessor is a program that modifies your source file prior to compilation. 
Ex: #define pi 3.1415 
Wherever the constant appears in your source file, the preprocessor replaces it by its value. The problem with this technique is that the replacement 
is done lexically, without any type checking, without any bound checking and without any scope checking.
- The second technique is to use the keyword const when defining a variable. Ex: const float pi = 3.1415; 
There are two main advantages over the first technique. First, the type of the constant is defined. "pi" is float. This allows some type checking by 
the compiler. Second, these constants are variables with a definite scope.
- The third technique is called enumeration. An enumeration defines a new type and limits the values of this type to a programmer defined set. 
Ex: enum COLOR { RED, BLUE, GREEN}; Each enumerated constant (sometimes called an enumerator) has an integer value. Unless specified, the first constant
has a value of zero. The values increase by one for each additional constant in the enumeration. So, RED equals 0, BLUE equals 1, and GREEN = 2.
The values of each constant can also be specified. Ex: enum SHAPE {SQUARE=5,RECTANGLE,TRIANGLE=17,CIRCLE,ELLIPSE};
The advantage of enumerations is that if a variable is declared to be the type of an enumeration, it has a type and its values are limited and checked 
during compiling.

L4 - Input and Output
- In C++, input and output are provided by the iostream library. Including this file defines and initializes the following objects for use in your 
program.
cin  - This object provides for input from the terminal (keyboard). 
cout  - This object provides for output to the screen. 
cerr  - This object provides unbuffered output to the standard error device, which defaults to the screen. Unbuffered means that any messages or data 
will be written immediately. With buffered input, data is saved to a buffer by the operating system. When the buffer is full, everything in it is 
written out. If a program crashes before the buffer is written, nothing in the buffer is output. Output via cerr is unbuffered to ensure that error 
messages will be written out.
clog - This object provides buffered output to the standard error device, which defaults to the screen.
- The input operator , >>, is used to put data into variables in your program. It is also called the insertion operator. 
The output operator , <<, is used to direct output to standard output or standard error. It is also called the extraction operator.

L5 - Conditional Processing, Part 1
- The if statement is used to conditionally execute a block of code based on whether a test condition is true. The else statement provides a way to 
execute one block of code if a condition is true, another if it is false. Ex: if (true) {statements;} else {statements;}
- Relational operators: ==, !=, >, >=, <, and <=, are used to compare two operands.

L6 - Conditional Processing, Part 2
- The switch statement is a construct that is used to replace deeply nested or chained if/else statements. The general form of a switch statement is:
 switch (variable) {case expression1: do something 1; break; case expression2: do something 2; break; .... default: do default processing; }
Each expression must be a constant. When an expression is found that is equal to the tested variable, execution continues until a break statement 
is encountered. It is possible to have a case without a break. This causes execution to fall through into the next case.
- C++ provides several logical operators: "&&" (AND), "||" (OR), "!" (NOT). 
Relational operators are of higher precedence than the logical and the order of evaluation is from left to right.

L7 - Looping
- The while loop is used to execute a block of code as long as some condition is true. If the condition is false from the start the block of code is not
executed at all. Its syntax is as follows: while ( tested condition is satisfied ) {block of code} 
- The largest unsigned number that can be stored in N bits is (2^N - 1).
- The do loop also executes a block of code as long as a condition is satisfied. The difference between a "do" loop and a "while" loop is that 
the while loop tests its condition at the top of its loop; the "do" loop tests its condition at the bottom of its loop.
 The "do" loops syntax is as follows: do {block of code} while ( condition is satisfied )
- The for loop can execute a block of code for a fixed number of repetitions. 
Its syntax is as follows: for ( initializations ; test conditions ; actions ) { block of code } 
L8 - An Introduction To Pointers - A pointer is a special type of variable that contains a memory address rather than a data value. They are useful for passing parameters into functions in a manner that allows a function to modify and return values to the calling routine. As a program is executing all variables are stored in memory, each at its own unique address or location. Just as data is modified when a normal variable is used, the value of the address stored in a pointer is modified as a pointer variable is manipulated. - Usually, the address stored in the pointer is the address of some other variable. Ex: int count = 5; int *ptr = &count; The unary operator & returns the address of a variable. To get the value that is stored at the memory location in the pointer it is necessary to dereference the pointer (done with the unary operator "*" ). Ex1: int total; total = *ptr; - Pointer to Array: Ex2: int array[10]; int *parr = array; (echivalent with: int *parr = &array[0]) , parr contains the address of the first element of values. - Pointer Arithmetic: Pointers can be incremented, decremented and manipulated using arithmetic expressions. L9 - Introduction to Classes - Classes are a software construct that can be used to emulate a real world object. Classes encapsulate data and abilities. A class is a programmer defined data type that has data, its members, and abilities, its methods. An object is a particular instance of a class. Ex: Dog rex; - A class is defined by using the keyword class followed by a name followed by the class definition in braces. The class definition contains the class members = data, and the class methods = functions. Ex: class Dog { public: void setAge(int age); void speak(); private: int age; int weight; }; - the keyword private indicates that the two members, age and weight, cannot be directly accessed from outside of the class. The keyword public indicates that the methods, can be called from code outside of the class. That is, they may be called by other parts of a program using objects of this class. - This technique of allowing access and manipulation of data members only through methods is referred to as data hiding. The interface to the class is public and the data is private. Public interface, private data is a key concept when designing classes. - Methods used to set or get members are called accessor methods or accessors. (ex: setAge()) Ex: rex.setAge(10); - The methods are implemented outside of the class definition; they must be identified as belonging to that class. This is done with the scope resolution operator , "::". It identifies each method, for example, getAge(), as belonging to the class Dog. Ex: void Dog::setAge(int age) { this->age = Varage; } - Every object has a special pointer call "this pointer", which refers to the object itself. So the members of the Dog class can be referred to as this->age or this->weight, as well as, age or weight (!!!). If "this" is a pointer to a class, then the member selection operator, "->", can be used to access the contents of its members. - Each class also has a special method, the constructor, that is called when an object of the class is instantiated (created). The constructor can be used to initialize variables, dynamically allocate memory or setup any needed resources. Another special method, the destructor, is called when an object is destroyed. An object is destroyed when it goes out of scope. If an object is created within a function, it will go out of scope when the function exits. The destructor is used to free any memory that was allocated and possible release other resources. Ex: class Dog { public: Dog(); //Constructor ~Dog(); //Destructor ... }; Dog::Dog() { age = 0; weight = 0; cout << "Dog Constructor Called" << endl; } Dog::~Dog() { cout << "Dog Destructor Called" << endl; } The constructor has the same name as the class. The destructor has the same name as the class prefixed by a tilde, "~". It is possible to have multiple constructors that differ in their number and/or type of parameters. The constructor that is used is based on the arguments used in its invocation. - Encapsulation: The property of being self-contained. Encapsulation is really data hiding. Private data, public interface. If the inner workings of the class are changed but the interface remains constant then programs using the class do not need to be modified, only recompiled. - Inheritance: A subclass may be derived from a class and inherit its methods and members. The subclass will be more specialized. For instance, we could create a vehicle class that has members and methods that apply to all vehicles. For instance, all vehicles might have a member to store velocity and a "brake" function. A plane class derived from the vehicle class could add specialized features such as an altitude member or a landing method. Inheritance allows code to be developed without reinventing the wheel at each step. - Polymorphism: Polymorphism refers to the ability of an object to behave in different ways based on the context. The same function may exist in related classes. For instance both a general "Car" class and derived "Ford" and "Chevy" classes might have accelerate functions. A program using these classes might at run-time choose to use one of these classes and the correct accelerate function will be resolved as the program runs. A second form of polymorphism results from the overloading of functions. Multiple functions can have the same name but different arguments. The correct one is chosen based on the arguments used in its invocation.
L10 - Arrays and Vectors - Arrays are a data structure that is used to store a group of objects of the same type sequentially in memory. This allows convenient and powerful manipulation of array elements using pointers. Vector is a container class from the C++ standard library. As is true for an array, it can hold objects of various types. Vector will also resize, shrink or grow, as elements are added. The standard library provides access to vectors via iterators, or subscripting. Iterators are classes that are abstractions of pointers. They provide access to vectors using pointer-like syntax but have other useful methods as well. The use of vectors and iterators is preferred to arrays and pointers. - Defining Arrays: An array is defined with this syntax: datatype arrayName[size]; Ex: class POINT { public: POINT() { x = 0; y = 0;} ~POINT(); private: int x; int y; } POINT dots[100]; - declared an array of objects of class POINT. A class to be used in an array must have a default constructor, that is, a constructor without arguments. The compiler uses this constructor when allocating space for the array when it is defined. - Using Arrays: Arrays in C++ are zero based. Suppose an array named myExample contains N elements. This array is indexed from 0 to (N-1). An array can be initialized with an explicit initialization list in its definition. Ex: int myArray[3] = {6, 7, 0}; - Relationship Between Pointers and Arrays: In some cases, a pointer can be used as a convenient way to access or manipulate the data in an array. The address of the first element of the array temperatures can be assigned to temp in two ways: parray = &array[0]; || parray = array; - Multidimensional Arrays: They are defined as follows: type arrName [V1][V2]; A common way to access the elements of a multidimensional arrays is with nested for loops. Ex: for (i = 0; i < V1; i++) {for (j = 0; j < V2; j++) { arrName[i][j] = value ; } } The C++ language performs no error checking on array bounds, which is one reason to use vectors rather than arrays. What will happen if a program accesses past the end of an array? It will access whatever is in memory at the location right after the end of the array. The program may continue to run with the incorrect value and no warnings are issued. What will happen if a program writes past the end of an array? That value will be written at that memory location. If the program writes unintentionally to memory locations that are not valid, the program may crash.
- Defining Vectors: To define a vector, it is necessary to include the vector class header file: #include <vector> . A vector of some type is defined as follows: vector<type> nameV(size); The vector class is a template. The C++ language allows classes and functions to be declared as templates. - Templates allow data types to be parameterized in the class or function definitions. Templates are a way to allow a class or function to work with different data types. For instance a vector of floats can be defined as: Ex: vector<float> amounts(50); //Defines a vector of 50 floats The template allows the same class, vector in this case, to be used with many different data types, including programmer defined classes. Ex: Class Dog {...} vector<Dog> dogs(99); // Defines a vector of 99 dogs. Note that if templates were not part of the language, a separate "vector" class would be needed for each data type. There would be a "vectori" for ints, "vectorf" for floats. For a user defined type to be used in a vector there are three additional requirements that must be satisfied. The type must supply a default value. For classes, this is the default constructor. The type must support the equality operator. For classes, this is an overloaded operator and the type must support the less-than operator. - Built-in data numeric and pointer data types such as int and float get initialized to zero when a vector is defined. A vector of classes is initialized by its default constructor. In contrast, arrays are not initialized be default in C++. Arrays should be explicitly initialized by the programmer with either code or an initialization list in the array definition. Vectors do not support explicit initialization list as do arrays. Does this mean that each element must be assigned a value individually? Fortunately, not. - Several different vector constructors exist. Here are examples of their use. Ex1: vector<float> weights(10,5.9); //Defines a vector of 10 weights all initialized to 5.9
Ex2: int w[10] = {0, 1, 2}; vector<int> weights(w,w+3); // define a vector of 3 int with the values of w array
- Using Vectors: Vectors can be accessed by subscripts and are zero based, just as arrays. Ex: cout << vector[i]; - Iterators: The standard library also provides access to vectors and other containers via iterators. Iterators are classes that are abstractions of pointers. They provide pointer like syntax with increment, decrement and dereferencing. The list class is essentially a linked-list !!!. Iterators can be use to step easily through a list, while a simple pointer cannot. The C++ standard library also provides a large number of generic algorithms such as sort and find, which accept iterator arguments. This allows the algorithms to be used consistently for any container class. An iterator to the start of a vector is defined as follows: vector<int> myInts(30); vector<int>::iterator iter = myInts.begin(); Iterators are classes. Each container class, vector, list, etc., needs its own special iterators due to its unique properties. So, there are multiple iterator classes. In order to hide the names of the actual iterator classes in the C++ library, each container class has a typedef for the correct iterator class, renaming it "iterator". So vector<int>::iterator refers to the iterator that is part of the vector class. The expression sets this iterator to the beginning of the vector. - Generic Algorithms: The C++ standard library includes many generic algorithms that can be used with any of the container classes, and also with the built-in array data type. This lesson will introduce some of the available routines. The generic algorithms are able to work in a container independent manner by accepting as arguments iterators to the start point and one past the end point of a range within the container. This range could, of course, span the entire container. For arrays, pointers rather than iterators may be used as arguments. To use the generic algorithms the header file <algorithm> must be included. - The algorithms find and count; the prototypes are: InputIterator find(InputIterator start, InputIterator end,const T& value); size_t count(InputIterator start, InputIterator end,const T& value); find returns an iterator to the first occurrence of the value if it is found, or an iterator to one past the range searched if not found. count returns the number of occurrences of the value found in the search range. Ex: vector<int> num(val,val+15); vector<int>::iterator start = num.begin(); vector<int>::iterator end = num.end(); vector<int>::iterator iter; iter = find(start,end,37); if (iter == end) { cout << "37 Not Found" << endl; } else { cout << "Found " << *iter << endl; } sort function: Prototype: sort(start_iterator, end_iterator); imi aranjeasa in ordine crescatoare datele dintr-un vector; Ex: vezi ex001. L11 - Strings - C++ has two methods for representing strings, C-style character arrays and the string class. Although the string class provides more functionality and is less error prone in use, it is not uncommon to see C-style character arrays in C++ code. - C-Style Character Arrays: Strings are stored as null, '\0', terminated character arrays. This representation has several weaknesses and should generally be avoided in C++ programming in favor of the use of the string class. A character array of sufficient size must be defined or allocated to hold the string. The array must be at least the length of the string plus one. One byte is needed to hold the null terminator. At times, it is necessary to explicitly add the null terminator. Pointers are commonly needed and used to access and manipulate the string data. C-style strings is used in C++ to handle command line arguments. These arguments are passed into a C++ program as C-style character arrays. - String Class: To use this class: #include <string> . Unlike C-style strings, the internal representation of the string data is hidden by the string class. The data is set, accessed and manipulated using the methods of the string class. Examples of creating and initializing strings: string S1; // Default constructor, no initialization; string S2("Hello"); // Initialization with a C-Style string; string S4(S2); // Initialization with another string object; S1 = S2; // Assignment of one string to another.
The default constructor creates an empty string. Other forms of the constructor take C-style character arrays or other string objects as arguments and initialize the new string with these values. Also, notice that it is possible to assign one string to another directly. - String Operations: The string class supports relational operators, for comparing strings as nr. of elements. The "+" operator is used for string concatenation. The subscript operator, [], can be used to access or set individual elements (characters) of a string. - String Class Methods: The string class has many methods for string manipulations such as appending, inserting, deleting, searching and replacing. L12 - File Input and Output - One important issue with writing results to output files is data format. To perform file input and output the include file fstream must be used. Fstream header contains class definitions for classes used in file i/o. Within a program needing file i/o, for each output file required, an object of class ofstream is instantiated. For each input file required, an object of class ifstream is instantiated. The ofstream object is used exactly as the cout object for standard output is used. The ifstream object is used exactly as the cin object for standard input is used. - The constructor for the ofstream class takes two arguments. The first specifies a file name as a C-style string, the second a file mode. There are two common file open modes, truncate and append. By default, if no mode is specified and the file exists, it is truncated. The mode is specified by using an enumerator define in the ios class. The ios class contains members which describe modes and states for the input and output classes. Ex: ofstream myFile("c:/temp/out.txt"); if (! myFile) cout << "Error opening output file"; - File modes: out = Open a file or stream for insertion (output); in = Open a file or stream for extraction (input);app = Append rather than truncate an existing file; Each insertion (output) will be written to the end of the file; trunc = Truncate existing file (default behavior); ate = Opens the file without truncating, but allows data to be written anywhere in the file; binary = Treat the file as binary rather than text. A binary file has data stored in internal formats, rather than readable text format. For example, a float would be stored as its internal four byte representation rather than as a string. - To combine modes one can use "|" . Ex: ofstream myFile("SomeFileName",ios::out | ios::trunc); - Useful Methods of Input and Output Classes: The ifstream class has several useful methods for input. These method are also in the class cin. Extraction Operator, >> = This overloaded operator handles all built in C++ data types. By default, any intervening white space is disregarded. That is, blanks, tabs, new lines, formfeeds and carriage returns are skipped over. get() = This form of get extracts a single character from the input stream. get(char &ch) = This form of get also extracts a single character from the input stream, but it stores the value in the character variable passed in as an argument. getline = read entire lines of text into a string. ignore(int count=1, int delim=traits_type::eof) = This method reads and discards "count" characters from the input stream. peek() = This method returns the next character from the input stream, but does not remove it. It is useful to look ahead at what the next character read will be. putback(char &ch) = This method puts ch onto the input stream. The character in ch will then be the next character read from the input stream. unget() = This method puts the last read character back into the input stream. read(char *buff, int count) = This method is used to perform an unformatted read of count bytes from the input stream into a character buffer. - The ofstream class has several useful methods for writing to an output stream. An output stream is standard output (usually the screen), a file or a buffer. These methods are also in the object cout. Insertion Operator, << . put(char ch) = This method puts a single character into the output stream. write(char *buff, int count) = This method is used to perform an unformatted write from a character buffer to the output stream. L13 - References - In C++, a reference is an alias (or synonym) to a object(variable). The real usefulness of references is when they are used to pass values into functions . They provide a way to return values from the function Declaration: int val; int &rVal = val; // Declares a reference to the integer object val. In this context, the "&" is called the reference operator. It indicates that rVal is a reference rather than an ordinary object. The reference identifier (rVal) may be used anywhere the referred identifier may be used. Any changes to the reference also apply to the original object. Changes to the original object are also seen through the reference. Ex: cout << "rVal is " << rVal << endl; - A reference must be initialized when it is created. Reference cannot be reassigned. If you attempt to reassign a reference, the result will be an assignment to the original referred object. References may also be made to user-defined objects as well as to variables of built-in types. - Most of the utility of references is when they are used with global functions or class methods. In this role, they provide a way to return multiple values from a function or method without using pointers and without the cumbersome pointer syntax. References used strictly as aliases, are of less utility. They are merely alternate identifiers for the same object. L14 - Functions, Basics - Functions are used to encapsulate a set of operations and return information to the main program or calling routine. Once a function is written, we need only be concerned with what the function does. That is, what data it requires and what outputs it produces. - A function is declared with a prototype. A function prototype consists of the return type, a function name and a parameter list. The function prototype is also called the function declaration. Function prototype: return_type function_name(list of parameters); - The function definition consist of the prototype and a function body, which is a block of code enclosed in parenthesis. A declaration or prototype is a way of telling the compiler the data types of the any return value and of any parameters, so it can perform error checking. The definition creates the actual function in memory. A function must be declared prior to its use. - The parameter list of a function may have parameters of any data type and may have from no to many parameters. The return statement can be used to return a single value from a function. Variables can be declared within a function. These variables are local variables and can only be used within the function. They have local scope . - The function definitions need not even be in the same file with your main function. When you use library functions, the definitions are not in your file. Main, along with the needed function declarations may be in one file. The functions themselves, that is, their definitions, may be separated, into multiple files. This is usually how any large project is organized. - Returning Multiple Values From Functions and Methods: C++ has two techniques that can be used if a variable is to be modified within a function, and the modified value is desired in the calling routine. The first is to pass a pointer to the variable to the function. The pointer can then be manipulated to change the value of the variable in the calling routine. It is interesting to note that the pointer itself is passed by value. The function cannot change the pointer itself since it gets a local copy of the pointer. However, the function can change the contents of memory, the variable, to which the pointer refers. The advantages of passing by pointer are that any changes to variables will be passed back to the calling routine and that multiple variables can be changed. Ex: void swap(int *x, int *y); int* px = &x; int*py = &y; swap(px,py); // sau swap(&x,&y); - The second method used in C++ to pass variables into functions in a way that their values are updated in the calling routine is to pass by reference. When references are passed into a function or class method, any changes made to the references will be seen in the calling routine. Ex: void swap(int &x, int &y); void main() { int x = 2; int y = 5; swap(x,y);} void swap(int &first, int &second) {int temp; temp = second; second = first; first = temp; }. Compared with the pointer version of the swap, the syntax is simplified. Why are pointers ever used? Remember that references cannot be reassigned in C++. If reassignment is necessary, then pointers must be used. - Passing Arrays to Functions: When an array is passed into a function, the function receives not a copy the array, but instead the address of the first element of the array. The function receives a pointer to the start of the array. Any modifications made via this pointer will be seen in the calling program. Ex: void doubleThem(int a[], int size); void main(){ int myInts[10]={...}; doubleThem(myInts, 10);} - Strings can be stored in C++ as null terminated character arrays, although the use of the string class from the standard library is preferred. Null terminated character arrays are also called C-style strings. - Default Parameters: It is possible to define defaults for some or all of the parameters of a function or class method in C++. Example of function prototypes with defaults: float calcSalesTax(float purchase, float taxRate = 0.085); - Note on Efficiency of Passing by Pointer or Reference: When a function is called, copies of variables passed by value in the argument list are created in a special section of memory called the stack. For large objects such as arrays or user-defined objects this can be costly in terms of execution time and memory used. This operation is avoided when arguments are passed by reference. When passed by pointer there also can be savings. Although a copy of the pointer is made on the stack, this can be less costly that copying large objects and possible needed to invoke constructors and destructors on those objects. L15 - Function Overloading: - Function overloading provides a way to have multiple functions with the same name. The same function name can be used for multiple functions provided each differs in the number or type of its parameters. For each call of the function, the compiler compares the number and type of the arguments in the call against the parameter lists of each version of the function. The compiler selects the appropriate version. This process is call function resolution. Ex: float FindMax(float a, float b); float FindMax(float a, float b, float c); int FindMax(int a, int b); - Functions differing only in their return type cannot be overloaded. Since the returned value may be implicitly converted, the compiler cannot resolve which version the programmer intended to use. Cases where overloading should not be used: - If the functions to be overloaded perform distinct operations, then overloading will mask this uniqueness and make their proper use harder. - A single function with default arguments can replace the overloaded functions. Use: float FindMax(float a, float b, float c = 0); rather than: float FindMax(float a, float b); float FindMax(float a, float b, float c); - Function templates offer a cleaner design. They offer a way to parameterize arguments that differ only by type. The net result is that a single function template can sometimes replace several overloaded versions of a function. L16 - Function Templates: - Templates, which can be used with classes, as well as with functions, are significant because they are used through out the C++ standard library. The most important reason to study templates is to better utilize the C++ libraries. Being able to write your own templates is secondary to this. - Function templates provide a way to parameterize the arguments or return types of a function. In the definition of the function, rather than specifying an explicit type of some of the arguments or the return value, a placeholder is used. Function templates provide a way to write a single function definition where the data type is a parameter. - Prototype (Declaration): template <class Type> void ConvertFToC(Type f, Type &c); A function template begins with the keyword template followed by a bracketed list of parameters. This is required for both declarations and definitions. The rest of the declaration or definition is exactly as it would normally be written, except that in place of an explicit data type, such as int, a token, in this case "Type", is used instead. Any tokens can be used in the parameter list. - If several different data types are parameterized, unique tokens must be chosen. Ex: template <class type1, class type2> type1 SomeFunction(type1 a, type2 b, type2 c); This function could be called as follows: int result; int x = 1; float y = 2, z = 3; result = SomeFunction(x,y,z); - Generally, the compiler deduces the data type of each parameter from the function calls and instantiates (creates) an appropriate version of the function for those data types. This process is called template instantiation. Rarely, the compiler is unable to deduce the types of the arguments from the call. In this case, it is possible to explicitly specify template arguments: result = SomeFunction<int,float>(x,y,z); - The keyword "typename" can be used in place of the keyword "class" in templates. They may be used interchangeably. Ex: template <typename type1, typename type2> type1 SomeFunction(type1 a, type2 b, type2 c); L17 - Classes, Members and Methods - A class is a programmer defined data type that encapsulates data and abilities. An object is a particular instance of a class. We design an "Image" class that will be used to store and manipulate an image. The keywords private and public are called access specifiers . Private indicates that a member or method may be accessed only be class methods and not by other parts of the program or from other classes. Methods as well as members may be private to a class. Private methods are usually helper functions for the other methods of the class. They may be called from within the other methods of the class but not from outside of the class. If no access specifiers are used, all members and methods have private access by default. C++ provides a third access specifier, protected. Protected is used to allow access to the internal data of a base class by derived classes. - Defining Methods: There are two ways to define methods. They may be defined within the class definition, which is most appropriate for small methods. Larger methods may be defined outside of the class. Methods defined within a class definition are automatically inlined(!!!). The compiler expands inlined functions or methods at their point of invocation. This means, that the compiler expands the code of inlined functions within object files at the point the functions are called. For a non-inlined function or method, execution jumps to the function or method as a program runs. Function variables are popped onto the stack, the function runs, and then function variables are popped off the stack (discarded). The downside is that the local expansion of the function or method consumes extra memory. The keyword inline may be used with methods defined outside the class definition..de testat. - Methods have full access to all data members and can call any of the methods of the class. The access specifiers limit access from the outside only. - Overloaded Methods and Default Arguments: The methods of a class can be overloaded. Each version of the function, or method, must differ in either the number and/or type of its arguments. As with ordinary functions it is also possible to specify default arguments for methods in a class. Any defaults must be specified starting at the right most parameters. That is, a default for a particular parameter can be specified only if all parameters to the right of it in the parameter list also have defaults. Ex: void someFunction(int arg1, int arg2, int arg3 = 52, int arg4 = 0);//Valid void someFunction(int arg1, int arg2, int arg3 = 52, int arg4);//Not Valid - The class definitions are typically within include files. The method definitions will typically be in separate source files. Users of a class should only need to examine include files to use it. - Classes As Members: The members of a class may be any built-in or user-defined data types, including other classes. A class may be contained by value within another class only if it is already defined. That is, the class definition of the contained class must appear in the source file prior to the definition of the containing class. Ex: class Engine {private: int numValues; ... }; class Car { public: ... //Accessor Methods private: int numDoors; Engine _myEngine; }; - If a class is declared but not defined in a file, a pointer or reference to it can be made within another class, but it cannot be included by value. The reason for this is that the compiler does not know the size of the contained class. Pointers and references are of fixed size; large enough to contain an address, so the exact size of the contained class is not required. Ex: class Engine; class Car {private: int x; Engine *myEngine; }; Being able to declare references and pointers to classes that are declared, but not defined is particularly useful. Several important data structures can be implemented using this ability. In particular, it allows an object to access other objects of the same class. Ex: class Node {...} class Link { private: Node _node; Link *next; Link *prev; }; L18 - Constructors and Destructors - A constructor is called whenever an object is defined or dynamically allocated using the "new" operator. The purpose of a constructor is to initialize data members and sometimes to obtain resources such as memory, or a mutex or lock on a shared resource such as a hardware device. - An object's destructor is called whenever an object goes out of scope or when the "delete" operator is called on a pointer to the object. The purpose of the destructor is clean up. It is used to free memory and to release any locks or mutexes on system resources. - Basic rules: A constructor is a method that has the same name as its class. A destructor is a method that has as its name the class name prefixed by a tilde, ~. Neither constructors nor destructors return values. They have no return type specified. Constructors can have arguments. Constructors can be overloaded. If any constructor is written for the class, the compiler will not generate a default constructor. The default constructor is a constructor with no arguments, or a constructor that provides defaults for all arguments. The container classes such as vector require default constructors to be available for the classes they hold. Dynamically allocated class arrays also require a default constructor. If any constructors are defined, you should always define a default constructor as well. Destructors have no arguments and thus cannot be overloaded. - A constructor with default parameters: Employee::Employee(int id = 0, double sal = 0.0){ _id = id; _sal = sal; } void main() {Employee john(2,20);} - The member initialization list provides an alternate syntax for initializing members of a class within a constructor. It is a comma-separated list of members along with their initial values, separated by a colon from the end of the parameter list and before the opening bracket of the constructor body. All data members could be specified in the initialization list. Ex: Employee::Employee(string name, int id = 3, double sal = 30): _name(name), _id(id), _sal(sal) {} - A common question is whether data members should be initialized within a member initialization list or within the body of the constructor. For members of intrinsic data types such as id and sal, it is a matter of preference. For members that are themselves classes, they should always be initialized in the member initialization list. The only way to initialize a member that is a class, that is, the only way to call a particular form of the member class's constructor with some arguments is via the initialization list. This way you control how the class member is initialized. Within the body of the constructor you invoke the default constructor followed by assignment. There are two other cases where members must be initialized within a member initialization list. Members that are either references or "const" must also be initialized within an initialization list. L19 - Dynamic Memory Allocation - In C++, space in memory for variables may be either statically or dynamically allocated. Statically allocated objects are of fixed, known size and the compiler arranges the required space as it turns source code into a binary or executable program. Statically allocated objects that are of local scope are put into a memory space known as the stack. Statically allocated objects of global scope live in the global address space. Suppose we don't know the size of an object until program execution. Examples of this are a buffer to hold a block of text of variable size, or an array with an undetermined number of elements. We could try to size the buffer or array to be large enough to hold the worst case, that is, to be big enough to hold anything we should encounter. The solution to this problem is dynamic memory allocation. - During program execution dynamically allocated memory comes form a pool of memory known as the heap or free store. It is allocated using the C++ operator "new" and freed using the operator "delete". Ex: int *IDpt = new int; . The "new" operator returns the address to the start of the allocated block of memory. This address must be stored in a pointer. New allocates a block of space of the appropriate size in the heap for the object. For instance, "new int" reserves four bytes. Notice that the reserved block of memory is anonymous; it has no identifier (name). Dynamically allocated memory is accessed indirectly via a pointer. It is possible for new to fail. This will be the case if no memory is available. In this case new throw a "bad_alloc" exception. - Objects dynamically allocated using the above syntax are uninitialized. They contain whatever random bits happen to be at their memory location. Before use, a value must be assigned. Ex: int *IDpt = new int; *IDpt = 5; . Alternatively, C++ provides a syntax which initializes the allocated object via the "new" operator. Ex: int *IDpt = new int(5); char *letter = new char('J'); . - Dynamically allocated objects introduce a new twist; they must be explicitly deleted when no longer needed by a program. This is done using the "delete" operator. Delete releases the memory used by the object. Ex: delete IDpt; - Dynamically Allocating Arrays: A dynamically allocated array is best initialized using a loop, as follows: int *buff = new int[1024]; for (i = 0; i < 1024; i++) { buff[i] = 52; //Assigns 52 to each element } ... delete[] buff; The square brackets after the delete tell the compiler to delete a dynamic array rather than a single object. - Dangling Pointers: Ex: int *myPointer = new int(10); delete myPointer; *myPointer = 5; . We've released the memory of the object whose address myPointer holds and then continued to use it. The problem is that although the program may run, this section of memory may be used by another dynamic object allocated after the delete. The values in that object will be corrupted by the continued use of myPointer. This is a very subtle programming bug and is very difficult to isolate. To avoid this bug, always set a pointer to 0, after the delete is called. Ex: myPointer = 0; L20 - Copy Constructors - A copy constructor is a special constructor that takes as its argument a reference to an object of the same class and creates a new object that is a copy. By default, the compiler provides a copy constructor that performs a member-by-member copy from the original object to the one being created. This is called a member wise or shallow copy. Although it may seem to be the desired behavior, in many cases a shallow copy is not satisfactory and could cause errors. Ex: Class_Name Object2Name(&Object1Name); - When a new object is created using our copy constructor, we need to be sure to allocate a new block of memory in the heap. By convention, the object passed into copy constructors is called "rhs", for right hand side. See ex020 L21 - Arrays of Class Objects - The following syntax is used to dynamically allocate a class object: class pt = new class(arg1, arg2, ....); delete pt; Ex: Cat *cat2pt = new Cat("felix","black"); - Methods of automatic created objects are accessed using the "." operator. For example, cat1.getName(). Methods of dynamically allocated objects are accessed using the "->" operator. For example, cat2pt->getName(). A default constructor can have either no arguments or provide defaults for all arguments. If no arguments are specified when dynamically allocating an object, the default constructor is called. It is the programmers responsibility to delete dynamically allocated objects when they are no longer needed. Otherwise, a memory leak is created. - Arrays of Class Objects: An array of class objects is defined in the same manner as build-in types. Cat myCats[27]; The default constructor is used because no arguments are specified. To specify multiple arguments for each Cat object, an alternate syntax allows a constructor to be specified within the array initialization list. Ex: Cat myCats[4] = { Cat("Chris","Gray"), Cat("Charles","White"), Cat("Cindy","BlueGray"), Cat("Cathy","Brown") }; The syntax of array initialization lists is cumbersome. In practice, it is easier to define an array of class objects without an initialization list (using the default constructor) and then to use accessor methods to set the values of members. - A array of class objects is dynamically allocated exactly like an array of objects of built-in type. Cat *myCats = new Cat[27]; Within the brackets is the number of objects to be allocated. The syntax does not allow for specification of initial values. The default constructor will be used. But, this is not much of a limitation. The initial values may still be set using the accessor methods of the class. Ex: Cat *arr3 = new Cat[2]; Cat *tmpP; tmpP = arr3; for(int i = 0; i < 2; i++) {tmpP->setName("dinamicArr"); tmpP->setColor(" 2 elemente"); tmpP++;} for(int i = 0; i < 2; i++){cout<< arr3->getName() << arr3->getColor(); cin.get();} To delete an array of dynamically allocated class objects: delete [] myCats; L22 - Mutable Members - Const provides a way to declare that an object is not modifiable. It can also be used with class methods to indicate to they will not modify their objects. The use of "const" can reduce bugs within your code by allowing the compiler to catch unintended modification of objects that should remain constant. The keyword mutable provides a way to allow modifications of a particular data members of a constant objects. - A const variable must be initialized to provide a value. Ex: const int limit = 25; - C++ also allows the declaration of const class objects. Ex: const Employee john; This declares the object john of class Employee to be constant. But there are problems with this code. First, since the object is const, we need a way to initialize it. Its members cannot be assigned either directly or indirectly via methods . The compiler's default constructor is insufficient, so a default constructor that initializes all members is required. Second, C++ allows methods to be declared as const. By declaring a method "const" we are in effect stating that the method cannot change its object. Ex: string getName() const {return _name;} . Only methods declared const can be called be a const object. This has real benefit. The compiler can check that methods declared const do not modify the object. Attempts to modify an object within a const method will be flagged by the compiler. Objects that are not const can invoke both const and non-const methods. Which methods should be declared as const? Certainly any method that is intended to simple return the value of a data member should be. - The keyword mutable is used to allow a particular data member of const object to be modified. This is particularly useful if most of the members should be constant but a few need to be updateable. Ex: mutable double _salary; The data member _salary has been declared as mutable. This means that this member may be modified even if it is part of a const object. L23 - Static Members and Methods - Static members provide a way to create objects (variables) that are shared by an entire class, rather than being part of a particular instance of the class, an object. Static methods provide a way to access and update static members. - Static Members: Suppose it's necessary to keep track of some data that's related to a class as a whole, rather than to a particular object or instance of that class. A simple example is a count of the total number of objects instantiated. We could create a global variable to hold this data. But the global variable is not really related to the class. Ideally, we want all data associated with the class to be encapsulated within that class. - The static keyword provides a way to declare that an object is to be shared by all instances of a class. Ex: class Employee { public: Employee(...) : _name(name), _id(id), _x(x) {_countOb++;} static int _countOb;}; int Employee::_countOb = 0; Employee unu; Employee doi; cout << Employee::_countOb; - Although the static member is declared within the class, it is defined outside of the class. A definition allocates space for a variable; a declaration simply gives the compiler information about its type. This makes sense because a class definition does not allocate space for the class. Space is allocated when an object is instantiated (defined). Since all objects of a class share a static data member, it does not belong within a particular object. It must be defined outside of any object. - There are two ways to access a publicly declared static member. It can be accessed via the class name (Employee, in our example) or via any object (_objName.objMethod()) of the class. When accessing a static member via the class name, the scope resolution operator is used: className::staticVar; - When accessing a static member via a pointer to a particular object, the "arrow" operator is used. Ex: Employee doi; Employee *pdoi = &doi; cout << pdoi -> _countOb; - Static Methods: In the previous section, we saw a static member declared with public access. It was accessible to the program at large, outside of any object of the class. This works, but it violates a goal of good object oriented design, data hiding. The data of a class should be accessed through the methods of a class, not directly. Static methods provide a way to access and update static members. Ex: class Employee {public: Employee(...): ... {_countOb++;} ~Employee(){_countOb--;} static int getcount() {return _countOb;} private: static int _countOb; };void main(){Employee unu; cout << Employee::getcount(); Employee doi; Employee *pdoi = &doi; cout << pdoi->getcount() - Static methods can only access static members, not any other class members. This makes sense. It would not be possible to determine which non-static members, that is, which object's members to access when the static method was called on a class. Non-static methods can access both static and non-static members. This is also reasonable. Since these methods are called on a particular object, its members as well as static members that belong to the class can be uniquely distinguished. L24 - This Pointer - In addition to the explicit parameters in their argument lists, every class member function (method) receives an additional hidden parameter, the "this" pointer. The "this" pointer addresses the object on which the method was called. There are several cases when the "this" pointer is absolutely required to implement desired functionality within class methods. - Each object maintains its own set of data members, but all objects of a class share a single set of methods. A natural question is then if only a single copy of each method exists, and its used by multiple objects, how are the proper data members accessed and updated. The compiler uses the "this" pointer to internally reference the data members of a particular object. - During compilation, the compiler inserts code for the "this" pointer into the function. The function: void setSalary(double sal){ salary = sal; } actually runs with the following pseudo-code: void setSalary(Employee *this, float sal) { this->salary = sal; } The correct object is identified via the "this" pointer. - Concatenating Calls: Explicit use of the "this" pointer allows the concatenation of calls on an object. Suppose we have a class used to represent a point in 2D space. We will code setX, setY and doubleMe methods. To handle a series of operations on a single point object we concatenate the calls: Point lower; lower.setX(20).setY(30).doubleMe(); The member access operators, dot, ".", and arrow, "->", are left associative. That means that the above expression is evaluated from left to right. That is, "setY(30)" is called on the result of lower.setX(20). If lower.setX(20) returned an object of the class point rather than void, we would have what we need. This can be accomplished with the "this" pointer. We code each method to return a reference to an object of the class type. We return a reference rather than a copy for efficiency. - The Point class that supports concatenation of calls: class Point { public: Point& setX(int x) { _x = x; return *this; } Point& setY(int y) { _y = y; return *this; } Point& doubleMe() { _x *= 2; _y *= 2; return *this; } private: int _x; int _y; }; - Resolving Ambiguities: The "this" pointer is also useful if it is desired to use the same identifier for both a local variable within a method and for a class member. Ex: Point& setX(int x) { this->x = x; return *this; } L25-26 - Operator Overloading - Operator overloading provides a way to define and use operators such as +, -, *, /, and [] for user defined types such as classes and enumerations. By defining operators, the use of a class can be made as simple and intuitive as the use of intrinsic data types. C++ allows any of its built in operators to be overloaded in a class. - The syntax for overloaded operators, where op is the operator being overloaded: return_type operator op (parameter list); Ex: 27 - Overloaded operators can be declared with within a class or outside in global or namespace scope. In C++ a function declared as a friend to a class can access and manipulate the non-public members of that class. - General Rules for Operator Overloading: Only existing operator symbols may be overloaded. New symbols, such as ** for exponentiation, cannot be defined. The operators ::, .*, . and ?: cannot be overloaded. Operators =, [], () and -> can only be defined as members of a class and not as global functions. At least one operand for any overload must be a class or enumeration type. It is not possible to overload operators involving only built-in data types. For example, an attempt to overload addition, +, for the int data type would result in a compiler error. The arity or number of operands for an operator may not be changed. For example, addition, +, may not be defined to take other than two arguments regardless of data type. The precedence of operators is not changed be overloading. - Overloaded Operators in Global and Namespace Scope: In addition to being defined in class scope (within a class), overloaded operators may be defined in global or namespace scope. Global scope means that the operator is defined outside of any function (including main) or class. Namespace scope means that the operator is defined outside of any class but within a namespace, possible within the main program. - The line: c = a + b; is expanded by the compiler as c = a.operator+(b); That is, the overloaded operator for + is called on the Class object "a" with an argument equal to "b" - A method declared as a friend to a class can access the non-public members and methods of that class, as well as the public members and methods of that class. This can simplify coding, but at a cost. Declaring a method as a friend removes the isolation of data. Generally, avoid friends. Classes can also be declared friends of each other. The argument for declaring methods as friends of classes is that sometimes a method is intimately related to a class but cannot be declared as a member of that class. Whether to declare a method as a friend or to use accessor methods is a judgment call. - A method is defined to be a friend of a class by declaring it with the keyword "friend" within the definition of the class. Ex: friend Add operator+ (int i, Add operand); L27 - Relationships Between Classes - The three main types of relationships between classes are generalization (inheritance), aggregation, and association. Generalization: This implies an "is a" relationship. One class is derived from another, the base class. Generalization is implemented as inheritance in C++. The derived class has more specialization. It may either override the methods of the base, or add new methods. For example a paperback class derived from a book class. Aggregation: This implies a "has a" relationship. One class is constructed from other classes, that is, it contains objects of any component classes. For example, a car class would contain objects such as tires, doors, engine, and seats. Association: Two or more classes interact in some manner. They may extract information from each other, or update each other in some way. As an example, a car class may need to interact with a road class. L28 - Scope and Lifespan - A variable's scope is the region of code in which the compiler can uniquely resolve its identifier. Within a particular scope, an identifier must be unique. In C++, objects can have local, global (namespace) or class scope. A variable with local scope is visible only within the function or code block in which it is defined. Variables with global scope are visible and can be used through out a program. Variables have global scope if they are defined in the part of the code that is outside of any class or function definition. A class definition also delimits a block of code and a scope. - Local scope: Functions or class methods define a local scope. Code blocks delimited by brackets, {}, and certain statements also define local scopes. Variables within a particular local scope can only be accessed by code within that particular code block. Outside of the block, they do not exist. The compiler must resolve each identifier within a scope to a particular memory location. - The lifetime of local objects is typically the duration of execution of the function or code block in which they are defined. When a function is entered, local variables are created (pushed) on the stack. When the function exists, the variables are popped from the stack and they cease to exist. During the next invocation of the function, new copies of the local variables are created and used. Objects that have a lifespan as just described are called automatic objects. If variables are created and destroyed in each invocation of a function, how can information be propagated from call to call? C++ allows static variables to be created. Unlike automatic local objects, static objects have a lifespan of the duration of the program. Example of the use of static variables: int roo() { int val = 0; static int callCount; callCount++; cout << callCount << endl; return val; }. The static variable callCount is increased by one each time the function is invoked and will maintain its value between function calls. - In addition to automatic and static objects, C++ has a third type of local object called a register object. Register objects have the same lifespan as automatic objects. They live for only the duration of a function call. But rather than being created on the stack like automatic objects, the keyword register instructs the compiler to try to store these variables in hardware registers that are part of the CPU. These registers have faster access times than the stack, which resides in memory. Long ago, frequently used variables would be stored in registers to speed program execution. With faster CPUs and memory, this technique has much less relevance. - Global Scope: Objects and functions defined outside any function, including main, or class are in global scope. The lifetime of a global object is the duration of the program. C++ also allows the global space to be divided into separate programmer defined namespaces. - Each global object or function can have only one definition, but may have multiple declarations. For an object, the definition allocates storage space. For a function, the definition includes the body of the function. A declaration simply informs the compiler of the existence of a particular object or function. Declarations are required for compiling. During linking, a definition for each object or function is found. This process is known as resolution. - Multiple Files: Larger programs typically consist of multiple files. A global object may be used in multiple files, but it can be defined in only one file. Each file is compiled independently by the compiler into object files. Then, all the objects are linked to create a single binary or executable. To resolve every identifier during the compilation phase, the compile must either find the definition of an object or function within the file it is processing, or must be told that the definition exists in another file. The later is done using the keyword extern. For functions, the extern keyword is implicit. Ex: file1.cpp: extern int x; int main() { cout << x } file2.cpp: int x = 12; - Header Files: Typically, declarations for functions and for global objects are placed in header files. This allows for easier maintenance. Ex: ex28.h: int f(); ex28.cpp: #include "ex28.h" void main(){ cout << f(); } int f(){ return 7; } - Common Bug: Global variables are initialized to 0 if no explicit initialization is provided. Local variables are not initialized if no explicit initialization is used. L29 - Inheritance - In C++, inheritance defines an "is a" relationship. The derived class is a type of its base class. For instance, a "cat" class could be derived from an "animal" class. A cat is a type of animal. A derived class inherits both the data members and methods of its base class. It may also define additional members and methods that support specialized functionality. - The syntax for the class declarations of the derived classes: class DerivedClass : public BaseClass {...}; . The keyword after the colon indicates the type of inheritance. Public inheritance is type inheritance(???). The subclass is a type of the base class. It inherits the public interface of the base class and can extend this interface. - The data members of the base class are declared as protected. Protected indicates that publicly derived subtypes of the base class will be able to directly access these variables. If they were declared to be private, only the base class could directly access them; subclasses could not. - To summarize, the parts of a class that are declared public can be accessed by the class itself, publicly derived subclasses, and other classes, functions and code (the whole world). The parts of a class declared as protected can be accessed by the class itself and by publicly derived subclasses. The parts of a class declared as private can be accessed only by the class itself. - C++ also allows two other types of inheritance, private and protected, which are less often used. - Constructors, including copy constructors, and destructors are not inherited. Each subtype has its own constructor and destructor. The reason for this is that each object of a subtype consists of multiple parts, a base class part and a subclass part. The base class constructor forms the base class part. The subclass constructor forms the subclass part. Destructors clean up their respective parts. The base class part of an object is always constructed first and destroyed last. The subclass part of an object is constructed last and destroyed first. - Overriding Methods: A derived class can use the methods of its base class(es), or it can override them. The method in the derived class must have the same signature and return type as the base class method to override. The signature is number and type of arguments and the constantness (const, non- const) of the method. When an object of the base class is used, the base class method is called. When an object of the subclass is used, its version of the method is used if the method is overridden. If the base class had overloaded a particular method, overriding a single one of the overloads will hide the rest of them. L30 - Polymorphism - Polymorphism in C++ is exhibited by the ability of a pointer or reference to a base class type to behave in different ways when it is used to manipulate objects of different subtypes of that base class. Said another way, in our code a base class pointer or reference is used to access an object of some subclass, possibly unknown at compile time and known only during execution time when the program is running. This base class pointer can access the correct subclass method. That is, it can access the method of the object of the subclass it is pointing to or referencing, rather than the corresponding method within the base class. - Suppose we need to construct a function that will work with all subclasses of a base class. With polymorphism, the function could be coded to expect a pointer or reference to the base class type and behave correctly and distinctly when different subclasses are passed in. In C++, the first requirement for polymorphism is that the objects must be accessed via pointer or reference. - A second requirement for polymorphism to work is that the methods whose polymorphic behavior is desired must be declared as virtual. Overridden methods not declared as virtual will behave correctly only for statically bound objects; those known at compile time. For base class pointers or references bound to objects during execution, polymorphism will not be seen if a method is not declared virtual. That means that the base class method will be used rather than the subclass method even if the subclass overrides the method. This is again for a base class pointer or reference used on an object of a subclass. - It is also possible to dynamically cast a base class pointer into a subclass pointer to get the desired behavior, but this is less useful since it requires knowledge of the subclass of the object that the base class pointer refers. - Virtual Functions: Any base class method intended to be overridden in any subclass should be declared as virtual. Once a method is declared as virtual, it is automatically virtual in all subclasses. The keyword virtual is optional, but recommended in subclasses. Polymorphism in C++ works only with pointers and references and only with methods declared as virtual. If a class declares any virtual methods, the destructor of the class should be declared as virtual as well. This ensures that regardless of the actual subclass of an object accessed via a base class pointer or reference, the correct destructor will clean it up. Ex: class Parinte {public: virtual ~Parinte(){} virtual void parole(){cout << "mai are rost???" << endl;}; class Fiu : public Parinte { public: virtual void parole(){cout << "overriding the parole method" << endl;}}; void poliF (Parinte tmp, Parinte* ptmp, Parinte& rtmp) { tmp.parole(); ptmp->parole(); rtmp.parole();} void main () { Fiu *doi = new Fiu; poliF(*doi, doi, *doi); delete doi; } - The method "parole" is declared as virtual. This will allow polymorphic behavior with base class pointers and references on subclasses of this object. The compiler creates a virtual function table that is used in the polymorphism mechanism. The more functions that are declared as virtual, the greater the overhead to create and maintain these tables. - To summarize: Polymorphism is seen with base class pointers (and references) to subclass objects. Methods must be declared as virtual for polymorphism to work. - C++ also supports polymorphism through RTTI (RunTime Type Identification) mechanisms. Through the use of the dynamic cast operator on a pointer or reference, it is possible to determine the subclass type referenced by a base class pointer or reference and take different paths of execution based on this. L31 - Multiple Inheritance - In C++, a class may have multiple base classes. That is, it may inherit the members and methods of multiple base classes. - C++ has a built in mechanism for Runtime Type Identification, RTTI. This means that as a program is running it is possible to determine the exact type of object a pointer or reference refers to. We can tell if a base class pointer holds the address of a base class object or a subclass object. - RTTI is achieved in C++ via the dynamic_cast operator, the typeid operator and the type_info class. - The dynamic_cast works at runtime, as your program executes. Ex: void analizaCredinta(Fiul* tmp){Nepotul* ntmp; ntmp=dynamic_cast<Nepotul*>(tmp); if(ntmp) ntmp->unicul(); else cout << "Fiul nu este unic";}(see ex30) Dynamic casting used in this manner is referred to as "casting down". We have casted a base class pointer down its hierarchy into a subclass pointer. But, explicitly checking an objects type is usually indicative of bad design. Generally, you should avoid explicitly testing the type of an object in your code. As additional classes are formed, every point in the code that makes explicit checks on object type may need modification. - C++ also allows static casts, implemented via the static_cast operator. Static casting occurs at compile time. No checking is done at runtime. The compiler trusts you, the programmer, to check that the casted object is of the necessary type. If you statically cast a pointer into some type that it is not, a runtime error may occur. Static casting is fine for many uses. It is inappropriate only when using the type of an object cannot be known until execution. - Multiple Inheritance: The sintax: class SubClass : public BaseClass1, public BaseClass2 {...} . The base classes need to be defined before the subclass. Actually, a forward declaration of base classes will also work. L32 - Virtual Inheritance - This lesson covers some ambiguities that can arise with multiple inheritance, and their solutions, including virtual inheritance. - The classes B and C inferit from the class A; the class D has multiple inheritance B and C. If we access members and methods that exist within A but are not overridden in D, there is an ambiguity. Are we trying to access the method from B's A class or from C's A class? The compiler will flag an error. To resolve this problem one has to specificaly access the right method. Ex: D-object.B-class::B-method; or D-object.C-class::C-method; - Another technique used to resolve ambiguities is called chaining up. A method that is ambiguous in a subclass is overridden in that subclass to call a particular base class's method. Ex: class Parinte {public: virtual void setnota (int x) { this->nota = x;} virtual int getnota() const {return this->nota;} protected: int nota; }; class Mama : public Parinte {...}; class Tata : public Parinte {...}; class Fiul : public Mama, public Tata { public: virtual void setnota (int x) { Tata::setnota(x);} virtual int getnota() const { return Tata::getnota();}}; void main(){ Fiul dan; dan.setnota(10); cout << "chaining up: dan accede la nota parintelui prin Tata: " << dan.getnota();} These methods call the methods they override. That is, they call the methods with the same signature, name and arguments, in a base class. This is chaining up. It resolves the ambiguity issue. - Virtual inheritance in C++ provides a way to specify that only a single copy of a common base class should be constructed. This way, our "Fiul" class will only have a single "Parinte" part. The ambiguity caused by multiple inheritance in the "Fiul" class is removed. The only change to our classes is the use of the keyword "virtual" in each intermediate base class of "Fiul". Ex: class Mama : virtual public Parinte {...}; class Tata : virtual public Parinte {...}; class Fiul : public Mama, public Tata {}; Fiul dan;
dan.setnota(10); cout << "Virtual Inheritance: dan accede la nota parintelui: " << dan.getnota();
- Notice there is only a single "Parinte" base class. This introduces a new issue. Normally, in C++ a class can explicitly initialize only its immediate base class. Let's suppose that the "Parinte" class now has a constructor that takes a string argument. Without virtual inheritance, the constructors for "Mama" or "Tata" would feed this argument to the constructor for "Parinte". - When using virtual inheritance, it is the responsibility of the most derived base class ("Fiul") to initialize virtual base classes. Contrast this with the usual case when a class initializes only its immediate base class. The most derived class, "Fiul", is initializing the base class "Parinte". See ex033 L33 - Abstract Data Types - An abstract data type is a class that is meant to serve only as an interface for derived classes; it cannot be instantiated. They serve as base classes to other classes that will be instantiated into objects. In C++, a pure virtual function is not only intended to be overridden in base classes, it must be. Pure virtual functions provide a way to force derived classes to implement a particular interface. - Conceptually, the "Parinte" class should be an abstract data type. A parinte is an abstraction, or concept. A parinte can provide implementations for some of these abilities through its non-virtual and non-pure virtual methods. It can also provide only an interface for other methods through the use of pure virtual functions. The interface of a method refers to the number and type of arguments and to the return type; it is what the outside world, other classes need to know to use the function. It describes what they must provide and what to expect back. - A pure virtual function in declared in C++ by initializing a virtual function to zero. Ex: virtual return_type function(arg1, arg2, ...) = 0; Any class containing any pure virtual functions is an abstract data type, ADT; it cannot be instantiated. All pure virtual functions must be overridden in any base class that will be instantiated. If every pure virtual function is not overridden, a subclass of an ADT will also be abstract. In this case, the subclass cannot be instantiated. - It is possible to provide an implementation for a pure virtual function. This implementation can provide support for methods in derived classes. A common use is for the subclass function to chain up the pure virtual function. The subclass function calls the pure virtual function to perform part of its work. By declaring the function as pure virtual in the base class, implementers are forced to override the function in subclasses and provide specialized behavior. Ex: class Parinte { public: virtual void absParinte() = 0;}; void Parinte::absParinte(){cout << " Parintele este abstract" << endl;}
class Fiul : public Parinte {public: virtual void absParinte() { cout << "Fiul este abstract" << endl; Parinte::absParinte();}}; void main(){ Fiul dan("bau"); dan.absParinte();}
L34 - Exceptions - An error typically refers to a problem that exists in a program when it is written and compiled. It could be a logic error that will result in incorrect results. In this case, the code is correct but the algorithm has an error. It could be a logic error that will result in incorrect results. In this case, the code is correct but the algorithm has an error. - Exceptions are errors or anomalies that occur during the execution of a program. They can be the result of unavailability of system resources, such as memory, file space, channels or from data dependent conditions such as a divide by zero, or numerical overflow. Exceptions may be raised and handled in different sections of code. Any object, including class objects may be passes back to the handler of an exception. These objects can contain data and methods to assist in handling the exception. - The section of code that causes or detects a run-time abnormality (divide by zero, out of memory) will "throw" an exception. The exception is the object that is thrown. It may be a simple object such as an int, or a class object, including programmer defined class objects. The exception is "caught" by another section of code. - The exception object, itself, is used to convey information from the section of code that throws the object to the section of code that catches the object. This separation of exception creation and exception handling is very significant. Higher level sections of code can better handle exceptions in many cases. Suppose an exception occurs in a library routine. That routine cannot know how to respond in a way that is appropriate for your program. In some cases the appropriate response might be to terminate the program. In other cases, the appropriate response might be a warning message. - Sections of code that can throw exceptions are surrounded in try blocks. Exceptions thrown from within try blocks are caught by a catch clause. Ex: void main(){ int x = 5; int y = 0; int result; int exceptionCode = 25; try {if (y == 0) {throw exceptionCode;} result = x/y; } catch (int e) { if (e == 25) {cout << "Nu poti sa imparti la zero" << endl; } else {cout << "Exception of unknown type" << endl;} } cout << " Goodbye" << endl; } - As execution enters the try block, the condition "y == 0" is evaluated and is true. The code in the "if" block is executed. An exception is thrown and execution jumps to the catch block that follows the try block. Since the type of the exception is an "int" and the catch clause matches "int", the clause is executed and an error message is printed. Note that the code immediately following the "throw", "result = x/y" is skipped. Also, note that after the exception is caught, execution continues with the code that follows that catch block. It does not return to the section of code following the throw. The variable "result" is never calculated. - The exception thrown is of type int and the catch block contains logic to decode this value and print an appropriate error message. This is bad design because it requires the catch block and its programmer to implement possibly complex logic and to be aware of possible exception codes. Remember an exception can be any type of object. - In C++, an exception is usually a class object. This allows more information to be bundled into the exception and also allows the exception to contain methods to assist the sections of code that handle or process the exception. See Ex: 035; - Stack Class: A stack is a data structure used to store values in a last in, first out (LIFO) order. Data is "pushed" onto the stack and "popped" off. Our stack will have the following limitations. It will be of fixed size. It will throw a stack overflow exception if an attempt is made to push data onto a full stack. It will throw a stack empty exception if an attempt is made to pop data off an empty stack. See Ex: 036; - When you enter any method (or function) all statically allocated local variables are created on the stack. When you exit a method, these variables are popped from the stack. Suppose that main calls functionA and that functionA calls functionB. functionB will throw two exceptions, ExceptionType1 and ExceptionType2. I am assuming that ExceptionType1 and ExceptionType2 are defined elsewhere. Ex: int functionB(int val) { if (val < 0) { throw ExceptionType1(); } else if (val > 100) { throw ExceptionType2(); } } int functionA(int val) { .... myval = functionB(val); return 2*myval; } void main() { int x; try { x = functionA(-9); } catch (ExceptionType1) { cout << "ExceptionType1" << endl; } } - main calls functionA, which calls functionB. Since functionB is passed a value less that zero, an exception of type ExceptionType1 is thrown. Since no exceptions are caught in functionB, execution jumps out of functionB back to functionA. FunctionB's variables are removed from the stack and any destructors for local objects are called. This is unwinding the stack. We are now in functionA. Again, it does not catch the exception, so its variables are removed from the program stack. Any destructors for local objects are called. The stack is further unwound. Now, we have reached main. Here, the exception is caught and handled.
- What would happen if main didn't catch the exception? This is the case with ExceptionType2. Main's variables would be removed from the stack and any destructors would be called. Since no handler is found, the terminate() system function is called and the program aborts. This default is undesirable. Attempts should be made to catch and handle all exceptions. L35 - Class Templates - Class templates provide a way to parameterize the types within a class. This means that rather than explicitly specifying the data type associated with a class member or method, a placeholder is used. When we instantiate an instance of the class, the actual data type is substituted for the parameter, or placeholder. The process of forming a class with specific data types from a class template is called template instantiation. - Typically, when building class templates, it is easier to first write and debug a concrete class. This class is then turned into a template. - Ex: template <class T> class Stack { public: ... private: T index; }; void main() {Stack<int> stack; // Template instantiation ...} Notice the syntax used for the class template. The keyword template is used, followed by a list of parameters each preceded by the keyword class (or equivalently by the keyword typename). The parameter list is enclosed using less than and greater than symbols. Everywhere the keyword int was used to specify a data type in the Stack class, the parameter T is substituted. - Multiple types may be parameterized within a single template. Here is the syntax for a class template with multiple parameters. Ex: template <class Parameter1, class Paramter2, ... > class ClassName { .... }; - With class templates, methods defined outside a class definition are prefixed by a template declaration and by the class name with its parameter list. Ex: template <class T> void Stack<T>::push(T val) {...} . Constructors and destructors can also be defined outside of a class definition. Ex: template <class T> Stack<T>::~Stack() { cout << "Destructor"; } template <class T> Stack<T>::Stack(): index(-1) { cout << "Constructor"; } - In addition to parameterizing data types within a class template, it is also possible to parameterize non-type parameters. These non-type parameters will serve as constants within instantiations of the class. Ex: template <typename token, int factor> class Convertor{...}; Convertor <double, 1.85> ktsConv;