CS 101:
Computer Programming and Utilization
Puru
withCS101 TAs and Staff
Course webpage: https://www.cse.iitb.ac.in/~cs101/
Lecture 20: C++ and object oriented programming
Object Oriented Programming
A methodology for designing programs
The C++ structure
• Member variables
− Basic facility provided in C++ to conveniently gather together information associated with an entity
− Inherited from the C language
• Member functions
− New feature introduced in C++
− Actions/operations that effect the entity
− User defined data type with variables and functions
Nested structures (structure is a data type!)
struct Point{
double x,y;
};
struct Disk{
Point center; // contains Point double radius;
};
Disk d;
d.radius = 10;
d.center = {15, 20};
// sets the x {member of center member of d
The Complete Definition of V3
struct V3{
double x, y, z;
double length const(){
return sqrt(x*x + y*y + z*z);
}
V3 sum const(V3 b){
V3 v;
v.x = x+b.x; v.y=y+b.y; v.z=z+b.z;
return v;
}
V3 scale const(double f){
V3 v;
v.x = x*f; v.y = y*f; v.z = z*f;
return v;
} }
The Queue structure
const int N=100;
struct Queue{
int elements[N], nwaiting,front;
bool insert(int v){
… }
bool remove(int &v){
… } };
Main Program
int main(){
Queue q;
q.front = q.nWaiting = 0;
while(true){
char c; cin >> c;
if(c == ‘d’){
int driver; cin >> driver;
if(!q.insert(driver)) cout <<“Q is full\n”;
}
else if(c == ‘c’){
int driver;
if(!q.remove(driver)) cout <<“No taxiavailable”;
else cout <<“Assigning <<driver<< endl;
} }
Objects As Software Components
• A software component can be built around a struct
• Just as a hardware component is useful for building big hardware systems,
so is a software component for building large software systems
• A software component must be convenient to use, and also safe, i.e., help in preventing programming errors
Packaged software components
• Analogous idea for software:
– Make functionality associated with a struct available to the user only through member functions (control panel)
– Do not allow the user to directly access the data members inside a struct. (Just as a user cannot touch the circuitry) The user does not need to know what goes on inside
• If you build a better fridge but keep the control panel the same as the previous model, the user does not need to relearn how to use the new fridge
– If you build a better version of the struct, but keep the member functions the same, the programs that use the struct need not change
– Reusable, modular, abstract!
The modern version of a struct
• Designer of the struct decides what happens during execution of standard operations such as:
– Creation of the object – Assignment
– Passing the object to a function
– Returning the object from a function
– Destroying the object when it is not needed
C++ specifics of structures, classes and objects
• Constructors
• Copy Constructors
• Destructors
• Operator overloading
• Overloading the assignment operator
• Access control
• Classes
• Graphics and input/output classes
The Queue Struct in Taxi Dispatch
const int N=100;
struct queue{
int elements[N],
nWaiting,front;
bool insert(int v){
… }
book remove(int &v){
… }
• Once the queue is created, we expect it to be used only through the member
functions, insert and remove
• Ideally, we do not
expect/want elements, nWaiting, front to be directly accessed
Main Program Using Queue
int main(){
Queue q;
q.front = q.nWaiting = 0;
while(true){
char c; cin >> c;
if(c == ‘d’){
int driver; cin >> driver;
if(!q.insert(driver)) cout <<“Q is full\n”;
}
else if(c == ‘c’){
int driver;
if(!q.remove(driver)) . . .
• Main program does use q through operations insert and remove
• However, at the beginning, q.front and q.nWaiting are directly manipulated
• Against the philosophy of software packaging!
The Constructor member function
• In C++, the programmer can define a special member function called a constructor which will always be called when an instance of the struct is created
• A constructor has the same name as the struct, and has no return type
• Why useful?
The Constructor member function
• When q is created in the main program, the
constructor is called automatically
struct Queue{
int elements[N], front, nWaiting;
Queue(){ // constructor nWaiting = 0;
front = 0;
}
// other member functions };
int main(){
Queue q;
// no need to set
// q.nWaiting, q.front // to 0.
}
Constructors In General
struct A{
…
A(parameters){
… } };
int main(){
A x(arguments);
}
• Constructor can take arguments
• The creation of the object x in main can be thought of as
happening in two steps
– Memory is allocated for x
– The constructor is called on x with the given arguments
• Many constructors possible, provided they have different signatures
Another example: Constructor for V3
struct V3{
double x,y,z;
V3(){
x = y = z = 0;
}
V3(double a){
x = y = z = a;
} };
int main();
V3 v1(5), v2;
}
• When defining v1, an argument is given
• So the constructor taking a single argument is called. Thus each component of v1 is set to 5
• When defining v2, no argument is given.
• So the constructor taking no
arguments gets called. Thus each component of v2 is set to 0
Remarks
• If and only if programmer does not define a constructor, will C++
define a constructor which takes no arguments, and does nothing – If a constructor taking arguments is defined, you implicitly tell
C++ that you want programmers to give arguments.
– if some programmer does not give arguments, C++ will flag it as an error
– If you want both kinds of initialization, define both kinds of constructor
• A constructor that does not take arguments (defined programmer or by C++) is called a default constructor
• If you define an array of struct, each element is initialized using the default constructor
The Copy Constructor
• Suppose an object is passed by value to a function
– It must be copied to the variable denoted by the parameter
• Suppose an object is returned by a function
– The value returned must be copied to a temporary variable in the calling program
• By default the copying operations are implemented by copying each member of one object to the corresponding member of the other object
– this default behaviour can be changed by defining a copy constructor
Example
struct Queue{
int elements[N], nWaiting, front;
Queue(const Queue &source){ // Copy constructor front = source.front;
nWaiting = source.nWaiting;
for(int i=front, j=0; j<nWaiting; j++){
elements[i] = source.elements[i];
i = (i+1) % N;
} };
Copy Constructor in the Example
• The copy constructor must take a single reference argument:
the object which is to be copied
• Note that the argument to the copy constructor must be a
reference, otherwise the copy constructor will have to be called to copy the argument!
• This is will result in an unending recursion
• Member elements are not copied fully. Only the useful part of it is copied
– More efficient
• More interesting use later
struct Queue{
int elements[N], nWaiting, front;
Queue(const Queue &source){
// Copy constructor
……
} };
Tracking the use of constructors
struct Queue{
int copyID;
Queue(){
cout << "copyId=" << copyId;
}
Queue(const Queue &source){
copyId = source.copyId;
cout << "copyId=" << copyId;
} };
Queue updateQueue(Queue q){
q.copyId = 3;
return q;
}
int main(){
Queue q;
q.copyId = 1;
Queue r(q);
r.copyId = 2;
Queue z=updateQueue(r);
z.copyID = 4;
Queue a = z;
}
What will be printed?
Destructors
• When control goes out of a block in which a variable is defined, that variable is destroyed
– Memory allocated for that variable is reclaimed
• You can define a destructor function, which will get executed before the memory is reclaimed
Destructor Example
• If a queue that you have defined goes out of scope, it will be destroyed
• If the queue contains elements at the time of destruction, it is likely an error
• So you may want to print a message warning the user
• It is usually an error to call the destructor explicitly. It will be called automatically when an object is to be destroyed. It should not get called twice.
• More interesting uses of the destructor will be considered in later chapters.
Destructor Example
struct Queue{
int elements[N], nWaiting, front;
. . .
~Queue(){ //Destructor
if(nWaiting>0) cout << “Warning:”
<<“ non-empty queue being destroyed.”
<< endl;
} };
Operator Overloading
• In Mathematics, arithmetic operators are used with numbers, but also other objects
Operator Overloading
• In Mathematics, arithmetic operators are used with numbers, but also other objects such as vectors
• Something like this is also possible in C++!
• An expression such as x @ y where @ is any “infix” operator is considered by C++ to be equivalent to
x.operator@(y) in which operator@ is a member function
• If the member function operator@ is defined, then that is called to execute x @ y
Example: Arithmetic on V3 objects
struct V3{
double x, y, z;
V3(double a, double b, double c){
x=a; y=b; z=c;
}
V3 operator+(V3 b){ // adding two V3s return V3(x+b.x, y+b.y, z+b.z); // constructor call }
V3 operator*(double f){ // multiplying a V3 by f return V3(x*f, y*f, z*f); // constructor call
}
Using V3 Arithmetic
int main(){
V3 u(1,2,3), a(4,5,6), s;
double t=10;
s = u*t + a*t*t*0.5;
cout << s.x <<‘ ‘<< s.y << ‘ ‘<< s.z << endl;
}
Remarks
• Expression involving vectors can be made to look very much like what you studied in Physics/Mathematics
• Other operators can also be overloaded, including unary operators (see the book)
• Overload operators only if they have a natural interpretation for the struct in question
• Otherwise you will confuse the reader of your program
Pointers to Structures
• Disk d1={{2,3},4}, *dptr;
• *dptr is defined to have type Disk, so dptr is a pointer to a variable of type Disk
• Normal pointer operations are allowed on structure pointers
• dptr = &d1;
• (*dptr).radius = 5; //changes the radius of d1
• Operator ->
– (*x).y is same as x->y
• dptr->radius = 5; // same effect as above
Pointers as Structure Members
struct Disk2{
double radius;
Point *centerptr;
}
Point p={10,20};
Disk2 d;
d.centerptr = &p;
cout << d.centerptr->x << endl; // will print 10.
The this Pointer
• So far, we have not provided a way to refer to the receiver itself inside the definition of a member function.
• Within the body of a member function, the keyword this points to the receiver i.e., the struct on which the member function has been invoked.
• Trivial use: write this->member instead of member directly struct V3{
double x, y, z;
double length(){
return sqrt(this->x * this->x + this->y * this->y
+ this->z * this->z);
} }
• More interesting use later.
Overloading The Assignment Operator
• Normally if you assign one struct to another, each member of the rhs is copied to the corresponding member of the lhs
• You can change this behaviour by defining member function operator= for the struct
• A return type must be defined if you wish to allow chained assignments, i.e., v1 = v2 = v3; which means v1 = (v2 = v3);
– The operation must return a reference to the left hand side object
Example
struct Queue{
...
Queue& operator=(Queue &rhs){
front = rhs.front;
nWaiting = rhs.nWaiting;
for(int i=0; i<nWaiting; i++){
elements[i] = rhs.elements[i];
i = (i+1) % N;
}
return *this;
} };
// only the relevant elements are copied
Access Control
• It is possible to restrict access to members or member functions of a struct
• Members declared public: no restriction
• Members declared private: Can be accessed only inside the definition of the struct
• Typical strategy
Declare all data members to be private, and some subset of function members to be public
Access Control Example
struct Queue{
private:
int elements[N], nWaiting, front;
public:
Queue(){ … } bool insert(int v){
..
}
bool remove(int &v){
..
} };
Remarks
• public:, private: : access specifiers
• An access specifier applies to all members defined following it, until another specifier is given
• Thus elements, nWaiting, front are private, while Queue(),
insert, remove are public
Remarks
• The default versions of the constructor, copy constructor, destructor, assignment operator are public
• If you specify any of these as private, then they cannot be invoked outside of the struct definition
• Thus if you make the copy constructor of a struct X private, then you will get an error if you try to pass a struct of type X by value
• Thus, as a designer of a struct, you can exercise great control over how the struct gets used
Classes
• A class is essentially the same as a struct, except:
– Any members/member functions in a struct are public by default
– Any members/member functions in a class are private by default
Classes
• Example: A Queue class:
class Queue{
int elements[N], nWaiting, front;
public:
Queue(){…}
bool remove(int &v){…}
bool insert(int v){…}
};
• The members - elements, nWaiting and front will be private.
Example (with class)
class V3{
double x,y,z;
V3(double v){
x = y = z = v;
}
double X(){
return x;
} };
class V3{
double x,y,z;
V3(double v);
double X();
};
//implementations V3::V3(double v){
x = y = z = v;
}
double V3::X(){
return x;
}
Example (with struct)
struct V3{
double x,y,z;
V3(double v){
x = y = z = v;
}
double X(){
return x;
} };
struct V3{
double x,y,z;
V3(double v);
double X();
};
//implementations V3::V3(double v){
x = y = z = v;
}
double V3::X(){
return x;
}
Concluding Remarks
• The notion of a packaged software component is important.
• Making data members private: hiding the implementation from the user
• Making some member functions public: providing an interface using which the object can be used
• Separation of the concerns of the developer and the user
• Idea similar to what we discussed in connection with ordinary functions
– The specification of the function must be clearly written down (analogous to interface)
– The user should not worry about how the function does its work (analogous to hiding data members)
Input Output Classes
• cin, cout : objects of class istream, ostream resp. predefined in C++
• <<, >> : operators defined for the objects of these classes
• ifstream: another class like istream
• You create an object of class ifstream and associate it with a file on your computer
• Now you can read from that file by invoking the >> operator!
• ofstream: a class like ostream, to be used for writing to files
• Must include header file <fstream> to uses ifstream and ofstream
Example of file i/o
#include <fstream>
#include <simplecpp>
int main(){
ifstream infile(“f1.txt”);
// constructor call.
// object infile is created and associated
// with f1.txt, which must be present in the current directory
ofstream outfile(“f2.txt”);
// constructor call. Object outfile is created and associated // with f2.txt, which will get created in the current directory
repeat(10){
int v;
infile >> v;
outfile << v;
}
// f1.txt must begin with 10 numbers. These will be read and // written to file f2.txt
“String theory”
• Iterative computations are demonstrated well on arrays
• strings … the system manages the array space for us
• Can assign and append to strings
• Can read a position: cout << message[px]
• Can write a position: message[px] = ‘q’
• That’s all we need for now
Printing a string in reverse
string message;
getline(cin, message);
int mx = message.size()-1;
while (mx >= 0) {
cout << message[mx];
--mx;
}
• mx updated in a completely predictable way
• Ideal candidate to write as for loop
Character at position mx in
string
message
Finding needles in a haystack
• Given two strings, needles and haystack
• needles has no repeated characters
• haystack may repeat characters
• How many characters in needles appear in haystack at least once?
• needles = “bat”, haystack = “tabla” à 3
• needles = “tab”, haystack = “bottle” à 2
One needle in a haystack
• Subproblem: given one character ch and a string find if ch appears in string at least once
char ch; // suitably initialized
string haystack; // suitably initialized int ans = 0; // will change to 1 if found
for (int hx = 0; hx < haystack.size(); ++hx) { if (ch == haystack[hx]) {
++ans;
break; // quit on first match }
Many needles: nested loop
main() {
string needles, haystack;
getline(cin, needles); getline(cin, haystack);
int ans = 0;
for (int nx=0; nx < needles.size(); ++nx) { char ch = needles[nx];
for (int hx = 0; hx < haystack.size(); ++hx) { if (ch == haystack[hx]) {
++ans;
break; // quit on first match }
} // ends haystack loop } // ends needles loop
Generalize to work in case needles can also
have repeated characters
Duplicate needles
• needles = “bat”, haystack = “tabla” à 3
• needles = “tab”, haystack = “bottle” à 2
• needles = “bata”, haystack = “tabla” à 3
• Two approaches
– Dedup needles before executing earlier code (reducing to known problem)
– Dedup needles “on the fly” (inside the nx loop)
Exercise: If the input strings have n and h
characters, at most how much time does
Generalize to arbitrary lengths
• “Hello” < “Help” but “Hello” > “Hell”
• Scan both strings from the beginning
• If differing character found, same as before
• If a string ends, it is “less” than the other
int ans=0, ax=0, bx=0, an=as.size(), bn=bs.size();
for (; ans==0 && ax < an && bx < bn; ++ax, ++bx) { if ( (ans = as[ax] – bs[bx]) != 0) break;
}
if (ans == 0) { ans = an – bn;
}
This results in an arbitrary integer as the return value in case of unequal
input string lengths, but the sign of ans is what is important