C++ is one of the oldest programming languages, created back in 1979. It has remained a crucial language for modern developers for its uses in large system performance for fields like video game development, operating systems, browsers, and both office and medical software.
It’s easy to see why developers want to learn C++. If you’re already learning C++, you’ve probably noticed that it can be challenging to pick up.
Worry not! C++ is still a valuable language to learn, so today we’ll walk you through some intermediate C++ concepts and examples to get you one step closer to mastering this challenging but sought-after language.
Here are the topics we’ll cover today:
- Using object-oriented programming in C++
- How do Strings work in C++?
- What is a Pointer in C++?
- What are Arrays in C++?
- What is a Vector in C++?
- Using C++ Maps
- Memory Management in C++
- What to Learn Next
Using object-oriented programming in C++
C++ is an object-oriented language. This paradigm is one of its defining differences from C and C#. In C++, we accomplish this by using objects and classes to store and manipulate data. Through this object-oriented approach, developers can implement the strategies of inheritance, polymorphism, abstraction, and encapsulation.
For a refresher on object-oriented concepts and terms, see What is Object Oriented Programming? OOP Explained in Depth.
When learning C++, it’s important to understand how to make the most of its OOP capabilities. Below, we’ll explore some examples of each of the core OOP strategies: inheritance, polymorphism, abstraction, and encapsulation.
Using Inheritance in C++
Setting up inheritance relationships in C++ is easy; we simply append a class declaration with : [parent name]()
. This allows us to share both public variables and methods from parent class to child.
Below, you’ll see how we can use this to create the BankAccount
class, pulling from the Account
class.
#include <iostream>
class Account{
public:
Account(double b): balance(b){}
void deposit(double amt){
balance += amt;
}
void withdraw(double amt){
balance -= amt;
}
double getBalance() const {
return balance;
}
private:
double balance;
};
class BankAccount: public Account{
public:
// using Account::Account;
BankAccount(double b): Account(b){}
void addInterest(){
deposit( getBalance()*0.05 );
}
};
int main(){
std::cout << std::endl;
BankAccount bankAcc(100.0);
bankAcc.deposit(50.0);
bankAcc.deposit(25.15);
bankAcc.withdraw(30);
bankAcc.addInterest();
std::cout << "bankAcc.getBalance(): " << bankAcc.getBalance() << std::endl;
std::cout << std::endl;
}
-->
bankAcc.getBalance(): 152.407
Above, we create two classes: the parent Account
and the child BankAccount
. We defined the public functions deposit
, withdraw
, and getBalance
in Account
as well as addInterest
in BankAccount
.
In our main section, we see how we use all of these functions on our new BankAccount
object, bankAcc
, regardless of if the function is from the parent or child class.
Using Polymorphism in C++
One of the most common applications of polymorphism in C++ is through function overriding. By having two functions of the same name in different classes, we can create code that completes different behavior depending on the inheritance of the selected object.
Below, we’ll see an example of how polymorphism can be used to implement a sample Draw
function across different shape classes:
To focus on overridden functions, we’ve just filled each
Draw
with a simple, uniquecount
statement. However, this could be replaced by a drawing algorithm and behave in the same way.
#include <iostream>
class Shape {
public:
Shape() {}
//defining a virtual function called Draw for shape class
virtual void Draw() { std::cout << "Drawing a Shape" << std::endl; }
};
class Rectangle : public Shape {
public:
Rectangle() {}
//Draw function defined for Rectangle class
virtual void Draw() { std::cout << "Drawing a Rectangle" << std::endl; }
};
class Triangle : public Shape {
public:
Triangle() {}
//Draw function defined for Triangle class
virtual void Draw() { std::cout << "Drawing a Triangle" << std::endl; }
};
class Circle : public Shape {
public:
Circle() {}
//Draw function defined for Circle class
virtual void Draw() { std::cout << "Drawing a Circle" << std::endl; }
};
int main()
{
Shape* s;
Triangle tri;
Rectangle rec;
Circle circ;
// store the address of Rectangle
s = &rec;
// call Rectangle Draw function
s->Draw();
// store the address of Triangle
s = &tri;
// call Triangle Draw function
s->Draw();
// store the address of Circle
s = ˆ
// call Circle Draw function
s->Draw();
return 0;
}
-->
Drawing a Rectangle
Drawing a Triangle
Drawing a Circle
Above we see a parent class, shape
, and three child classes, Triangle
, Rectangle
, and Circle
. The process of drawing each shape will differ depending on the shape itself. This leads us to use polymorphism to define a Draw
function for each of the classes.
C++ will prioritize the local function over the parent Draw
function by default. This means that we can call Draw
without worrying about which shape class the object is. If it is a rectangle, the output will be “Drawing a rectangle”. If it's a triangle, it will output “Drawing a triangle”, and so on.
Abstraction and Encapsulation
Abstraction works similarly in C++ as other hard-coded languages through the use of the private
and public
keywords. This is similar to abstraction, as abstraction acts to hide non-essential information. Encapsulation can be used to achieve abstraction via private getter and setter functions.
Below, we’ll see how to achieve both abstraction and encapsulation in C++:
#include <iostream>
using namespace std;
class abstraction
{
private:
int a, b;
public:
// public setter function
void set(int x, int y)
{
a = x;
b = y;
}
// public getter function
void display()
{
cout<<"a = " <<a << endl;
cout<<"b = " << b << endl;
}
};
int main()
{
abstraction obj;
obj.set(1, 2);
obj.display();
return 0;
}
-->
a = 1
b = 2
Here we first create an abstraction
class that initializes private variables a
and b
. We also define two public functions, set
and display
. This achieves encapsulation by separating variables from the outside world, so we can only access them via public functions. Using set
, we then change the values from inside main
. Finally, we print the variables using display
.
This ensures that users cannot directly change variables but can use them through the getter and setter functions, achieving abstraction by keeping the variables themselves secure and hidden from users.
How do Strings work in C++?
Strings in C++ are similar to those of other languages in that they’re a collection of ordered characters. However, in C++, there are two ways to create strings, either using C-style strings or the C++ string
class.
C style strings
are the old-fashioned way of creating strings in C++. Rather than a standard string object, C-style strings
consist of a null-terminated array of characters ending with the special character \0
. Due to this hidden character, C-style strings are always a length of one more character than the visible number of characters.
This size can either be unspecified to be set automatically to the required size or manually to any desired size.
char str[] = "Educative"; // automatic length set to 10
char str[10] = "Educative"; // manual set length to 10
We can also create strings in C++ using the C++ string
class that is built into the standard C++ library. This is the more popular method, as all memory management, allocation, and null termination are internally handled by the class. Another advantage is that the length of the string can be changed at runtime thanks to the dynamic allocation of memory.
Overall, these changes make the string
class more resistant to errors and provide many built-in functions like append()
, which adds to the end of the string, and length()
, which returns the number of characters in the string.
Below, we’ll learn how to use functions like these to complete common manipulations of strings.
How to print a string
We can print strings in C++ using the cout
global object along with the <<
operator, which precedes the printed content. We also include the endl
global object, which is used to skip a line after the operation for better readability. As both endl
and cout
are predefined objects of the global class ostream
, we must make sure to include the <iostream>
header in programs where they are needed.
Below we can see how we’d initialize and print str1
:
#include <iostream>
#include <string>
using namespace std;
int main() {
string str1 ("printed string"); //initializes the string
cout << str1 << endl; //prints string
return 0;
}
-->
printed string
How to calculate the length of a string
To calculate the length of a string, we can use either the length()
or size()
functions. Each performs identically, and each exists to aid in readability. These functions can also be used to measure the length of STL containers, like maps and vectors.
The synonymous functions are included to increase readability, while the length is intuitive to use for string. It would be more intuitive to refer to an array’s size than its length.
Below we’ll see how to print the length of string str1
with both the length
and size
functions.
#include <iostream>
#include <string>
using namespace std;
int main() {
string str1 = "Hello"; //initilization
//calculate length
cout << "myStr's length is " << str1.length() << endl;
cout << "myStr's size is " << str1.size() << endl;
return 0;
}
-->
myStr's length is 5
myStr's size is 5
How to concatenate a string
This final manipulation is a fancy way of saying “stick together two strings”. When doing this in C++ we can use either the +
operator or the predefined append
function which each achieve the same effect.
For simple test code, there is close to no difference between the two options. When used in larger, more complicated programs, however, append
will perform significantly faster than +
.
#include <iostream>
#include <string>
using namespace std;
int main() {
string str1= "combined ";
string str2 = "strings";
string str3 = str1 + str2;
cout << str3 << endl;
//OR
string str4 = str1.append(str2);
cout << str4;
}
-->
combined strings
combined strings
What is a Pointer in C++?
In C++, all variables must be stored somewhere within the host computer’s memory. To help programs find these variables without searching the memory, C++ allows us to use the special variable, pointers, to explicitly give the variable’s address.
Pointers carry two pieces of information:
- The memory address stored as the value of the pointer
- The data type indicating the type of variable it points to
Declaring a pointer is similar to declaring a standard variable, except that the pointer’s name is preceded by an asterisk.
int *ptr;
struct coord *pCrd;
void *vp;
Let's see how this can be used below:
#include <iostream>
using namespace std;
int main ()
{
int val1, val2;
int * mypointer;
mypointer = &val1;
*mypointer = 10;
mypointer = &val2;
*mypointer = 20;
cout << "firstvalue is " << val1 << '\n';
cout << "secondvalue is " << val2 << '\n';
return 0;
}
-->
firstvalue is 10
secondvalue is 20
First, we initialize two int variables, val1
, and val2
as well as an int pointer, mypointer
. We then set mypointer
to the address of val1
using the &
operator. Then the value mypointer
points to is set to 10
. Since mypointer
currently points to the address of val1
, this operation changes the value of val1
.
We then repeat that process, setting mypointer
to the address of val2
and the value at that location to 20
.
Pointers have two main advantages in C++: speed and memory use. Using pointers reduces runtime, as programs can access values more quickly when they are given direct memory addresses.
Pointer cheat sheet
As pointers are one of the more unique elements of C++, it can be tricky to remember all you can do with them. For help, here's our quick guide on basic pointer syntax:
What are Arrays in C++?
C++ arrays are a collection of similar data types stored under the same name. They’re often visualized as a row of i
boxes that can be selected by calling the box’s index to access the value held within.
Tip: Array index values begin at 0, meaning the first element in the array would be accessed by calling the element
0
rather than1
The length of an array is set (either explicitly or implicitly) at declaration and cannot change without remaking the array entirely. This, however, makes the array a very memory-efficient structure compared to the vector, as no memory is used after the array is initialized.
#include <iostream>
using namespace std;
int main() {
int arr[5] = {19, 10, 5, 6, 14}; //initializing the array with 5 values
cout << "The value of arrr[0], that is, the first value in the array is: " << arr[0] << endl;
cout<< "The value of arrr[1], that is, the second value in the array is: " << arr[1] << endl;
cout<< "The value of arrr[2], that is, the third value in the array is: " << arr[2] << endl;
cout<< "The value of arrr[3], that is, the fourth value in the array is: " << arr[3] << endl;
cout<< "The value of arrr[4], that is, the fifth value in the array is: " << arr[4] << endl;
int arr2[] = {1,2,3,4}; //we don't specify the size and the compiler assumes a size of 4
}
-->
The value of arrr[0], that is, the first value in the array is: 19
The value of arrr[1], that is, the second value in the array is: 10
The value of arrr[2], that is, the third value in the array is: 5
The value of arrr[3], that is, the fourth value in the array is: 6
The value of arrr[4], that is, the fifth value in the array is: 14
How to find the length of an array in C++
Unlike STL containers and strings, we cannot find the length of an array using length
or size
. Instead, we use either the sizeof()
operator or by using pointer arithmetic.
Let's see how we can use each, starting with sizeof
:
#include <iostream>
using namespace std;
int main() {
int arr[] = {1,2,3,4,5,6};
int arrSize = sizeof(arr)/sizeof(arr[0]);
cout << "The size of the array is: " << arrSize;
return 0;
}
-->
The size of the array is: 6
Unlike the length
function, sizeof
actually returns the number of bytes the selected object takes up in memory. The size of each element of an array varies from array to array, so we cannot assume it is only one byte.
To get around this, we use each element within the same array and will use the same amount of memory. Therefore, if we divide the total bytes used by the array, sizeof(arr)
, by the number of bytes used by the first element, sizeof(arr[0])
, we get the number of elements in the array.
Another way to find the size is using pointer arithmetic:
#include <iostream>
using namespace std;
int main() {
int arr[] = {1,2,3,4,5,6};
int arrSize = *(&arr + 1) - arr;
cout << "The size of the array is: " << arrSize;
return 0;
}
-->
The size of the array is: 6
We can achieve a similar effect if we consider that the size of an array is equal to the difference between the address of the final element and the first element of the array.
Here’s a step-by-step breakdown:
-
(&arr + 1)
points to the memory address right after the end of the array. -
(&arr + 1)
simply casts the above address to anint *
. - Subtracting the address of the start of the array from the address of the end of the array gives the length of the array.
What is a Vector in C++?
C++ vectors are STL containers that act as a more refined version of string arrays. They simplify the process of inserting and deleting values at the cost of using more memory. Vectors store elements in a contiguous manner, so the elements are stored in memory side by side. Unlike arrays, vectors are dynamic, so their size can change on demand and can be traversed using iterators like begin()
(for the beginning of the vector) and end()
(for the end of the vector).
Vectors are best for situations where you will add or subtract values regularly, and memory is largely available.
First, let's see how to use the resize
function to activate the vector’s handy dynamic capabilities:
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> numbers;
numbers.resize(7);
cout<<numbers.size()<<endl;
numbers.resize(4);
cout<<numbers.size();
}
-->
7
4
Resize sets the maximum number of elements in a vector to the specified number. Above, we first initialize the vector numbers
, then set its size using resize(7)
. Say we then realize that we will cut 3 of the 7 elements. We would use resize(4)
to trim the extra 3 elements to avoid unused elements cluttering the vector.
Below, we’ll see how to create a vector, use the push_back
function to add values, and begin
and end
functions to print:
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> v; // Vector's Implementation
// Inserting Values in Vector
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
cout << "Output from begin to end: ";
for (auto i = v.begin(); i != v.end(); ++i)
cout << *i << " ";
}
-->
Output from begin to end: 1 2 3 4 5
Tip: Vectors resize automatically to fit new elements when using
push_back
orinsert
.
In C++, we must first initialize vector v
and then populate it with values. We want the values to go on the end, not beginning, so we use the push_back
function, which inserts the new element on the end. From there, we print each value in the vector between its beginning, selected with the begin
function, and end, selected with the end
function.
Using C++ Maps
Maps are a type of container that stores elements using key-value pairs. Each element on a map has a unique key as an identifier and a value that is retrieved when the key is called. Keys are automatically sorted from smallest to largest, which makes searching maps very quick.
When initialized, both the key and value data types must be set as well as the map object’s name.
Tip: Integers are the most common type of key used with maps; however, they can be other types as well.
The most important functions used with maps are insert()
, which adds a new element to the map, and find()
, which pulls the element with a corresponding key and returns its value.
Below, we see both of these functions in action with our Employees
map:
#include <string.h>
#include <iostream>
#include <map>
#include <utility>
using namespace std;
int main()
{
// Initializing a map with integer keys
// and corresponding string values
map<int, string> Employees;
//Inserting values in map using insert function
Employees.insert ( std::pair<int, string>(101,"Aaron") );
Employees.insert ( std::pair<int, string>(102,"Amanda") );
Employees.insert ( std::pair<int, string>(105,"Ryan") );
// Finding the value corresponding to the key '102'
std::map<int, string>::iterator it = Employees.find(102);
if (it != Employees.end()){
std::cout <<endl<< "Value of key = 102 => " << Employees.find(102)->second << '\n';
}
}
-->
Value of key = 102 => Amanda
Maps are helpful for containing data that will be searched often and breakdown well into an associative structure, such as a company’s email registry.
How to sort a map by value in C++
As stated above, maps sort by key automatically. However, it may be beneficial to sort by value instead. We can do this by copying the elements to a vector of key-value pairs and then sorting the vector before finally printing.
Let's see that in action:
#include <iostream>
#include <map>
#include <vector>
#include <algorithm> // for sort function
using namespace std;
// utility comparator function to pass to the sort() module
bool sortByVal(const pair<string, int> &a,
const pair<string, int> &b)
{
return (a.second < b.second);
}
int main()
{
// create the map
map<string, int> mymap = {
{"coconut", 10}, {"apple", 5}, {"peach", 30}, {"mango", 8}
};
cout << "The map, sorted by keys, is: " << endl;
map<string, int> :: iterator it;
for (it=mymap.begin(); it!=mymap.end(); it++)
{
cout << it->first << ": " << it->second << endl;
}
cout << endl;
// create a empty vector of pairs
vector<pair<string, int>> vec;
// copy key-value pairs from the map to the vector
map<string, int> :: iterator it2;
for (it2=mymap.begin(); it2!=mymap.end(); it2++)
{
vec.push_back(make_pair(it2->first, it2->second));
}
// // sort the vector by increasing order of its pair's second value
sort(vec.begin(), vec.end(), sortByVal);
// print the vector
cout << "The map, sorted by value is: " << endl;
for (int i = 0; i < vec.size(); i++)
{
cout << vec[i].first << ": " << vec[i].second << endl;
}
return 0;
}
-->
The map, sorted by keys, is:
apple: 5
coconut: 10
mango: 8
peach: 30
The map, sorted by value is:
apple: 5
mango: 8
coconut: 10
peach: 30
Memory Management in C++
One of the most unique facets of C++ is the need to explicitly allocate heap memory in runtime, a process called dynamic memory allocation. This is handled automatically by the compiler in other languages like Java and JavaScript, giving the programmer less control in favor of ease.
To allocate memory in C++, we use the new
operator for a variable and new[]
for an array. Each returns the memory address where that data will be stored. We can use this in conjunction with the pointers we discussed earlier to then assign a value to that address.
See how the new
operator and pointers are used together below:
// declares an int pointer
int* var;
// allocate memory for variable
// using the new keyword
var = new int;
// assign value to allocated memory
*var = 45;
C++ does not have an automatic garbage collection system, meaning that you must manually deallocate memory from pointers once they’re no longer needed. To do this, we use the delete
keyword, which frees up the memory for the given variable or container.
int* var;
var = new int;
delete var;
If we forget to deallocate memory, we’ll get a memory leak due to unused pointers still holding allocated memory.
Tracking allocations and deallocations
Up until now, our example has one new
and one delete
, whereas practical programs may have dozens. When at a larger scale, it's harder to know if you’ve deleted all unneeded pointers or to find those unused pointers. To do a simple check, we can simply count the number of new
allocations and compare that to the number of delete
deallocations.
#include "myNew.hpp"
// #include "myNew2.hpp"
// #include "myNew3.hpp"
#include <iostream>
#include <string>
class MyClass{
float* p= new float[100];
};
class MyClass2{
int five= 5;
std::string s= "hello";
};
int main(){
int* myInt= new int(1998);
double* myDouble= new double(3.14);
double* myDoubleArray= new double[2]{1.1,1.2};
MyClass* myClass= new MyClass;
MyClass2* myClass2= new MyClass2;
delete myDouble;
delete [] myDoubleArray;
delete myClass;
delete myClass2;
getInfo();
}
-->
Number of allocations: 6
Number of deallocations: 4
Here we can see that we have 6 allocations but only 4 deallocations, meaning 2 of our pointers are still taking up memory. Though it does not tell us where these rogue pointers are, it does point us in the right direction if we’ve fully cleaned up after our program.
Attention to memory management is key to being successful as a C++ developer, as larger, complex programs have many more opportunities for mismanaged memory issues. If left unresolved, these issues can slow performance and even cause crashes in the program.
Tip: STL containers and C++ Strings automatically manage their memory allocation. Try using more of these to avoid memory leaks or having to micromanage every variable/container.
What to learn next
Congrats! You’ve now learned some of the intermediate C++ concepts that will make you a proficient and hire-able C++ developer.
As we discussed earlier, C++ is a difficult language to master, even for those with experience in other languages. However, once you get more familiar with its advanced capabilities, you’ll find that C++ grants you an unmatched level of control over often streamlined features like memory allocation.
As you continue your C++ learning journey, here are some advanced topics to check out next:
- Smart Pointers
- Move and copy semantics
- Abstract Base classes
- Virtual Methods
- Templates
Educative’s new Grokking Coding Interview Patterns in C+ learning path walks you through these advanced topics and more, all with text-based interactive lessons and practice problems. These modules are career-focused, presenting the material you’ll need on the job, all written by expert C++ developers.
Happy learning!
Continue reading about C++ on Educative
- C++ is a good first language to learn
- A Tutorial on Modern Multithreading and Concurrency in C++
- So you know C++. Now it's time to learn the standard library
Start a discussion
Why do you think learning C++ is a good idea for young developers? Was this article helpful? Let us know in the comments below!