C++ — один из старейших языков программирования, созданный еще в 1979 году. Он остается важнейшим языком для современных разработчиков, поскольку он используется для повышения производительности больших систем в таких областях, как разработка видеоигр, операционные системы, браузеры и т. д. как офисное, так и медицинское программное обеспечение.

Легко понять, почему разработчики хотят изучать C++. Если вы уже изучаете C++, вы, вероятно, заметили, что его сложно освоить.

Не беспокойтесь! C++ по-прежнему ценен для изучения, поэтому сегодня мы познакомим вас с некоторыми промежуточными концепциями и примерами C++, чтобы сделать вас на один шаг ближе к освоению этого сложного, но востребованного языка.

Вот темы, которые мы рассмотрим сегодня:

  • Использование объектно-ориентированного программирования на C++
  • Как работают строки в C++?
  • Что такое указатель в C++?
  • Что такое массивы в C++?
  • Что такое вектор в C++?
  • Использование карт C++
  • Управление памятью в С++
  • Что изучать дальше

Использование объектно-ориентированного программирования на C++

C++ — это объектно-ориентированный язык. Эта парадигма является одним из определяющих отличий от C и C#. В C++ мы достигаем этого, используя объекты и классы для хранения данных и управления ими. Благодаря этому объектно-ориентированному подходу разработчики могут реализовать стратегии наследования, полиморфизма, абстракции и инкапсуляции.

Дополнительную информацию об объектно-ориентированных концепциях и терминах см. в разделе Что такое объектно-ориентированное программирование? Подробное объяснение ООП.

При изучении C++ важно понимать, как максимально использовать его возможности ООП. Ниже мы рассмотрим несколько примеров каждой из основных стратегий ООП: наследование, полиморфизм, абстракция и инкапсуляция.

Использование наследования в C++

Настроить отношения наследования в C++ несложно; мы просто добавляем объявление класса с : [parent name](). Это позволяет нам совместно использовать как общедоступные переменные, так и методы от родительского класса к дочернему.

Ниже вы увидите, как мы можем использовать это для создания класса BankAccount, используя класс Account.

#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

Выше мы создаем два класса: родительский Account и дочерний BankAccount. Мы определили общедоступные функции deposit, withdraw и getBalance в Account, а также addInterest в BankAccount.

В нашем основном разделе мы видим, как мы используем все эти функции в нашем новом объекте BankAccount, bankAcc, независимо от того, принадлежит ли функция родительскому или дочернему классу.

Использование полиморфизма в C++

Одним из наиболее распространенных применений полиморфизма в C++ является переопределение функций. Имея две функции с одинаковыми именами в разных классах, мы можем создать код, который выполняет различное поведение в зависимости от наследования выбранного объекта.

Ниже мы увидим пример того, как полиморфизм можно использовать для реализации примера функции Draw в разных классах фигур:

Чтобы сосредоточиться на переопределенных функциях, мы только что заполнили каждый Draw простым уникальным оператором count. Однако его можно заменить алгоритмом рисования и вести себя так же.

#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

Выше мы видим родительский класс shape и три дочерних класса Triangle, Rectangle и Circle. Процесс рисования каждой формы будет отличаться в зависимости от самой формы. Это приводит нас к использованию полиморфизма для определения функции Draw для каждого из классов.

C++ по умолчанию отдает приоритет локальной функции над родительской функцией Draw. Это означает, что мы можем вызывать Draw, не беспокоясь о том, к какому классу формы относится объект. Если это прямоугольник, вывод будет «Рисование прямоугольника». Если это треугольник, будет выведено «Рисование треугольника» и так далее.

Абстракция и инкапсуляция

Абстракция работает в C++ так же, как и в других жестко запрограммированных языках, за счет использования ключевых слов private и public. Это похоже на абстракцию, поскольку абстракция скрывает несущественную информацию. Инкапсуляция может использоваться для достижения абстракции с помощью частных функций получения и установки.

Ниже мы увидим, как добиться как абстракции, так и инкапсуляции в 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

Здесь мы сначала создаем класс abstraction, который инициализирует частные переменные a и b. Мы также определяем две общедоступные функции, set и display. Это обеспечивает инкапсуляцию путем отделения переменных от внешнего мира, поэтому мы можем получить к ним доступ только через публичные функции. Используя set, мы затем меняем значения внутри main. Наконец, мы печатаем переменные, используя display.

