2015-12-03 18:19:34 +03:00
|
|
|
|
---
|
|
|
|
|
language: c++
|
|
|
|
|
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"
|
|
|
|
|
- язык с поддержкой абстракции данных
|
|
|
|
|
- язык с поддержкой объектно-ориентированого программирования
|
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 указывает на количество аргументов,
|
2016-04-06 03:07:54 +03:00
|
|
|
|
// а argv является массивом C-подобных строк (char*), который непосредсвенно
|
2015-12-05 05:23:07 +03:00
|
|
|
|
// содержит аргументы.
|
|
|
|
|
// Первым аргументом всегда передается имя программы.
|
2016-04-06 03:07:54 +03:00
|
|
|
|
// argc и argv могут быть опущены, если вы не планируете работать с аругментами
|
2015-12-05 05:23:07 +03:00
|
|
|
|
// коммандной строки.
|
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()
|
|
|
|
|
{
|
2016-04-06 03:07:54 +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);
|
|
|
|
|
|
2016-04-06 03:07:54 +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 есть простой способ назначения типа перечисления, что
|
|
|
|
|
// полезно в случае сериализации данных и преобразований между конечным типом и
|
|
|
|
|
// соответствующими константами.
|
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()
|
|
|
|
|
{
|
|
|
|
|
cout << "Goodbye " << name << "\n";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 {
|
|
|
|
|
|
|
|
|
|
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() { };
|
|
|
|
|
|
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
|
|
|
|
// Во время компиляции компилятор фактически генерирует копии каждого шаблона
|
|
|
|
|
// с замещенными параметрами, поэтому полное определение класса должно присутствовать
|
|
|
|
|
// при каждом вызове. Именно поэтому классы шаблонов полностью определены в
|
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
|
|
|
|
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
|
|
|
|
|
2015-12-05 17:07:14 +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
|
|
|
|
|
2015-12-05 17:07:14 +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
|
|
|
|
|
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 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
|
|
|
|
|
// - Контейнеры - стандартная библиотека связанных списков, векторы
|
2016-04-06 03:07:54 +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")
|
|
|
|
|
// копирования конструктора.
|
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>());
|
|
|
|
|
|
|
|
|
|
```
|
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
|
|
|
|
<http://cppreference.com/w/cpp>
|
|
|
|
|
|
2015-12-05 17:07:14 +03:00
|
|
|
|
Дополнительные ресурсы могут быть найдены на <http://cplusplus.com>
|