Skip to content

C++ Data Structures

While built-in types like arrays handle collections of the same data type, C++ allows you to define your own composite data types using struct (structure) and class. These let you group together data items of potentially different types under a single name.

Structures are particularly useful for representing records. Imagine managing a digital library; you’d want to track several attributes for each book:

  • Title (string)
  • Author (string)
  • Subject (string)
  • ISBN (string or number)
  • Publication Year (number)

You define a structure using the struct keyword. This creates a new custom data type composed of other types (members). The basic syntax is:

struct StructureName {
DataType1 memberName1;
DataType2 memberName2;
// ... other members
};
// Optional: Variable declaration: StructureName variable1, variable2;

StructureName becomes a new type in your program. Each member definition is like a standard variable declaration (e.g., int year;, std::string title;). You can declare variables of this new type immediately after the definition or later in your code. Here’s how you might declare a Book structure using modern C++ types like std::string:

#include <string> // Required for std::string
#include <vector> // Often useful for collections of structs
struct Book {
std::string title;
std::string author;
std::string subject;
std::string isbn; // Using string for ISBN is flexible
int publicationYear;
};
// Now you can declare variables of type Book:
Book myFavoriteBook;

Note: In C++, unlike C, you don’t need to write struct Book myFavoriteBook;. You can directly use the structure name Book as the type.

To access a member of a structure variable, use the member access operator (.), which is a period placed between the variable name and the member name.

Here’s an example demonstrating how to define, declare, initialize, and access members of Book objects:

#include <iostream>
#include <string>
#include <vector>
// Use the Book struct defined previously
struct Book {
std::string title;
std::string author;
std::string subject;
std::string isbn;
int publicationYear;
};
int main() {
// Declare and initialize Book objects using designated initializers (C++20)
// or aggregate initialization (earlier C++ versions)
Book book1 = {"The Hitchhiker's Guide", "Douglas Adams", "Science Fiction", "978-0345391803", 1979};
Book book2; // Declare another book
// Assign values using the member access operator (.)
book2.title = "Effective Modern C++";
book2.author = "Scott Meyers";
book2.subject = "C++ Programming";
book2.isbn = "978-1491903995";
book2.publicationYear = 2014;
// Print Book1 info
std::cout << "Book 1 Title: " << book1.title << std::endl;
std::cout << "Book 1 Author: " << book1.author << std::endl;
std::cout << "Book 1 Year: " << book1.publicationYear << std::endl;
std::cout << "--------------------" << std::endl;
// Print Book2 info
std::cout << "Book 2 Title: " << book2.title << std::endl;
std::cout << "Book 2 Author: " << book2.author << std::endl;
std::cout << "Book 2 Year: " << book2.publicationYear << std::endl;
return 0;
}

When compiled and executed, this code might produce:

Book 1 Title: The Hitchhiker's Guide
Book 1 Author: Douglas Adams
Book 1 Year: 1979
--------------------
Book 2 Title: Effective Modern C++
Book 2 Author: Scott Meyers
Book 2 Year: 2014

You can pass structures to functions just like any other variable. However, passing large structures by value (which creates a copy) can be inefficient. It’s often better to pass them by reference (&) or, if the function shouldn’t modify the structure, by constant reference (const&).

#include <iostream>
#include <string>
#include <vector>
// Use the Book struct defined previously
struct Book {
std::string title;
std::string author;
std::string subject;
std::string isbn;
int publicationYear;
};
// Function to print book details (passes by const reference to avoid copying)
void printBook(const Book& book) { // Note: const Book&
std::cout << "Title: " << book.title << std::endl;
std::cout << "Author: " << book.author << std::endl;
std::cout << "Subject: " << book.subject << std::endl;
std::cout << "ISBN: " << book.isbn << std::endl;
std::cout << "Year: " << book.publicationYear << std::endl;
std::cout << "--------------------" << std::endl;
}
int main() {
Book book1 = {"The Hitchhiker's Guide", "Douglas Adams", "Science Fiction", "978-0345391803", 1979};
Book book2 = {"Effective Modern C++", "Scott Meyers", "C++ Programming", "978-1491903995", 2014};
// Print info using the function
printBook(book1);
printBook(book2);
return 0;
}

Passing by const& signals that the function will not change the Book object and allows the compiler to potentially optimize by avoiding a full copy.

You can also work with pointers to structures, similar to pointers to other types:

Book* bookPtr;

You can assign the address of a structure variable to a pointer using the address-of operator (&):

Book book1 = {/* ... initializers ... */};
bookPtr = &book1;

To access members of a structure through a pointer, use the arrow operator (->):

std::cout << "Title: " << bookPtr->title << std::endl;

Here’s the previous example rewritten to use pointers when calling printBook:

#include <iostream>
#include <string>
#include <vector>
// Use the Book struct defined previously
struct Book {
std::string title;
std::string author;
std::string subject;
std::string isbn;
int publicationYear;
};
// Function now accepts a pointer to a constant Book
void printBookPtr(const Book* bookPtr) { // Note: const Book*
if (bookPtr == nullptr) { // Always check pointers!
std::cerr << "Error: Null pointer passed to printBookPtr.\n";
return;
}
std::cout << "Title: " << bookPtr->title << std::endl; // Use -> operator
std::cout << "Author: " << bookPtr->author << std::endl;
std::cout << "Subject: " << bookPtr->subject << std::endl;
std::cout << "ISBN: " << bookPtr->isbn << std::endl;
std::cout << "Year: " << bookPtr->publicationYear << std::endl;
std::cout << "--------------------" << std::endl;
}
int main() {
Book book1 = {"The Hitchhiker's Guide", "Douglas Adams", "Science Fiction", "978-0345391803", 1979};
Book book2 = {"Effective Modern C++", "Scott Meyers", "C++ Programming", "978-1491903995", 2014};
// Print info by passing the address of each book
printBookPtr(&book1);
printBookPtr(&book2);
return 0;
}

While pointers are fundamental, modern C++ often favors references or smart pointers (std::unique_ptr, std::shared_ptr) over raw pointers to manage memory and object lifetime more safely. Passing by reference (const&) as shown in the previous section is generally preferred over passing by raw pointer when simply accessing object data.

While C++ automatically makes the structure name (Book) available as a type, you might want to create aliases for complex types. The modern C++ way to do this is with the using keyword:

// Alias for a potentially complex type
using Bookshelf = std::vector<Book>;
// Alias for a pointer to a Book
using BookPtr = Book*;
int main() {
Bookshelf myLibrary; // Use the alias 'Bookshelf'
myLibrary.push_back({"Book Title", /*...*/});
Book book3 = {/*...*/};
BookPtr ptrToBook3 = &book3; // Use the alias 'BookPtr'
std::cout << ptrToBook3->title << std::endl;
return 0;
}

This using syntax is generally preferred over the older C-style typedef for creating type aliases in modern C++ because it’s more consistent, especially with templates.

In C++, struct and class are very similar. The main technical difference is their default member visibility: members of a struct are public by default, while members of a class are private by default. Conventionally, struct is often used for simple data aggregates where direct member access is intended, while class is used when you want to enforce encapsulation (hiding data and providing controlled access through methods). You’ll learn more about classes and encapsulation later.