OOP: classes!C has structs, C++ has classes.. |
OOP, ie. Object Oriented Programming, is based on two very important, and useful, principles: encapsulation, inheritance.
So far, we have been dealing with 'loose' (unpackaged, ungrouped), built-in datatypes - int, bool, double, array, etc.
In the real world, we have entities which are described not just by one datatype, but a set (group) of them: driver's license, student, car, 3D vector, RGB color..
We therefore need a way to GROUP primitive datatypes (int, array etc) into a more meaningful whole - that is what a struct (C) and class (C++) does.
We can define a struct called 'student' like so:
struct student {
int id;
char classif; // one of F, S, J, N, M, P
float GPA;
};
student s1, s2; // no need to say struct student s1,s2;
s1.id = 12345;
s1.classif = 'J';
s1.GPA = 3.8;
s2.id = 13579;
s2.classif = 'M';
s2.GPA = 2.98;
cout << s1.id << endl;
cout << s2.GPA << endl;
We have created a NEW TYPE called 'student', with MEMBERS (ie. data components) id, classif and GPA (which are of primitive, built-in types).
We have ENCAPSULATED id, classif and GPA into a bigger type called 'student'.
struct student {
int id;
char classif; // one of F, S, J, N, M, P
float GPA;
};
student s1 = {12345, 'J', 3.8};
student s2 = s1;
cout << s2.GPA << endl;
Let us create another struct, a 3D vector:
struct vector {
float x,y,z; // three components
};
vector u = {1,0,0}, v = {0,1,0};
cout << u.x << endl;
u.x = -1;
cout << u.x << endl;
Our new datatype (vector) has three members, which are (always) PUBLIC - they can be manipulated FROM OUTSIDE the struct definition, eg. u.x = -1. Our encapsulation is not a solid one..
We always need code (functions) that manipulate data. Given our vector struct, what if we wanted to add two vectors?
We define a standalone (loose) function 'add()' that adds two vector objects and returns their sum.
Similarly we can define subtract(), scale(), negate(), unitize(), cross() and dot(), in order to create a very useful collection of vector-related functions.
If we do the above, we'll have encapsulated data, operated on by loose (unencapsulated) functions!
The logical step is to make add() part of the struct definition.
We have encapsulated things more: we now have function members inside our struct, that have access to the data components - the functions are not freestanding (loose) anymore.
Member functions are called METHODS, so our vector struct has three (data) MEMBERS and three (function) METHODS.
We use the dot notation to indicate that printVec() [for example] is to be run on u, then v, then u again. We also use the dot notation to access members (eg. u.x=3.14;). FYI - the dot notation is also used in Java, Python, JavaScript, C#..
So a struct does provide decent encapsulation, where we can group primitive datatypes into a meaningful collection, and also specify methods to operate on the data collection.
Given two vectors u and v, we can now normalize each (u.unitize(), v.unitize()), then take their dot product (u.dot(v), or v.dot(u)), use it to scale a third vector w (w.scale(u.dot(v))), and so on.
We no longer think in terms of raw x, y, z floats, or pass data into free-standing functions - our code looks more "natural", ie. mirrors operations we'd like to perform on our data. THAT is the benefit of encapsulation.
But, everything (data, functions) is all PUBLIC! In other words, there is no place in our struct to create a member (eg. vectorLength) that is INVISIBLE FROM THE OUTSIDE, or create a method (eg. calcLength()) that is also INVISIBLE FROM THE OUTSIDE.
We need a way to make certain (or even ALL!) data PRIVATE, and make certain (NOT ALL!) methods PRIVATE -- these would be for use 'within', ie. be accessed only from inside member function definitions. For this, we need a 'class' definition.