Это гарантирует, что пользователи не могут напрямую изменять переменные, но могут использовать их через функции получения и установки, достигая абстракции, сохраняя сами переменные безопасными и скрытыми от пользователей.

Как работают строки в C++?

Строки в C++ аналогичны строкам в других языках тем, что представляют собой набор упорядоченных символов. Однако в C++ есть два способа создания строк: либо с использованием строк в стиле C, либо с помощью класса C++ string.

C style strings — это старомодный способ создания строк в C++. Вместо стандартного строкового объекта C-style strings состоит из массива символов, заканчивающегося нулем, заканчивающегося специальным символом \0. Из-за этого скрытого символа строки в стиле C всегда имеют длину на один символ больше, чем видимое количество символов.

Этот размер может быть либо неуказанным для автоматической установки требуемого размера, либо вручную до любого желаемого размера.

char str[] = "Educative"; // automatic length set to 10
char str[10] = "Educative"; // manual set length to 10

Мы также можем создавать строки в C++, используя класс C++ string, встроенный в стандартную библиотеку C++. Это более популярный метод, так как все управление памятью, ее выделение и нулевое завершение внутренне обрабатываются классом. Еще одним преимуществом является то, что длина строки может быть изменена во время выполнения благодаря динамическому выделению памяти.

В целом, эти изменения делают класс string более устойчивым к ошибкам и предоставляют множество встроенных функций, таких как append(), которая добавляет в конец строки, и length(), которая возвращает количество символов в строке.

Ниже мы узнаем, как использовать подобные функции для выполнения обычных манипуляций со строками.

Как напечатать строку

Мы можем печатать строки в C++, используя глобальный объект cout вместе с оператором <<, который предшествует напечатанному содержимому. Мы также включаем глобальный объект endl, который используется для пропуска строки после операции для лучшей читабельности. Поскольку и endl, и cout являются предопределенными объектами глобального класса ostream, мы должны обязательно включить заголовок <iostream> в программы, где они необходимы.

Ниже мы можем увидеть, как мы инициализируем и печатаем 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

Как вычислить длину строки

Чтобы вычислить длину строки, мы можем использовать функции length() или size(). Каждый работает одинаково, и каждый существует для удобства чтения. Эти функции также можно использовать для измерения длины контейнеров STL, таких как карты и векторы.

Синонимические функции включены для повышения удобочитаемости, а длина строки интуитивно понятна. Было бы более интуитивно понятно ссылаться на размер массива, чем на его длину.

Ниже мы увидим, как напечатать длину строки str1 с помощью функций length и size.

#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

Как объединить строку

Эта последняя манипуляция — причудливый способ сказать «склеить две струны». Делая это в C++, мы можем использовать либо оператор +, либо предопределенную функцию append, каждая из которых обеспечивает одинаковый эффект.

Для простого тестового кода между этими двумя вариантами почти нет разницы. Однако при использовании в более крупных и сложных программах append будет работать значительно быстрее, чем +.

#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

Что такое указатель в C++?

В C++ все переменные должны храниться где-то в памяти главного компьютера. Чтобы помочь программам найти эти переменные без поиска в памяти, C++ позволяет нам использовать специальную переменную, указатели, для явного указания адреса переменной.

Указатели несут две части информации:

  • Адрес памяти, хранящийся как значение указателя
  • Тип данных, указывающий тип переменной, на которую он указывает

Объявление указателя похоже на объявление стандартной переменной, за исключением того, что перед именем указателя стоит звездочка.

int *ptr; 
struct coord *pCrd; 
void *vp;

Давайте посмотрим, как это можно использовать ниже:

#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

Сначала мы инициализируем две переменные типа int, val1 и val2, а также указатель типа int, mypointer. Затем мы устанавливаем mypointer в адрес val1 с помощью оператора &. Затем значение mypointer указывает на 10. Поскольку mypointer в настоящее время указывает на адрес val1, эта операция изменяет значение val1.

Затем мы повторяем этот процесс, устанавливая mypointer на адрес val2 и значение в этом месте на 20.

У указателей в C++ есть два основных преимущества: скорость и использование памяти. Использование указателей сокращает время выполнения, поскольку программы могут быстрее обращаться к значениям, если им заданы прямые адреса памяти.

