2015-12-03 18:19:34 +03:00
---
2022-08-01 23:39:47 +03:00
language: C++
2015-12-03 18:19:34 +03:00
filename: learncpp-ru.cpp
contributors:
- ["Steven Basart", "http://github.com/xksteven"]
- ["Matt Kline", "https://github.com/mrkline"]
- ["Geoff Liu", "http://geoffliu.me"]
- ["Connor Waters", "http://github.com/connorwaters"]
translators:
- ["Bohdan Shtepan", "http://modern-dev.com"]
lang: ru-ru
2015-12-03 20:41:19 +03:00
---
C++ - компилируемый, статически типизированный язык программирования общего назначения, который,
[как заявляет создатель языка Бьёрн Страуструп ](http://channel9.msdn.com/Events/Lang-NEXT/Lang-NEXT-2014/Keynote ),
был разработан как
- "лучшая замена C"
- язык с поддержкой абстракции данных
2022-06-15 12:17:20 +03:00
- язык с поддержкой объектно-ориентированного программирования
2015-12-03 21:11:29 +03:00
- язык с поддержкой обобщенного программирования
Хотя е г о синтаксис может показаться более трудным или сложным для понимания, чем в более современных языках,
2016-04-06 03:07:54 +03:00
он широко применяется, так как код, написанный на C++, компилируется в набор инструкций, которые могут быть выполнены напрямую
2015-12-03 21:11:29 +03:00
процессором. C++ широко используется для разработки программного обеспечения, являясь одним из самых популярных языков
программирования. Область е г о применения включает создание операционных систем, разнообразных прикладных программ, драйверов
2015-12-05 05:23:07 +03:00
устройств, приложений для встраиваемых систем, высокопроизводительных серверов, а также развлекательных приложений (игр).
```c++
//////////////////
// Сравнение с C
//////////////////
// C++ практически представляет собой надмножество C и имеет схожий синтаксис
// для объявления переменных, примитивов и функций.
2016-04-06 03:07:54 +03:00
// Так же, как и в С , точкой входа в программу является функция с именем main,
2015-12-05 05:23:07 +03:00
// которая возвращает целочисленное значение.
// Это значение является кодом ответа программы.
// Смотрите https://goo.gl/JYGKyv для более подробной информации.
int main(int argc, char** argv)
{
2016-04-06 03:07:54 +03:00
// Аргументы командной строки, переданные в программу, хранятся в переменных
// argc и argv, так же, как и в C.
2015-12-05 05:23:07 +03:00
// argc указывает на количество аргументов,
2019-12-09 18:53:02 +03:00
// а argv является массивом C-подобных строк (char*), который непосредственно
2015-12-05 05:23:07 +03:00
// содержит аргументы.
// Первым аргументом всегда передается имя программы.
2019-12-09 18:53:02 +03:00
// argc и argv могут быть опущены, если вы не планируете работать с аргументами
// командной строки.
2016-04-06 03:07:54 +03:00
// Тогда сигнатура функции будет иметь следующий вид: int main()
2015-12-05 05:23:07 +03:00
// Возвращаемое значение 0 указывает на успешное завершение программы.
return 0;
}
// Тем не менее, C++ имеет свои отличия:
2016-04-06 03:07:54 +03:00
// В C++ символьные литералы имеют тип char.
2015-12-05 05:23:07 +03:00
sizeof('c') == sizeof(char) == 1
2016-04-06 03:07:54 +03:00
// В C символьные литералы - целые числа.
2015-12-05 05:23:07 +03:00
sizeof('c') == sizeof(int)
2016-04-06 03:07:54 +03:00
// C++ имеет строгое прототипирование.
2015-12-05 05:23:07 +03:00
void func(); // функция, которая не принимает аргументов.
2016-04-06 03:07:54 +03:00
// В языке C
2015-12-05 05:23:07 +03:00
void func(); // функция, которая может принять сколько угодно аргументов.
// Использование nullptr вместо NULL в C++.
int* ip = nullptr;
// Стандартные заголовочные файлы С доступны в С ++,
// но с префиксом "с " и не имеют суффикса .h.
#include <cstdio>
int main()
{
printf("Hello, world!\n");
return 0;
}
///////////////////////
// Перегрузка функций
///////////////////////
// С ++ поддерживает перегрузку функций, при условии,
// что каждая функция принимает различные параметры.
void print(char const* myString)
{
printf("String %s\n", myString);
}
void print(int myInt)
{
printf("My int is %d", myInt);
}
int main()
{
print("Hello"); // Использование void print(const char*)
print(15); // Использование void print(int)
}
/////////////////////////////
// Аргументы функций по умолчанию
/////////////////////////////
// Вы можете предоставить аргументы по умолчанию для функции,
// если они не предоставлены при вызове функции.
void doSomethingWithInts(int a = 1, int b = 4)
{
// Здесь что-то делаем с числами
}
int main()
{
doSomethingWithInts(); // a = 1, b = 4
doSomethingWithInts(20); // a = 20, b = 4
doSomethingWithInts(20, 5); // a = 20, b = 5
}
2016-04-06 03:07:54 +03:00
// Аргументы по умолчанию должны быть в конце списка аргументов.
2015-12-05 05:23:07 +03:00
void invalidDeclaration(int a = 1, int b) // Ошибка!
{
}
/////////////
// Пространства имен
/////////////
// Пространства имен предоставляют отдельные области для переменной,
// функции и других объявлений.
// Пространства имен могут быть вложенными.
namespace First {
namespace Nested {
void foo()
{
printf("This is First::Nested::foo\n");
}
} // конец пространства имен Nested
} // конец пространства имен First
namespace Second {
void foo()
{
printf("This is Second::foo\n")
}
}
void foo()
{
printf("This is global foo\n");
}
int main()
{
2019-12-09 18:53:02 +03:00
// Включает все функции из пространства имен Second в текущую область видимости.
2015-12-05 05:23:07 +03:00
// Обратите внимание, что простой вызов foo() больше не работает,
2016-04-06 03:07:54 +03:00
// так как теперь не ясно, вызываем ли мы foo из пространства имен Second, или
2015-12-05 05:23:07 +03:00
// из глобальной области видимости.
using namespace Second;
Second::foo(); // напечатает "This is Second::foo"
First::Nested::foo(); // напечатает "This is First::Nested::foo"
::foo(); // напечатает "This is global foo"
}
///////////////
2016-04-06 03:07:54 +03:00
// Ввод и вывод
2015-12-05 05:23:07 +03:00
///////////////
2016-04-06 03:07:54 +03:00
// Ввод и вывод в C++ использует потоки
// cin, cout и cerr представляют потоки stdin, stdout и stderr.
2015-12-05 17:07:14 +03:00
// < < - оператор вставки , > > - оператор извлечения.
2015-12-05 05:23:07 +03:00
2015-12-05 17:07:14 +03:00
#include <iostream> // Включение файла для работы с потоками Ввода\Вывода.
2015-12-05 05:23:07 +03:00
2015-12-05 17:07:14 +03:00
using namespace std; // Потоки доступны в пространстве имен std (стандартная библиотека)
2015-12-05 05:23:07 +03:00
int main()
{
int myInt;
2016-04-06 03:07:54 +03:00
// Выводит в stdout (или в терминал/на экран)
2015-12-05 05:23:07 +03:00
cout << "Enter your favorite number:\n";
2015-12-05 17:07:14 +03:00
// Принимает ввод
2015-12-05 05:23:07 +03:00
cin >> myInt;
2015-12-05 17:07:14 +03:00
// cout может принимать форматирование
2015-12-05 05:23:07 +03:00
cout << "Your favorite number is " < < myInt << " \n";
2015-12-05 17:07:14 +03:00
// напечатает "Your favorite number is < myInt > "
2015-12-05 05:23:07 +03:00
cerr < < "Used for error messages";
}
//////////
// Строки
//////////
2015-12-05 17:07:14 +03:00
// Строки в C++ являются объектами и имеют много функций-членов.
2015-12-05 05:23:07 +03:00
#include <string>
2016-04-06 03:07:54 +03:00
using namespace std; // Строки также доступны в пространстве имен std (стандартная библиотека)
2015-12-05 05:23:07 +03:00
string myString = "Hello";
string myOtherString = " World";
2015-12-05 17:07:14 +03:00
// + используется для конкатенации строк.
2015-12-05 05:23:07 +03:00
cout < < myString + myOtherString ; / / " Hello World "
cout < < myString + " You " ; / / " Hello You "
2015-12-05 17:07:14 +03:00
// Строки в C++ могут изменяться и имеют семантику значений.
2015-12-05 05:23:07 +03:00
myString.append(" Dog");
cout < < myString ; / / " Hello Dog "
/////////////
2015-12-05 17:07:14 +03:00
// Ссылки
2015-12-05 05:23:07 +03:00
/////////////
2016-04-06 03:07:54 +03:00
// Кроме указателей, доступных в C,
2015-12-05 17:07:14 +03:00
// C++ имеет _с с ылки_ .
// Это такой тип указателя, который не может быть переназначен после инициализации
// и не может иметь значения null.
// Ссылки имеют схожий с переменными синтаксис:
// * больше не используется для разыменования и
// & (адрес) не используется для назначения.
2015-12-05 05:23:07 +03:00
using namespace std;
string foo = "I am foo";
string bar = "I am bar";
2016-04-06 03:07:54 +03:00
string& fooRef = foo; // Здесь создается ссылка на foo.
2015-12-05 17:07:14 +03:00
fooRef += ". Hi!"; // Изменяет foo по ссылке
cout < < fooRef ; / / Печатает " I am foo . Hi ! "
2015-12-05 05:23:07 +03:00
2016-04-06 03:07:54 +03:00
// Н е переназначает "fooRef". Это то же самое, что и "foo = bar", и
2015-12-05 05:23:07 +03:00
// foo == "I am bar"
2015-12-05 17:07:14 +03:00
// после этой строчки.
cout < < & fooRef < < endl ; / / Печатает адрес foo
2015-12-05 05:23:07 +03:00
fooRef = bar;
2015-12-05 17:07:14 +03:00
cout < < & fooRef < < endl ; / / По-прежнему печатает адрес foo
cout < < fooRef ; / / Печатает " I am bar "
2015-12-05 05:23:07 +03:00
2015-12-05 17:07:14 +03:00
// Адрес fooRef остается тем же, то есть он по-прежнему ссылается на foo.
2015-12-05 05:23:07 +03:00
2016-04-06 03:07:54 +03:00
const string& barRef = bar; // Создает константную ссылку.
// Так же, как и в C, константные значения (а также указатели и ссылки) не могут быть изменены.
barRef += ". Hi!"; // Ошибка, константная ссылка не может быть изменена.
2015-12-05 05:23:07 +03:00
2016-04-06 03:07:54 +03:00
// Обходной путь: Прежде чем мы рассмотрим указатели более детально, нам нужно ознакомиться
// с концепцией, известной как "временный объект". Представьте, что мы имеем следующий код
2015-12-05 05:23:07 +03:00
string tempObjectFun() { ... }
string retVal = tempObjectFun();
2016-04-06 03:07:54 +03:00
// Вот что на самом деле происходит во второй строке:
2015-12-05 17:07:14 +03:00
// - tempObjectFun возвращает строковый объект
2016-04-06 03:07:54 +03:00
// - из возвращаемого объекта создается новая строка в качестве аргумента конструктору
2015-12-05 17:07:14 +03:00
// - возвращаемый объект уничтожается
2016-04-06 03:07:54 +03:00
// Возвращаемый объект называется временным объектом. Временные объекты создаются,
2015-12-05 17:07:14 +03:00
// когда функция возвращает объект, и уничтожаются в конце выполнения обрамляющего
// выражения (По крайней мере, так это описывает спецификация, хотя компиляторы могут
// изменять это поведение. Для более подробной информации смотрите "оптимизация
2016-04-06 03:07:54 +03:00
// возвращаемого значения".) Таким образом в этом коде:
2015-12-05 05:23:07 +03:00
foo(bar(tempObjectFun()))
2016-04-06 03:07:54 +03:00
// предполагая, что foo и bar существуют, объект, возвращаемый tempObjectFun, передается
2015-12-05 17:07:14 +03:00
// в bar, и уничтожается перед вызовом foo.
2015-12-05 05:23:07 +03:00
2015-12-05 17:07:14 +03:00
// Возвращаемся к указателям. Исключением для правила "в конце выполнения обрамляющего
2016-04-06 03:07:54 +03:00
// выражения" является временный объект, привязанный к ссылке const, в этом случае
2015-12-05 17:07:14 +03:00
// е г о жизненный цикл продлевается до текущей области видимости:
2015-12-05 05:23:07 +03:00
void constReferenceTempObjectFun() {
2015-12-05 17:07:14 +03:00
// constRef получает временный объект, и он действителен до конца этой функции.
2015-12-05 05:23:07 +03:00
const string& constRef = tempObjectFun();
...
}
2015-12-05 17:07:14 +03:00
// В C++11 предоставлен еще один тип ссылок специально для временных объектов.
2016-04-06 03:07:54 +03:00
// objects. Вы не можете объявить переменную этого типа, но он имеет приоритет
2015-12-05 17:07:14 +03:00
// в резолюции перегрузки:
2015-12-05 05:23:07 +03:00
2015-12-05 17:07:14 +03:00
void someFun(string& s) { ... } // Обычная ссылка
void someFun(string& & s) { ... } // Ссылка на временный объект
2015-12-05 05:23:07 +03:00
string foo;
2015-12-05 17:07:14 +03:00
someFun(foo); // Выполняет версию с обычной ссылкой
someFun(tempObjectFun()); // Выполняет версию с временной ссылкой.
2015-12-05 05:23:07 +03:00
2015-12-05 17:07:14 +03:00
// Например, существуют следующие две версии конструктора std::basic_string:
2015-12-05 05:23:07 +03:00
basic_string(const basic_string& other);
basic_string(basic_string& & other);
2017-01-03 10:41:35 +03:00
// Идея в том, что если мы конструируем новую строку из временного объекта (который
2015-12-05 17:07:14 +03:00
// так или иначе будет уничтожен), мы можем использовать более эффективный конструктор,
// который "спасает" части этой временной строки. Эта концепция была названа
// "move semantics".
2015-12-05 05:23:07 +03:00
/////////////////////
2015-12-05 17:07:14 +03:00
// Перечисления
2015-12-05 05:23:07 +03:00
/////////////////////
2016-04-06 03:07:54 +03:00
// Перечисления - способ объявления констант и установки их значений, в основном
2015-12-05 17:07:14 +03:00
// использующийся для упрощения чтения кода.
2015-12-05 05:23:07 +03:00
enum ECarTypes
{
Sedan,
Hatchback,
SUV,
Wagon
};
ECarTypes GetPreferredCarType()
{
return ECarTypes::Hatchback;
}
2015-12-05 17:07:14 +03:00
// Н а момент выхода C++11 есть простой способ назначения типа перечисления, что
2017-01-03 10:41:35 +03:00
// полезно в случае сериализации данных и преобразований между конечным типом и
2015-12-05 17:07:14 +03:00
// соответствующими константами.
2015-12-05 05:23:07 +03:00
enum ECarTypes : uint8_t
{
Sedan, // 0
Hatchback, // 1
SUV = 254, // 254
Hybrid // 255
};
void WriteByteToFile(uint8_t InputValue)
{
2015-12-05 17:07:14 +03:00
// Сериализуем InputValue в файл
2015-12-05 05:23:07 +03:00
}
void WritePreferredCarTypeToFile(ECarTypes InputCarType)
{
2016-04-06 03:07:54 +03:00
// Перечисление неявно преобразуется в uint8_t из-за ранее объявленного
2015-12-05 17:07:14 +03:00
// типа перечисления.
2015-12-05 05:23:07 +03:00
WriteByteToFile(InputCarType);
}
2015-12-05 17:07:14 +03:00
// С другой стороны, чтобы избежать случайного приведения к целочисленному типу или
// другому перечислению, вы можете создать класс перечисления, который не будет
// преобразовываться неявно.
2015-12-05 05:23:07 +03:00
enum class ECarTypes : uint8_t
{
Sedan, // 0
Hatchback, // 1
SUV = 254, // 254
Hybrid // 255
};
void WriteByteToFile(uint8_t InputValue)
{
2015-12-05 17:07:14 +03:00
// Сериализуем InputValue в файл
2015-12-05 05:23:07 +03:00
}
void WritePreferredCarTypeToFile(ECarTypes InputCarType)
{
2015-12-05 17:07:14 +03:00
// Хотя ECarTypes имеет тип uint8_t, код не будет скомпилирован из-за того,
// что перечисление было объявлено как класс перечисления.
2015-12-05 05:23:07 +03:00
WriteByteToFile(InputCarType);
}
//////////////////////////////////////////
2015-12-05 17:07:14 +03:00
// Классы и объектно-ориентированное программирование
2015-12-05 05:23:07 +03:00
//////////////////////////////////////////
2015-12-05 17:07:14 +03:00
// Пример классов
2015-12-05 05:23:07 +03:00
#include <iostream>
2015-12-05 17:07:14 +03:00
// Объявление класса.
// Обычно классы объявляют в заголовочном (.h или .hpp) файле.
2015-12-05 05:23:07 +03:00
class Dog {
2016-04-06 03:07:54 +03:00
// Переменные-члены и функции являются приватными по умолчанию.
2015-12-05 05:23:07 +03:00
std::string name;
int weight;
2015-12-05 17:07:14 +03:00
// В с е члены после этой сроки являются открытыми
// пока "private:" или "protected:" не будет объявлено.
2015-12-05 05:23:07 +03:00
public:
2015-12-05 17:07:14 +03:00
// Конструктор по умолчанию
2015-12-05 05:23:07 +03:00
Dog();
2015-12-05 17:07:14 +03:00
// Объявление функций-членов
// Обратите внимание, мы используем std::string здесь вместо использования
2015-12-05 05:23:07 +03:00
// using namespace std;
2015-12-05 17:07:14 +03:00
// выше.
// Никогда не размещайте выражение "using namespace" в заголовке.
2015-12-05 05:23:07 +03:00
void setName(const std::string& dogsName);
void setWeight(int dogsWeight);
2015-12-05 17:07:14 +03:00
// Функции, которые не изменяют состояние объекта,
// должны быть помечены как const.
2016-04-06 03:07:54 +03:00
// Это позволяет вызывать их, если дана const ссылка на объект.
// Обратите внимание, функции должны быть явно объявлены как _virtual_ ,
2015-12-05 17:07:14 +03:00
// если вы хотите перегрузить их в производных классах.
2016-04-06 03:07:54 +03:00
// Функции не являются виртуальными по умолчанию для повышения производительности.
2015-12-05 05:23:07 +03:00
virtual void print() const;
2016-04-06 03:07:54 +03:00
// Также функции могут быть определены внутри тела класса.
2015-12-05 17:07:14 +03:00
// Функции, определенные следующим образом, автоматически встроены.
2015-12-05 05:23:07 +03:00
void bark() const { std::cout < < name << " barks ! \n"; }
2015-12-05 17:07:14 +03:00
// Наряду с конструкторами, в C++ есть деструкторы.
2016-04-06 03:07:54 +03:00
// Они вызываются, когда объект удаляется или выпадает из области видимости.
// Это активирует мощную парадигму программирования, известную как RAII
2015-12-05 17:07:14 +03:00
// (смотрите ниже)
// Деструктор должен быть виртуальным, если класс будет производным.
2016-04-06 03:07:54 +03:00
// Если он не виртуальный, тогда деструктор производного класса не будет вызван,
2015-12-05 17:07:14 +03:00
// если объект удален по ссылке или указателю базового класса.
2015-12-05 05:23:07 +03:00
virtual ~Dog();
2016-04-06 03:07:54 +03:00
}; // Определение класса должно завершаться точкой с запятой.
2015-12-05 05:23:07 +03:00
2015-12-05 17:07:14 +03:00
// Функции-члены класса, как правило, реализуются в .cpp файлах.
2015-12-05 05:23:07 +03:00
Dog::Dog()
{
std::cout << "A dog has been constructed\n";
}
2015-12-05 17:07:14 +03:00
// Объекты (такие как строки) должны передаваться по ссылке если вы будете
// изменять их, или const-ссылке если нет.
2015-12-05 05:23:07 +03:00
void Dog::setName(const std::string& dogsName)
{
name = dogsName;
}
void Dog::setWeight(int dogsWeight)
{
weight = dogsWeight;
}
2015-12-05 17:07:14 +03:00
// Обратите внимание, "virtual" требуется только в объявлении, не в определении.
2015-12-05 05:23:07 +03:00
void Dog::print() const
{
std::cout << "Dog is " < < name << " and weighs " << weight << " kg \n";
}
Dog::~Dog()
{
2017-01-03 10:41:35 +03:00
std::cout << "Goodbye " < < name << " \n";
2015-12-05 05:23:07 +03:00
}
int main() {
2015-12-05 17:07:14 +03:00
Dog myDog; // Печатает "A dog has been constructed"
2015-12-05 05:23:07 +03:00
myDog.setName("Barkley");
myDog.setWeight(10);
2015-12-05 17:07:14 +03:00
myDog.print(); // Печатает "Dog is Barkley and weighs 10 kg"
2015-12-05 05:23:07 +03:00
return 0;
2015-12-05 17:07:14 +03:00
} // Печатает "Goodbye Barkley"
2015-12-05 05:23:07 +03:00
2015-12-05 17:07:14 +03:00
// Интерфейсы:
2015-12-05 05:23:07 +03:00
2015-12-05 17:07:14 +03:00
// Этот класс наследует все открытые и защищенные члены класса Dog
2016-04-06 03:07:54 +03:00
// так же, как и все закрытые, но не может непосредственно получить доступ к закрытым
2015-12-05 17:07:14 +03:00
// членам\методам без открытых или защищенных методов для этого.
2015-12-05 05:23:07 +03:00
class OwnedDog : public Dog {
2019-11-17 11:17:07 +03:00
public:
2015-12-05 05:23:07 +03:00
void setOwner(const std::string& dogsOwner);
2015-12-05 17:07:14 +03:00
// Переопределяем поведение функции печати для всех OwnedDog. Смотрите
// https://goo.gl/3kuH2x для боле общего введения, если вы не знакомы
// с концепцией полиморфизма подтипов (включения).
2016-04-06 03:07:54 +03:00
// Ключевое слово override является необязательным, но указывает, что метод
2015-12-05 17:07:14 +03:00
// на самом деле перегружается в базовом классе.
2015-12-05 05:23:07 +03:00
void print() const override;
private:
std::string owner;
};
2015-12-05 17:07:14 +03:00
// Тем временем, в соответствующем .cpp файле:
2015-12-05 05:23:07 +03:00
void OwnedDog::setOwner(const std::string& dogsOwner)
{
owner = dogsOwner;
}
void OwnedDog::print() const
{
2015-12-05 17:07:14 +03:00
Dog::print(); // Вызывает функцию print в базовом классе Dog
2015-12-05 05:23:07 +03:00
std::cout << "Dog is owned by " < < owner << " \n";
2015-12-05 17:07:14 +03:00
// Печатает "Dog is < name > and weights < weight > "
2015-12-05 05:23:07 +03:00
// "Dog is owned by < owner > "
}
//////////////////////////////////////////
2015-12-05 17:07:14 +03:00
// Инициализация и перегрузка операторов.
2015-12-05 05:23:07 +03:00
//////////////////////////////////////////
2015-12-05 17:07:14 +03:00
// В C++ вы можете перегрузить поведение таких операторов: +, -, *, / и др..
// Это делается путем определения функции, которая вызывается,
// когда используется оператор.
2015-12-05 05:23:07 +03:00
#include <iostream>
using namespace std;
class Point {
public:
2015-12-05 17:07:14 +03:00
// Значения по умолчанию для переменных-членов могут быть установлены
// следующим образом.
2015-12-05 05:23:07 +03:00
double x = 0;
double y = 0;
2015-12-05 17:07:14 +03:00
// Определяем новый конструктор, который инициализирует Point с о значениями
// по умолчанию (0, 0)
2015-12-05 05:23:07 +03:00
Point() { };
2017-01-03 10:41:35 +03:00
// Следующий синтаксис известен как список инициализации и является верным способом
2015-12-05 17:07:14 +03:00
// инициализировать значения членов класса.
2015-12-05 05:23:07 +03:00
Point (double a, double b) :
x(a),
y(b)
2016-04-06 03:07:54 +03:00
{ /* Ничего не делайте, кроме инициализации значений */ }
2015-12-05 05:23:07 +03:00
2016-04-06 03:07:54 +03:00
// Перегружаем оператор +.
2015-12-05 05:23:07 +03:00
Point operator+(const Point& rhs) const;
2015-12-05 17:07:14 +03:00
// Перегружаем оператор +=.
2015-12-05 05:23:07 +03:00
Point& operator+=(const Point& rhs);
2015-12-05 17:07:14 +03:00
// Имеет смысл добавить перегрузку операторов - и -=,
// но для краткости мы опустим эти детали.
2015-12-05 05:23:07 +03:00
};
Point Point::operator+(const Point& rhs) const
{
2015-12-05 17:07:14 +03:00
// Создает новую точку, которая является суммой этой точки и rhs.
2015-12-05 05:23:07 +03:00
return Point(x + rhs.x, y + rhs.y);
}
Point& Point::operator+=(const Point& rhs)
{
x += rhs.x;
y += rhs.y;
return *this;
}
int main () {
Point up (0,1);
Point right (1,0);
2015-12-05 17:07:14 +03:00
// Здесь происходит вызов оператора + класса Point
// Точка "up" вызывает + (функция) с параметром "right"
2015-12-05 05:23:07 +03:00
Point result = up + right;
2015-12-05 17:07:14 +03:00
// Печатает "Result is upright (1,1)"
2015-12-05 05:23:07 +03:00
cout << "Result is upright (" < < result.x << ',' << result . y << ") \n";
return 0;
}
/////////////////////
2015-12-05 17:07:14 +03:00
// Шаблоны
2015-12-05 05:23:07 +03:00
/////////////////////
2015-12-05 17:07:14 +03:00
// Шаблоны в С ++, в основном, используются для обобщенного программирования, хотя
2016-04-06 03:07:54 +03:00
// они гораздо более мощны, чем дженерики в других языках. Они также поддерживают
2015-12-05 17:07:14 +03:00
// явные, частные и функциональные типы классов; на самом деле, они являются
2016-04-06 03:07:54 +03:00
// тьюринг-полным языком, встроенным в C++!
2015-12-05 05:23:07 +03:00
2015-12-05 17:07:14 +03:00
// Мы начнем с наиболее распространенного типа обобщенного программирования. Чтобы
// определить класс или функцию, которая принимает параметр типа:
2015-12-05 05:23:07 +03:00
template< class T >
class Box {
public:
2015-12-05 17:07:14 +03:00
// В этом классе T может быть любого типа.
2015-12-05 05:23:07 +03:00
void insert(const T& ) { ... }
};
2016-04-06 03:07:54 +03:00
// В о время компиляции компилятор фактически генерирует копии каждого шаблона
// с замещенными параметрами, поэтому полное определение класса должно присутствовать
2019-11-17 11:17:07 +03:00
// при каждом вызове. Именно поэтому шаблоны классов полностью определены в
2015-12-05 17:07:14 +03:00
// заголовочных файлах.
2015-12-05 05:23:07 +03:00
2019-11-17 11:17:07 +03:00
// Чтобы создать экземпляр шаблона класса на стеке:
2015-12-05 05:23:07 +03:00
Box< int > intBox;
2015-12-05 17:07:14 +03:00
// и вы можете использовать е г о , как и ожидалось:
2015-12-05 05:23:07 +03:00
intBox.insert(123);
2015-12-05 17:07:14 +03:00
// Вы, конечно, можете использовать вложенные шаблоны:
2015-12-05 05:23:07 +03:00
Box< Box < int > > boxOfBox;
boxOfBox.insert(intBox);
2015-12-05 17:07:14 +03:00
// Вплоть до С ++11, вы должны были ставить пробел между двумя символами '>', иначе '>>'
2016-04-06 03:07:54 +03:00
// принимался парсером, как оператор сдвига вправо.
2015-12-05 05:23:07 +03:00
2015-12-05 17:07:14 +03:00
// Иногда вы можете увидеть
2015-12-05 05:23:07 +03:00
// template< typename T >
2016-04-06 03:07:54 +03:00
// вместо этого. В этом случае ключевые слова 'class' и 'typename' _в о с но вно м_
2015-12-05 17:07:14 +03:00
// взаимозаменяемыми. Для более подробной информации смотрите
2015-12-05 05:23:07 +03:00
// http://en.wikipedia.org/wiki/Typename
2015-12-05 17:07:14 +03:00
// (да-да, это ключевое слово имеет собственную страничку на вики).
2015-12-05 05:23:07 +03:00
2019-11-17 11:17:07 +03:00
// Аналогичным образом, шаблон функции:
2015-12-05 05:23:07 +03:00
template< class T >
void barkThreeTimes(const T& input)
{
input.bark();
input.bark();
input.bark();
}
2015-12-05 17:07:14 +03:00
// Обратите внимание, что здесь ничего не указано о типе параметра. Компилятор
2016-04-06 03:07:54 +03:00
// будет генерировать и затем проверять на тип каждый вызов шаблона, поэтому
2015-12-05 17:07:14 +03:00
// данная функция работает с любым типом 'T', который имеет метод 'bark'.
2015-12-05 05:23:07 +03:00
Dog fluffy;
2016-04-06 03:07:54 +03:00
fluffy.setName("Fluffy");
2015-12-05 17:07:14 +03:00
barkThreeTimes(fluffy); // Печатает "Fluffy barks" три раза.
2015-12-05 05:23:07 +03:00
2019-11-17 11:17:07 +03:00
// Параметры шаблона не должны быть классами:
2015-12-05 05:23:07 +03:00
template< int Y >
void printMessage() {
cout < < "Learn C++ in " < < Y < < " minutes ! " < < endl ;
}
2015-12-05 17:07:14 +03:00
// В конце концов, вы можете явно специализировать шаблоны для более эффективного
// кода. Конечно, большинство реальных случаев использования специализации
// не так тривиально, как это. Обратите внимание, вам все еще нужно явно объявить
// функцию (или класс) в качестве шаблона, даже если вы явно указали все параметры.
2015-12-05 05:23:07 +03:00
template< >
void printMessage< 10 > () {
cout < < "Learn C++ faster in only 10 minutes!" < < endl ;
}
2015-12-05 17:07:14 +03:00
printMessage< 20 > (); // Печатает "Learn C++ in 20 minutes!"
printMessage< 10 > (); // Печатает "Learn C++ faster in only 10 minutes!"
2015-12-05 05:23:07 +03:00
/////////////////////
2015-12-05 17:07:14 +03:00
// Обработка исключений
2015-12-05 05:23:07 +03:00
/////////////////////
2015-12-05 17:07:14 +03:00
// Стандартная библиотека предоставляет несколько типов исключений
// (смотрите http://en.cppreference.com/w/cpp/error/exception)
// но, в принципе, любой тип может быть брошен в качестве исключения.
2015-12-05 05:23:07 +03:00
#include <exception>
#include <stdexcept>
2016-04-06 03:07:54 +03:00
// В с е исключения, брошенные в блоке _try_ могут быть пойманы в последующем блоке
2015-12-05 17:07:14 +03:00
// _catch_ .
2015-12-05 05:23:07 +03:00
try {
2015-12-05 17:07:14 +03:00
// Н е выделяйте память в куче для исключений с помощью ключевого слова _new_ .
2015-12-05 05:23:07 +03:00
throw std::runtime_error("A problem occurred");
}
2016-04-06 03:07:54 +03:00
// Поймайте исключение по константной ссылке, если оно является объектом
2015-12-05 05:23:07 +03:00
catch (const std::exception& ex)
{
std::cout < < ex.what ( ) ;
}
2016-04-06 03:07:54 +03:00
// Ловит любое исключение, не пойманное предыдущим блоком _catch_
2015-12-05 05:23:07 +03:00
catch (...)
{
std::cout < < "Unknown exception caught";
2015-12-05 17:07:14 +03:00
throw; // Повторный выброс исключения
2015-12-05 05:23:07 +03:00
}
///////
2015-12-05 17:07:14 +03:00
// Получение р е с у р с а есть инициализация (RAII)
2015-12-05 05:23:07 +03:00
///////
2015-12-05 17:07:14 +03:00
// Программная идиома объектно-ориентированного программирования, смысл которой
// заключается в том, что с помощью тех или иных программных механизмов получение
// некоторого р е с у р с а неразрывно совмещается с инициализацией, а освобождение -
// с уничтожением объекта.
2015-12-05 05:23:07 +03:00
2019-11-17 11:17:07 +03:00
// Чтобы понять, насколько это полезно,
2015-12-05 17:07:14 +03:00
// рассмотрим функцию, которая использует обработчик файлов в С :
2015-12-05 05:23:07 +03:00
void doSomethingWithAFile(const char* filename)
{
2015-12-05 17:07:14 +03:00
// Для начала, предположим, ничего не может потерпеть неудачу.
2015-12-05 05:23:07 +03:00
2015-12-05 17:07:14 +03:00
FILE* fh = fopen(filename, "r"); // Открываем файл в режиме чтения.
2015-12-05 05:23:07 +03:00
doSomethingWithTheFile(fh);
doSomethingElseWithIt(fh);
2015-12-05 17:07:14 +03:00
fclose(fh); // Закрываем обработчик файла.
2015-12-05 05:23:07 +03:00
}
2016-04-06 03:07:54 +03:00
// К сожалению, вещи быстро осложняются обработкой ошибок.
// Предположим, fopen может потерпеть неудачу, тогда doSomethingWithTheFile и
// doSomethingElseWithIt вернут коды ошибок, если потерпят неудачу.
2015-12-05 17:07:14 +03:00
// (Исключения являются предпочтительным способом обработки ошибок,
// но некоторые программисты, особенно те, кто имеет большой опыт работы с С ,
// не согласны с аргументами о полезности исключений).
// Теперь мы должны проверить каждый вызов на наличие ошибок и закрыть обработчик
2016-04-06 03:07:54 +03:00
// файла, если он есть.
2015-12-05 05:23:07 +03:00
bool doSomethingWithAFile(const char* filename)
{
2015-12-05 17:07:14 +03:00
FILE* fh = fopen(filename, "r"); // Открывает файл в режиме чтения
2016-04-06 03:07:54 +03:00
if (fh == nullptr) // В случае неудачи возвращаемый указатель принимает значение null.
return false; // Сообщает о неудаче вызывающему.
2015-12-05 05:23:07 +03:00
2015-12-05 17:07:14 +03:00
// Предположим, каждая функция возвращает false в случае неудачи
2015-12-05 05:23:07 +03:00
if (!doSomethingWithTheFile(fh)) {
2016-04-06 03:07:54 +03:00
fclose(fh); // Закрываем обработчик файла, чтобы не было утечек
2015-12-05 17:07:14 +03:00
return false; // Сообщает о б ошибке.
2015-12-05 05:23:07 +03:00
}
if (!doSomethingElseWithIt(fh)) {
2016-04-06 03:07:54 +03:00
fclose(fh); // Закрываем обработчик файла, чтобы не было утечек
2015-12-05 17:07:14 +03:00
return false; // Сообщает о б ошибке.
2015-12-05 05:23:07 +03:00
}
2016-04-06 03:07:54 +03:00
fclose(fh); // Закрываем обработчик файла, чтобы не было утечек
2015-12-05 17:07:14 +03:00
return true; // Указывает на успех
2015-12-05 05:23:07 +03:00
}
2015-12-05 17:07:14 +03:00
// C-программисты часто упорядочивают это с помощью goto:
2015-12-05 05:23:07 +03:00
bool doSomethingWithAFile(const char* filename)
{
FILE* fh = fopen(filename, "r");
if (fh == nullptr)
return false;
if (!doSomethingWithTheFile(fh))
goto failure;
if (!doSomethingElseWithIt(fh))
goto failure;
2016-04-06 03:07:54 +03:00
fclose(fh); // Закрываем файл.
2015-12-05 17:07:14 +03:00
return true; // Указывает на успех
2015-12-05 05:23:07 +03:00
failure:
fclose(fh);
2015-12-05 17:07:14 +03:00
return false; // Сообщает о б ошибке.
2015-12-05 05:23:07 +03:00
}
2015-12-05 17:07:14 +03:00
// Если функции указывают на ошибки с помощью исключений, вещи становятся проще,
// но все еще не оптимальны.
2015-12-05 05:23:07 +03:00
void doSomethingWithAFile(const char* filename)
{
2015-12-05 17:07:14 +03:00
FILE* fh = fopen(filename, "r"); // Открываем файл в режиме чтения
2015-12-05 05:23:07 +03:00
if (fh == nullptr)
throw std::runtime_error("Could not open the file.");
try {
doSomethingWithTheFile(fh);
doSomethingElseWithIt(fh);
}
catch (...) {
2015-12-05 17:07:14 +03:00
fclose(fh); // Убедитесь, что закрываете файл, если происходит ошибка.
throw; // Затем повторно бросает исключение.
2015-12-05 05:23:07 +03:00
}
2016-04-06 03:07:54 +03:00
fclose(fh); // Закрываем файл.
2015-12-05 17:07:14 +03:00
// Успех
2015-12-05 05:23:07 +03:00
}
2015-12-05 17:07:14 +03:00
// Сравните это с использованием класса потока файла (fstream) в С ++, который
2016-04-06 03:07:54 +03:00
// использует свой деструктор, чтобы закрыть файл. Еще раз взгляните выше,
2015-12-05 17:07:14 +03:00
// деструктор вызывается автоматически, когда объект выпадает из области видимости.
2015-12-05 05:23:07 +03:00
void doSomethingWithAFile(const std::string& filename)
{
2015-12-05 17:07:14 +03:00
// ifstream определяет файловый поток
std::ifstream fh(filename); // Открыть файл
2015-12-05 05:23:07 +03:00
2015-12-05 17:07:14 +03:00
// Что-то делать с файлом
2015-12-05 05:23:07 +03:00
doSomethingWithTheFile(fh);
doSomethingElseWithIt(fh);
2015-12-05 17:07:14 +03:00
} // Здесь файл автоматически закрывается в деструкторе.
// Это имеет _о г р о мне йшие _ преимущества:
// 1. Неважно, что произойдет,
2016-04-06 03:07:54 +03:00
// ресурсы (в данном случае дескриптор файла) будут очищены.
// После того, как вы правильно напишете деструктор,
// Больше будет _не во змо жно _ закрыть обработчик файлов или допустить утечку.
2015-12-05 17:07:14 +03:00
// 2. Обратите внимание, что код намного проще.
2016-04-06 03:07:54 +03:00
// Деструктор закрывает файловый поток "за кулисами", и вам больше не нужно о б
// этом беспокоиться.
2015-12-05 17:07:14 +03:00
// 3. Код устойчив к исключениям.
2016-04-06 03:07:54 +03:00
// Исключение может быть брошено в любом месте в функции, и это никак не повлияет
2015-12-05 17:07:14 +03:00
// на очистку.
// Весь идиоматический код на С ++ широко использует RAII для всех ресурсов.
2016-04-06 03:07:54 +03:00
// Дополнительные примеры включат:
2015-12-05 17:07:14 +03:00
// - Использование памяти unique_ptr и shared_ptr
2017-01-03 10:41:35 +03:00
// - Контейнеры - стандартная библиотека связанных списков, векторы
2016-04-06 03:07:54 +03:00
// (т.е . самоизменяемые массивы), хэш-таблицы и все остальное автоматически
// уничтожается сразу же, когда выходит за пределы области видимости.
2019-12-09 18:53:02 +03:00
// - Использование мьютексов lock_guard и unique_lock
2015-12-05 17:07:14 +03:00
// Контейнеры с пользовательскими классами в качестве ключей требуют
2016-04-06 03:07:54 +03:00
// сравнивающих функций в самом объекте или как указатель на функцию. Примитивы
2015-12-05 17:07:14 +03:00
// имеют компараторы по умолчанию, но вы можете перегрузить их.
2015-12-05 05:23:07 +03:00
class Foo {
public:
int j;
Foo(int a) : j(a) {}
};
struct compareFunction {
bool operator()(const Foo& a, const Foo& b) const {
return a.j < b.j ;
}
};
2015-12-05 17:07:14 +03:00
// это не допускается (хотя это может варьироваться в зависимости от компилятора)
// std::map< Foo , int > fooMap;
2015-12-05 05:23:07 +03:00
std::map< Foo , int , compareFunction > fooMap;
fooMap[Foo(1)] = 1;
fooMap.find(Foo(1)); //true
/////////////////////
2015-12-05 17:07:14 +03:00
// Веселые вещи
2015-12-05 05:23:07 +03:00
/////////////////////
2016-04-06 03:07:54 +03:00
// Аспекты С ++, которые могут быть удивительными для новичков (и даже для некоторых
2015-12-05 17:07:14 +03:00
// ветеранов). Этот раздел, к сожалению, очень неполон. С ++ является одним из самых
// простых языков, где очень легко выстрелить с е б е в ногу.
2015-12-05 05:23:07 +03:00
2015-12-05 17:07:14 +03:00
// Вы можете перегрузить приватные методы!
2015-12-05 05:23:07 +03:00
class Foo {
virtual void bar();
};
class FooSub : public Foo {
2015-12-05 17:07:14 +03:00
virtual void bar(); // Перегружает Foo::bar!
2015-12-05 05:23:07 +03:00
};
2015-12-05 17:07:14 +03:00
// 0 == false == NULL (в основном)!
2015-12-05 05:23:07 +03:00
bool* pt = new bool;
2015-12-05 17:07:14 +03:00
*pt = 0; // Устанавливает значение указателя 'pt' в false.
2016-04-06 03:07:54 +03:00
pt = 0; // Устанавливает значение 'pt' в нулевой указатель. О б е строки проходят
2015-12-05 17:07:14 +03:00
// компиляцию без ошибок.
2015-12-05 05:23:07 +03:00
2015-12-05 17:07:14 +03:00
// nullptr приходит на помощь:
2015-12-05 05:23:07 +03:00
int* pt2 = new int;
2015-12-05 17:07:14 +03:00
*pt2 = nullptr; // Н е пройдет компиляцию
pt2 = nullptr; // Устанавливает pt2 в null.
2015-12-05 05:23:07 +03:00
2015-12-05 17:07:14 +03:00
// Существует исключение для булевых значений.
// Это позволит вам проверить указатели с помощью if(!ptr),
// но как следствие вы можете установить nullptr в bool напрямую!
*pt = nullptr; // Это по прежнему проходит компиляцию, даже если '*pt' - bool!
2015-12-05 05:23:07 +03:00
// '=' != '=' != '='!
2015-12-05 17:07:14 +03:00
// Вызывает Foo::Foo(const Foo& ) или некий вариант (смотрите "move semantics")
2017-07-27 19:26:17 +03:00
// конструктора копирования.
2015-12-05 05:23:07 +03:00
Foo f2;
Foo f1 = f2;
2015-12-05 17:07:14 +03:00
// Вызывает Foo::Foo(const Foo& ) или вариант, но копирует только часть 'Foo' из
// 'fooSub'. Любые другие члены 'fooSub' пропускаются. Иногда это ужасное поведение
// называют "object slicing."
2015-12-05 05:23:07 +03:00
FooSub fooSub;
Foo f1 = fooSub;
2015-12-05 17:07:14 +03:00
// Вызывает Foo::operator=(Foo& ) или вариант.
2015-12-05 05:23:07 +03:00
Foo f1;
f1 = f2;
2015-12-05 17:07:14 +03:00
// Как по-настоящему очистить контейнер:
2015-12-05 05:23:07 +03:00
class Foo { ... };
vector< Foo > v;
for (int i = 0; i < 10 ; + + i )
v.push_back(Foo());
2016-04-06 03:07:54 +03:00
// В следующей точке размер v устанавливается в 0, но деструктор не вызывается
2015-12-05 17:07:14 +03:00
// и не происходит очистка ресурсов!
2015-12-05 05:23:07 +03:00
v.empty();
2015-12-05 17:07:14 +03:00
v.push_back(Foo()); // Новые значения копируются в первый вставленный Foo
2015-12-05 05:23:07 +03:00
2016-04-06 03:07:54 +03:00
// Настоящее уничтожение всех значений v. Смотрите раздел о временном объекте
2015-12-05 17:07:14 +03:00
// для объяснения того, как это работает.
2015-12-05 05:23:07 +03:00
v.swap(vector< Foo > ());
```
2024-04-06 18:33:50 +03:00
2016-04-06 03:07:54 +03:00
## Дальнейшее чтение:
2015-12-05 05:23:07 +03:00
2018-10-14 03:06:28 +03:00
* Наиболее полное и обновленное руководство по С ++ можно найти на [CPP Reference ](http://cppreference.com/w/cpp ).
* Дополнительные ресурсы могут быть найдены на [CPlusPlus ](http://cplusplus.com ).
2018-10-14 03:21:14 +03:00
* Учебник, посвященный основам языка и настройке среды кодирования, доступен в [TheChernoProject - C ++ ](https://www.youtube.com/playlist?list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb ).