Structures and Unions in C++
A structure (struct) is a structured data type that allows grouping different data types into a single unit. Struct members can be accessed directly with the dot operator (.). A struct is similar to a class, except that all members of a struct are public by default, while a class's default is private.
A union has syntax similar to a struct but with a critical difference: all members of a union share the same memory location. A union is only as large as its largest member. Unions are useful when you need to store different data types at the same memory location (for example, in an interpreter or parser).
Differences between struct and class:
- Struct: members default to public, inheritance defaults to public.
- Class: members default to private, inheritance defaults to private.
- Functionally, a struct can do everything a class can (including methods, constructors, and destructors).
Structs with Functions
Structs can have methods (member functions). Constructors and destructors can also be added for automatic initialization and cleanup.
Nested Structs
A struct can contain another struct as a member. This enables the formation of hierarchical data structures such as trees or graphs.
Code Example
#include <iostream>
#include <string>
using namespace std;
// Point struct
struct Point {
double x;
double y;
double distanceTo(const Point& other) const {
double dx = x - other.x;
double dy = y - other.y;
return dx * dx + dy * dy;
}
void print() const {
cout << "(" << x << ", " << y << ")" << endl;
}
};
// Student struct with a constructor
struct Student {
string name;
int scores[3];
double average;
Student(string n, int v1, int v2, int v3) {
name = n;
scores[0] = v1;
scores[1] = v2;
scores[2] = v3;
computeAverage();
}
void computeAverage() {
average = (scores[0] + scores[1] + scores[2]) / 3.0;
}
void print() const {
cout << "Name: " << name << endl;
cout << "Scores: " << scores[0] << ", " << scores[1] << ", " << scores[2] << endl;
cout << "Average: " << average << endl;
}
};
// Nested struct
struct Address {
string street;
string city;
string province;
};
struct Contact {
string name;
string phone;
Address address; // nested struct
};
// Union for a variant type
union DataSensor {
int intVal;
float floatVal;
char charVal;
};
// Struct with bit fields
struct Flags {
unsigned int active : 1;
unsigned int mode : 3;
unsigned int level : 4;
};
// Enum for the union data type
enum DataType { TYPE_INT, TYPE_FLOAT, TYPE_CHAR };
struct Variant {
DataType type;
union {
int i;
float f;
char c;
} data;
void print() const {
switch (type) {
case TYPE_INT:
cout << "Integer: " << data.i << endl;
break;
case TYPE_FLOAT:
cout << "Float: " << data.f << endl;
break;
case TYPE_CHAR:
cout << "Char: " << data.c << endl;
break;
}
}
};
int main() {
// Creating and using a Point struct
Point p1 = {3.0, 4.0};
Point p2 = {6.0, 8.0};
cout << "Point p1: ";
p1.print();
cout << "Point p2: ";
p2.print();
cout << "Distance^2 p1-p2: " << p1.distanceTo(p2) << endl;
// Student struct with a constructor
cout << "\n";
Student student("Mike", 85, 90, 78);
student.print();
// Nested struct
cout << "\n";
Contact contact;
contact.name = "John";
contact.phone = "08123456789";
contact.address.street = "10 Independence St.";
contact.address.city = "Chicago";
contact.address.province = "Illinois";
cout << "Name: " << contact.name << endl;
cout << "Phone: " << contact.phone << endl;
cout << "Address: " << contact.address.street << ", "
<< contact.address.city << ", " << contact.address.province << endl;
// Pointer to a struct
Point* ptrPoint = &p1;
cout << "\nVia pointer: " << ptrPoint->x << ", " << ptrPoint->y << endl;
ptrPoint->print();
// Array of structs
cout << "\n--- Array of Structs ---" << endl;
Student list[] = {
Student("Alice", 90, 88, 92),
Student("John", 75, 80, 70),
Student("Anna", 95, 92, 98)
};
for (const Student& s : list) {
s.print();
cout << endl;
}
// Union
cout << "--- Union ---" << endl;
DataSensor sensor;
sensor.intVal = 42;
cout << "Int: " << sensor.intVal << endl;
sensor.floatVal = 3.14f;
cout << "Float: " << sensor.floatVal << endl;
cout << "Int (after float was written): " << sensor.intVal << endl;
// Int now holds the reinterpreted bits of the float
sensor.charVal = 'A';
cout << "Char: " << sensor.charVal << endl;
// Sizeof
cout << "\nSizeof Point: " << sizeof(Point) << " bytes" << endl;
cout << "Sizeof DataSensor: " << sizeof(DataSensor) << " bytes" << endl;
cout << "Sizeof Student: " << sizeof(Student) << " bytes" << endl;
// Variant type with a union
cout << "\n--- Variant Type ---" << endl;
Variant v1, v2, v3;
v1.type = TYPE_INT;
v1.data.i = 100;
v1.print();
v2.type = TYPE_FLOAT;
v2.data.f = 2.718f;
v2.print();
v3.type = TYPE_CHAR;
v3.data.c = 'Z';
v3.print();
// Struct as a return value
cout << "\n--- Return Struct ---" << endl;
Point midpoint = {(p1.x + p2.x) / 2, (p1.y + p2.y) / 2};
cout << "Midpoint: ";
midpoint.print();
// Struct initialization
cout << "\n--- Struct Initialization ---" << endl;
Point p3{}; // zero-initialize
cout << "p3 (zero-init): ";
p3.print();
Point p4{10.0, 20.0}; // aggregate initialization
cout << "p4: ";
p4.print();
return 0;
}
Code Explanation
struct Point { double x; double y; ... };— Defines a struct with two data members (x,y) and two methods (distanceTo,print). Methods in a struct work exactly the same as methods in a class.p1.print()— Accesses a struct method with the.operator. Exactly the same way of accessing a class method.Student("Mike", 85, 90, 78)— Calls a struct constructor. The constructor runs when the object is created and can be used for initialization and initial calculations.this->computeAverage()— A struct also has athispointer that points to the object itself, just like a class.Contact::address::street— A nested struct is accessed with chained dot operators.contact.addressreturns anAddressobject, then.streetaccesses its field.ptrPoint->x— The->operator is used when accessing struct members through a pointer. Equivalent to(*ptrPoint).x.Student list[] = { ... }— An array of structs. Each element is a completeStudentobject.sizeof(DataSensor)— The size of a union is the same as the size of its largest member.DataSensoris only the size of afloat(4 bytes) because that is its largest member.sensor.intVal = 42; sensor.floatVal = 3.14f;— Writing to different union members overwrites the previous data. Only one member is valid at a time.union { int i; float f; char c; } data;— An anonymous union inside a struct. Members can be accessed directly asvariant.data.i.unsigned int active : 1;— A bit field. Allocates a specific number of bits.activeis only 1 bit (0 or 1),modeis 3 bits (0-7),levelis 4 bits (0-15). Useful for flags and saving memory.Point p3{};— Zero-initialization. All members are set to 0. Useful for avoiding garbage values.Point p4{10.0, 20.0};— Aggregate initialization. Members are initialized in declaration order.
Everyday Use Cases
Student management system: Each student has a name, student ID, major, and array of grades. The Student struct groups all this data. An array of structs stores data for all students. Methods inside the struct compute the GPA automatically.
Graphics programming: Structs Point, Rectangle, and Color are used to represent graphical objects. A Rectangle can contain two Point objects (top-left and bottom-right).
IoT sensor: Unions are useful when a sensor sends data in various formats. A Variant struct with a union and an enum allows an interpreter to process data without excessive memory allocation.
Conclusion
A struct is a way to group data of different types into a single unit. Structs support methods, constructors, and nested structs. A union stores different types at the same memory location — it saves memory, but only one member is valid at a time. Use structs for complex data such as records and objects, and use unions for situations that require type punting or memory savings, such as embedded systems and parsers.