Build Your First C++ Class: Bundling Data and Behaviour Together
Define a C++ class, add member variables and functions, and use access specifiers to keep your data safe from accidental misuse.
By the end of this page, you will know how to define a class with member variables and member functions, use public and private to control access, write a constructor that initialises an object in a valid state, and create multiple independent objects from a single class definition.
What and Why
Imagine you are writing a program that tracks bank accounts. Each account has an owner name and a balance. You also need operations: deposit, withdraw, check the current balance.
Without classes you might juggle separate variables for every account:
std::string owner1 = "Alice";
double balance1 = 1000.0;
std::string owner2 = "Bob";
double balance2 = 500.0;This works for two accounts. For hundreds it becomes unmanageable β and nothing stops you from accidentally pairing balance1 with owner2, or setting a balance to -99999.
A class solves this by grouping related data and the operations on that data into a single named type. Once you define BankAccount, you can create as many account objects as you need. Each object carries its own data, and the operations always act on the right one.
Two ideas underpin every class you will ever write:
- Encapsulation β hide the internal data; expose only what callers need to see.
- Abstraction β callers think about what an account does, not how it does it.
You will see both in action as you build the examples below.
Step by Step
1. The Bare-Minimum Class
A class declaration starts with the class keyword, a name, a pair of braces, and β critically β a semicolon after the closing brace.
#include <iostream>
#include <string>
class BankAccount {
public:
std::string owner;
double balance;
};
int main() {
BankAccount alice;
alice.owner = "Alice";
alice.balance = 1000.0;
std::cout << alice.owner << " has $" << alice.balance << "\n";
return 0;
}Compile with any C++11-capable compiler:
g++ -std=c++11 -o bank bank.cppalice is an object β a concrete instance of the BankAccount blueprint. The dot operator (.) lets you read or write its members.
2. Adding Member Functions
Right now any caller can write alice.balance = -9999. Moving the logic inside the class fixes that. Functions defined inside a class are called member functions.
#include <iostream>
#include <string>
class BankAccount {
public:
std::string owner;
double balance;
void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
void withdraw(double amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
}
}
void print() const {
std::cout << owner << ": $" << balance << "\n";
}
};
int main() {
BankAccount alice;
alice.owner = "Alice";
alice.balance = 0.0;
alice.deposit(500.0);
alice.deposit(200.0);
alice.withdraw(100.0);
alice.print(); // Alice: $600
return 0;
}Notice const after print(). That tells the compiler β and anyone reading the code β that print promises not to modify the object. Mark every read-only function const from day one.
3. Access Specifiers: public vs private
Making balance public still allows alice.balance = -9999 from outside the class. The right tool is a private section.
#include <iostream>
#include <string>
class BankAccount {
public:
BankAccount(std::string ownerName, double initialBalance)
: owner(ownerName), balance(initialBalance) {}
void deposit(double amount) {
if (amount > 0) balance += amount;
}
bool withdraw(double amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
return true;
}
return false;
}
double getBalance() const { return balance; }
std::string getOwner() const { return owner; }
private:
std::string owner;
double balance;
};
int main() {
BankAccount alice("Alice", 1000.0);
alice.deposit(250.0);
alice.withdraw(100.0);
std::cout << alice.getOwner() << " has $" << alice.getBalance() << "\n";
// alice.balance = -9999; // compile error β balance is private
return 0;
}private members are invisible outside the class. Callers can only use what you expose in public.
The BankAccount(...) function is a constructor β it runs once at object creation and ensures the object always starts in a valid state. The : owner(ownerName), balance(initialBalance) syntax is a member initialiser list: it initialises members directly rather than assigning to them inside the body. Prefer this form; it is more efficient and is the idiomatic C++11 style.
Common Patterns
Pattern 1 β In-Class Default Values
C++11 lets you give members a default value right where they are declared. The constructor only needs to override what differs:
#include <iostream>
class Counter {
public:
explicit Counter(int start = 0) : count(start) {}
void increment() { ++count; }
int value() const { return count; }
private:
int count = 0; // in-class initialiser β C++11 and later
};
int main() {
Counter c;
c.increment();
c.increment();
std::cout << c.value() << "\n"; // 2
}explicit on a single-argument constructor prevents the compiler from silently converting an int to a Counter when you did not intend it. Prefer explicit unless you specifically want that implicit conversion.
Pattern 2 β Immutable Snapshot
Sometimes you want an object whose state is fixed after construction. Mark members const and provide no mutating functions:
#include <iostream>
#include <string>
class TransactionRecord {
public:
TransactionRecord(std::string from, std::string to, double amount)
: from(from), to(to), amount(amount) {}
void print() const {
std::cout << from << " -> " << to << " $" << amount << "\n";
}
private:
const std::string from;
const std::string to;
const double amount;
};
int main() {
TransactionRecord t("Alice", "Bob", 50.0);
t.print();
}Pattern 3 β Classes as Building Blocks
Classes compose naturally. One class can own another as a member:
#include <iostream>
#include <string>
#include <vector>
class BankAccount {
public:
BankAccount(std::string owner, double balance)
: owner(owner), balance(balance) {}
std::string getOwner() const { return owner; }
double getBalance() const { return balance; }
private:
std::string owner;
double balance;
};
class Bank {
public:
void addAccount(BankAccount account) {
accounts.push_back(account);
}
void printAll() const {
for (const auto& acc : accounts) {
std::cout << acc.getOwner() << ": $" << acc.getBalance() << "\n";
}
}
private:
std::vector<BankAccount> accounts;
};
int main() {
Bank myBank;
myBank.addAccount(BankAccount("Alice", 1000.0));
myBank.addAccount(BankAccount("Bob", 500.0));
myBank.printAll();
}What Can Go Wrong
Forgetting the Semicolon After the Closing Brace
class Foo {
int x;
} // β missing semicolon
// The error appears on the next line and looks completely unrelated.Every class definition must end with };. When you see baffling errors on the line after a class, check the brace first.
Uninitialised Built-In Members
class Rect {
public:
int width;
int height;
int area() const { return width * height; } // undefined behaviour if not set!
};
int main() {
Rect r;
std::cout << r.area(); // reads garbage values
}Built-in types (int, double, bool) are not automatically zeroed when you create a stack object. Always initialise them β in a constructor, with an in-class initialiser (int width = 0;), or via value-initialisation (Rect r{}).
Calling a Non-const Function on a const Reference
void display(const BankAccount& acc) {
acc.deposit(10.0); // error: deposit is not marked const
}A const& parameter restricts you to const member functions only. If deposit modifies balance, it cannot be const β and the fix is to restructure your design, not to remove the const from the parameter.
Members Are private by Default in a class
class Secret {
int x = 42; // private β no access specifier was written
};
int main() {
Secret s;
std::cout << s.x; // error: 'x' is private
}In a class, anything written before the first access specifier is private. Add public: above the members you want callers to reach. (struct behaves the opposite way: its members are public by default.)
Quick Reference
| Concept | Syntax | Notes |
|---|---|---|
| Class definition | class Name { ... }; | Semicolon after } is mandatory |
| Public member | public: int x; | Accessible from anywhere |
| Private member | private: int x; | Accessible inside the class only |
| Default visibility | class β private, struct β public | Differs between the two keywords |
| Constructor | Name(args) : member(arg) {} | Member initialiser list is preferred |
| In-class initialiser | int x = 0; | C++11 and later |
| Const member function | int get() const { ... } | Promises not to modify the object |
| Create an object | Name obj(args); | Calls the matching constructor |
| Access a member | obj.member or obj.method() | Use -> when going through a pointer |
What's Next
- Constructors and Destructors β control exactly what happens when objects are created and destroyed
- Inheritance Basics β build new classes from existing ones without duplicating code
- const Correctness β use
constconsistently to catch bugs at compile time - Abstract Classes β define interfaces that derived classes must implement