Intermediate C++ tutorial: Strings, maps, memory, and more
Tags: cpp, tutorial, programming, productivity
Published: 2022-11-03 18:48:02

**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++](https://www.educative.io/blog/how-to-learn-cpp-the-guide-for-beginners?eid=5082902844932096). 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](https://www.educative.io/blog/learn-cpp-for-2022?eid=5082902844932096), 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++](#OOP)
* [How do Strings work in C++?](#strings)
* [What is a Pointer in C++?](#pointers)
* [What are Arrays in C++?](#arrays)
* [What is a Vector in C++?](#vectors)
* [Using C++ Maps](#maps)
* [Memory Management in C++](#mem-management)
* [What to Learn Next](#next)

## Using object-oriented programming in C++

C++ is an [object-oriented language](https://www.educative.io/blog/is-cpp-object-oriented-programming-language?eid=5082902844932096). 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](https://www.educative.io/blog/object-oriented-programming?eid=5082902844932096).

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, unique `count` 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 = &circ;
  // 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.

<a name="strings"></a>

## 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.

```cpp
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](https://www.educative.io/blog/so-you-know-c-now-its-time-to-learn-the-standard-library?eid=5082902844932096). 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
```

<a name="pointers"></a>

## 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.

```cpp
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:

![Cpp cheatsheet](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jdfpqfk5bk88dqzappbh.png)

<a name="arrays"></a>

## What are Arrays in C++?

[C++ arrays](https://www.educative.io/blog/pass-array-to-function-c-plus-plus?eid=5082902844932096) 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 than `1`

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](https://www.educative.io/edpresso/cpp-vector-vs-array?eid=5082902844932096), 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 an `int *`.
* Subtracting the address of the start of the array from the address of the end of the array​ gives the length of the array.

<a name="vectors"></a>

## What is a Vector in C++?

[C++ vectors](https://www.educative.io/blog/what-is-cpp-vector?eid=5082902844932096) 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` or `insert`.
 
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.

<a name="maps"></a>

## 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
```
 
<a name="mem-management"></a>

## 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:

```cpp
// 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.

```cpp
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.
 
<a name="next"></a>

## 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+](https://www.educative.io/courses/grokking-coding-interview-patterns-cpp?utm_campaign=coding-interview&utm_source=medium&utm_medium=&utm_content=&utm_term=&eid=5082902844932096)** 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](https://www.educative.io/blog/c-is-a-good-first-language-to-learn?eid=5082902844932096)
* [A Tutorial on Modern Multithreading and Concurrency in C++](https://www.educative.io/blog/modern-multithreading-and-concurrency-in-cpp?eid=5082902844932096)
* [So you know C++. Now it's time to learn the standard library](https://www.educative.io/blog/so-you-know-c-now-its-time-to-learn-the-standard-library?eid=5082902844932096)

### 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!