17. Week 9 Tuesday: Classes and the Private Keyword
≪ 16. Week 8 Thursday: Practise with Structs | Table of Contents | 18. Week 10 Tuesday: Practise with Classes and Pointers ≫Last week, when we were talking about making structs, we noted that we could often write our main program without knowing how the member functions in our structs were implemented (either in the tea house or in the fractions). This is one feature that’s really nice about objects in C++ — they can be treated as black boxes, and the people using them don’t have to know the inner workings.
More importantly, however, we didn’t have to manually change any of the member variables in our structs. For example, once we created a drink
object, we didn’t touch the member variables at all. Our only way of interacting with them was indirectly through the member functions, such as when we printed out the contents or computed the price of a drink.
This is a design principle called encapsulation: only the minimal functionality necessary should be exposed and be used. Everything else in the struct or class ought to remain hidden.
But there’s nothing really stopping us from going in and changing those member functions yet. We can change the contents of a drink
object freely after creating them, and someone more malicious can change the denominator of a fraction
object to 0
if they really wanted to. This is the role of the private
keyword: it hides things that people using the struct shouldn’t be allowed to directly modify or access, and this allows us to ensure that the internal state of a class remains consistent with our expectations (e.g., keeping a nonzero denominator).
Classes and Private Members
We’ll introduce a new keyword called the class
. It works exactly the same way as a struct
, but we now need to clarify which parts of the class are public
— that is, accessible from outside the class — and private
— that is, only accessible by the class itself. Besides this, there are no differences between structs and classes!
Let’s give a quick example with meaningless member variables and member functions:
1#include <iostream>
2using namespace std;
3
4class foo {
5private:
6 int a, b;
7
8public:
9 // constructor
10 foo(int _a, int _b) : a(_a), b(_b) {}
11
12 // mystery function
13 void bar(const foo other) const {
14 cout << "US: " << a << " " << b << endl;
15 cout << "THEM: " << other.a << " " << other.b << endl;
16 }
17};
18
19int main() {
20 foo f1(1, 2);
21 foo f2(3, 4);
22
23 f1.bar(f2);
24
25 return 0;
26}
Trying to write something like cout << f1.a << endl;
from within the main function now results in a build error. The a
variable is private and therefore inaccessible from the main function, which is not part of the foo
class.
Note that the bar
function can still access other.a
and other.b
. Private functions can not only access the implicit argument’s private variables, but also other explicit arguments’ private variables when they’re of the same class.
A good general rule-of-thumb is that all member variables should be private and that all member functions and constructors should be public. This forces us to design classes as objects that perform actions themselves rather than raw bundles of data that get modified directly — think about how we wrote a .add
function for fractions rather than directly changing the numerator and denominator.
This doesn’t really make a big difference in small programs and problems like this one, and it probably won’t make much of a difference for any other program that we write for the remainder of this class. However, if you end up creating more complex programs with many moving parts that you have to manage carefully, exposing internal functionality of a class (i.e., the member variables) can have unpredictable consequences and difficult-to-track mistakes.
Problem 1.
Write a program that accepts a paragraph of text as input (i.e., a string without line breaks), then determines the character(s) that occur the most often in the text (case insensitive, including spaces).
Design a class called char_tracker
to help you do this. What internal data does it need to store? How should its functionality be exposed to the rest of the program?
As an extended hint, perhaps it’s good to provide an outline to how the char_tracker
class should work. It needs to keep track of all the characters that appear, as well as how often they occur. One way to do this is to have two parallel vectors: one being vector<char> appeared
and another being vector<int> counts
. These vectors should be in sync. They should be the same length, and for any index i
, counts.at(i)
should be the number of times the character appeared.at(i)
many times.
For instance, if appeared = {'a', 'c'}
and counts = {5, 10}
, then a
appeared 5
times and c
appeared 10
times.
We could load every character in the ASCII table into appeared
the moment we construct a char_tracker
object and initialise all the counts
to 0
. But this is profoundly annoying, especially with the case insensitivity, and it’s hard to tell exactly how to set this sort of thing up anyways. Doing this manually, needless to say, is not an option.
There should be two public functions exposed to the world in this class: one to “witness” a new character in the string of text the user supplies, which updates the appeared
and counts
vectors under the hood; and another function to determine the most frequent character(s). Thus, our class definition should be as follows:
1class char_tracker {
2public:
3 // default constructor:
4 // doesn't need any information to make one of these!
5 char_tracker();
6
7 // adds a single character to the internal records.
8 void witness(char c);
9
10 // determines the most common characters and returns them
11 // all in a string.
12 string most_frequent() const;
13
14private:
15 // keeps track of all the characters that have appeard
16 vector<char> appeared;
17
18 // keeps track of how often each character has appeared
19 vector<int> counts;
20};
Try implementing the three member functions yourself, then putting together the main function to make things work. To test it out, you can copy and paste a paragraph from your favourite Wikipedia article and see which letter is used the most often.