The feature of classes is what sets "C++" apart from the "C" language that it evolved from. Classes allow "C++" to let coding be done in an object oriented fashion. Most of the modern languages support object oriented programming. It wasn't always like that. For a long time programming was done in procedural languages like "C", "Fortran" and "Pascal". Object oriented programming lets us create classes where we can place our data variables and member functions. Thinking of the problem domain in terms of objects allows us to organize and reuse our code in a more natural way than procedural programming. This might not make a difference when a few hundred lines of code are involved but becomes important when the program is huge with number of lines going into thousands. Almost all of modern programming utilizes the object oriented programming approach.
A class defines a type. It consists of properties and methods. We can organize our code in terms of the problem domain.
#include <string>#include <iostream>using namespace std ;class Person { string firstName ; string lastName ; int age ; void print() { cout << "Name:" << firstName << " " << lastName << " Age: " << age << endl ; } };int main(){ Person personObj ; return 0 ;}
The above defines a class with the keyword "class" . In this class we place the properties such as "firstName" and "lastName" and we also have a method that operates on the properties. We can then use the class as a type to create our objects.
Person personObj ;
This is very similar to defining variables with the types such as :
int x1 ;
A variable "personObj" is created on the stack.
When defining our class we can choose to make the data variables or member functions private. By default
the access specifier is private.
Ex:
#include <string>#include <iostream>using namespace std ;class Person { string firstName ; string lastName ; int age ; void print() { cout << "Name:" << firstName << " " << lastName << " Age: " << age << endl ; } };int main(){ Person personObj ; personObj.firstName = "Robert" ; return 0 ;}
Output:
access1.cpp: In function ‘int main()’:
access1.cpp:34:14: error: ‘std::__cxx11::string Person::firstName’ is private within this context
personObj.firstName = "Robert" ;
^~~~~~~~~
access1.cpp:10:11: note: declared private here
string firstName ;
^~~~~~~~~
[amittal@hills access_speci
We did not specify any access specifiers ( default access specifier was private) and so we could not access the variable from outside. What "outside" means is that once we create an object of the class and then we try to access the variables. In the above case the statement:
personObj.firstName = "Robert" ;
caused the compiler error. Note even if the data variable is private it can always be access by any method of the class even if the method is private. Let us change the access specifier so that we get rid of the compiler error.
#include <string>#include <iostream>using namespace std ;class Person { public: string firstName ; string lastName ; int age ; void print() { cout << "Name:" << firstName << " " << lastName << " Age: " << age << endl ; } };int main(){ Person personObj ; personObj.firstName = "Robert" ; personObj.lastName = "Newman" ; personObj.age = 82 ; personObj.print() ; return 0 ;}
Output:
[amittal@hills access_specifiers]$ g++ access2.cpp
[amittal@hills access_specifiers]$ ./a.out
Name:Robert Newman Age: 82
The "private" and "public" specifiers are hints to the compiler. Remember the CPU does not have a "public/private" scope concept. We can access the private variables by address manipulation.
#include <string>#include <iostream>using namespace std ;class Person { private: int age ; string firstName ; string lastName ; public: void setAge( int ageP ) { age = ageP ; } int getAge() { return ( age ) ; } void setFirstName( string firstNameP ) { firstName = firstNameP ; } string getFirstName() { return ( firstName ) ; } void setLastName( string lastNameP ) { lastName = lastNameP ; } string getLastName() { return ( lastName ) ; } void print() { cout << "Name:" << firstName << " " << lastName << " Age: " << age << endl ; } };int main(){ Person personObj ; personObj.setFirstName( "Robert" ) ; personObj.setLastName( "Newman" ) ; personObj.setAge( 82) ; personObj.print() ; //Get age directly by addresses Person* ptr = &personObj ; int ageHack = *( (int*)ptr ) ; cout << "ageHack =" << ageHack << endl ; return 0 ;}
[amittal@hills access_specifiers]$ ./a.out
Name:Robert Newman Age: 82
ageHack =82
In the above we take advantage of the way a class object is stored in the ram.
RAM
-------------
Person age
firstName
lastName
We take the address of the person object first.
Person* ptr = &personObj ;
Then the address is type cast to a pointer of type int.
(int*)ptr
What we are saying is the the address points to an integer. Then we obtain the integer at that address with the * .
int ageHack = *( (int*)ptr ) ;
We have obtained a value for the age even though the variable was declared as private.
Encapsulation
We normally want to assign the properties with a private access and give functions public assess.
#include <string>#include <iostream>using namespace std ;class Person { private: string firstName ; string lastName ; int age ; public: void setFirstName( string firstNameP ) { firstName = firstNameP ; } string getFirstName() { return firstName ; } void setLastName( string lastNameP ) { lastName = lastNameP ; } string getLastName() { return lastName ; } void setAge( int ageP ) { age = ageP ; } int getAge() { return age ; } void print() { cout << "Name:" << firstName << " " << lastName << " Age: " << age << endl ; } };int main(){ Person personObj ; personObj.setFirstName ( "Robert" ) ; personObj.setLastName( "Newman" ) ; personObj.setAge( 82 ) ; personObj.print() ; return 0 ;}
This approach has couple of advantages. The first being that we can implement validation in one place. If we set the age as:
personObj.age = 82 ;
then a potential user of the class could inadvertently type:
personObj.age = 882
By restricting access to a function we can place the validation in the function "setAge" :
void setAge( int ageP )
{
if ( age < 0 || age > 200 )
{
cout << "Invalid age" << endl ;
return ;
}
age = ageP ;
}
The other advantage is that we can change the data properties inside our class without affecting how the class got used. Let's say we changed
int age ;
to
unsigned char age ;
We can do so without the user changing the code that used the function. User called the function "setAge()" and that part does not change. This is possible because we have kept the implementation ( data part ) separate from the interface( the public methods ) . This is called encapsulation .
Exercise:
1) Create a class "vehicle" that contains the properties "weight" , "top speed" and
"number of wheels" and a method to print the properties. Create another class method to set and obtain the weight. Create an object of this class in the main function and set the weight and then use the print method to print the contents out.
In the examples above we have written the "Person" class in a single file. Normally the header file contains the declaration of the class and the ".cpp" file contains the body of the methods. Another file may want to use the class and only needs to include the header file for the class in order to use the class.
"person.h"
#ifndef PERSON_H#define PERSON_H#include <string>#include <iostream>using namespace std ;class Person { private: string firstName ; string lastName ; int age ; public: void print() ; void setData( string firstNameP, string lastNameP , int ageP ) ;
};#endif
"person.cpp"
#include "person.h" void Person::setData( string firstNameP, string lastNameP , int ageP )
{
}
void Person::print() { cout << "Name:" << firstName << " " << lastName << " Age: " << age << endl ; }
"ex2.cpp"
#include <iostream>#include <string>#include "person.h"using namespace std ;int main(){ Person person1 ; person1.print() ; Person* personPtr ; personPtr = new Person() ; personPtr->print() ; //personPtr = 0 ; delete personPtr ; return(0) ;}
As the above code shows we include the "person.h" and can then use the "Person" class .
Exercise:
1) Create a folder and separate your earlier vehicle class into a vehicle.h , vehicle.cpp and main.cpp.
There are 2 different ways of creating objects in C++ . One is on the stack and other on heap. The stack object is created with the statement:
Person person1 ;
After the execution of the above statement an object has been created on the stack. It's constructor has also been called and we are ready to start using the object. The other way is to dynamically allocate the memory for the object.
personPtr = new Person() ;
personPtr->print() ;
This also creates an object but the object is created on the heap and then the constructor is called . If we have a pointer to int such as:
int* ptr l
Then we can access the value that the pointer points to by using the syntax:
*ptr1
We can use similar notation to access a class methods and data using a pointer.
(*personPtr).print() ;
C++ gives us an alternate and easier way to do this.
personPtr->print() ;
We can use the "->" notation if the variable is a pointer.
Remember if something is created on the heap then it must be deleted by the programmer . This is what happens in the line:
delete personPtr ;
The "delete" statement didn't have to be in this function . It could be in another function also. What happens if our program keeps running but we forgot to do "delete" on this particular object. There will be a memory leak in the program. Of course when the program ends then the memory will be released for the whole program.
Notice in the header file we have the lines:
#ifndef PERSON_H
#define PERSON_H
#endif
These are preprocessing directives. Remember there are 2 steps in C++ compilation. At first the preprocessor goes through the ".cpp" file and processes it to produce another modified ".cpp" file that it compiles. Also an include statement normally copies the contents of the "include" file and pastes it in the ".cpp" file. Say we have a file "main.cpp" that uses the Person.h file.
Ex: main.cpp
#include "person.h"
#include "myutil.h"
int main()
{
//Bunch of statements
return ( 1) ;
}
Now it turns out "myutil.h" is also using "person.h".
Ex myutil.h
#include "person.h"
So if we look at our "main.cpp" we see that it includes "person.h" but by including "myutil.h" it will also include what's in "util.h" and that is "person.h" so the file "person.h" gets included twice. This will cause a compiler error as C++ does not allow duplicate declarations. To prevent duplicate header files
we use the include guard. The "ifndef" means if the variable "PERSON_H" is not defined. The name "PERSON_H" can be anything but the convention is put the name of the header file with underscore and then "H" with the whole name being in capitol. For the first time we encounter include "person.h" we define the variable "PERSON_H" in the line:
#define PERSON_H
What the preprocessor does is it includes everything to the end "#endif" . Now let's say we encounter the header file again:
#ifndef PERSON_H
When the preprocessor encounters the above line it will check if the variable "PERSON_H" is defined and since it is the preprocessor will not enter the if conditions and will not include lines from the header file making sure that the file contents of "person.h" are not included more than once in a ".cpp" file. Remember the role of header files is only inside "cpp" files. During compilation we only give ".cpp" files to the "g++" command.
A constructor is a method that is called for us to initialize the variables of a class and any other initialization work that we may want to do. Let's try to write our own method called "initialize" .
Ex: demo.cpp
#include <iostream>using namespace std ;class Demo { public: int x1 ; int x2 ; void initialize() { x1 = 5 ; x2 = 10 ; cout << "Inside the initialize method." << endl ; }};int main(){ Demo demo1Obj ; demo1Obj.initialize() ; cout << demo1Obj.x1 << " : " << demo1Obj.x2 << endl ; return(0) ;}
Output:
[amittal@hills default]$ ./a.out
Inside the initialize method.
5 : 10
We have defined a method to do the initialization. To keep things simple all the data variables are defined as "public" . In the main method we can call "initialize" and set the values of our data variables. Even though this does what we want it to; it is not very elegant. What if we forget to call the "initialize" method ? The constructor feature provides a better method.
Let us write the above program using a constructor.
#include <iostream>using namespace std ;class Demo { public: int x1 ; int x2 ; Demo() { x1 = 5 ; x2 = 10 ; cout << "Inside the initialize method." << endl ; }};int main(){ Demo demo1Obj ; cout << demo1Obj.x1 << " : " << demo1Obj.x2 << endl ;}
Output:
[amittal@hills default]$ ./a.out
Inside the initialize method.
5 : 10
We have defined the constructor as:
Demo()
The constructor has the same name as the class and does not have a return type. Why not ? We are not calling the constructor and getting a return value. Rather the constructor is called for us when we create the object at the line:
Demo demo1Obj ;
We can confirm that by the following program:
#include <iostream>using namespace std ;class Demo { public: int x1 ; int x2 ; Demo() { x1 = 5 ; x2 = 10 ; cout << "Inside the constructor method." << endl ; }};int main(){ cout << "Before creating the object." << endl ; Demo demo1Obj ; cout << "After creating the object." << endl ; cout << demo1Obj.x1 << " : " << demo1Obj.x2 << endl ;}
Output:
[amittal@hills default]$ ./a.out
Before creating the object.
Inside the constructor method.
After creating the object.
5 : 10
As we can see the constructor method got called at the time the object was created. The same applies to dynamic creation of objects.
#include <iostream>using namespace std ;class Demo { public: int x1 ; int x2 ; Demo() { x1 = 5 ; x2 = 10 ; cout << "Inside the initialize method." << endl ; }};int main(){ cout << "Before creating the object." << endl ; Demo* demo1Obj ; demo1Obj = new Demo() ; cout << "After creating the object." << endl ; cout << demo1Obj->x1 << " : " << demo1Obj->x2 << endl ; delete demo1Obj ;}
Output:
[amittal@hills default]$ ./a.out
Before creating the object.
Inside the initialize method.
After creating the object.
5 : 10
The syntax for creating a dynamic object is :
new Demo()
If we don't have a constructor for our class then the compiler will create one for us that will not have any arguments or a body for that matter. The default constructor does not do anything .
We can also define constructors with arguments.
Ex:
#include <iostream>using namespace std ;class Demo { public: int x1 ; int x2 ; Demo(int x1P, int x2P) { x1 = x1P ; x2 = x2P ; cout << "Inside the construtor method." << endl ; }};int main(){ Demo demo1Obj ;}
Output:
[amittal@hills default]$ g++ demo5.cpp
demo5.cpp: In function ‘int main()’:
demo5.cpp:25:8: error: no matching function for call to ‘Demo::Demo()’
Demo demo1Obj ;
We have a single constructor that takes arguments and we try to create an object without supplying any arguments to the constructor. However the compiler sees that we have a constructor that takes arguments and do not have a constructor that does not take any arguments. The compiler in this case will not create the default constructor without any arguments and will force us to use the constructor that we have defined that takes arguments. We can correct the problem by supplying the arguments at the time of creation.
Ex:
#include <iostream>
using namespace std ; class Demo { public: int x1 ; int x2 ; Demo(int x1P, int x2P) { x1 = x1P ; x2 = x2P ; cout << "Inside the construtor method." << endl ; } }; int main() { Demo demo1Obj( 4 , 6 ) ; }
Output:
[amittal@hills default]$ g++ demo6.cpp
[amittal@hills default]$ ./a.out
Inside the construtor method.
[amittal@hills default]$
Overloaded Constructors
We can create several different constructors with different arguments for a single class.
#include <iostream>
using namespace std ; class Demo { public: int x1 ; int x2 ; Demo(int x1P, int x2P) { x1 = x1P ; x2 = x2P ; cout << "Inside the construtor method." << endl ; } Demo() { cout << "Inside the default construtor." << endl ; } }; int main() { Demo demo1Obj( 4 , 6 ) ; Demo demo2Obj ; }
Output:
[amittal@hills default]$ ./a.out
Inside the construtor method.
Inside the default construtor.
Default Values
We can also have constructors with arguments that take on default values.
Ex:
#include <iostream>
using namespace std ; class Demo { public: int x1 ; int x2 ; Demo(int x1P = 4, int x2P = 5) { x1 = x1P ; x2 = x2P ; cout << "Inside the construtor method." << endl ; } Demo() { cout << "Inside the default construtor." << endl ; } }; int main() { Demo demo1Obj( 4 , 6 ) ; Demo demo2Obj ; }
Output:
[amittal@hills default]$ g++ demo8.cpp
demo8.cpp: In function ‘int main()’:
demo8.cpp:33:9: error: call of overloaded ‘Demo()’ is ambiguous
Demo demo2Obj ;
We need to be careful that the compiler does not get confused. In the above case the second object "demo2Obj" can be constructed by either of the 2 constructors that we have defined.
Rewriting the above as:
#include <iostream>
using namespace std ; class Demo { public: int x1 ; int x2 ; Demo(int x1P = 4, int x2P = 5) { x1 = x1P ; x2 = x2P ; cout << "Inside the construtor method." << endl ; } }; int main() { Demo demo1Obj( 4 , 6 ) ; Demo demo2Obj ; }
Now the second object uses the default values since we are not supplying any arguments in the constructor. If we have defined at least one constructor then the default constructor is not constructed by the system.
Exercise:
1) Refer to your class "vehicle" from previous exercise that contains the properties "weight" , "top speed" and
"number of wheels" and a method to print the properties.
Place an include guard in the header file. Create a constructor to set the 3 values of "weight", "top speed" and "number of wheels"
Create 2 objects of type vehicle; one that is on the stack and another that is on the heap.
Similar to a constructor we have a destructor method. A destructor is called when an object is destroyed. This will happen for objects that are created on the stack when they go out of scope.
Ex:
#include <iostream>using namespace std;class Package { private: int value; public: Package() { value = 7; cout << "Constructor:" << value << endl; } Package(int v) { value = v; cout << "Constructor:" << value << endl; } ~Package() { cout << "Destructor:" << value << endl; }};int main() { Package obj1(4); Package obj2 ; Package obj3(2); return 0; }
Output:
$ ./a.exe
Constructor:4
Constructor:7
Constructor:2
Destructor:2
Destructor:7
Destructor:4
We see the destructor getting called when the objects go out of scope. The way these objects are created on the stack is normally in the manner:
Stack
obj3
obj2 ^
obj1 |
The "obj3" is destroyed first and then obj2 and then obj1. We can see that the destructor for "obj3" got called first. Objects on the stack are destroyed in the reverse order of their creation.
Notice we used:
Package obj2 ;
If we used
Package obj2() ;
then the code also compiles but it means something else. It means that it is a declaration of a function ! The name of the function is "obj2" and it returns an object of type Package. So we declared a function but did not receive an error during compilation. That's because we didn't use it. The following example shows the compilation error.
Ex:
#include <iostream>using namespace std;class Package { private: int value; public: Package() { value = 7; cout << value << endl; } Package(int v) { value = v; cout << value << endl; } ~Package() { cout << value << endl; }};
int main() { Package obj1(4); //Package obj5(5); Package obj2() ; Package obj3(2) ; obj2() ; return 0; }
Output:
[amittal@hills order_of_destruction]$ g++ demo2.cpp
/tmp/ccV1eFdE.o: In function `main':
demo2.cpp:(.text+0x33): undefined reference to `obj2()'
collect2: error: ld returned 1 exit status
[amittal@hills order_of_destruction]$
To destroy an object on the heap we have to use the "delete" method.
Ex:
#include <iostream>using namespace std;class Package { private: int value; public: Package() { value = 7; cout << "Constructor:" << value << endl; } void print() { cout << "Inside print method:" << value << endl; } Package(int v) { value = v; cout << "Constructor:" << value << endl; } ~Package() { cout << "Destructor:" << value << endl; }};int main() { Package* obj1 = new Package(4); Package* obj2 = new Package() ; Package* obj3 = new Package(3) ; obj1->print() ; (*obj1).print() ; //Setting the pointer to NULL will not destruct the object //Object destruction can only happen with delete. //obj1 = NULL ; delete( obj1 ) ; delete ( obj2 ) ; delete ( obj3 ) ; return 0; }Output:
[amittal@hills destructor]$ ./a.out
Constructor:4
Constructor:7
Constructor:3
Inside print method:4
Inside print method:4
Destructor:4
Destructor:7
Destructor:3
Couple of points to note about creation of objects. We have to use the "new" notation but for default constructor we have to use :
Package* obj2 = new Package() ;
where as when creating objects with a constructor that does not take any arguments on the stack we used:
Package obj2 ;
When we access a member we can use:
obj2->print() ;
We can also do:
(*obj1).print() ;
When we say (*obj1) we are specifying that the obj1 is a pointer and we want the object that it's pointing to. Once we have that we can use the regular notation of using "." to access the method.
//obj1 = NULL ;
Note that setting a pointer variable to NULL does not destroy an object. In fact it prevents us from destroying an object as we have lost the address that existed in the pointer variable. The only way to destroy the object on the heap is through the "delete" call. That call can be made at any time; in another function. If the memory is never freed then it still occupies space in the heap and we have introduced something called a memory leak. If this process is repeated then at some point we will run out of heap space.
Copy Constructors
Sometimes an object is created using another object. In this case a special kind of constructor called a "Copy Constructor" gets called. A default copy constructor is created if we don't define our own. In some cases this can create problems.
#include <iostream>using namespace std ; class Demo { public: int x1 ; int x2 ; int* ptr ; Demo() { cout << "Inside the constructor method." << endl ; ptr = new int[10] ; } ~Demo() { cout << "Inside the destructor method." << endl ; delete[] ptr ; } }; int main() { Demo demo1 ; Demo demo2 = demo1 ; }
Output:
$ g++ c1.cpp ; ./a.exe
Inside the constructor method.
Inside the destructor method.
Inside the destrutcor method.
Aborted (core dumped)
We create an object called "demo1" . The constructor allocates some memory that gets assigned to the variable "ptr" . We then use this object to initialize another object "demo2". The default copy constructor gets called and that copies all the data values bit by bit to the variables of "demo1" . Thus the "ptr" of "demo2" will take on the value of "ptr" of "demo1" The object "demo1" gets destructed and releases the memory for "ptr" . The object "demo2" gets destructed and also deletes "ptr" that points to the same address and we get an exception in the program. We should define our own copy constructor to handle this case.
#include <iostream>using namespace std ; class Demo { public: int x1 ; int x2 ; int* ptr ; Demo() { cout << "Inside the construtor method." << endl ; ptr = new int[10] ; //Initialize with some values for( int i1=0; i1<10 ; i1++ ) { ptr[i1] = i1+ 1; } } Demo(const Demo& demoObj ) { cout << "Inside the copy construtor method." << endl ; ptr = new int[10] ; //Copy the values in the array from demoObj for( int i1=0; i1<10 ; i1++ ) { ptr[i1] = demoObj.ptr[i1] ; } } ~Demo() { cout << "Inside the destrutor method." << endl ; delete[] ptr ; } void print() { for( int i1=0; i1<10 ; i1++ ) { cout << ptr[i1] << " " ; } cout << endl ; } }; int main() { Demo demo1 ; Demo demo2 = demo1 ; demo2.print() ; }
The signature for a copy constructor is of the form:
Demo(const Demo& demoObj )
We function name is the name of the class and the parameter is a reference. It must be a reference and not a normal object such as "Demo demoObj". Otherwise the parameter "demoObj" will try to get created through the calling method and we have the copy constructor method getting called in a loop.
The 2 other cases in which he copy constructor will get called is when passing the object to the arguments of a function and when a function returns an object. The function returning an object doesn't always end up calling the copy constructor due to compiler optimization.
#include <iostream>using namespace std ; class Demo { private: int z1 ; public: int x1 ; int x2 ; string name ; int* ptr ; Demo(string nameP ) { name = nameP ; cout << "Inside the construtor method."<< name << endl ; ptr = new int[10] ; } Demo(const Demo& demoObj ) { cout << "Inside the copy constructor method." << demoObj.name << endl ; ptr = new int[10] ; name = demoObj.name ; //We have access to another object of the same class's //private data. x1 = demoObj.z1 ; } ~Demo() { cout << "Inside the destructor method:" << name << endl ; delete[] ptr ; } }; void function1( Demo obj1 ) { cout << "Inside function1" << endl ; } int main() { { Demo demo1("Local1" ) ; cout << "Step1:" << endl ; function1( demo1 ) ; cout << "Step2" << endl ; } }
Output:
$ g++ c4b.cpp ; ./a.exe
Inside the constructor method.Local1
Step1:
Inside the copy constructor method.Local1
Inside function1
Inside the destructor method:Local1
Step2
Inside the destructor method:Local1
The above shows the copy constructor getting called when we call the function "function1" .
We are going to construct our own string class called "mystring" . The below files have been written out for you: "main.cpp" , "mystring.h" , "mystring.cpp" .
Add a constructor without any arguments that sets the ptr to null .
Add the copy constructor to the mystring class and run the program.
To compile:
g++ main.cpp mystring.cpp
File: "mystring.h"
#ifndef MYSTRING_H #define MYSTRING_H #include <string.h> #include <iostream> using namespace std ; class mystring { private: char* ptr ; public: mystring( const char* str1 ) ; ~mystring() ; void print() ; }; #endif
File: "mystring.cpp"
#include "mystring.h" mystring::mystring( const char* str1 ) { cout << "Inside the constructor." << endl ; if ( str1 == NULL ) ptr = NULL ; else { int len = strlen( str1 ) + 1; ptr = new char[ len ] ; strcpy ( ptr, str1 ) ; } } mystring::~mystring() { cout << "Inside the destructor." << endl ; if ( ptr != NULL ) delete[] ptr ; } void mystring::print() { cout << ptr << endl ; }
File: "main.cpp"
#include "mystring.h" int main() { mystring str1( "Marvin" ) ; mystring str2 = str1 ; str1.print() ; return 0 ; }
Let's say we have the below code:
#include <iostream> using namespace std ; int cube( int x1 ) { return ( x1 * x1 * x1 ) ; } int main() { int result1 ; result1 = cube(4) ; return 0 ; }
The function cube has a very small body. Function calls are expensive if the body is small and we are calling the functions often . In such cases it is more efficient to tell the compiler that it can replace the function call with the body of the function. We can only give the hint to the compiler and not force it as such. It could rewrite the above code as:
#include <iostream>using namespace std ;inline int cube( int x1 ){ return ( x1 * x1 * x1 ) ;} int main() { int result1 ; result1 = cube(4) ; return 0 ; }
and rewriting by the compiler might produce :
#include <iostream>using namespace std ;inline int cube( int x1 ){ return ( x1 * x1 * x1 ) ;} int main() { int result1 ; result1 = 4 * 4 * 4 ; return 0 ; }
If we write the class body methods in the class declaration then those functions are automatically inline without us explicitly using the work "inline" .
Ex:
#include <iostream>
using namespace std ; class Demo { public: int x1 ; int x2 ; Demo(int x1P = 4, int x2P = 5) { x1 = x1P ; x2 = x2P ; cout << "Inside the construtor method." << endl ; } void print() { x1 = x1P ; x2 = x2P ; cout << "Inside the constructor method." << endl ; } };
int main() { Demo demo1Obj( 4 , 6 ) ; Demo demo2Obj ; demo2Obj.print() ; }
In the above example both the constructor and the print method are inline implicitly .
1)
If we create an object dynamically we must delete it once we are done with it otherwise we introduce a leak.
#include <iostream>#include <memory>using namespace std ;class Demo { public: int x1 ; int x2 ; Demo(int x1P , int x2P ) { x1 = x1P ; x2 = x2P ; //x1 = 5 ; //x2 = 10 ; cout << "Inside the constructor. " << endl ; } ~Demo() { cout << "Inside the destructor. " << endl ; }};int main(){ cout << "Before creating the object." << endl ; //Creating the object Demo* demo1Obj = new Demo( 5,6) ; delete ( demo1Obj ) ; cout << "End of main -----" << endl ;}
Output:
[amittal@hills Memory_Considerations]$ g++ class1.cpp
[amittal@hills Memory_Considerations]$ ./a.out
Before creating the object.
Inside the constructor.
Inside the destructor.
End of main -----
2)
If we assign an address of an existing object to a pointer variable and that object is on a stack then we should not delete it manually. A stack object is deleted by the compiler once it goes out of scope.
#include <iostream>
#include <memory>
using namespace std ;
class Demo
{
public:
int x1 ; int x2 ;
Demo(int x1P , int x2P )
{
x1 = x1P ; x2 = x2P ;
//x1 = 5 ;
//x2 = 10 ;
cout << "Inside the constructor. " << endl ;
}
~Demo()
{
cout << "Inside the destructor. " << endl ;
}
};
int main()
{
cout << "Before creating the object." << endl ;
Demo demo1Object( 5,6) ;
//Creating the object
Demo* demo2ObjPtr = &demo1Object ;
delete ( demo2ObjPtr ) ;
cout << "End of main -----" << endl ;
}
Output:
[amittal@hills Memory_Considerations]$ ./a.out
Before creating the object.
Inside the constructor.
Inside the destructor.
*** Error in `./a.out': free(): invalid pointer: 0x00007ffd400630f8 ***
We receive an error because even though our delete has an address; the address is that of a stack object which will be deleted by the compiler . We should not use delete on such objects. Delete method should only be used on an object created with "new" .
Corrected one:
File: "class3.cpp"
#include <iostream>#include <memory>using namespace std ;class Demo { public: int x1 ; int x2 ; Demo(int x1P , int x2P ) { x1 = x1P ; x2 = x2P ; //x1 = 5 ; //x2 = 10 ; cout << "Inside the constructor. " << endl ; } ~Demo() { cout << "Inside the destructor. " << endl ; }};int main(){ cout << "Before creating the object." << endl ; Demo demo1Object( 5,6) ; //Creating the object Demo* demo2ObjPtr = &demo1Object ; cout << "End of main -----" << endl ;}
Output:
Before creating the object.
Inside the constructor.
End of main -----
Inside the destructor.
We can see that the destructor gets called properly. There was only 1 object created in "main" and that was created on the stack so the compiler took care of destroying it.
3)
We have to be careful not to destroy an object twice that was created on the heap as that will also result in an error.
File: "class4.cpp"
#include <iostream>#include <memory>using namespace std ;class Demo { public: int x1 ; int x2 ; Demo(int x1P , int x2P ) { x1 = x1P ; x2 = x2P ; //x1 = 5 ; //x2 = 10 ; cout << "Inside the constructor. " << endl ; } ~Demo() { cout << "Inside the destructor. " << endl ; }};int main(){ cout << "Before creating the object." << endl ; Demo* demo2ObjPtr = new Demo( 4, 6 ) ; delete ( demo2ObjPtr ) ; delete ( demo2ObjPtr ) ; cout << "End of main -----" << endl ;}
Output:
[amittal@hills Memory_Considerations]$ ./a.out
Before creating the object.
Inside the constructor.
Inside the destructor.
Inside the destructor.
*** Error in `./a.out': double free or corruption (fasttop): 0x00000000014e5c20 ***
======= Backtrace: =========
/lib64/libc.so.6(+0x81429)[0x7eff960f2429]
Exercise
Create an array 2 of mystring objects with the values "Marvin" and "Hagler" .
Create an array of 2 mystring objects dynamically using the new operator with the no argument constructor.
Instance and Static Members
We can have static members in a class. A static member is like a global variable and only one instance is ever created regardless of how many class objects are created.
#include <iostream>using namespace std ;class Demo { public: static int z1 ; int x1 ; int x2 ;};int Demo::z1 = 0 ;int main(){ Demo obj1 ; obj1.x1 = 2 ; obj1.x2 = 3 ; obj1.z1 = 1 ; Demo obj2 ; obj2.x1 = 4 ; obj2.x2 = 5 ; obj1.z1 = 2 ; cout << obj1.x1 << " " << obj1.x2 << " " << obj1.z1 << endl ; cout << obj2.x1 << " " << obj2.x2 << " " << obj2.z1 << endl ; return 0 ;}
Output:
$./a.exe
2 3 2
4 5 2
We use the "static" qualifier to indicate that the variable is static. For static variables we also need to do another declaration outside the class such as:
int Demo::z1 = 0 ;
We then create 2 objects of the class Demo and assign values. Observe that the static variable "z1" only has 1 value printed out. It does not belong to an object. The object variables have different values printed out.
We can also have static method members in a class.
File: "static2.cpp"
#include <iostream>using namespace std ;class Demo { public: static int z1 ; static void print() { cout << "z1:" << z1 << endl ; } int x1 ; int x2 ;};int Demo::z1 = 0 ;int main(){ Demo obj1 ; obj1.x1 = 2 ; obj1.x2 = 3 ; obj1.z1 = 1 ; Demo obj2 ; obj2.x1 = 4 ; obj2.x2 = 5 ; obj1.z1 = 2 ; cout << obj1.x1 << " " << obj1.x2 << " " << obj1.z1 << endl ; cout << obj2.x1 << " " << obj2.x2 << " " << obj2.z1 << endl ; obj1.print() ; obj2.print() ; Demo::z1 = 100 ; Demo::print() ; return 0 ;}
We can refer to the static variables using the class "Demo" instead of the object variables. We can use the syntax "Demo::z1" and "Demo::print()" ti refer to the variable and the method name. Since these are global variables we can use the object variable or the class name. However using class names makes it easier to read the code. Note that we cannot place the class non-static variables inside a static function. Since we don't need to have an object in order to call this method.
Ex:
#include <iostream>using namespace std ;class Demo { public: static int z1 ; static void print() { cout << "z1:" << z1 << " " << x1 << endl ; } int x1 ; int x2 ;};int Demo::z1 = 0 ;int main(){ Demo obj1 ; obj1.x1 = 2 ; obj1.x2 = 3 ; obj1.z1 = 1 ; Demo obj2 ; obj2.x1 = 4 ; obj2.x2 = 5 ; obj1.z1 = 2 ; cout << obj1.x1 << " " << obj1.x2 << " " << obj1.z1 << endl ; cout << obj2.x1 << " " << obj2.x2 << " " << obj2.z1 << endl ; obj1.print() ; obj2.print() ; Demo::z1 = 100 ; Demo::print() ; return 0 ;}
$ g++ static4.cpp
static4.cpp: In static member function ‘static void Demo::print()’:
static4.cpp:11:33: error: invalid use of member ‘Demo::x1’ in static member function
11 | cout << "z1:" << z1 << " " << x1 << endl ;
| ^~
static4.cpp:15:10: note: declared here
15 | int x1 ;
Exercise
Complete the "TO DO" sections.
#include <iostream>using namespace std ;class car { public: static int numberCarsCreated ; static void print() { cout << "numberCarsCreated:" << numberCarsCreated << endl ; } car() { //TO DO INCREMENT THE STATIC VARIABLE }};//TO DO DEFINE AND INITIALIZE THE STATIC VARIABLEint main(){ car car1 ; car car2 ; car car3 ; car car4 ; car::print() ; return 0 ;}
We are able to define a class to represent a concept. In certain situations we would like one class to contain an object of another class.
File: "car1.cpp"
#include <iostream>using namespace std ;class engine { public: int cyclinders ; engine() { cout << "Engine constructor." << endl ; cyclinders = 4 ; } engine(int cyclindersP ) { cout << "Engine constructor with an argument." << endl ; cyclinders = cyclindersP ; }};class car { public: engine engineObj ;};int main(){ car carObject ; cout << "Cylinders : " << carObject.engineObj.cyclinders << endl ; return ( 1 ) ;}
We are able to create a car object in the above example and that creates an engine object using the engine's default constructor. What if we wanted to construct the engine object with the constructor that takes an argument ? This is where initialization lists come in.
There are times when data members need to be initialized but we can't do it in the class itself. C++ has a special mechanism to handle that and that is the initialization list.
Ex:
#include <iostream>using namespace std ;class engine { public: int cyclinders ; engine() { } engine(int cyclindersP ) { cyclinders = cyclindersP ; }};class car { public: const int someConstant ; int& someReference ; engine engineObj ; car(int x1, int x2 , int x3 ) : someConstant(x1) , someReference(x2), engineObj( x3 ){//someConstant = x1 ;// engineObj( x3) ;}};int main(){ car carObject( 1 , 2 , 3 ) ; cout << carObject.engineObj.cyclinders << endl ; return(0) ;}
In the above we have a "car" that has a constant integer called "someConstant" . We can use the initialization list to set that to some value. The same for the reference and a class object whose constructor needs some members.
#include <iostream>using namespace std ;class house { public: int noOfRooms ; house(int noOfRoomsP ) { noOfRooms = noOfRoomsP ; }};int main(){ house houseObject( 3 ) ; cout << houseObject.noOfRooms << endl ; return(0) ;}
In the above comment out the line in the constructor:
noOfRooms = noOfRoomsP ;
Use initialize list to assign the value . Do not change the code in the main function.
Exercises
1)
There are several parts to this exercise.
#include <string>
#include <iostream>
using namespace std ;
class Person
{
string firstName ;
string lastName ;
int age ;
void print()
{
cout << "Name:" << firstName << " " << lastName
<< " Age: " << age << endl ;
}
};
int main()
{
Person personObj ;
personObj.firstName = "Jack" ;
return ( 1 ) ;
}
a)
The code above produces a compiler error because the access specifier by default is private. Fix this by making the data members private and put the methods including the "print" method in the public section. Add the methods inline inside the class declaration. Add the methods "setData" with the below signature .
void setData(string firstNameP, string lastNameP, ageP )
Change the main code to:
int main()
{
Person personObj ;
personObj.setData( "Jack" , "Dempsey" , 52 ) ;
personObj.print() ;
return ( 1 ) ;
}
Make sure your program compiles and runs correctly.
b) Separate your class code into 2 files "person.h" and "person.cpp" . In the "person.cpp" include the "person.h". In your "main.cpp" include the file "person.h" and compile your code using the command:
g++ main.cpp person.cpp
The header file has been written out for you.
File: "person.h"
#ifndef PERSON_H
#define PERSON_H
#include <string>
#include <iostream>
using namespace std ;
class Person
{
private:
string firstName ;
string lastName ;
int age ;
public:
void setData(string firstNameP, string lastNameP, int ageP ) ;
void print() ;
};
#endif
c)
Add a constructor to the "Person" class that takes the following signature and does the same job as "setData" . Add a "destructor" to the class .
Constructor signature.
Modify the "main.cpp" to use the constructor instead of "setData" . Also create another person object dynamically in the main.
File: "main.cpp"
#include <string>
#include <iostream>
#include "person.h"
int main()
{
Person personObj( "Jack" , "Dempsey" , 52 ) ;
Person personObj1( "Harry" , "Wills" , 32 ) ;
Person* personObj2 = new Person( "Gene" , "Tunney" , 51 ) ;
personObj.print() ;
personObj2->print() ;
delete personObj2 ;
return ( 1 ) ;
}
Output should be:
Name:Jack Dempsey Age: 52
Name:Gene Tunney Age: 51
Destructor for:Gene Tunney
Destructor for:Harry Wills
Destructor for:Jack Dempsey
Solutions
1)
a)
File: "main.cpp"
#include <string>
#include <iostream>
using namespace std ;
class Person
{
private:
string firstName ;
string lastName ;
int age ;
public:
void setData(string firstNameP, string lastNameP, int ageP )
{
firstName = firstNameP ;
lastName = lastNameP ;
age = ageP ;
}
void print()
{
cout << "Name:" << firstName << " " << lastName
<< " Age: " << age << endl ;
}
};
int main()
{
Person personObj ;
personObj.setData( "Jack" , "Dempsey" , 52 ) ;
personObj.print() ;
return ( 1 ) ;
}
b)
File: "person.cpp"
#include <string>
#include <iostream>
#include "person.h"
void Person::setData(string firstNameP, string lastNameP, int ageP )
{
firstName = firstNameP ;
lastName = lastNameP ;
age = ageP ;
}
void Person::print()
{
cout << "Name:" << firstName << " " << lastName
<< " Age: " << age << endl ;
}
File: "person.h"
#ifndef PERSON_H
#define PERSON_H
#include <string>
#include <iostream>
using namespace std ;
class Person
{
private:
string firstName ;
string lastName ;
int age ;
public:
void setData(string firstNameP, string lastNameP, int ageP ) ;
void print() ;
};
#endif
File:"main.cpp"
#include <string>
#include <iostream>
#include "person.h"
int main()
{
Person personObj ;
personObj.setData( "Jack" , "Dempsey" , 52 ) ;
personObj.print() ;
return ( 1 ) ;
}
c)
File: "Person.h"
#ifndef PERSON_H
#define PERSON_H
#include <string>
#include <iostream>
using namespace std ;
class Person
{
private:
string firstName ;
string lastName ;
int age ;
public:
Person( string firstNameP, string lastNameP, int ageP ) ;
~Person() ;
void setData(string firstNameP, string lastNameP, int ageP ) ;
void print() ;
};
#endif
File: "Person.cpp"
#include <string>
#include <iostream>
#include "person.h"
Person::Person( string firstNameP, string lastNameP, int ageP )
{
firstName = firstNameP ;
lastName = lastNameP ;
age = ageP ;
}
Person::~Person()
{
cout << "Destructor for:" << firstName << " " << lastName << endl ;
}
void Person::setData(string firstNameP, string lastNameP, int ageP )
{
firstName = firstNameP ;
lastName = lastNameP ;
age = ageP ;
}
void Person::print()
{
cout << "Name:" << firstName << " " << lastName
<< " Age: " << age << endl ;
}
File: "main.cpp"
#include <string>
#include <iostream>
#include "person.h"
int main()
{
Person personObj( "Jack" , "Dempsey" , 52 ) ;
Person personObj1( "Harry" , "Wills" , 32 ) ;
Person* personObj2 = new Person( "Gene" , "Tunney" , 51 ) ;
personObj.print() ;
personObj2->print() ;
delete personObj2 ;
return ( 1 ) ;
}
//hacking
//creating objects using pointer
//include guard
//inline function
//constructor
//calling one constructor from another
//arrays
//destructor
Initializer List
Class Notes
File: "access1.cppp"
Shows how private and puble are used.
c4b
File: "access2.cpp"
Take the class methods outside the class.
Files: "access3.cpp" , "rect.h" , "rect.cpp
The "Rectangle" class is placed in 2 files "rect.h" and "rect.cpp" and used from "access3.cpp"