Шпаргалка по указателям

Поскольку указатели являются одним из самых уникальных элементов C++, может быть сложно запомнить все, что вы можете с ними делать. Для справки, вот наше краткое руководство по основному синтаксису указателя:

Что такое массивы в C++?

Массивы C++ — это набор похожих типов данных, хранящихся под одним именем. Их часто визуализируют как ряд из i блоков, которые можно выбрать, вызвав индекс блока, чтобы получить доступ к значению, хранящемуся внутри.

Совет. Значения индекса массива начинаются с 0, что означает, что доступ к первому элементу массива можно получить, вызвав элемент 0, а не 1

Длина массива устанавливается (явно или неявно) при объявлении и не может быть изменена без полной переделки массива. Это, однако, делает массив очень эффективной структурой памяти по сравнению с вектором, так как после инициализации массива память не используется.

#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

Как найти длину массива в С++

В отличие от контейнеров и строк STL, мы не можем найти длину массива, используя length или size. Вместо этого мы используем либо оператор sizeof(), либо арифметику указателей.

Давайте посмотрим, как мы можем использовать каждый из них, начиная с 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

В отличие от функции length, sizeof фактически возвращает количество байтов, которое выбранный объект занимает в памяти. Размер каждого элемента массива варьируется от массива к массиву, поэтому мы не можем предположить, что это всего один байт.

Чтобы обойти это, мы используем каждый элемент в одном массиве и будем использовать одинаковый объем памяти. Следовательно, если мы разделим общее количество байтов, используемых массивом, sizeof(arr), на количество байтов, используемых первым элементом, sizeof(arr[0]), мы получим количество элементов в массиве.

Другой способ найти размер — использовать арифметику указателя:

#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

Аналогичного эффекта можно добиться, если учесть, что размер массива равен разнице между адресом конечного элемента и адресом первого элемента массива.

Вот пошаговая разбивка:

  • (&arr + 1) указывает на адрес памяти сразу после конца массива.
  • (&arr + 1) просто преобразует вышеуказанный адрес в int *.
  • Вычитание адреса начала массива из адреса конца массива дает длину массива.

Что такое вектор в C++?

Векторы C++ — это контейнеры STL, которые действуют как более совершенная версия строковых массивов. Они упрощают процесс вставки и удаления значений за счет использования большего объема памяти. Векторы хранят элементы непрерывно, поэтому элементы хранятся в памяти рядом друг с другом. В отличие от массивов, векторы являются динамическими, поэтому их размер может изменяться по запросу и может быть пройден с помощью итераторов, таких как begin() (для начала вектора) и end() (для конца вектора).

Векторы лучше всего подходят для ситуаций, когда вы будете регулярно добавлять или вычитать значения, а память в значительной степени доступна.

Во-первых, давайте посмотрим, как использовать функцию resize для активации удобных динамических возможностей вектора:

#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 устанавливает максимальное количество элементов в векторе на указанное число. Выше мы сначала инициализируем вектор numbers, затем устанавливаем его размер с помощью resize(7). Скажем, мы тогда понимаем, что мы вырежем 3 из 7 элементов. Мы бы использовали resize(4), чтобы обрезать лишние 3 элемента, чтобы неиспользуемые элементы не загромождали вектор.

Ниже мы увидим, как создать вектор, использовать функцию push_back для добавления значений и функции begin и end для печати:

#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

Совет. Размеры векторов автоматически изменяются в соответствии с новыми элементами при использовании push_back или insert.

В C++ мы должны сначала инициализировать вектор v, а затем заполнить его значениями. Мы хотим, чтобы значения шли в конце, а не в начале, поэтому мы используем функцию push_back, которая вставляет новый элемент в конец. Оттуда мы печатаем каждое значение в векторе между его началом, выбранным с помощью функции begin, и концом, выбранным с помощью функции end.

Использование карт C++

Карты — это тип контейнера, в котором хранятся элементы с использованием пар ключ-значение. Каждый элемент на карте имеет уникальный ключ в качестве идентификатора и значение, которое извлекается при вызове ключа. Ключи автоматически сортируются от меньшего к большему, что делает поиск карт очень быстрым.

При инициализации должны быть заданы типы данных ключа и значения, а также имя объекта карты.

Совет. Целые числа являются наиболее распространенным типом ключа, используемого с картами; однако они могут быть и других типов.

