CS 101:
Computer Programming and Utilization
Puru
withCS101 TAs and Staff
Course webpage: https://www.cse.iitb.ac.in/~cs101/
Lecture 19: more Structures, Classes and Objects
Object Oriented Programming
A methodology for designing programs
On Design
• Whenever you design something complex, it is useful to have a plan
• Example:
Plan for designing a building:
− Understand the requirements
− Understand the constraints: budget, land area
− Plan how many floors to have
− What should be on each floor
• A plan/methodology is also useful when designing (large) projects
and similarly while designing programs
Object Oriented Programming
• Understand what is required and write clear specifications (needed in all methodologies)
• Identify the entities involved in the problem
E.g., in a library management program: books, patrons
• Identify the information associated with each entity
− Fixed information: name of the book
− Variable information (state): who has borrowed the book at present
• Organize code so that the entities and their actions/inter relationships are explicitly represented in the code
− Information associated with entities: structure variables
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
Defining a structure type
General form
struct structure-type{
member1-type member1-name;
member2-type member2-name;
...
};
// Don’t forget the semicolon!
Example
struct Book{
char title[50];
double price;
};
A structure-type is a user-defined data type, just as int, char, double
are primitive data types
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
Parameter Passing by Value
struct Point{double x, y;};
Point midpoint(Point a, Point b){
Point mp;
mp.x = (a.x + b.x)/2;
mp.y = (a.y + b.y)/2;
return mp;
}
int main(){
Point p={10,20}, q={50,60};
Point r = midpoint(p,q);
cout << r.x << endl;
cout << midpoint(p,q).x << endl;
Parameter Passing by Reference
struct Point{double x, y;};
Point midpoint( const Point &a, const Point &b){
Point mp;
mp.x = (a.x + b.x)/2;
mp.y = (a.y + b.y)/2;
return mp;
}
int main(){
Point p={10,20}, q={50,60};
Point r = midpoint(p,q);
cout << r.x << endl;
Using struct V3
V3 sum(const V3 &a, const V3 &b){
V3 v;
v.x = a.x + b.x; v.y = a.y + b.y; v.z = a.z + b.z;
return v;
}
V3 scale(const V3 &a, double f){
V3 v;
v.x = a.x * f; v.y = a.y * f; v.z = a.z * f;
return v;
}
double length(const V3 &v){
return sqrt(v.x*v.x + v.y*v.y + v.z*v.z);
}
Member functions
• It is not enough to just define a struct to hold vectors, usually we will also define functions which work on structures/entiries
• In C++, you can make the functions a part of the struct definition itself.
Such functions are called member functions.
• By collecting together relevant functions into the definition of the
struct, the code becomes better organized (object oriented!)
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;
}
One More Example: Taxi Dispatch
• Problem statement: Clients arrive and have to be assigned to (earliest waiting) taxies
• An important part of the solution was a blackboard on which we wrote down the ids of the waiting taxies
• How would we implement this using OOP?
– Create a struct to represent each entity:
– customer, taxi, blackboard?
The Queue structure
const int N=100;
struct Queue{
int elements[N], nwaiting,front;
bool insert(int v){
… }
bool remove(int &v){
… } };
Member Function Insert
• A value can be inserted only if the queue has space
• The value must be inserted into the next empty index in the queue
• The number of waiting elements in the queue is updated
• Return value indicates whether operation was successful
Member Function Insert
• A value can be inserted only if the queue has space
• The value must be inserted into the next empty index in the queue
• The number of waiting elements in the queue is updated
• Return value indicates whether operation was successful
struct Queue{
…
bool insert(int v){
if(nWaiting >= N) return false;
elements[(front + nWaiting)%N] = v; nWaiting++;
return true;
Member Function remove
• A value can be removed only if the queue is non-empty
• The value must be removed from the front of the queue. The front should be incremented mod N
• The number of waiting elements in the queue is decremented by 1
• Return value indicates whether operation was successful
struct Queue{
…
bool remove(int &v){
if(nWaiting < 1) return false;
v=elements[front]; front=(front+1)%N; nWaiting--;
return true;
}
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;
}
Remarks
• The member functions only contain the logic of how to manage the queue
• The main program only contains the logic of dealing with taxis and customers
• The new program has become simpler compared to the earlier
version, where the above two were mixed up together
Structures vs Arrays
• User defined data type, collection of variables of multiple types
• Members can be accessed using the “.” operator
• Structure name denotes the super variable consisting of the entire collection of contained variables
• Structures can be copied using assignments. Also copied when passed by value, or returned from a function
• Member functions can be written to represent actions of the entities represented by the structure
• Collection of variables of single data type
• Members can be accessed using the “[ ]” operator
• Array name denotes pointer to first element of array
• Array elements need to be explicity copied
• Array elements can be accessed by an expression whose value can be computed at run time
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
• Hardware devices that you buy from the market are packaged, and made safe to use
– Fridge, television : no danger of getting an electric shock.
– A “control panel” is provided on the device. A user does not
have to change capacitor values to change the channel on a
television
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
The modern version of a struct
• Can behave like a packaged component
• Designer of the struct provides member functions
• Designer of the struct decides what happens during execution of standard operations
• Once structs are designed in this manner, using them becomes convenient and less error-prone
• Structs endowed with above features are more commonly called
objects
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
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
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
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);
}
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 such as
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
• 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);
} }
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;
} };
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
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;
}