Week 03 / 14 Object-Oriented Programming
Introduction to classes
From struct to class. How to define a class with attributes and methods, which kinds of methods exist, how objects are created, and how public and private protect an object's state.
- Compare struct, union and class
- Define a class with attributes and methods
- Define a method inside the class and outside it with the scope operator `::`
- Tell the kinds of methods apart (access, processing, helper, const)
- Create objects and explain that each object has its own attributes
- Use public, private and protected, and know the default access in struct and class
Why a struct is not enough
In week 2 we grouped data with struct. The lab file from week 3 does exactly that:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>
using namespace std;
struct Student
{
int age;
string name;
int* grades;
};
int main()
{
// ex1: in: input a word
// out: and we have to take out all the vowels
//char s[20], res[20];
//cout << "Input a word: ";
//cin.getline(s, 20);
//int j = 0;
//for (int i = 0; i < strlen(s); i++)
//{
// if (s[i] != 'a' && s[i] != 'e' && s[i] != 'i' && s[i] != 'o' && s[i] != 'u' && s[i] != 'A' && s[i] != 'E' && s[i] != 'I' && s[i] != 'O' && s[i] != 'U')
// {
// res[j] = s[i];
// j++;
// }
//}
//res[j] = '\0';
//cout << "The resulted word without voewls is: " << res << endl;
// ex 2:
// in: insert a phrase
// out: count the words
//char phrase[100];
//cout << "Insert a phrase: ";
//int i = 0, countWords = 0;
//char* p;
//char* context = NULL;
//cin.getline(phrase, 100);
//p = strtok_s(phrase, " ", &context);
//while (p != NULL)
//{
// countWords++;
// p = strtok_s(NULL, " ", &context);
//}
// orrrr not really ok, check at home!!
//while (phrase[i] != '\0')
//{
// if (phrase[i] == ' ' && phrase[i + 1] != ' ' && phrase[i + 1] != '\0' && phrase[i - 1] != ' ')
// {
// countWords++;
// }
// i++;
//}
//cout << "The number of words within the phrase is: " << countWords << endl;
// structs
Student stud;
stud.name = "Marcel";
stud.age = 20;
stud.grades = new int[5] {10, 8, 9, 10, 6};
// next class
return 0;
} Anyone can write anything into stud.age or stud.grades. Nothing stops this:
stud.age = -40; // compiles, and now the data is nonsense
A struct groups data. It does not protect it. A class groups data and the operations that are allowed on it, and it can hide the data so that only those operations can change it.
struct, union, class
| Members | Default access | Typical use | |
|---|---|---|---|
struct | data and functions, each with its own memory | public | simple groups of data |
class | data and functions, each with its own memory | private | types with rules |
union | all data members share the same memory | public | one value out of several types |
In a union only one member is valid at a time: the last one you wrote.
union Value {
int i;
double d;
};
Value v;
v.i = 42; // i is the active member
v.d = 3.5; // now d is active; reading v.i from here on is wrong
Reading a member other than the last one written is undefined behaviour in C++. You will rarely need union in this course. It is here so you know what it is.
Defining a class
A class has attributes (data members, the state) and methods (member functions, the behaviour).
class Account {
double balance = 0; // attribute (private by default)
public:
void deposit(double amount) { // method defined inside the class
if (amount > 0) balance += amount;
}
bool withdraw(double amount); // only declared here
double getBalance() const { return balance; }
}; // don't forget the ';'
bool Account::withdraw(double amount) { // defined outside, with ::
if (amount <= 0 || amount > balance) return false;
balance -= amount;
return true;
}
Account::withdraw reads “the withdraw that belongs to Account”. Without Account:: it would be a free function that knows nothing about balance.
bool prints as 1 or 0. What does the program print?
#include <iostream>
using namespace std;
class Account {
double balance = 0;
public:
void deposit(double amount) { if (amount > 0) balance += amount; }
bool withdraw(double amount);
double getBalance() const { return balance; }
};
bool Account::withdraw(double amount) {
if (amount <= 0 || amount > balance) return false;
balance -= amount;
return true;
}
int main() {
Account acc;
acc.deposit(100);
acc.deposit(-50);
bool ok1 = acc.withdraw(30);
bool ok2 = acc.withdraw(500);
cout << ok1 << " " << ok2 << " " << acc.getBalance();
} Kinds of methods
- Access methods read or change one attribute in a controlled way:
getBalance(). A setter, if you have one, validates what it receives. - Processing methods do the real work of the object:
deposit,withdraw. They are the reason the class exists. - Helper methods are
private. They are used by other methods and are not part of the public interface. - const methods promise not to change the object:
double getBalance() const. The compiler checks the promise. - Constructors and the destructor are special methods. They are next week’s topic.
A method can also be inline (defined in the class body) or defined outside with ::. The behaviour is the same. Long methods usually go outside, so the class body stays readable.
Objects
A class is a type. An object is a variable of that type. Each object has its own copy of the attributes. The methods exist once and work on the object they are called on.
#include <iostream>
using namespace std;
class Counter {
int value = 0;
public:
void increment() { value++; }
int get() const { return value; }
};
int main() {
Counter a, b;
a.increment();
a.increment();
b.increment();
cout << a.get() << " " << b.get();
} Inside a method, this is a pointer to the current object. value++ in a.increment() really means this->value++, with this == &a.
Objects live where any variable lives: local objects on the stack, objects created with new on the heap.
Counter a, b; - two objects on the stack, each with its own value.
Step 1/4
Access specifiers
| Specifier | Who can use the member |
|---|---|
public | everyone |
private | only the methods of the class (and its friends, week 6) |
protected | the class and the classes derived from it (week 8) |
Access is checked by the compiler. This is deliberately broken code:
Account acc;
acc.balance = 1000000; // compile error: 'double Account::balance' is private within this context
Encapsulation means: the attributes are private, and the public methods keep the object valid. A rule that must always hold for an object, such as “the balance is never negative”, is called an invariant. If only the methods can change the state, you only need to check the invariant in the methods.
Run the program below. Then uncomment the last line and read the compiler message.
#include <iostream>
using namespace std;
struct PointS { // struct: members are public by default
int x = 0, y = 0;
};
class Rectangle { // class: members are private by default
double width = 1, height = 1;
public:
bool setSize(double w, double h) {
if (w <= 0 || h <= 0) return false; // keep the rectangle valid
width = w;
height = h;
return true;
}
double area() const { return width * height; }
};
int main() {
PointS p;
p.x = 3; // allowed: public
cout << "point: " << p.x << " " << p.y << endl;
Rectangle r;
cout << "area: " << r.area() << endl;
r.setSize(2, 5);
cout << "area: " << r.area() << endl;
cout << "accepted: " << r.setSize(-1, 4) << endl;
cout << "area: " << r.area() << endl;
// r.width = -3; // try it: compile error, width is private
}Input (stdin)
Check yourself
Score: 0/7