Creating a New Class
It is generally a good practice to define your new classes in separate files. This makes maintaining and reading the code easier. To do this, use the following steps in CodeBlocks: Click File->New->Class... Give your new class a name, uncheck "Has destructor" and check "Header and implementation file shall be in same folder", then click the "Create" button. Note that two new files have been added to your project: The new files act as templates for our new class. - MyClass.h is the header file. - MyClass.cpp is the source file. ---------------------------------------------------------------------------------------------------------- Source & Header
The header file (.h) holds the function declarations (prototypes) and variable declarations. It currently includes a template for our new MyClass class, with one default constructor.
MyClass.h
#ifndef MYCLASS_H #define MYCLASS_H class MyClass { public: MyClass(); protected: private: }; #endif // MYCLASS_H
The implementation of the class and its methods go into the source file (.cpp). Currently it includes just an empty constructor.
MyClass.cpp
#include "MyClass.h"
MyClass::MyClass() { //ctor }
The #ifndef and #define statements in the header file will be discussed in the upcoming lessons. -------------------------------------------------------------------------------------------------------- Scope Resolution Operator
The double colon in the source file (.cpp) is called the scope resolution operator, and it's used for the constructor definition:
#include "MyClass.h" MyClass::MyClass() { //ctor }
The scope resolution operator is used to define a particular class' member functions, which have already been declared. Remember that we defined the constructor prototype in the header file. So, basically, MyClass::MyClass() refers to the MyClass() member function - or, in this case, constructor - of the MyClass class. ----------------------------------------------------------------------------------------------------------- Source & Header
To use our classes in our main, we need to include the header file.
For example, to use our newly created MyClass in main: #include <iostream> #include "MyClass.h" using namespace std; int main() { MyClass obj; }
The header declares "what" a class (or whatever is being implemented) will do, while the cpp source file defines "how" it will perform those features. ------------------------------------------------------------------------------------------------------------- Destructors
Remember constructors? They're special member functions that are automatically called when an object is created. Destructors are special functions, as well. They're called when an object is destroyed or deleted. Objects are destroyed when they go out of scope, or whenever the delete expression is applied to a pointer directed at an object of a class. -------------------------------------------------------------------------------------------------------------- Destructors
The name of a destructor will be exactly the same as the class, only prefixed with a tilde (~). A destructor can't return a value or take any parameters.
class MyClass { public: ~MyClass() { // some code } };
Destructors can be very useful for releasing resources before coming out of the program. This can include closing files, releasing memory, and so on. --------------------------------------------------------------------------------------------------------------- Destructors
For example, let's declare a destructor for our MyClass class, in its header file MyClass.h:
class MyClass { public: MyClass(); ~MyClass(); }; ---------------------------------------------------------------------------------------------------------------- Destructors After declaring the destructor in the header file, we can write the implementation in the source file MyClass.cpp:
#include "MyClass.h" #include <iostream> using namespace std; MyClass::MyClass() { cout<<"Constructor"<<endl; } MyClass::~MyClass() { cout<<"Destructor"<<endl; }
Note that we included the <iostream> header, so that we can use cout. -------------------------------------------------------------------------------------------------------------------- Destructors
Since destructors can't take parameters, they also can't be overloaded. Each class will have just one destructor. Defining a destructor is not mandatory; if you don't need one, you don't have to define one. -------------------------------------------------------------------------------------------------------------------- Destructors
Let's return to our main.
#include <iostream> #include "MyClass.h" using namespace std; int main() { MyClass obj; return 0; }
We included the class' header file and then created an object of that type.
This returns the following output: Constructor Destructor
When the program runs, it first creates the object and calls the constructor. The object is deleted and the destructor is called when the program's execution is completed. Remember that we printed "Constructor" from the constructor and "Destructor" from the destructor. --------------------------------------------------------------------------------------------------------------------- #ifndef & #define
We created separate header and source files for our class, which resulted in this header file.
#ifndef MYCLASS_H #define MYCLASS_H class MyClass { public: MyClass(); protected: private: };
#endif // MYCLASS_H
ifndef stands for "if not defined". The first pair of statements tells the program to define the MyClass header file if it has not been defined already. endif ends the condition. This prevents a header file from being included more than once within one file. ------------------------------------------------------------------------------------------------------------------------ Member Functions
Let's create a sample function called myPrint() in our class.
MyClass.h
class MyClass { public: MyClass(); void myPrint(); };
MyClass.cpp
#include "MyClass.h" #include <iostream> using namespace std; MyClass::MyClass() { } void MyClass::myPrint() { cout <<"Hello"<<endl; }
Since myPrint() is a regular member function, it's necessary to specify its return type in both the declaration and the definition. ------------------------------------------------------------------------------------------------------------------------- Dot Operator
Next, we'll create an object of the type MyClass, and call its myPrint() function using the dot (.) operator:
#include "MyClass.h" int main() { MyClass obj; obj.myPrint(); }
// Outputs "Hello" -------------------------------------------------------------------------------------------------------------------------- Pointers
We can also use a pointer to access the object's members. The following pointer points to the obj object:
MyClass obj; MyClass *ptr = &obj;
The type of the pointer is MyClass, as it points to an object of that type. -------------------------------------------------------------------------------------------------------------------------- Fill in the blanks to declare a pointer to ''obj'': Sally obj; Sally *sallyPtr = &obj; ------------------------------------------------------------------------------------------------------------------------- Selection Operator
The arrow member selection operator (->) is used to access an object's members with a pointer.
MyClass obj; MyClass *ptr = &obj; ptr->myPrint();
When working with an object, use the dot member selection operator (.). When working with a pointer to the object, use the arrow member selection operator (->). -------------------------------------------------------------------------------------------------------------------------- Type in the missing arrow member selection operator to call myPrint() function via sallyPtr. Sally obj; Sally *sallyPtr = &obj; sallyPtr -> myPrint(); ---------------------------------------------------------------------------------------------- Constants
A constant is an expression with a fixed value. It cannot be changed while the program is running. Use the const keyword to define a constant variable.
const int x = 42;
All constant variables must be initialized at the time of their creation. -------------------------------------------------------------------------------------------------------- Constant Objects
As with the built-in data types, we can make class objects constant by using the const keyword.
const MyClass obj;
All const variables must be initialized when they're created. In the case of classes, this initialization is done via constructors. If a class is not initialized using a parameterized constructor, a public default constructor must be provided - if no public default constructor is provided, a compiler error will occur. Once a const class object has been initialized via the constructor, you cannot modify the object's member variables. This includes both directly making changes to public member variables and calling member functions that set the value of member variables. When you've used const to declare an object, you can't change its data members during the object's lifetime.
--------------------------------------------------------------------------------------------------------- Constant Objects
Only non-const objects can call non-const functions. A constant object can't call regular functions. Hence, for a constant object to work you need a constant function.
To specify a function as a const member, the const keyword must follow the function prototype, outside of its parameters' closing parenthesis. For const member functions that are defined outside of the class definition, the const keyword must be used on both the function prototype and definition. For example:
MyClass.h
class MyClass { public: void myPrint() const; };
MyClass.cpp
#include "MyClass.h" #include <iostream> using namespace std; void MyClass::myPrint() const { cout <<"Hello"<<endl; }
Now the myPrint() function is a constant member function. As such, it can be called by our constant object:
int main() { const MyClass obj; obj.myPrint(); } // Outputs "Hello" ------------------------------------------------------------------------------------------------------------ Type in the missing keywords to declare a constant printAge() member function for the Student class.
class Student { public: void printAge() const; }; ----------------------------------------------------------------------------------------------- Constant Objects
Attempting to call a regular function from a constant object results in an error. In addition, a compiler error is generated when any const member function attempts to change a member variable or to call a non-const member function. Defining constant objects and functions ensures that corresponding data members cannot be unexpectedly modified. ------------------------------------------------------------------------------------------------ Member Initializers
Recall that constants are variables that cannot be changed, and that all const variables must be initialized at time of creation. C++ provides a handy syntax for initializing members of the class called the member initializer list (also called a constructor initializer). ------------------------------------------------------------------------------------------------- Member Initializers
Consider the following class:
class MyClass { public: MyClass(int a, int b) { regVar = a; constVar = b; } private: int regVar; const int constVar; };
This class has two member variables, regVar and constVar. It also has a constructor that takes two parameters, which are used to initialize the member variables. Running this code returns an error, because one of its member variables is a constant, which cannot be assigned a value after declaration. In cases like this one, a member initialization list can be used to assign values to the member variables.
class MyClass { public: MyClass(int a, int b) : regVar(a), constVar(b) { } private: int regVar; const int constVar; };
Note that in the syntax, the initialization list follows the constructor parameters. The list begins with a colon (:), and then lists each variable to be initialized, along with the value for that variable, with a comma to separate them. Use the syntax variable(value) to assign values. The initialization list eliminates the need to place explicit assignments in the constructor body. Also, the initialization list does not end with a semicolon. ------------------------------------------------------------------------------------------- You have a class ''Student'' with two members, "age" and "num". Fill in the blanks to initialize those members in the constructor initializer list with the corresponding values. Student::Student(int a, int b) :age(a), num (b) { } -------------------------------------------------------------------------------------------- Member Initializers
Let's write the previous example using separate header and source files.
MyClass.h
class MyClass { public: MyClass(int a, int b); private: int regVar; const int constVar; };
MyClass.cpp
MyClass::MyClass(int a, int b) : regVar(a), constVar(b) { cout << regVar << endl; cout << constVar << endl; }
We have added cout statements in the constructor to print the values of the member variables. Our next step is to create an object of our class in main, and use the constructor to assign values.
#include "MyClass.h" int main() { MyClass obj(42, 33); }
/*Outputs 42 33 */
The constructor is used to create the object, assigning two parameters to the member variables via the member initialization list. ------------------------------------------------------------------------------------------------------- Member Initializers
The member initialization list may be used for regular variables, and must be used for constant variables. Even in cases in which member variables are not constant, it makes good sense to use the member initializer syntax. ------------------------------------------------------------------------------------------------------ Composition
In the real world, complex objects are typically built using smaller, simpler objects. For example, a car is assembled using a metal frame, an engine, tires, and a large number of other parts. This process is called composition. In C++, object composition involves using classes as member variables in other classes. This sample program demonstrates composition in action. It contains Person and Birthday classes, and each Person will have a Birthday object as its member.
Birthday:
class Birthday { public: Birthday(int m, int d, int y) : month(m), day(d), year(y) { } private: int month; int day; int year; };
Our Birthday class has three member variables. It also has a constructor that initializes the members using a member initialization list. The class was declared in a single file for the sake of simplicity. Alternatively, you could use header and source files. ------------------------------------------------------------------------------------------------------- Composition
Let's also add a printDate() function to our Birthday class:
class Birthday { public: Birthday(int m, int d, int y) : month(m), day(d), year(y) { } void printDate() { cout<<month<<"/"<<day <<"/"<<year<<endl; } private: int month; int day; int year; }; --------------------------------------------------------------------------------------------------------------- Composition
Next, we can create the Person class, which includes the Birthday class.
#include <string> #include "Birthday.h" class Person { public: Person(string n, Birthday b) : name(n), bd(b) { } private: string name; Birthday bd; };
The Person class has a name and a Birthday member, and a constructor to initialize them. Ensure that the corresponding header files are included. ---------------------------------------------------------------------------------------------------------------- Composition
Now, our Person class has a member of type Birthday:
class Person { public: Person(string n, Birthday b) : name(n), bd(b) { } private: string name; Birthday bd; };
Composition is used for objects that share a has-a relationship, as in "A Person has a Birthday". ------------------------------------------------------------------------------------------------------------------ Composition
Let's add a printInfo() function to our Person class, that prints the data of the object:
class Person { public: Person(string n, Birthday b) : name(n), bd(b) { } void printInfo() { cout << name << endl; bd.printDate(); } private: string name; Birthday bd; };
Notice that we can call the bd member's printDate() function, since it's of type Birthday, which has that function defined. --------------------------------------------------------------------------------------------------------------------- Composition
Now that we've defined our Birthday and Person classes, we can go to our main, create a Birthday object, and then pass it to a Person object.
int main() { Birthday bd(2, 21, 1985); Person p("David", bd); p.printInfo(); }
/*Outputs David 2/21/1985 */
We've created a Birthday object for the date of 2/21/1985. Next, we created a Person object and passed the Birthday object to its constructor. Finally, we used the Person object's printInfo() function to print its data. In general, composition serves to keep each individual class relatively simple, straightforward, and focused on performing one task. It also enables each sub-object to be self-contained, allowing for reusability (we can use the Birthday class within various other classes). --------------------------------------------------------------------------------------------------------------------- Friend Functions
Normally, private members of a class cannot be accessed from outside of that class. However, declaring a non-member function as a friend of a class allows it to access the class' private members. This is accomplished by including a declaration of this external function within the class, and preceding it with the keyword friend. In the example below, someFunc(), which is not a member function of the class, is a friend of MyClass and can access its private members.
class MyClass { public: MyClass() { regVar = 0; }; private: int regVar;
friend void someFunc(MyClass &obj); };
Note that when passing an object to the function, we need to pass it by reference, using the & operator. ------------------------------------------------------------------------------------------------------------------------- Friend Functions
The function someFunc() is defined as a regular function outside the class. It takes an object of type MyClass as its parameter, and is able to access the private data members of that object.
class MyClass { public: MyClass() { regVar = 0; }; private: int regVar; friend void someFunc(MyClass &obj); }; void someFunc(MyClass &obj) { obj.regVar = 42; cout << obj.regVar; }
The someFunc() function changes the private member of the object and prints its value. To make its members accessible, the class has to declare the function as a friend in its definition. You cannot "make" a function a friend to a class without the class "giving away" its friendship to that function. ------------------------------------------------------------------------------------------------------------------------- Friend Functions
Now we can create an object in main and call the someFunc() function: int main() { MyClass obj; someFunc(obj); }
//Outputs 42
someFunc() had the ability to modify the private member of the object and print its value. Typical use cases of friend functions are operations that are conducted between two different classes accessing private members of both. You can declare a function friend across any number of classes. Similar to friend functions, you can define a friend class, which has access to the private members of another class. -------------------------------------------------------------------------------------------------------------------------- This
Every object in C++ has access to its own address through an important pointer called the this pointer. Inside a member function this may be used to refer to the invoking object. Let's create a sample class:
class MyClass { public: MyClass(int a) : var(a) { } private: int var; };
Friend functions do not have a this pointer, because friends are not members of a class. -------------------------------------------------------------------------------------------------------------------------- This
The printInfo() method offers three alternatives for printing the member variable of the class.
class MyClass { public: MyClass(int a) : var(a) { } void printInfo() { cout << var<<endl; cout << this->var<<endl; cout << (*this).var<<endl; } private: int var; };
All three alternatives will produce the same result. this is a pointer to the object, so the arrow selection operator is used to select the member variable. --------------------------------------------------------------------------------------------------------------------------- This
To see the result, we can create an object of our class and call the member function.
#include <iostream> using namespace std; class MyClass { public: MyClass(int a) : var(a) { } void printInfo() { cout << var <<endl; cout << this->var <<endl; cout << (*this).var <<endl; } private: int var; };
int main() { MyClass obj(42); obj.printInfo(); }
/* Outputs 42 42 42 */
All three of the ways to access the member variable work. Note that only member functions have a this pointer. ---------------------------------------------------------------------------------- This
You may be wondering why it's necessary to use the this keyword, when you have the option of directly specifying the variable. The this keyword has an important role in operator overloading, which will be covered in the following lesson. ----------------------------------------------------------------------------------- Operator Overloading
Most of the C++ built-in operators can be redefined or overloaded. Thus, operators can be used with user-defined types as well (for example, allowing you to add two objects together). Operators that can't be overloaded include :: | .* | . | ?: ------------------------------------------------------------------------------------- Operator Overloading
Let's declare a sample class to demonstrate operator overloading:
class MyClass { public: int var; MyClass() {} MyClass(int a) : var(a) { } };
Our class has two constructors and one member variable. We will be overloading the + operator, to enable adding two objects of our class together. --------------------------------------------------------------------------------------- Operator Overloading
Overloaded operators are functions, defined by the keyword operator followed by the symbol for the operator being defined. An overloaded operator is similar to other functions in that it has a return type and a parameter list.
In our example we will be overloading the + operator. It will return an object of our class and take an object of our class as its parameter.
class MyClass { public: int var; MyClass() {} MyClass(int a) : var(a) { } MyClass operator+(MyClass &obj) { } };
Now, we need to define what the function does. ------------------------------------------------------------------------------------------- Operator Overloading
We need our + operator to return a new MyClass object with a member variable equal to the sum of the two objects' member variables.
class MyClass { public: int var; MyClass() {} MyClass(int a) : var(a) { }
MyClass operator+(MyClass &obj) { MyClass res; res.var= this->var+obj.var; return res; } };
Here, we declared a new res object. We then assigned the sum of the member variables of the current object (this) and the parameter object (obj) to the res object's var member variable. The res object is returned as the result. This gives us the ability to create objects in main and use the overloaded + operator to add them together.
int main() { MyClass obj1(12), obj2(55); MyClass res = obj1+obj2;
cout << res.var; }
//Outputs 67
With overloaded operators, you can use any custom logic needed. However, it's not possible to alter the operators' precedence, grouping, or number of operands. ---------------------------------------------------------------------------------------------------- Fill in the blanks to define an overloaded + operator for the class ''Test''. Test Test::operator+ (Test obj) { Test newObj; newObj.mem = mem + obj.mem; return newObj; } ---------------------------------------------------------------------------------------------------- Fill in the blanks to declare a pointer to the ''st'', where ''st'' is of type ''Student'', then call printAge() via the pointer. Student st; Student * stPtr = &st; stPtr -> printAge(); ------------------------------------------------------------------------------------------------------ For a class ''Test'' with two private members named ''mem'' and ''mem2'', fill in the blanks to print out their values in the printValues() function using the ''this'' keyword.
void Test::printValues() { cout << this -> mem; cout << this -> mem2; } ------------------------------------------------------------------------------------------------------- Inheritance
Inheritance is one of the most important concepts of object-oriented programming. Inheritance allows us to define a class based on another class. This facilitates greater ease in creating and maintaining an application. The class whose properties are inherited by another class is called the Base class. The class which inherits the properties is called the Derived class. For example, the Daughter class (derived) can be inherited from the Mother class (base). The derived class inherits all feature from the base class, and can have its own additional features.
The idea of inheritance implements the is a relationship. For example, mammal IS-A animal, dog IS-A mammal, hence dog IS-A animal as well. --------------------------------------------------------------------------------------------------------- Inheritance
To demonstrate inheritance, let's create a Mother class and a Daughter class:
class Mother { public: Mother() {}; void sayHi() { cout << "Hi"; } };
class Daughter { public: Daughter() {}; };
The Mother class has a public method called sayHi(). The next step is to inherit (derive) the Daughter from the Mother. ---------------------------------------------------------------------------------------------------------- Inheritance
This syntax derives the Daughter class from the Mother class.
class Daughter : public Mother { public: Daughter() {}; };
The Base class is specified using a colon and an access specifier: public means, that all public members of the base class are public in the derived class. In other words, all public members of the Mother class become public members of the Daughter class. ------------------------------------------------------------------------------------------------------------ Inheritance
As all public members of the Mother class become public members for the Daughter class, we can create an object of type Daughter and call the sayHi() function of the Mother class for that object:
#include <iostream> using namespace std; class Mother { public: Mother() {}; void sayHi() { cout << "Hi"; } };
class Daughter: public Mother { public: Daughter() {}; };
int main() { Daughter d; d.sayHi(); } //Outputs "Hi"
A derived class inherits all base class methods with the following exceptions: - Constructors, destructors - Overloaded operators - The friend functions A class can be derived from multiple classes by specifying the base classes in a comma-separated list. For example: class Daughter: public Mother, public Father -------------------------------------------------------------------------------------------- Access Specifiers
Up to this point, we have worked exclusively with public and private access specifiers. Public members may be accessed from anywhere outside of the class, while access to private members is limited to their class and friend functions. As we've seen previously, it's a good practice to use public methods to access private class variables. -------------------------------------------------------------------------------------------- Protected
There is one more access specifier - protected. A protected member variable or function is very similar to a private member, with one difference - it can be accessed in the derived classes.
class Mother { public: void sayHi() { cout << var; }
private: int var=0;
protected: int someVar; };
Now someVar can be accessed by any class that is derived from the Mother class. ---------------------------------------------------------------------------------------------- Type of Inheritance
Access specifiers are also used to specify the type of inheritance. Remember, we used public to inherit the Daughter class:
class Daughter: public Mother
private and protected access specifiers can also be used here.
Public Inheritance: public members of the base class become public members of the derived class and protected members of the base class become protected members of the derived class. A base class's private members are never accessible directly from a derived class, but can be accessed through calls to the public and protected members of the base class. Protected Inheritance: public and protected members of the base class become protected members of the derived class. Private Inheritance: public and protected members of the base class become private members of the derived class. Public inheritance is the most commonly used inheritance type. If no access specifier is used when inheriting classes, the type becomes private by default. ------------------------------------------------------------------------------------------------ Fill in the blanks to declare a protected member in the Base class and to access it from the Derived class' ''foo'' function.
class Base { protected : int baseVar; }; class Derived : public Base { public: void foo() { baseVar = 12; } }; ------------------------------------------------------------------------------------------------- Inheritance
When inheriting classes, the base class' constructor and destructor are not inherited. However, they are being called when an object of the derived class is created or deleted.
To further explain this behavior, let's create a sample class that includes a constructor and a destructor:
class Mother { public: Mother() { cout <<"Mother ctor"<<endl; } ~Mother() { cout <<"Mother dtor"<<endl; } };
Creating an object in main results in the following output:
int main() { Mother m; } /* Outputs Mother ctor Mother dtor */
The object is created and then deleted, when the program finishes to run. -------------------------------------------------------------------------------------------------- Fill in the blanks to define a constructor and a destructor for the ''Mother'' class. Mother ::Mother() { cout << "constructor" << endl; } Mother::~Mother () { cout << "destructor" << endl; } -------------------------------------------------------------------------------------------------- Inheritance
Next, let's create a Daughter class, with its own constructor and destructor, and make it a derived class of the Mother:
class Daughter: public Mother { public: Daughter() { cout <<"Daughter ctor"<<endl; } ~Daughter() { cout <<"Daughter dtor"<<endl; } }; ---------------------------------------------------------------------------------------------------- Inheritance
Now, what happens when we create a Daughter object?
int main() { Daughter m; }
/*Outputs Mother ctor Daughter ctor Daughter dtor Mother dtor */
Note that the base class' constructor is called first, and the derived class' constructor is called next. When the object is destroyed, the derived class's destructor is called, and then the base class' destructor is called. You can think of it as the following: The derived class needs its base class in order to work - that is why the base class is set up first. ------------------------------------------------------------------------------------------------------ Summary
Constructors The base class constructor is called first.
Destructors The derived class destructor is called first, and then the base class destructor gets called. This sequence makes it possible to specify initialization and de-initialization scenarios for your derived classes. ------------------------------------------------------------------------------------------------------- Polymorphism
The word polymorphism means "having many forms". Typically, polymorphism occurs when there is a hierarchy of classes and they are related by inheritance.
C++ polymorphism means that a call to a member function will cause a different implementation to be executed depending on the type of object that invokes the function. Simply, polymorphism means that a single function can have a number of different implementations. -------------------------------------------------------------------------------------------------------- Polymorphism
Polymorphism can be demonstrated more clearly using an example: Suppose you want to make a simple game, which includes different enemies: monsters, ninjas, etc. All enemies have one function in common: an attack function. However, they each attack in a different way. In this situation, polymorphism allows for calling the same attack function on different objects, but resulting in different behaviors.
The first step is to create the Enemy class.
class Enemy { protected: int attackPower; public: void setAttackPower(int a){ attackPower = a; } };
Our Enemy class has a public method called setAttackPower, which sets the protected attackPower member variable. --------------------------------------------------------------------------------------------------------- Polymorphism
Our second step is to create classes for two different types of enemies, Ninjas and Monsters. Both of these new classes inherit from the Enemy class, so each has an attack power. At the same time, each has a specific attack function.
class Ninja: public Enemy { public: void attack() { cout << "Ninja! - "<<attackPower<<endl; } };
class Monster: public Enemy { public: void attack() { cout << "Monster! - "<<attackPower<<endl; } };
As you can see, their individual attack functions differ. Now we can create our Ninja and Monster objects in main. int main() { Ninja n; Monster m; }
Ninja and Monster inherit from Enemy, so all Ninja and Monster objects are Enemy objects. This allows us to do the following:
Enemy *e1 = &n; Enemy *e2 = &m;
We've now created two pointers of type Enemy, pointing them to the Ninja and Monster objects. -------------------------------------------------------------------------------------------------------- Polymorphism
Now, we can call the corresponding functions: int main() { Ninja n; Monster m; Enemy *e1 = &n; Enemy *e2 = &m;
e1->setAttackPower(20); e2->setAttackPower(80);
n.attack(); m.attack(); }
/* Output: Ninja! - 20 Monster! - 80 */
We would have achieved the same result by calling the functions directly on the objects. However, it's faster and more efficient to use pointers. Also, the pointer demonstrates, that you can use the Enemy pointer without actually knowing that it contains an object of the subclass. --------------------------------------------------------------------------------------------------------- Virtual Functions
The previous example demonstrates the use of base class pointers to the derived classes. Why is that useful? Continuing on with our game example, we want every Enemy to have an attack() function. To be able to call the corresponding attack() function for each of the derived classes using Enemy pointers, we need to declare the base class function as virtual. Defining a virtual function in the base class, with a corresponding version in a derived class, allows polymorphism to use Enemy pointers to call the derived classes' functions. Every derived class will override the attack() function and have a separate implementation:
class Enemy { public: virtual void attack() { } };
class Ninja: public Enemy { public: void attack() { cout << "Ninja!"<<endl; } };
class Monster: public Enemy { public: void attack() { cout << "Monster!"<<endl; } };
A virtual function is a base class function that is declared using the keyword virtual. ------------------------------------------------------------------------------------------------------------ Virtual Functions
Now, we can use Enemy pointers to call the attack() function. int main() { Ninja n; Monster m; Enemy *e1 = &n; Enemy *e2 = &m;
e1->attack(); e2->attack(); }
/* Output: Ninja! Monster! */
As the attack() function is declared virtual, it works like a template, telling that the derived class might have an attack() function of its own. ------------------------------------------------------------------------------------------------------------ Virtual Functions
Our game example serves to demonstrate the concept of polymorphism; we are using Enemy pointers to call the same attack() function, and generating different results.
e1->attack(); e2->attack();
If a function in the base class is virtual, the function's implementation in the derived class is called according to the actual type of the object referred to, regardless of the declared type of the pointer. A class that declares or inherits a virtual function is called a polymorphic class. ------------------------------------------------------------------------------------------------------------- Virtual Functions
Virtual functions can also have their implementation in the base class: class Enemy { public: virtual void attack() { cout << "Enemy!"<<endl; } };
class Ninja: public Enemy { public: void attack() { cout << "Ninja!"<<endl; } };
class Monster: public Enemy { public: void attack() { cout << "Monster!"<<endl; } };
Now, when you create an Enemy pointer, and call the attack() function, the compiler will call the function, which corresponds to the object's type, to which the pointer points:
int main() { Ninja n; Monster m; Enemy e;
Enemy *e1 = &n; Enemy *e2 = &m; Enemy *e3 = &e;
e1->attack(); // Outputs "Ninja!"
e2->attack(); // Outputs "Monster!"
e3->attack(); // Outputs "Enemy!" }
This is how polymorphism is generally used. You have different classes with a function of the same name, and even the same parameters, but with different implementations. -------------------------------------------------------------------------------------------------- Pure Virtual Function
In some situations you'd want to include a virtual function in a base class so that it may be redefined in a derived class to suit the objects of that class, but that there is no meaningful definition you could give for the function in the base class. The virtual member functions without definition are known as pure virtual functions. They basically specify that the derived classes define that function on their own. The syntax is to replace their definition by =0 (an equal sign and a zero):
class Enemy { public: virtual void attack() = 0; };
The = 0 tells the compiler that the function has no body. ---------------------------------------------------------------------------------------------------- Pure Virtual Functions
A pure virtual function basically defines, that the derived classes will have that function defined on their own. Every derived class inheriting from a class with a pure virtual function must override that function. If the pure virtual function is not overridden in the derived class, the code fails to compile and results in an error when you try to instantiate an object of the derived class. ----------------------------------------------------------------------------------------------------- Pure Virtual Functions
The pure virtual function in the Enemy class must be overridden in its derived classes.
class Enemy { public: virtual void attack() = 0; };
class Ninja: public Enemy { public: void attack() { cout << "Ninja!"<<endl; } };
class Monster: public Enemy { public: void attack() { cout << "Monster!"<<endl; } }; ------------------------------------------------------------------------------------------------------- Abstract Classes
You cannot create objects of the base class with a pure virtual function. Running the following code will return an error: Enemy e; // Error These classes are called abstract. They are classes that can only be used as base classes, and thus are allowed to have pure virtual functions. You might think that an abstract base class is useless, but it isn't. It can be used to create pointers and take advantage of all its polymorphic abilities. For example, you could write:
Ninja n; Monster m; Enemy *e1 = &n; Enemy *e2 = &m;
e1->attack(); e2->attack();
In this example, objects of different but related types are referred to using a unique type of pointer (Enemy*), and the proper member function is called every time, just because they are virtual. -------------------------------------------------------------------------------------------------------- Function Templates
Functions and classes help to make programs easier to write, safer, and more maintainable. However, while functions and classes do have all of those advantages, in certain cases they can also be somewhat limited by C++'s requirement that you specify types for all of your parameters. For example, you might want to write a function that calculates the sum of two numbers, similar to this:
int sum(int a, int b) { return a+b; } --------------------------------------------------------------------------------------------------------- Function Templates
We can now call the function for two integers in our main.
int sum(int a, int b) { return a+b; }
int main () { int x=7, y=15; cout << sum(x, y) << endl; } // Outputs 22
The function works as expected, but is limited solely to integers. ---------------------------------------------------------------------------------------------------------- Function Templates
It becomes necessary to write a new function for each new type, such as doubles.
double sum(double a, double b) { return a+b; }
Wouldn't it be much more efficient to be able to write one version of sum() to work with parameters of any type? Function templates give us the ability to do that! With function templates, the basic idea is to avoid the necessity of specifying an exact type for each variable. Instead, C++ provides us with the capability of defining functions using placeholder types, called template type parameters. To define a function template, use the keyword template, followed by the template type definition:
template <class T>
We named our template type T, which is a generic data type. ------------------------------------------------------------------------------------------------------------ Function Templates
Now we can use our generic data type T in the function:
template <class T> T sum(T a, T b) { return a+b; }
int main () { int x=7, y=15; cout << sum(x, y) << endl; }
// Outputs 22
The function returns a value of the generic type T, taking two parameters, also of type T. Our new function worked exactly as the previous one for integer values did. ------------------------------------------------------------------------------------------------------------- Function Templates
The same function can be used with other data types, for example doubles: template <class T> T sum(T a, T b) { return a+b; }
int main () { double x=7.15, y=15.54; cout << sum(x, y) << endl; } // Outputs 22.69
The compiler automatically calls the function for the corresponding type. When creating a template type parameter, the keyword typename may be used as an alternative to the keyword class: template <typename T>. In this context, the keywords are identical, but throughout this course, we'll use the keyword class. --------------------------------------------------------------------------------------------------------------- Function Templates
Template functions can save a lot of time, because they are written only once, and work with different types. Template functions reduce code maintenance, because duplicate code is reduced significantly. Enhanced safety is another advantage in using template functions, since it's not necessary to manually copy functions and change types. --------------------------------------------------------------------------------------------------------------- Function Templates
Function templates also make it possible to work with multiple generic data types. Define the data types using a comma-separated list. Let's create a function that compares arguments of varying data types (an int and a double), and prints the smaller one.
template <class T, class U>
As you can see, this template declares two different generic data types, T and U. ---------------------------------------------------------------------------------------------------------------- Function Templates
Now we can continue with our function declaration: template <class T, class U> T smaller(T a, U b) { return (a < b ? a : b); }
The ternary operator checks the a<b condition and returns the corresponding result. The expression (a < b ? a : b) is equivalent to the expression if a is smaller than b, return a, else, return b. ------------------------------------------------------------------------------------------------------------------ Function Templates
In our main, we can use the function for different data types: template <class T, class U> T smaller(T a, U b) { return (a < b ? a : b); }
int main () { int x=72; double y=15.34; cout << smaller(x, y) << endl; }
// Outputs 15
The output converts to an integer, because we specified the function template's return type to be of the same type as the first parameter (T), which is an integer. ------------------------------------------------------------------------------------------------------------------- Function Templates
T is short for Type, and is a widely used name for type parameters. It's not necessary to use T, however; you can declare your type parameters using any identifiers that work for you. The only terms you need to avoid are C++ keywords. Remember that when you declare a template parameter, you absolutely must use it in your function definition. Otherwise, the compiler will complain!
|