struct vs class, Member Variables and Functions
C++ lets you define your own types that bundle together related data and the operations that work on that data. This is the foundation of object-oriented programming: instead of passing a loose collection of variables to every function, you group them into a single entity — a class or struct — and give it behaviors as member functions. A bank account object knows its balance and knows how to deposit, withdraw, and report its balance. The data and the functions that use it live together.
struct — a bundle of related data
The simplest user-defined type in C++ is the struct. A struct groups several variables (called member variables or data members) under one name. You access member variables through an object using the dot operator .
struct Point {
double x;
double y;
}; // note the semicolon — required after struct/class definition
// Create objects (instances) of the type:
Point origin; // uninitialized — dangerous, use aggregate init:
Point p1 = {3.0, 4.0};
Point p2 {1.5, -2.5}; // brace initialization (C++11)
// Access members with the dot operator:
std::cout << p1.x << ", " << p1.y; // 3, 4
// Modify members:
p1.x = 10.0;A struct whose members all have public access (the default for struct) is called a Plain Old Data (POD) or aggregate. It has no special invariants to maintain — any combination of member values is valid. When you need to enforce constraints (e.g., balance must not go negative), that's when you want a class with private data and controlled access.
class — data with controlled access
A class is identical to a struct in C++ with one difference: the default access for members is private instead of public. That is literally the only language difference. By convention, struct is used for simple data bundles (POD-style, mostly public members), and class is used when you want to hide implementation details and expose a controlled interface. The public, private, and protected keywords work the same in both.
class BankAccount {
public:
// Member functions (methods) — the public interface
void deposit(double amount);
bool withdraw(double amount);
double getBalance() const; // 'const' means this function doesn't modify the object
private:
// Member variables — hidden from outside code
double balance = 0.0;
std::string owner;
};Members declared under private:can only be accessed by the class's own member functions. Code outside the class cannot read or write balance directly — it must go through the public interface. This encapsulation means you can change the internal implementation (say, store cents as an integer instead of dollars as a double) without breaking any code that uses the class.
Defining member functions
Member functions are defined either inline inside the class body, or outside the class using the ClassName:: scope prefix. Outside definitions are common in header/source file pairs.
// Inline definitions inside the class body:
class Counter {
public:
void increment() { count++; }
void reset() { count = 0; }
int getCount() const { return count; }
private:
int count = 0;
};
// Outside definitions using ClassName:: prefix:
class BankAccount {
public:
void deposit(double amount);
double getBalance() const;
private:
double balance = 0.0;
};
void BankAccount::deposit(double amount) {
if (amount > 0)
balance += amount;
}
double BankAccount::getBalance() const {
return balance;
}The const qualifier after a member function signature marks it as a const member function — it promises not to modify any member variable of the object. Getter functions (those that only read data) should always be marked const. This is important because const objects can only call const member functions.
Creating and using objects
An object is an instance of a class — you create one just like a variable of a built-in type. Member functions are called through the dot operator, just like member variables. For a pointer to an object, use the arrow operator ->.
BankAccount account; // create an object on the stack account.deposit(1000.0); // call a member function account.deposit(250.0); std::cout << account.getBalance(); // 1250 // account.balance = 999; // compile error: balance is private // Multiple objects are independent — each has its own data: BankAccount savings; savings.deposit(5000.0); // savings is separate from account // Via pointer (e.g., if the object was heap-allocated): BankAccount* p = new BankAccount(); p->deposit(100.0); // arrow instead of dot for pointers double bal = p->getBalance(); delete p; // manual cleanup for raw new
struct vs class — the practical distinction
| Aspect | struct | class |
|---|---|---|
| Default access | public | private |
| Language difference | None beyond default access | None beyond default access |
| Convention: use when | Simple data bundle, POD, all members public | Encapsulated type with invariants, private data |
| Can have member functions? | Yes | Yes |
| Can have constructors? | Yes | Yes |
Key rules to remember
struct and class are nearly identical — the only language difference is default access
By convention, use struct for simple data aggregates and class for types with private data and a controlled interface.
Make data members private and provide public member functions to access them
Private data enforces invariants (e.g., balance >= 0) and lets you change the internal representation without breaking callers.
Mark getter (read-only) member functions const
void f() const; guarantees the function won't modify the object. Const objects can only call const member functions.
Each object has its own copy of the member variables
BankAccount a and BankAccount b each have their own balance. Changing a.balance does not affect b.balance.
Use dot (.) for objects, arrow (->) for pointers to objects
account.deposit() vs p->deposit(). The arrow is syntactic sugar for (*p).deposit().