Наиболее важными функциями, используемыми с картами, являются insert(), которая добавляет новый элемент на карту, и find(), которая извлекает элемент с соответствующим ключом и возвращает его значение.

Ниже мы видим обе эти функции в действии с нашей картой Employees:

#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

Карты полезны для хранения данных, которые будут часто запрашиваться, и хорошо разбиваются на ассоциативную структуру, такую ​​как реестр электронной почты компании.

Как отсортировать карту по значению в C++

Как сказано выше, карты сортируются по ключу автоматически. Однако вместо этого может быть полезно сортировать по значению. Мы можем сделать это, скопировав элементы в вектор пар ключ-значение, а затем отсортировав вектор перед окончательной печатью.

Давайте посмотрим, что в действии:

#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

Управление памятью в С++

Одним из самых уникальных аспектов C++ является необходимость явного выделения памяти кучи во время выполнения, процесс, называемый динамическим выделением памяти. Это автоматически обрабатывается компилятором в других языках, таких как Java и JavaScript, что дает программисту меньше контроля в пользу простоты.

Чтобы выделить память в C++, мы используем оператор new для переменной и new[] для массива. Каждый возвращает адрес памяти, где эти данные будут храниться. Мы можем использовать это в сочетании с указателями, которые мы обсуждали ранее, чтобы затем присвоить значение этому адресу.

Посмотрите, как оператор new и указатели используются вместе ниже:

// 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++ не имеет системы автоматической сборки мусора, а это означает, что вы должны вручную освобождать память от указателей, когда они больше не нужны. Для этого мы используем ключевое слово delete, которое освобождает память для данной переменной или контейнера.

int* var;
var = new int;
delete var;

Если мы забудем освободить память, мы получим утечку памяти из-за того, что неиспользуемые указатели все еще удерживают выделенную память.

Отслеживание выделений и освобождений

До сих пор в нашем примере было одно new и одно delete, тогда как в практических программах их может быть десятки. В более крупном масштабе сложнее понять, удалили ли вы все ненужные указатели или найти эти неиспользуемые указатели. Чтобы выполнить простую проверку, мы можем просто подсчитать количество new распределений и сравнить их с количеством delete освобождений.

#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

Здесь мы видим, что у нас есть 6 распределений, но только 4 освобождения, что означает, что 2 наших указателя все еще занимают память. Хотя он не сообщает нам, где находятся эти неверные указатели, он указывает в правильном направлении, если мы полностью очистили свою программу.

Внимание к управлению памятью является ключом к успеху разработчика C++, поскольку в больших и сложных программах гораздо больше возможностей для неправильного управления памятью. Если оставить эти проблемы нерешенными, они могут снизить производительность и даже вызвать сбои в работе программы.

Совет. Контейнеры STL и строки C++ автоматически управляют выделением памяти. Попробуйте использовать больше из них, чтобы избежать утечек памяти или необходимости микроуправления каждой переменной/контейнером.

Что изучать дальше

Поздравляю! Теперь вы изучили некоторые базовые понятия C++, которые сделают вас опытным разработчиком C++, которого можно нанять.

Как мы уже говорили ранее, C++ — сложный язык для освоения даже для тех, кто имеет опыт работы с другими языками. Однако, как только вы поближе познакомитесь с его расширенными возможностями, вы обнаружите, что C++ предоставляет вам непревзойденный уровень контроля над часто упрощенными функциями, такими как выделение памяти.

По мере того, как вы продолжаете свое путешествие по изучению C++, вот некоторые дополнительные темы, которые стоит проверить в следующий раз:

  • Умные указатели
  • Перемещение и копирование семантики
  • Абстрактные базовые классы
  • Виртуальные методы
  • Шаблоны

Новый курс обучения Grokking Coding Interview Patterns in C+ от Educative проведет вас по этим сложным темам и многим другим, используя текстовые интерактивные уроки и практические задачи. Эти модули ориентированы на карьеру и содержат материалы, которые вам понадобятся в работе. Все они написаны опытными разработчиками C++.

Удачного обучения!

Продолжить чтение о C++ на Educative

Начать обсуждение

Как вы думаете, почему изучение C++ — хорошая идея для молодых разработчиков? Была ли эта статья полезна? Дайте нам знать в комментариях ниже!