C++ is an object oriented language that encourages you to "encapsulate". The first step of this is to group concepts into objects described in terms of their data values and the operations that can be performed on that data.
So one could define an Account
class consisting of an id and balance and functions to deposit or withdraw currency. This definition forms the type, but when you "instantiate" (create an instance of) this type, i.e.
Account a;
then the variable a
refers to an object of type Account in this scope.
Sometimes you need a class that can store and track objects of other types. This is what is sometimes referred to as a "container class" and typically it combines a store and a count.
Say we want to store some float
s. We could just write:
float store[64];
std::cout << "Enter the first number: ";
std::cin >> store[0];
But how would we track how many floats we have? We would probably need a counter.,
float store[64];
int stored = 0;
std::cout << "Enter the first number: ";
std::cin >> store[0];
stored++;
std::cout << "Enter the second number: ";
std::cin >> store[1];
stored++;
This works, and it's not terribly difficult, but if you are writing a function that expects to take a store and it's size, how do you express that?
void myFunction(std::string label, float* store, int count);
This requires two arguments and it's not exactly explicit.
C++ is about encapsulation: this idea of a "store" with a count of the contents could be encapsulated into a class:
struct Store {
float store_[64] {};
int count_ {0};
};
this is a container. We can now write our function that takes an object that contains other values with a single parameter:
void myFunction(std::string label, Store& store); // & here = by reference
If this was 'C' you would write code that directly manipulated the values in the store:
store.store_[N] = 1;
store.count_++;
but that's messy, we didn't check there was room. In C++, we can encapsulate this into the class description with member functions and hide the member variables so that you have to go through our proscribed interface to manipulate the data.
#include <iostream>
class Store {
enum { MaxCount = 64 };
float store_[MaxCount] {};
size_t count_ = 0;
public:
// return the maximum number of elements we can store
size_t capacity() const { return MaxCount; }
// true/false: is the store empty?
bool empty() const { return count_ == 0; }
// return the current count
size_t size() const { return count_; }
bool add(float value) {
if (count_ >= capacity()) {
std::cerr << "store is full!\n";
return false;
}
store_[count_] = value;
++count_;
}
// reset
void clear() {
count_ = 0; // we don't actually need to change the store
}
// allow array-like usage
const float& operator[](size_t index) const { return store_[index]; }
float& operator[](size_t index) { return store_[index]; }
// provide bounds-checked array-ish access
float at(size_t index) const {
if (index >= count_)
throw std::invalid_argument("array index out of bounds");
return store_[index];
}
};
int main() {
Store store;
for (size_t i = 0; i < store.capacity(); ++i) {
std::cout << "Enter number #" << i << " or -ve to stop: " << std::flush;
float f = -1;
std::cin >> f;
std::cout << "\n" << f << "\n";
if (f < 0)
break;
store.add(f);
}
std::cout << "You entered " << store.size() << " values:";
for (size_t i = 0; i < store.size(); ++i) {
std::cout << ' ' << store[i];
}
std::cout << '\n';
}
Live demo: http://ideone.com/boE3Ki