Structured and Related Data
Real-world programs frequently work with records composed of several related values. A student has a name, an age, and marks. A product has an identifier, a name, a price, and an availability status. Although these values have different data types, they collectively describe one entity.
C++ structures make it possible to combine such related values into a single user-defined type. This keeps records organized, improves readability, and makes collections of related entities easier to process.
The Problem with Separate Data Structures
Suppose a program must read records for n students and print them in reverse order. Every record contains:
A name stored as a
string.An age stored as an
int.Marks stored as a
double.
One possible solution is to use three parallel vectors. The values at the same index collectively represent one student.
#include <iostream>#include <string>#include <vector>using namespace std;int main() { int n; cin >> n; vector<string> names(n); vector<int> ages(n); vector<double> marks(n); for (int index = 0; index < n; index++) { cin >> names[index] >> ages[index] >> marks[index]; } for (int index = n - 1; index >= 0; index--) { cout << names[index] << ' ' << ages[index] << ' ' << marks[index] << '\n'; } return 0;}The solution works, but each student's information is scattered across three vectors. To access one student's complete record, the program must read the same index from three separate containers.
The vectors must also remain perfectly synchronized. If an element is inserted into one vector but not the others, the records become misaligned. A structure avoids this problem by storing all the information belonging to one student together.
Defining a Structure
A structure is defined using the struct keyword, followed by the name of the new type. The variables declared inside it are called members or data members.
#include <string>using namespace std;struct Student { string name; int age; double marks;};Student learner{"Aman", 18, 91.5};The semicolon after the closing brace is required. The definition introduces Student as a new type. It does not create a student object by itself.
The declaration of learner creates one Student object. Its values follow the order in which the members were declared:
"Aman"initializesname.18initializesage.91.5initializesmarks.
Accessing Structure Members
The member-access operator, represented by a dot, accesses a member of a structure object.
learner.nameaccesses the student's name.learner.ageaccesses the student's age.learner.marksaccesses the student's marks.
Members can be read, printed, compared, and updated just like independent variables of the corresponding types.
#include <iostream>#include <string>using namespace std;struct Student { string name; int age; double marks;};int main() { Student learner{"Aman", 18, 91.5}; cout << learner.name << '\n'; cout << learner.age << '\n'; cout << learner.marks << '\n'; learner.marks += 2.5; cout << learner.marks << '\n';}Using a Vector of Structures
Once Student has been defined, a vector can store complete Student objects. Each vector element now represents one complete student rather than one isolated field.
#include <iostream>#include <string>#include <vector>using namespace std;struct Student { string name; int age; double marks;};int main() { int n; cin >> n; vector<Student> details(n); for (int index = 0; index < n; index++) { cin >> details[index].name >> details[index].age >> details[index].marks; } for (int index = n - 1; index >= 0; index--) { cout << details[index].name << ' ' << details[index].age << ' ' << details[index].marks << '\n'; } return 0;}At index i, details[i] is a complete Student object. The dot operator then selects the required member. All related values remain grouped in one record.
Type and Object
A type describes the form of data that objects of that type will contain. An object is an actual instance that stores values.
In int value = 2;, int is the type and value is an object. Similarly, after defining a Point structure, Point is a user-defined type, while first and second can be separate objects of that type.
#include <iostream>#include <string>using namespace std;struct Point { int x; int y;};struct Product { int id; string name; double price; bool available;};int main() { Point first{3, 5}; Point second{8, 2}; Product item{101, "Keyboard", 1499.0, true}; cout << first.x << ' ' << first.y << '\n'; cout << second.x << ' ' << second.y << '\n'; cout << item.name << ' ' << item.price << '\n';}A structure is a user-defined type, but it is still a data type in the general C++ sense. It can be used wherever an appropriate type is expected: for variables, function parameters, return values, arrays, vectors, references, and pointers.
Members Can Have Different Types
The members of a structure do not need to share the same type. A Product can contain an integer identifier, a string name, a decimal price, and a Boolean availability flag.
Every member should describe some part of the same logical entity. Grouping unrelated values merely because a structure permits it would make the design harder to understand.
Aggregate Initialization
A simple structure can be initialized using braces. Positional values correspond to members in declaration order.
For the following type:
The first initializer belongs to
name.The second belongs to
age.The third belongs to
marks.
Positional aggregate initialization cannot skip a member in the middle. For example, placing 89.2 in the second position attempts to initialize the integer age. Because braced initialization rejects narrowing conversions, such an initialization is ill-formed rather than silently moving the value to marks.
Default Member Initializers
Default member initializers provide fallback values. They are used when an object does not explicitly supply a value for the corresponding member.
#include <iostream>#include <string>using namespace std;struct Student { string name = "Unknown"; int age = 9; double marks = 100.0;};int main() { Student first{"Aman", 18, 91.5}; Student second{}; Student third{"Rahul"}; cout << first.name << ' ' << first.age << ' ' << first.marks << '\n'; cout << second.name << ' ' << second.age << ' ' << second.marks << '\n'; cout << third.name << ' ' << third.age << ' ' << third.marks << '\n';}The objects receive the following values:
firstuses all three explicitly supplied values.seconduses all three default member initializers.thirduses"Rahul"for the name and the defaults for age and marks.
Without default member initializers, a declaration such as Student learner; default-constructs the string as empty, but fundamental members such as int and double can remain indeterminate. Reading such members before assigning values causes undefined behavior.
Using Student learner{}; value-initializes omitted fundamental members to zero when no explicit default member initializers exist.
Updating Members
A member can be updated using ordinary assignment and compound-assignment operators:
student.age = 20;replaces the age.student.marks += 5;adds five marks.product.available = false;changes the availability status.
The object is selected first, followed by the dot operator and the member name.
Independent Objects and Structure Copies
Assigning one structure object to another copies its members. The two objects are independent afterward when their members are value-like types such as integers, strings, and doubles.
This is similar to copying an integer. If y receives the value of x, changing y later does not modify x.
#include <iostream>#include <string>using namespace std;struct Student { string name; int age; double marks;};int main() { Student original{"Aman", 18, 91.5}; Student copy = original; copy.name = "Rahul"; copy.marks = 95.0; cout << original.name << ' ' << original.marks << '\n'; cout << copy.name << ' ' << copy.marks << '\n';}Changing copy does not change original. Each member was copied into a separate object.
Passing Structures to Functions
Pass by Value
Passing a structure by value creates a copy for the function. Modifications made to that parameter do not affect the caller's object. For large structures, the copy may also be unnecessarily expensive.
Pass by Constant Reference
A constant reference provides efficient read-only access to the original object:
const Student& student
No structure copy is made, and the function cannot modify the object through that reference.
Pass by Non-Constant Reference
A non-constant reference provides access to the original object and allows the function to modify it:
Student& student
#include <iostream>#include <string>using namespace std;struct Student { string name; int age; double marks;};void printStudent(const Student& student) { cout << student.name << ' ' << student.age << ' ' << student.marks << '\n';}void addBonus(Student& student, double bonus) { student.marks += bonus;}int main() { Student learner{"Aman", 18, 91.5}; printStudent(learner); addBonus(learner, 2.0); printStudent(learner); return 0;}Returning a Structure
A function can return a structure object just as it can return an integer, string, or double. The function's return type must be the structure type.
This is useful when a function constructs a record or computes several related results. Returning a small structure by value is normal C++; compilers can usually eliminate unnecessary copies.
Traversing a Vector of Structures
A vector of structures can be traversed using indexes or a range-based loop.
An index-based loop is useful when the position is required.
A range-based loop by value copies each structure.
A range-based loop using
const Type&reads each original object without copying it.A range-based loop using
Type&permits modification of each original object.
#include <iostream>#include <string>#include <vector>using namespace std;struct Student { string name; int marks;};int main() { vector<Student> students{ {"Aman", 82}, {"Rhea", 94}, {"Kabir", 89} }; for (const Student& student : students) { cout << student.name << ' ' << student.marks << '\n'; } return 0;}Nested Structures
A structure member can itself be another structure. This is useful when one entity naturally contains another structured entity.
For example, an employee has a name and an address. The address contains a city and a PIN code. Instead of placing all these fields directly inside Employee, an independent Address type can represent the address.
#include <iostream>#include <string>using namespace std;struct Address { string city; int pinCode;};struct Employee { string name; Address address;};int main() { Employee person{ "Aman", {"Pune", 411001} }; cout << person.name << '\n'; cout << person.address.city << '\n'; cout << person.address.pinCode << '\n';}Access begins with the outer object:
person.addressaccesses the nestedAddressobject.person.address.cityaccesses the city inside that address.person.address.pinCodeaccesses the PIN code.
Structures can be nested through multiple levels when the data model requires it.
Pointers to Structures
A pointer can store the address of a structure object. If pointer points to a Student, a member can be accessed by dereferencing the pointer and then applying the dot operator:
(*pointer).marks
Parentheses are required because the dot operator has higher precedence than the dereference operator. C++ provides the arrow operator as a shorter equivalent:
pointer->marks
#include <iostream>#include <string>using namespace std;struct Student { string name; int age; double marks;};int main() { Student learner{"Aman", 18, 91.5}; Student* pointer = &learner; cout << (*pointer).name << '\n'; cout << pointer->age << '\n'; pointer->marks += 3.0; cout << learner.marks << '\n'; return 0;}Both expressions access the same object. Updating a member through the pointer updates learner because the pointer refers to that original object.
Problem 1: Create a Point
Given two integers x and y, create and return a Point structure containing those coordinates.
For input 3 5, the resulting object contains x = 3 and y = 5.
Approach
Define a
Pointstructure withxandymembers.Create a
Pointobject inside the function.Initialize its members with the supplied coordinates.
Return the complete structure object.
#include <iostream>using namespace std;struct Point { int x; int y;};Point createPoint(int x, int y) { Point coordinates{x, y}; return coordinates;}int main() { int x, y; cin >> x >> y; Point point = createPoint(x, y); cout << point.x << ' ' << point.y << '\n';}Complexity Analysis
Time complexity:
O(1).Auxiliary space:
O(1).
Problem 2: Move a Point
Given a point and two displacement values, dx and dy, move the point by adding dx to its x-coordinate and dy to its y-coordinate.
For point (3, 5), dx = 2, and dy = -1:
The new x-coordinate is
3 + 2 = 5.The new y-coordinate is
5 + (-1) = 4.
The resulting point is (5, 4).
#include <iostream>using namespace std;struct Point { int x; int y;};Point movePoint(Point point, int dx, int dy) { point.x += dx; point.y += dy; return point;}int main() { int x, y; cin >> x >> y; int dx, dy; cin >> dx >> dy; Point point{x, y}; point = movePoint(point, dx, dy); cout << point.x << ' ' << point.y << '\n';}The function receives the point by value, modifies its local copy, and returns the updated object. The caller assigns the returned point back to its variable.
Complexity Analysis
Time complexity:
O(1).Auxiliary space:
O(1).
Problem 3: Rectangle Measurements
Given the length and width of a rectangle, calculate its area and perimeter. Because the function produces two related results, a Measurements structure can return them together.
The formulas are:
area = length * widthperimeter = 2 * (length + width)
For a rectangle with length 5 and width 3:
Area is
5 * 3 = 15.Perimeter is
2 * (5 + 3) = 16.
#include <iostream>using namespace std;struct Rectangle { double length; double width;};struct Measurements { double area; double perimeter;};Measurements computeMeasurements(const Rectangle& rectangle) { Measurements result; result.area = rectangle.length * rectangle.width; result.perimeter = 2 * (rectangle.length + rectangle.width); return result;}int main() { double length, width; cin >> length >> width; Rectangle rectangle{length, width}; Measurements result = computeMeasurements(rectangle); cout << result.area << '\n'; cout << result.perimeter << '\n';}The rectangle is passed by constant reference because the function only needs to read it. The two calculated values are returned together as one structured result.
Complexity Analysis
Time complexity:
O(1).Auxiliary space:
O(1).
Problem 4: Find a Product by ID
Given a list of products and a target product ID, return the index of the first product whose ID matches the target. Return -1 if no matching product exists.
Every product contains an ID and a name. The complete product object cannot be compared directly with an integer target. The search must specifically compare products[index].id with targetId.
For example, if product ID 205 appears at index 1, the function returns 1. A target such as 2059, when absent, produces -1.
#include <iostream>#include <string>#include <vector>using namespace std;struct Product { int id; string name;};int findById(const vector<Product>& products, int targetId) { for (int index = 0; index < products.size(); index++) { if (products[index].id == targetId) { return index; } } return -1;}int main() { int n; cin >> n; vector<Product> products(n); for (int index = 0; index < n; index++) { cin >> products[index].id >> products[index].name; } int targetId; cin >> targetId; cout << findById(products, targetId) << '\n';}The vector is passed by constant reference because the search should neither copy nor modify it.
Complexity Analysis
Time complexity:
O(n)in the worst case.Auxiliary space:
O(1), excluding the input vector.
Problem 5: Find the Top Student
Given a non-empty list of students, find the student with the highest marks. If multiple students have the same highest marks, return the first one.
For the records:
Aman 82Rhea 94Kabir 89
Rhea is the top student. If Kabir's marks are also changed to 94, Rhea must still be selected because she appears first.
Approach
Because the list is non-empty, initialize the current top student with the first element.
Start checking from index
1.Replace the current top only when a student has strictly greater marks.
Return the final
Studentobject.
A strict > comparison preserves the first student in a tie. Using >= would repeatedly replace the current answer and eventually select the last tied student.
#include <iostream>#include <string>#include <vector>using namespace std;struct Student { string name; int marks;};Student findTopStudent(const vector<Student>& students) { Student top = students[0]; for (int index = 1; index < students.size(); index++) { if (students[index].marks > top.marks) { top = students[index]; } } return top;}int main() { int n; cin >> n; vector<Student> students(n); for (int index = 0; index < n; index++) { cin >> students[index].name >> students[index].marks; } Student top = findTopStudent(students); cout << top.name << ' ' << top.marks << '\n';}Complexity Analysis
Time complexity:
O(n).Auxiliary space:
O(1), excluding the input vector.
Problem 6: Update an Employee's Address
Given an employee and a new address, replace the employee's existing address with the new one. This problem demonstrates updating a nested structure.
The function can receive the employee by non-constant reference so that it modifies the original object. The new address can be received by constant reference because it only needs to be read.
#include <iostream>#include <string>using namespace std;struct Address { string city; int pinCode;};struct Employee { string name; Address address;};void updateAddress(Employee& employee, const Address& newAddress) { employee.address = newAddress;}int main() { Employee employee{ "Aman", {"Pune", 411001} }; Address newAddress{"Mumbai", 400001}; updateAddress(employee, newAddress); cout << employee.name << '\n'; cout << employee.address.city << '\n'; cout << employee.address.pinCode << '\n';}Assigning newAddress to employee.address copies both address members into the nested object.
Complexity Analysis
Time complexity:
O(1)for these fixed-size records.Auxiliary space:
O(1).
Key Takeaways
A structure combines related values, even when those values have different types.
The
structdefinition introduces a user-defined type; objects are individual instances of that type.Structure members are accessed using the dot operator.
Brace initializers correspond to members in declaration order.
Default member initializers provide safe fallback values.
Copying a structure normally creates an independent object containing copies of its members.
Use a constant reference for efficient read-only function parameters.
Use a non-constant reference when a function must modify the caller's object.
Functions can return complete structure objects.
A vector of structures keeps complete records together.
Structures can contain other structures, producing nested records.
Members accessed through a structure pointer can use either
(*pointer).memberorpointer->member.Structures improve readability by giving related data a meaningful name and a unified representation.
Be the first to add a comment.