Most computer programming, including OOP, is just about memory. Programs are only useful because they manipulate data within memory.
When a program declares an int,
x:
int x;
The system sets aside some bytes of memory for the program's use. It sets out just enough bytes to represent each of the possible values of
int. If
int needs four bytes, it'll set out four bytes.
The definition of a class or struct type informs the system how many bytes must be set aside for an object of the new type:
1 2 3 4 5
|
struct interval
{
int lower;
int upper;
};
|
In this case
interval describes a memory layout large enough for two
ints. If
int needs 4 bytes, declaring a class of type
interval causes the system to set aside 8 bytes. Therefore, the phrase
interval three_through_ten;
Instructs the system to set aside 8 bytes of memory.
The definition of
interval also tells the system that the phrase
three_through_ten.lower talks about the first 4 bytes in the 8 byte block named
three_through_ten. Similarly, we've also told the system that the phrase
three_through_ten.upper refers to the second set of 4 bytes.
Because those "sub chunks" (which are called
member subobjects in C++'s jargon) can be referred to separately, they can be modified separately too:
1 2
|
three_through_ten.lower = 3;
three_through_ten.upper = 10;
|
.
Which sets the first and second chunks of four bytes within
three_through_ten separately.
I can also tell the compiler what other phrases mean involving an
interval like
zero_through_ten. By defining the function
1 2 3 4
|
int range(interval ivl)
{
return ivl.upper - ivl.lower;
}
|
This tells the compiler that the phrase
range(three_through_ten) subtracts the
int in the first four bytes of an
interval from the last four. In this model, the phrase computes the "distance" or "range" spanned by the interval. The distance between ten and three is 7, in this case.
Notice how names are chosen which help reflect a useful concept - a "useful" model of an interval between integers.
range() could also be a member function: For instance
1 2 3 4 5 6 7
|
struct interval
{
int lower;
int upper;
int range() const { return upper - lower; }
};
|
Gives essentially the same meaning as before to a slightly different phrase
three_through_ten.range()
Although there are some subtle differences between the two which don't matter right now.
If some (contrived) inheritance is thrown into the mix:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
struct interval
{
int lower;
int upper;
int range() const { return upper - lower; }
};
struct sub_array: public interval
{
int* begin() { return data + lower; }
int* end() { return data + upper; }
int* data;
};
|
The compiler understands that
sub_array describes a data layout identical to
1 2 3 4 5
|
struct sub_array
{
interval base_subobject;
int* data;
};
|
Such that
sub_array describes a memory layout large enough for one
interval and one
int*. Probably
sub_array describes a layout that's 16 bytes in size.
1 2
|
int my_array[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
sub_array sa; // system sets aside 16 bytes: 8 for the interval, 8 for the pointer.
|
The inheritance means (to humans) that every
sub_array is an interval. Therefore, we can treat
sa as if it was an
interval - using the phrases
sa.begin and
sa.end even though the chunk of memory called
begin isn't mentioned explicitly with the definition of
sub_array.
1 2 3 4
|
sa.data = my_array; // last 8 bytes within sa
sa.upper = 3; // first four bytes within sa
sa.lower = 5; // second four bytes within sa
std::cout << sa.range() << '\n'; // prints 2
|
By defining member functions begin() and end() we've told the compiler enough to figure out what this means:
for (int i: sa) std::cout << i << '\n';
Let's put it together:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
#include <iostream>
struct interval
{
int lower;
int upper;
int range() const { return upper - lower; }
};
struct sub_array: public interval
{
int* begin() { return data + lower; }
int* end() { return data + upper; }
int* data;
};
int main()
{
int my_array[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
sub_array sa;
sa.data = my_array;
sa.lower = 3;
sa.upper = 8;
std::cout << "the sub_array sa spans " << sa.range() << " values" << '\n';
for (int i: sa) std::cout << i << '\n';
}
|
Live demo:
https://godbolt.org/z/M1j4qa
We're describing memory along with the operations associated with it: that's OOP.