Advertisement
Guest User

Untitled

a guest
Apr 8th, 2020
198
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 39.13 KB | None | 0 0
  1.  
  2. //////////////////
  3. // Сравнение с C
  4. //////////////////
  5.  
  6. // C++ практически представляет собой надмножество C и имеет схожий синтаксис
  7. // для объявления переменных, примитивов и функций.
  8.  
  9. // Так же, как и в С, точкой входа в программу является функция с именем main,
  10. // которая возвращает целочисленное значение.
  11. // Это значение является кодом ответа программы.
  12. // Смотрите https://goo.gl/JYGKyv для более подробной информации.
  13. int main(int argc, char** argv)
  14. {
  15.     // Аргументы командной строки, переданные в программу, хранятся в переменных
  16.     // argc и argv, так же, как и в C.
  17.     // argc указывает на количество аргументов,
  18.     // а argv является массивом C-подобных строк (char*), который непосредственно
  19.     // содержит аргументы.
  20.     // Первым аргументом всегда передается имя программы.
  21.     // argc и argv могут быть опущены, если вы не планируете работать с аргументами
  22.     // командной строки.
  23.     // Тогда сигнатура функции будет иметь следующий вид: int main()
  24.  
  25.     // Возвращаемое значение 0 указывает на успешное завершение программы.
  26.     return 0;
  27. }
  28.  
  29. // Тем не менее, C++ имеет свои отличия:
  30.  
  31. // В C++ символьные литералы имеют тип char.
  32. sizeof('c') == sizeof(char) == 1
  33.  
  34. // В C символьные литералы - целые числа.
  35. sizeof('c') == sizeof(int)
  36.  
  37.  
  38. // C++ имеет строгое прототипирование.
  39. void func(); // функция, которая не принимает аргументов.
  40.  
  41. // В языке C
  42. void func(); // функция, которая может принять сколько угодно аргументов.
  43.  
  44. // Использование nullptr вместо NULL в C++.
  45. int* ip = nullptr;
  46.  
  47. // Стандартные заголовочные файлы С доступны в С++,
  48. // но с префиксом "с" и не имеют суффикса .h.
  49. #include <cstdio>
  50.  
  51. int main()
  52. {
  53.     printf("Hello, world!\n");
  54.     return 0;
  55. }
  56.  
  57. ///////////////////////
  58. // Перегрузка функций
  59. ///////////////////////
  60.  
  61. // С++ поддерживает перегрузку функций, при условии,
  62. // что каждая функция принимает различные параметры.
  63.  
  64. void print(char const* myString)
  65. {
  66.     printf("String %s\n", myString);
  67. }
  68.  
  69. void print(int myInt)
  70. {
  71.     printf("My int is %d", myInt);
  72. }
  73.  
  74. int main()
  75. {
  76.     print("Hello"); // Использование void print(const char*)
  77.     print(15); // Использование void print(int)
  78. }
  79.  
  80. /////////////////////////////
  81. // Аргументы функций по умолчанию
  82. /////////////////////////////
  83.  
  84. // Вы можете предоставить аргументы по умолчанию для функции,
  85. // если они не предоставлены при вызове функции.
  86.  
  87. void doSomethingWithInts(int a = 1, int b = 4)
  88. {
  89.     // Здесь что-то делаем с числами
  90. }
  91.  
  92. int main()
  93. {
  94.     doSomethingWithInts();      // a = 1,  b = 4
  95.     doSomethingWithInts(20);    // a = 20, b = 4
  96.     doSomethingWithInts(20, 5); // a = 20, b = 5
  97. }
  98.  
  99. // Аргументы по умолчанию должны быть в конце списка аргументов.
  100.  
  101. void invalidDeclaration(int a = 1, int b) // Ошибка!
  102. {
  103. }
  104.  
  105.  
  106. /////////////
  107. // Пространства имен
  108. /////////////
  109.  
  110. // Пространства имен предоставляют отдельные области для переменной,
  111. // функции и других объявлений.
  112. // Пространства имен могут быть вложенными.
  113.  
  114. namespace First {
  115.     namespace Nested {
  116.         void foo()
  117.         {
  118.             printf("This is First::Nested::foo\n");
  119.         }
  120.     } // конец пространства имен Nested
  121. } // конец пространства имен First
  122.  
  123. namespace Second {
  124.     void foo()
  125.     {
  126.         printf("This is Second::foo\n")
  127.     }
  128. }
  129.  
  130. void foo()
  131. {
  132.     printf("This is global foo\n");
  133. }
  134.  
  135. int main()
  136. {
  137.     // Включает все функции из пространства имен Second в текущую область видимости.
  138.     // Обратите внимание, что простой вызов foo() больше не работает,
  139.     // так как теперь не ясно, вызываем ли мы foo из пространства имен Second, или
  140.     // из глобальной области видимости.
  141.     using namespace Second;
  142.  
  143.     Second::foo(); // напечатает "This is Second::foo"
  144.     First::Nested::foo(); // напечатает "This is First::Nested::foo"
  145.     ::foo(); // напечатает "This is global foo"
  146. }
  147.  
  148. ///////////////
  149. // Ввод и вывод
  150. ///////////////
  151.  
  152. // Ввод и вывод в C++ использует потоки
  153. // cin, cout и cerr представляют потоки stdin, stdout и stderr.
  154. // << - оператор вставки, >> - оператор извлечения.
  155.  
  156. #include <iostream> // Включение файла для работы с потоками Ввода\Вывода.
  157.  
  158. using namespace std; // Потоки доступны в пространстве имен std (стандартная библиотека)
  159.  
  160. int main()
  161. {
  162.    int myInt;
  163.  
  164.    // Выводит в stdout (или в терминал/на экран)
  165.    cout << "Enter your favorite number:\n";
  166.    // Принимает ввод
  167.    cin >> myInt;
  168.  
  169.    // cout может принимать форматирование
  170.    cout << "Your favorite number is " << myInt << "\n";
  171.    // напечатает "Your favorite number is <myInt>"
  172.  
  173.     cerr << "Used for error messages";
  174. }
  175.  
  176. //////////
  177. // Строки
  178. //////////
  179.  
  180. // Строки в C++ являются объектами и имеют много функций-членов.
  181. #include <string>
  182.  
  183. using namespace std; // Строки также доступны в пространстве имен std (стандартная библиотека)
  184.  
  185. string myString = "Hello";
  186. string myOtherString = " World";
  187.  
  188. // + используется для конкатенации строк.
  189. cout << myString + myOtherString; // "Hello World"
  190.  
  191. cout << myString + " You"; // "Hello You"
  192.  
  193. // Строки в C++ могут изменяться и имеют семантику значений.
  194. myString.append(" Dog");
  195. cout << myString; // "Hello Dog"
  196.  
  197.  
  198. /////////////
  199. // Ссылки
  200. /////////////
  201.  
  202. // Кроме указателей, доступных в C,
  203. // C++ имеет _ссылки_.
  204. // Это такой тип указателя, который не может быть переназначен после инициализации
  205. // и не может иметь значения null.
  206. // Ссылки имеют схожий с переменными синтаксис:
  207. // * больше не используется для разыменования и
  208. // & (адрес) не используется для назначения.
  209.  
  210. using namespace std;
  211.  
  212. string foo = "I am foo";
  213. string bar = "I am bar";
  214.  
  215.  
  216. string& fooRef = foo; // Здесь создается ссылка на foo.
  217. fooRef += ". Hi!"; // Изменяет foo по ссылке
  218. cout << fooRef; // Печатает "I am foo. Hi!"
  219.  
  220. // Не переназначает "fooRef". Это то же самое, что и "foo = bar", и
  221. //   foo == "I am bar"
  222. // после этой строчки.
  223. cout << &fooRef << endl; // Печатает адрес foo
  224. fooRef = bar;
  225. cout << &fooRef << endl; // По-прежнему печатает адрес foo
  226. cout << fooRef;  // Печатает "I am bar"
  227.  
  228. // Адрес fooRef остается тем же, то есть он по-прежнему ссылается на foo.
  229.  
  230.  
  231. const string& barRef = bar; // Создает константную ссылку.
  232. // Так же, как и в C, константные значения (а также указатели и ссылки) не могут быть изменены.
  233. barRef += ". Hi!"; // Ошибка, константная ссылка не может быть изменена.
  234.  
  235. // Обходной путь: Прежде чем мы рассмотрим указатели более детально, нам нужно ознакомиться
  236. // с концепцией, известной как "временный объект". Представьте, что мы имеем следующий код
  237. string tempObjectFun() { ... }
  238. string retVal = tempObjectFun();
  239.  
  240. // Вот что на самом деле происходит во второй строке:
  241. //   - tempObjectFun возвращает строковый объект
  242. //   - из возвращаемого объекта создается новая строка в качестве аргумента конструктору
  243. //   - возвращаемый объект уничтожается
  244. // Возвращаемый объект называется временным объектом. Временные объекты создаются,
  245. // когда функция возвращает объект, и уничтожаются в конце выполнения обрамляющего
  246. // выражения (По крайней мере, так это описывает спецификация, хотя компиляторы могут
  247. // изменять это поведение. Для более подробной информации смотрите "оптимизация
  248. // возвращаемого значения".) Таким образом в этом коде:
  249. foo(bar(tempObjectFun()))
  250.  
  251. // предполагая, что foo и bar существуют, объект, возвращаемый tempObjectFun, передается
  252. // в bar, и уничтожается перед вызовом foo.
  253.  
  254. // Возвращаемся к указателям. Исключением для правила "в конце выполнения обрамляющего
  255. // выражения" является временный объект, привязанный к ссылке const, в этом случае
  256. // его жизненный цикл продлевается до текущей области видимости:
  257.  
  258. void constReferenceTempObjectFun() {
  259.   // constRef получает временный объект, и он действителен до конца этой функции.
  260.   const string& constRef = tempObjectFun();
  261.   ...
  262. }
  263.  
  264. // В C++11 предоставлен еще один тип ссылок специально для временных объектов.
  265. // objects. Вы не можете объявить переменную этого типа, но он имеет приоритет
  266. // в резолюции перегрузки:
  267.  
  268. void someFun(string& s) { ... }  // Обычная ссылка
  269. void someFun(string&& s) { ... }  // Ссылка на временный объект
  270.  
  271. string foo;
  272. someFun(foo);  // Выполняет версию с обычной ссылкой
  273. someFun(tempObjectFun());  // Выполняет версию с временной ссылкой.
  274.  
  275. // Например, существуют следующие две версии конструктора std::basic_string:
  276. basic_string(const basic_string& other);
  277. basic_string(basic_string&& other);
  278.  
  279. // Идея в том, что если мы конструируем новую строку из временного объекта (который
  280. // так или иначе будет уничтожен), мы можем использовать более эффективный конструктор,
  281. // который "спасает" части этой временной строки. Эта концепция была названа
  282. // "move semantics".
  283.  
  284. /////////////////////
  285. // Перечисления
  286. /////////////////////
  287.  
  288. // Перечисления - способ объявления констант и установки их значений, в основном
  289. // использующийся для упрощения чтения кода.
  290. enum ECarTypes
  291. {
  292.   Sedan,
  293.   Hatchback,
  294.   SUV,
  295.   Wagon
  296. };
  297.  
  298. ECarTypes GetPreferredCarType()
  299. {
  300.     return ECarTypes::Hatchback;
  301. }
  302.  
  303. // На момент выхода C++11 есть простой способ назначения типа перечисления, что
  304. // полезно в случае сериализации данных и преобразований между конечным типом и
  305. // соответствующими константами.
  306. enum ECarTypes : uint8_t
  307. {
  308.   Sedan, // 0
  309.   Hatchback, // 1
  310.   SUV = 254, // 254
  311.   Hybrid // 255
  312. };
  313.  
  314. void WriteByteToFile(uint8_t InputValue)
  315. {
  316.     // Сериализуем InputValue в файл
  317. }
  318.  
  319. void WritePreferredCarTypeToFile(ECarTypes InputCarType)
  320. {
  321.     // Перечисление неявно преобразуется в uint8_t из-за ранее объявленного
  322.     // типа перечисления.
  323.     WriteByteToFile(InputCarType);
  324. }
  325.  
  326. // С другой стороны, чтобы избежать случайного приведения к целочисленному типу или
  327. // другому перечислению, вы можете создать класс перечисления, который не будет
  328. // преобразовываться неявно.
  329. enum class ECarTypes : uint8_t
  330. {
  331.   Sedan, // 0
  332.   Hatchback, // 1
  333.   SUV = 254, // 254
  334.   Hybrid // 255
  335. };
  336.  
  337. void WriteByteToFile(uint8_t InputValue)
  338. {
  339.     // Сериализуем InputValue в файл
  340. }
  341.  
  342. void WritePreferredCarTypeToFile(ECarTypes InputCarType)
  343. {
  344.     // Хотя ECarTypes имеет тип uint8_t, код не будет скомпилирован из-за того,
  345.     // что перечисление было объявлено как класс перечисления.
  346.     WriteByteToFile(InputCarType);
  347. }
  348.  
  349. //////////////////////////////////////////
  350. // Классы и объектно-ориентированное программирование
  351. //////////////////////////////////////////
  352.  
  353. // Пример классов
  354. #include <iostream>
  355.  
  356. // Объявление класса.
  357. // Обычно классы объявляют в заголовочном (.h или .hpp) файле.
  358. class Dog {
  359.     // Переменные-члены и функции являются приватными по умолчанию.
  360.     std::string name;
  361.     int weight;
  362.  
  363. // Все члены после этой сроки являются открытыми
  364. // пока "private:" или "protected:" не будет объявлено.
  365. public:
  366.  
  367.     // Конструктор по умолчанию
  368.     Dog();
  369.  
  370.     // Объявление функций-членов
  371.     // Обратите внимание, мы используем std::string здесь вместо использования
  372.     // using namespace std;
  373.     // выше.
  374.     // Никогда не размещайте выражение "using namespace" в заголовке.
  375.     void setName(const std::string& dogsName);
  376.  
  377.     void setWeight(int dogsWeight);
  378.  
  379.     // Функции, которые не изменяют состояние объекта,
  380.     // должны быть помечены как const.
  381.     // Это позволяет вызывать их, если дана const ссылка на объект.
  382.     // Обратите внимание, функции должны быть явно объявлены как _virtual_,
  383.     // если вы хотите перегрузить их в производных классах.
  384.     // Функции не являются виртуальными по умолчанию для повышения производительности.
  385.     virtual void print() const;
  386.  
  387.     // Также функции могут быть определены внутри тела класса.
  388.     // Функции, определенные следующим образом, автоматически встроены.
  389.     void bark() const { std::cout << name << " barks!\n"; }
  390.  
  391.     // Наряду с конструкторами, в C++ есть деструкторы.
  392.     // Они вызываются, когда объект удаляется или выпадает из области видимости.
  393.     // Это активирует мощную парадигму программирования, известную как RAII
  394.     // (смотрите ниже)
  395.     // Деструктор должен быть виртуальным, если класс будет производным.
  396.     // Если он не виртуальный, тогда деструктор производного класса не будет вызван,
  397.     // если объект удален по ссылке или указателю базового класса.
  398.     virtual ~Dog();
  399.  
  400. }; // Определение класса должно завершаться точкой с запятой.
  401.  
  402. // Функции-члены класса, как правило, реализуются в .cpp файлах.
  403. Dog::Dog()
  404. {
  405.     std::cout << "A dog has been constructed\n";
  406. }
  407.  
  408. // Объекты (такие как строки) должны передаваться по ссылке если вы будете
  409. // изменять их, или const-ссылке если нет.
  410. void Dog::setName(const std::string& dogsName)
  411. {
  412.     name = dogsName;
  413. }
  414.  
  415. void Dog::setWeight(int dogsWeight)
  416. {
  417.     weight = dogsWeight;
  418. }
  419.  
  420. // Обратите внимание, "virtual" требуется только в объявлении, не в определении.
  421. void Dog::print() const
  422. {
  423.     std::cout << "Dog is " << name << " and weighs " << weight << "kg\n";
  424. }
  425.  
  426. Dog::~Dog()
  427. {
  428.     std::cout << "Goodbye " << name << "\n";
  429. }
  430.  
  431. int main() {
  432.     Dog myDog; // Печатает "A dog has been constructed"
  433.     myDog.setName("Barkley");
  434.     myDog.setWeight(10);
  435.     myDog.print(); // Печатает "Dog is Barkley and weighs 10 kg"
  436.     return 0;
  437. } // Печатает "Goodbye Barkley"
  438.  
  439. // Интерфейсы:
  440.  
  441. // Этот класс наследует все открытые и защищенные члены класса Dog
  442. // так же, как и все закрытые, но не может непосредственно получить доступ к закрытым
  443. // членам\методам без открытых или защищенных методов для этого.
  444. class OwnedDog : public Dog {
  445.  
  446. public:
  447.     void setOwner(const std::string& dogsOwner);
  448.  
  449.     // Переопределяем поведение функции печати для всех OwnedDog. Смотрите
  450.     // https://goo.gl/3kuH2x для боле общего введения, если вы не знакомы
  451.     // с концепцией полиморфизма подтипов (включения).
  452.     // Ключевое слово override является необязательным, но указывает, что метод
  453.     // на самом деле перегружается в базовом классе.
  454.     void print() const override;
  455.  
  456. private:
  457.     std::string owner;
  458. };
  459.  
  460. // Тем временем, в соответствующем .cpp файле:
  461.  
  462. void OwnedDog::setOwner(const std::string& dogsOwner)
  463. {
  464.     owner = dogsOwner;
  465. }
  466.  
  467. void OwnedDog::print() const
  468. {
  469.     Dog::print(); // Вызывает функцию print в базовом классе Dog
  470.     std::cout << "Dog is owned by " << owner << "\n";
  471.     // Печатает "Dog is <name> and weights <weight>"
  472.     //        "Dog is owned by <owner>"
  473. }
  474.  
  475. //////////////////////////////////////////
  476. // Инициализация и перегрузка операторов.
  477. //////////////////////////////////////////
  478.  
  479. // В C++ вы можете перегрузить поведение таких операторов: +, -, *, / и др..
  480. // Это делается путем определения функции, которая вызывается,
  481. // когда используется оператор.
  482.  
  483. #include <iostream>
  484. using namespace std;
  485.  
  486. class Point {
  487. public:
  488.     // Значения по умолчанию для переменных-членов могут быть установлены
  489.     // следующим образом.
  490.     double x = 0;
  491.     double y = 0;
  492.  
  493.     // Определяем новый конструктор, который инициализирует Point со значениями
  494.     // по умолчанию (0, 0)
  495.     Point() { };
  496.  
  497.     // Следующий синтаксис известен как список инициализации и является верным способом
  498.     // инициализировать значения членов класса.
  499.     Point (double a, double b) :
  500.         x(a),
  501.         y(b)
  502.     { /* Ничего не делайте, кроме инициализации значений */ }
  503.  
  504.     // Перегружаем оператор +.
  505.     Point operator+(const Point& rhs) const;
  506.  
  507.     // Перегружаем оператор +=.
  508.     Point& operator+=(const Point& rhs);
  509.  
  510.     // Имеет смысл добавить перегрузку операторов - и -=,
  511.     // но для краткости мы опустим эти детали.
  512. };
  513.  
  514. Point Point::operator+(const Point& rhs) const
  515. {
  516.     // Создает новую точку, которая является суммой этой точки и rhs.
  517.     return Point(x + rhs.x, y + rhs.y);
  518. }
  519.  
  520. Point& Point::operator+=(const Point& rhs)
  521. {
  522.     x += rhs.x;
  523.     y += rhs.y;
  524.     return *this;
  525. }
  526.  
  527. int main () {
  528.     Point up (0,1);
  529.     Point right (1,0);
  530.     // Здесь происходит вызов оператора + класса Point
  531.     // Точка "up" вызывает + (функция) с параметром "right"
  532.     Point result = up + right;
  533.     // Печатает "Result is upright (1,1)"
  534.     cout << "Result is upright (" << result.x << ',' << result.y << ")\n";
  535.     return 0;
  536. }
  537.  
  538. /////////////////////
  539. // Шаблоны
  540. /////////////////////
  541.  
  542. // Шаблоны в С++, в основном, используются для обобщенного программирования, хотя
  543. // они гораздо более мощны, чем дженерики в других языках. Они также поддерживают
  544. // явные, частные и функциональные типы классов; на самом деле, они являются
  545. // тьюринг-полным языком, встроенным в C++!
  546.  
  547. // Мы начнем с наиболее распространенного типа обобщенного программирования. Чтобы
  548. // определить класс или функцию, которая принимает параметр типа:
  549. template<class T>
  550. class Box {
  551. public:
  552.     // В этом классе T может быть любого типа.
  553.     void insert(const T&) { ... }
  554. };
  555.  
  556. // Во время компиляции компилятор фактически генерирует копии каждого шаблона
  557. // с замещенными параметрами, поэтому полное определение класса должно присутствовать
  558. // при каждом вызове. Именно поэтому шаблоны классов полностью определены в
  559. // заголовочных файлах.
  560.  
  561. // Чтобы создать экземпляр шаблона класса на стеке:
  562. Box<int> intBox;
  563.  
  564. // и вы можете использовать его, как и ожидалось:
  565. intBox.insert(123);
  566.  
  567. // Вы, конечно, можете использовать вложенные шаблоны:
  568. Box<Box<int> > boxOfBox;
  569. boxOfBox.insert(intBox);
  570.  
  571. // Вплоть до С++11, вы должны были ставить пробел между двумя символами '>', иначе '>>'
  572. // принимался парсером, как оператор сдвига вправо.
  573.  
  574. // Иногда вы можете увидеть
  575. //   template<typename T>
  576. // вместо этого. В этом случае ключевые слова 'class' и 'typename' _в основном_
  577. // взаимозаменяемыми. Для более подробной информации смотрите
  578. //   http://en.wikipedia.org/wiki/Typename
  579. // (да-да, это ключевое слово имеет собственную страничку на вики).
  580.  
  581. // Аналогичным образом, шаблон функции:
  582. template<class T>
  583. void barkThreeTimes(const T& input)
  584. {
  585.     input.bark();
  586.     input.bark();
  587.     input.bark();
  588. }
  589.  
  590. // Обратите внимание, что здесь ничего не указано о типе параметра. Компилятор
  591. // будет генерировать и затем проверять на тип каждый вызов шаблона, поэтому
  592. // данная функция работает с любым типом 'T', который имеет метод 'bark'.
  593.  
  594. Dog fluffy;
  595. fluffy.setName("Fluffy");
  596. barkThreeTimes(fluffy); // Печатает "Fluffy barks" три раза.
  597.  
  598. // Параметры шаблона не должны быть классами:
  599. template<int Y>
  600. void printMessage() {
  601.   cout << "Learn C++ in " << Y << " minutes!" << endl;
  602. }
  603.  
  604. // В конце концов, вы можете явно специализировать шаблоны для более эффективного
  605. // кода. Конечно, большинство реальных случаев использования специализации
  606. // не так тривиально, как это. Обратите внимание, вам все еще нужно явно объявить
  607. // функцию (или класс) в качестве шаблона, даже если вы явно указали все параметры.
  608. template<>
  609. void printMessage<10>() {
  610.   cout << "Learn C++ faster in only 10 minutes!" << endl;
  611. }
  612.  
  613. printMessage<20>();  // Печатает "Learn C++ in 20 minutes!"
  614. printMessage<10>();  // Печатает "Learn C++ faster in only 10 minutes!"
  615.  
  616.  
  617. /////////////////////
  618. // Обработка исключений
  619. /////////////////////
  620.  
  621. // Стандартная библиотека предоставляет несколько типов исключений
  622. // (смотрите http://en.cppreference.com/w/cpp/error/exception)
  623. // но, в принципе, любой тип может быть брошен в качестве исключения.
  624. #include <exception>
  625. #include <stdexcept>
  626.  
  627. // Все исключения, брошенные в блоке _try_ могут быть пойманы в последующем блоке
  628. // _catch_.
  629. try {
  630.     // Не выделяйте память в куче для исключений с помощью ключевого слова _new_.
  631.     throw std::runtime_error("A problem occurred");
  632. }
  633.  
  634. // Поймайте исключение по константной ссылке, если оно является объектом
  635. catch (const std::exception& ex)
  636. {
  637.     std::cout << ex.what();
  638. }
  639.  
  640. // Ловит любое исключение, не пойманное предыдущим блоком _catch_
  641. catch (...)
  642. {
  643.     std::cout << "Unknown exception caught";
  644.     throw; // Повторный выброс исключения
  645. }
  646.  
  647. ///////
  648. // Получение ресурса есть инициализация (RAII)
  649. ///////
  650.  
  651. // Программная идиома объектно-ориентированного программирования, смысл которой
  652. // заключается в том, что с помощью тех или иных программных механизмов получение
  653. // некоторого ресурса неразрывно совмещается с инициализацией, а освобождение -
  654. // с уничтожением объекта.
  655.  
  656. // Чтобы понять, насколько это полезно,
  657. // рассмотрим функцию, которая использует обработчик файлов в С:
  658. void doSomethingWithAFile(const char* filename)
  659. {
  660.     // Для начала, предположим, ничего не может потерпеть неудачу.
  661.  
  662.     FILE* fh = fopen(filename, "r"); // Открываем файл в режиме чтения.
  663.  
  664.     doSomethingWithTheFile(fh);
  665.     doSomethingElseWithIt(fh);
  666.  
  667.     fclose(fh); // Закрываем обработчик файла.
  668. }
  669.  
  670. // К сожалению, вещи быстро осложняются обработкой ошибок.
  671. // Предположим, fopen может потерпеть неудачу, тогда doSomethingWithTheFile и
  672. // doSomethingElseWithIt вернут коды ошибок, если потерпят неудачу.
  673. //  (Исключения являются предпочтительным способом обработки ошибок,
  674. //   но некоторые программисты, особенно те, кто имеет большой опыт работы с С,
  675. //   не согласны с аргументами о полезности исключений).
  676. // Теперь мы должны проверить каждый вызов на наличие ошибок и закрыть обработчик
  677. // файла, если он есть.
  678. bool doSomethingWithAFile(const char* filename)
  679. {
  680.     FILE* fh = fopen(filename, "r"); // Открывает файл в режиме чтения
  681.     if (fh == nullptr) // В случае неудачи возвращаемый указатель принимает значение null.
  682.         return false; // Сообщает о неудаче вызывающему.
  683.  
  684.     // Предположим, каждая функция возвращает false в случае неудачи
  685.     if (!doSomethingWithTheFile(fh)) {
  686.         fclose(fh); // Закрываем обработчик файла, чтобы не было утечек
  687.         return false; // Сообщает об ошибке.
  688.     }
  689.     if (!doSomethingElseWithIt(fh)) {
  690.         fclose(fh); // Закрываем обработчик файла, чтобы не было утечек
  691.         return false; // Сообщает об ошибке.
  692.     }
  693.  
  694.     fclose(fh); // Закрываем обработчик файла, чтобы не было утечек
  695.     return true; // Указывает на успех
  696. }
  697.  
  698. // C-программисты часто упорядочивают это с помощью goto:
  699. bool doSomethingWithAFile(const char* filename)
  700. {
  701.     FILE* fh = fopen(filename, "r");
  702.     if (fh == nullptr)
  703.         return false;
  704.  
  705.     if (!doSomethingWithTheFile(fh))
  706.         goto failure;
  707.  
  708.     if (!doSomethingElseWithIt(fh))
  709.         goto failure;
  710.  
  711.     fclose(fh); // Закрываем файл.
  712.     return true; // Указывает на успех
  713.  
  714. failure:
  715.     fclose(fh);
  716.     return false; // Сообщает об ошибке.
  717. }
  718.  
  719. // Если функции указывают на ошибки с помощью исключений, вещи становятся проще,
  720. // но все еще не оптимальны.
  721. void doSomethingWithAFile(const char* filename)
  722. {
  723.     FILE* fh = fopen(filename, "r"); // Открываем файл в режиме чтения
  724.     if (fh == nullptr)
  725.         throw std::runtime_error("Could not open the file.");
  726.  
  727.     try {
  728.         doSomethingWithTheFile(fh);
  729.         doSomethingElseWithIt(fh);
  730.     }
  731.     catch (...) {
  732.         fclose(fh); // Убедитесь, что закрываете файл, если происходит ошибка.
  733.         throw; // Затем повторно бросает исключение.
  734.     }
  735.  
  736.     fclose(fh); // Закрываем файл.
  737.     // Успех
  738. }
  739.  
  740. // Сравните это с использованием класса потока файла (fstream) в С++, который
  741. // использует свой деструктор, чтобы закрыть файл. Еще раз взгляните выше,
  742. // деструктор вызывается автоматически, когда объект выпадает из области видимости.
  743. void doSomethingWithAFile(const std::string& filename)
  744. {
  745.     // ifstream определяет файловый поток
  746.     std::ifstream fh(filename); // Открыть файл
  747.  
  748.     // Что-то делать с файлом
  749.     doSomethingWithTheFile(fh);
  750.     doSomethingElseWithIt(fh);
  751.  
  752. } // Здесь файл автоматически закрывается в деструкторе.
  753.  
  754. // Это имеет _огромнейшие_ преимущества:
  755. // 1. Неважно, что произойдет,
  756. //    ресурсы (в данном случае дескриптор файла) будут очищены.
  757. //    После того, как вы правильно напишете деструктор,
  758. //    Больше будет _невозможно_ закрыть обработчик файлов или допустить утечку.
  759. // 2. Обратите внимание, что код намного проще.
  760. //    Деструктор закрывает файловый поток "за кулисами", и вам больше не нужно об
  761. //     этом беспокоиться.
  762. // 3. Код устойчив к исключениям.
  763. //    Исключение может быть брошено в любом месте в функции, и это никак не повлияет
  764. //    на очистку.
  765.  
  766. // Весь идиоматический код на С++ широко использует RAII для всех ресурсов.
  767. // Дополнительные примеры включат:
  768. // - Использование памяти unique_ptr и shared_ptr
  769. // - Контейнеры - стандартная библиотека связанных списков, векторы
  770. //   (т.е. самоизменяемые массивы), хэш-таблицы и все остальное автоматически
  771. //    уничтожается сразу же, когда выходит за пределы области видимости.
  772. // - Использование мьютексов lock_guard и unique_lock
  773.  
  774. // Контейнеры с пользовательскими классами в качестве ключей требуют
  775. // сравнивающих функций в самом объекте или как указатель на функцию. Примитивы
  776. // имеют компараторы по умолчанию, но вы можете перегрузить их.
  777. class Foo {
  778. public:
  779.     int j;
  780.     Foo(int a) : j(a) {}
  781. };
  782. struct compareFunction {
  783.     bool operator()(const Foo& a, const Foo& b) const {
  784.         return a.j < b.j;
  785.     }
  786. };
  787. // это не допускается (хотя это может варьироваться в зависимости от компилятора)
  788. // std::map<Foo, int> fooMap;
  789. std::map<Foo, int, compareFunction> fooMap;
  790. fooMap[Foo(1)]  = 1;
  791. fooMap.find(Foo(1)); //true
  792.  
  793. /////////////////////
  794. // Веселые вещи
  795. /////////////////////
  796.  
  797. // Аспекты С++, которые могут быть удивительными для новичков (и даже для некоторых
  798. // ветеранов). Этот раздел, к сожалению, очень неполон. С++ является одним из самых
  799. // простых языков, где очень легко выстрелить себе в ногу.
  800.  
  801. // Вы можете перегрузить приватные методы!
  802. class Foo {
  803.   virtual void bar();
  804. };
  805. class FooSub : public Foo {
  806.   virtual void bar();  // Перегружает Foo::bar!
  807. };
  808.  
  809.  
  810. // 0 == false == NULL (в основном)!
  811. bool* pt = new bool;
  812. *pt = 0; // Устанавливает значение указателя 'pt' в false.
  813. pt = 0;  // Устанавливает значение 'pt' в нулевой указатель. Обе строки проходят
  814.         // компиляцию без ошибок.
  815.  
  816. // nullptr приходит на помощь:
  817. int* pt2 = new int;
  818. *pt2 = nullptr; // Не пройдет компиляцию
  819. pt2 = nullptr;  // Устанавливает pt2 в null.
  820.  
  821. // Существует исключение для булевых значений.
  822. // Это позволит вам проверить указатели с помощью if(!ptr),
  823. // но как следствие вы можете установить nullptr в bool напрямую!
  824. *pt = nullptr;  // Это по прежнему проходит компиляцию, даже если '*pt' - bool!
  825.  
  826.  
  827. // '=' != '=' != '='!
  828. // Вызывает Foo::Foo(const Foo&) или некий вариант (смотрите "move semantics")
  829. // конструктора копирования.
  830. Foo f2;
  831. Foo f1 = f2;
  832.  
  833. // Вызывает Foo::Foo(const Foo&) или вариант, но копирует только часть 'Foo' из
  834. // 'fooSub'. Любые другие члены 'fooSub' пропускаются. Иногда это ужасное поведение
  835. // называют "object slicing."
  836. FooSub fooSub;
  837. Foo f1 = fooSub;
  838.  
  839. // Вызывает Foo::operator=(Foo&) или вариант.
  840. Foo f1;
  841. f1 = f2;
  842.  
  843.  
  844. // Как по-настоящему очистить контейнер:
  845. class Foo { ... };
  846. vector<Foo> v;
  847. for (int i = 0; i < 10; ++i)
  848.   v.push_back(Foo());
  849.  
  850. // В следующей точке размер v устанавливается в 0, но деструктор не вызывается
  851. // и не происходит очистка ресурсов!
  852. v.empty();
  853. v.push_back(Foo());  // Новые значения копируются в первый вставленный Foo
  854.  
  855. // Настоящее уничтожение всех значений v. Смотрите раздел о временном объекте
  856. // для объяснения того, как это работает.
  857. v.swap(vector<Foo>());
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement