Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- //////////////////
- // Сравнение с C
- //////////////////
- // C++ практически представляет собой надмножество C и имеет схожий синтаксис
- // для объявления переменных, примитивов и функций.
- // Так же, как и в С, точкой входа в программу является функция с именем main,
- // которая возвращает целочисленное значение.
- // Это значение является кодом ответа программы.
- // Смотрите https://goo.gl/JYGKyv для более подробной информации.
- int main(int argc, char** argv)
- {
- // Аргументы командной строки, переданные в программу, хранятся в переменных
- // argc и argv, так же, как и в C.
- // argc указывает на количество аргументов,
- // а argv является массивом C-подобных строк (char*), который непосредственно
- // содержит аргументы.
- // Первым аргументом всегда передается имя программы.
- // argc и argv могут быть опущены, если вы не планируете работать с аргументами
- // командной строки.
- // Тогда сигнатура функции будет иметь следующий вид: int main()
- // Возвращаемое значение 0 указывает на успешное завершение программы.
- return 0;
- }
- // Тем не менее, C++ имеет свои отличия:
- // В C++ символьные литералы имеют тип char.
- sizeof('c') == sizeof(char) == 1
- // В C символьные литералы - целые числа.
- sizeof('c') == sizeof(int)
- // C++ имеет строгое прототипирование.
- void func(); // функция, которая не принимает аргументов.
- // В языке C
- 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
- }
- // Аргументы по умолчанию должны быть в конце списка аргументов.
- 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()
- {
- // Включает все функции из пространства имен Second в текущую область видимости.
- // Обратите внимание, что простой вызов foo() больше не работает,
- // так как теперь не ясно, вызываем ли мы foo из пространства имен Second, или
- // из глобальной области видимости.
- using namespace Second;
- Second::foo(); // напечатает "This is Second::foo"
- First::Nested::foo(); // напечатает "This is First::Nested::foo"
- ::foo(); // напечатает "This is global foo"
- }
- ///////////////
- // Ввод и вывод
- ///////////////
- // Ввод и вывод в C++ использует потоки
- // cin, cout и cerr представляют потоки stdin, stdout и stderr.
- // << - оператор вставки, >> - оператор извлечения.
- #include <iostream> // Включение файла для работы с потоками Ввода\Вывода.
- using namespace std; // Потоки доступны в пространстве имен std (стандартная библиотека)
- int main()
- {
- int myInt;
- // Выводит в stdout (или в терминал/на экран)
- cout << "Enter your favorite number:\n";
- // Принимает ввод
- cin >> myInt;
- // cout может принимать форматирование
- cout << "Your favorite number is " << myInt << "\n";
- // напечатает "Your favorite number is <myInt>"
- cerr << "Used for error messages";
- }
- //////////
- // Строки
- //////////
- // Строки в C++ являются объектами и имеют много функций-членов.
- #include <string>
- using namespace std; // Строки также доступны в пространстве имен std (стандартная библиотека)
- string myString = "Hello";
- string myOtherString = " World";
- // + используется для конкатенации строк.
- cout << myString + myOtherString; // "Hello World"
- cout << myString + " You"; // "Hello You"
- // Строки в C++ могут изменяться и имеют семантику значений.
- myString.append(" Dog");
- cout << myString; // "Hello Dog"
- /////////////
- // Ссылки
- /////////////
- // Кроме указателей, доступных в C,
- // C++ имеет _ссылки_.
- // Это такой тип указателя, который не может быть переназначен после инициализации
- // и не может иметь значения null.
- // Ссылки имеют схожий с переменными синтаксис:
- // * больше не используется для разыменования и
- // & (адрес) не используется для назначения.
- using namespace std;
- string foo = "I am foo";
- string bar = "I am bar";
- string& fooRef = foo; // Здесь создается ссылка на foo.
- fooRef += ". Hi!"; // Изменяет foo по ссылке
- cout << fooRef; // Печатает "I am foo. Hi!"
- // Не переназначает "fooRef". Это то же самое, что и "foo = bar", и
- // foo == "I am bar"
- // после этой строчки.
- cout << &fooRef << endl; // Печатает адрес foo
- fooRef = bar;
- cout << &fooRef << endl; // По-прежнему печатает адрес foo
- cout << fooRef; // Печатает "I am bar"
- // Адрес fooRef остается тем же, то есть он по-прежнему ссылается на foo.
- const string& barRef = bar; // Создает константную ссылку.
- // Так же, как и в C, константные значения (а также указатели и ссылки) не могут быть изменены.
- barRef += ". Hi!"; // Ошибка, константная ссылка не может быть изменена.
- // Обходной путь: Прежде чем мы рассмотрим указатели более детально, нам нужно ознакомиться
- // с концепцией, известной как "временный объект". Представьте, что мы имеем следующий код
- string tempObjectFun() { ... }
- string retVal = tempObjectFun();
- // Вот что на самом деле происходит во второй строке:
- // - tempObjectFun возвращает строковый объект
- // - из возвращаемого объекта создается новая строка в качестве аргумента конструктору
- // - возвращаемый объект уничтожается
- // Возвращаемый объект называется временным объектом. Временные объекты создаются,
- // когда функция возвращает объект, и уничтожаются в конце выполнения обрамляющего
- // выражения (По крайней мере, так это описывает спецификация, хотя компиляторы могут
- // изменять это поведение. Для более подробной информации смотрите "оптимизация
- // возвращаемого значения".) Таким образом в этом коде:
- foo(bar(tempObjectFun()))
- // предполагая, что foo и bar существуют, объект, возвращаемый tempObjectFun, передается
- // в bar, и уничтожается перед вызовом foo.
- // Возвращаемся к указателям. Исключением для правила "в конце выполнения обрамляющего
- // выражения" является временный объект, привязанный к ссылке const, в этом случае
- // его жизненный цикл продлевается до текущей области видимости:
- void constReferenceTempObjectFun() {
- // constRef получает временный объект, и он действителен до конца этой функции.
- const string& constRef = tempObjectFun();
- ...
- }
- // В C++11 предоставлен еще один тип ссылок специально для временных объектов.
- // objects. Вы не можете объявить переменную этого типа, но он имеет приоритет
- // в резолюции перегрузки:
- void someFun(string& s) { ... } // Обычная ссылка
- void someFun(string&& s) { ... } // Ссылка на временный объект
- string foo;
- someFun(foo); // Выполняет версию с обычной ссылкой
- someFun(tempObjectFun()); // Выполняет версию с временной ссылкой.
- // Например, существуют следующие две версии конструктора std::basic_string:
- basic_string(const basic_string& other);
- basic_string(basic_string&& other);
- // Идея в том, что если мы конструируем новую строку из временного объекта (который
- // так или иначе будет уничтожен), мы можем использовать более эффективный конструктор,
- // который "спасает" части этой временной строки. Эта концепция была названа
- // "move semantics".
- /////////////////////
- // Перечисления
- /////////////////////
- // Перечисления - способ объявления констант и установки их значений, в основном
- // использующийся для упрощения чтения кода.
- enum ECarTypes
- {
- Sedan,
- Hatchback,
- SUV,
- Wagon
- };
- ECarTypes GetPreferredCarType()
- {
- return ECarTypes::Hatchback;
- }
- // На момент выхода C++11 есть простой способ назначения типа перечисления, что
- // полезно в случае сериализации данных и преобразований между конечным типом и
- // соответствующими константами.
- enum ECarTypes : uint8_t
- {
- Sedan, // 0
- Hatchback, // 1
- SUV = 254, // 254
- Hybrid // 255
- };
- void WriteByteToFile(uint8_t InputValue)
- {
- // Сериализуем InputValue в файл
- }
- void WritePreferredCarTypeToFile(ECarTypes InputCarType)
- {
- // Перечисление неявно преобразуется в uint8_t из-за ранее объявленного
- // типа перечисления.
- WriteByteToFile(InputCarType);
- }
- // С другой стороны, чтобы избежать случайного приведения к целочисленному типу или
- // другому перечислению, вы можете создать класс перечисления, который не будет
- // преобразовываться неявно.
- enum class ECarTypes : uint8_t
- {
- Sedan, // 0
- Hatchback, // 1
- SUV = 254, // 254
- Hybrid // 255
- };
- void WriteByteToFile(uint8_t InputValue)
- {
- // Сериализуем InputValue в файл
- }
- void WritePreferredCarTypeToFile(ECarTypes InputCarType)
- {
- // Хотя ECarTypes имеет тип uint8_t, код не будет скомпилирован из-за того,
- // что перечисление было объявлено как класс перечисления.
- WriteByteToFile(InputCarType);
- }
- //////////////////////////////////////////
- // Классы и объектно-ориентированное программирование
- //////////////////////////////////////////
- // Пример классов
- #include <iostream>
- // Объявление класса.
- // Обычно классы объявляют в заголовочном (.h или .hpp) файле.
- class Dog {
- // Переменные-члены и функции являются приватными по умолчанию.
- std::string name;
- int weight;
- // Все члены после этой сроки являются открытыми
- // пока "private:" или "protected:" не будет объявлено.
- public:
- // Конструктор по умолчанию
- Dog();
- // Объявление функций-членов
- // Обратите внимание, мы используем std::string здесь вместо использования
- // using namespace std;
- // выше.
- // Никогда не размещайте выражение "using namespace" в заголовке.
- void setName(const std::string& dogsName);
- void setWeight(int dogsWeight);
- // Функции, которые не изменяют состояние объекта,
- // должны быть помечены как const.
- // Это позволяет вызывать их, если дана const ссылка на объект.
- // Обратите внимание, функции должны быть явно объявлены как _virtual_,
- // если вы хотите перегрузить их в производных классах.
- // Функции не являются виртуальными по умолчанию для повышения производительности.
- virtual void print() const;
- // Также функции могут быть определены внутри тела класса.
- // Функции, определенные следующим образом, автоматически встроены.
- void bark() const { std::cout << name << " barks!\n"; }
- // Наряду с конструкторами, в C++ есть деструкторы.
- // Они вызываются, когда объект удаляется или выпадает из области видимости.
- // Это активирует мощную парадигму программирования, известную как RAII
- // (смотрите ниже)
- // Деструктор должен быть виртуальным, если класс будет производным.
- // Если он не виртуальный, тогда деструктор производного класса не будет вызван,
- // если объект удален по ссылке или указателю базового класса.
- virtual ~Dog();
- }; // Определение класса должно завершаться точкой с запятой.
- // Функции-члены класса, как правило, реализуются в .cpp файлах.
- Dog::Dog()
- {
- std::cout << "A dog has been constructed\n";
- }
- // Объекты (такие как строки) должны передаваться по ссылке если вы будете
- // изменять их, или const-ссылке если нет.
- void Dog::setName(const std::string& dogsName)
- {
- name = dogsName;
- }
- void Dog::setWeight(int dogsWeight)
- {
- weight = dogsWeight;
- }
- // Обратите внимание, "virtual" требуется только в объявлении, не в определении.
- void Dog::print() const
- {
- std::cout << "Dog is " << name << " and weighs " << weight << "kg\n";
- }
- Dog::~Dog()
- {
- std::cout << "Goodbye " << name << "\n";
- }
- int main() {
- Dog myDog; // Печатает "A dog has been constructed"
- myDog.setName("Barkley");
- myDog.setWeight(10);
- myDog.print(); // Печатает "Dog is Barkley and weighs 10 kg"
- return 0;
- } // Печатает "Goodbye Barkley"
- // Интерфейсы:
- // Этот класс наследует все открытые и защищенные члены класса Dog
- // так же, как и все закрытые, но не может непосредственно получить доступ к закрытым
- // членам\методам без открытых или защищенных методов для этого.
- class OwnedDog : public Dog {
- public:
- void setOwner(const std::string& dogsOwner);
- // Переопределяем поведение функции печати для всех OwnedDog. Смотрите
- // https://goo.gl/3kuH2x для боле общего введения, если вы не знакомы
- // с концепцией полиморфизма подтипов (включения).
- // Ключевое слово override является необязательным, но указывает, что метод
- // на самом деле перегружается в базовом классе.
- void print() const override;
- private:
- std::string owner;
- };
- // Тем временем, в соответствующем .cpp файле:
- void OwnedDog::setOwner(const std::string& dogsOwner)
- {
- owner = dogsOwner;
- }
- void OwnedDog::print() const
- {
- Dog::print(); // Вызывает функцию print в базовом классе Dog
- std::cout << "Dog is owned by " << owner << "\n";
- // Печатает "Dog is <name> and weights <weight>"
- // "Dog is owned by <owner>"
- }
- //////////////////////////////////////////
- // Инициализация и перегрузка операторов.
- //////////////////////////////////////////
- // В C++ вы можете перегрузить поведение таких операторов: +, -, *, / и др..
- // Это делается путем определения функции, которая вызывается,
- // когда используется оператор.
- #include <iostream>
- using namespace std;
- class Point {
- public:
- // Значения по умолчанию для переменных-членов могут быть установлены
- // следующим образом.
- double x = 0;
- double y = 0;
- // Определяем новый конструктор, который инициализирует Point со значениями
- // по умолчанию (0, 0)
- Point() { };
- // Следующий синтаксис известен как список инициализации и является верным способом
- // инициализировать значения членов класса.
- Point (double a, double b) :
- x(a),
- y(b)
- { /* Ничего не делайте, кроме инициализации значений */ }
- // Перегружаем оператор +.
- Point operator+(const Point& rhs) const;
- // Перегружаем оператор +=.
- Point& operator+=(const Point& rhs);
- // Имеет смысл добавить перегрузку операторов - и -=,
- // но для краткости мы опустим эти детали.
- };
- Point Point::operator+(const Point& rhs) const
- {
- // Создает новую точку, которая является суммой этой точки и rhs.
- 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);
- // Здесь происходит вызов оператора + класса Point
- // Точка "up" вызывает + (функция) с параметром "right"
- Point result = up + right;
- // Печатает "Result is upright (1,1)"
- cout << "Result is upright (" << result.x << ',' << result.y << ")\n";
- return 0;
- }
- /////////////////////
- // Шаблоны
- /////////////////////
- // Шаблоны в С++, в основном, используются для обобщенного программирования, хотя
- // они гораздо более мощны, чем дженерики в других языках. Они также поддерживают
- // явные, частные и функциональные типы классов; на самом деле, они являются
- // тьюринг-полным языком, встроенным в C++!
- // Мы начнем с наиболее распространенного типа обобщенного программирования. Чтобы
- // определить класс или функцию, которая принимает параметр типа:
- template<class T>
- class Box {
- public:
- // В этом классе T может быть любого типа.
- void insert(const T&) { ... }
- };
- // Во время компиляции компилятор фактически генерирует копии каждого шаблона
- // с замещенными параметрами, поэтому полное определение класса должно присутствовать
- // при каждом вызове. Именно поэтому шаблоны классов полностью определены в
- // заголовочных файлах.
- // Чтобы создать экземпляр шаблона класса на стеке:
- Box<int> intBox;
- // и вы можете использовать его, как и ожидалось:
- intBox.insert(123);
- // Вы, конечно, можете использовать вложенные шаблоны:
- Box<Box<int> > boxOfBox;
- boxOfBox.insert(intBox);
- // Вплоть до С++11, вы должны были ставить пробел между двумя символами '>', иначе '>>'
- // принимался парсером, как оператор сдвига вправо.
- // Иногда вы можете увидеть
- // template<typename T>
- // вместо этого. В этом случае ключевые слова 'class' и 'typename' _в основном_
- // взаимозаменяемыми. Для более подробной информации смотрите
- // http://en.wikipedia.org/wiki/Typename
- // (да-да, это ключевое слово имеет собственную страничку на вики).
- // Аналогичным образом, шаблон функции:
- template<class T>
- void barkThreeTimes(const T& input)
- {
- input.bark();
- input.bark();
- input.bark();
- }
- // Обратите внимание, что здесь ничего не указано о типе параметра. Компилятор
- // будет генерировать и затем проверять на тип каждый вызов шаблона, поэтому
- // данная функция работает с любым типом 'T', который имеет метод 'bark'.
- Dog fluffy;
- fluffy.setName("Fluffy");
- barkThreeTimes(fluffy); // Печатает "Fluffy barks" три раза.
- // Параметры шаблона не должны быть классами:
- template<int Y>
- void printMessage() {
- cout << "Learn C++ in " << Y << " minutes!" << endl;
- }
- // В конце концов, вы можете явно специализировать шаблоны для более эффективного
- // кода. Конечно, большинство реальных случаев использования специализации
- // не так тривиально, как это. Обратите внимание, вам все еще нужно явно объявить
- // функцию (или класс) в качестве шаблона, даже если вы явно указали все параметры.
- template<>
- void printMessage<10>() {
- cout << "Learn C++ faster in only 10 minutes!" << endl;
- }
- printMessage<20>(); // Печатает "Learn C++ in 20 minutes!"
- printMessage<10>(); // Печатает "Learn C++ faster in only 10 minutes!"
- /////////////////////
- // Обработка исключений
- /////////////////////
- // Стандартная библиотека предоставляет несколько типов исключений
- // (смотрите http://en.cppreference.com/w/cpp/error/exception)
- // но, в принципе, любой тип может быть брошен в качестве исключения.
- #include <exception>
- #include <stdexcept>
- // Все исключения, брошенные в блоке _try_ могут быть пойманы в последующем блоке
- // _catch_.
- try {
- // Не выделяйте память в куче для исключений с помощью ключевого слова _new_.
- throw std::runtime_error("A problem occurred");
- }
- // Поймайте исключение по константной ссылке, если оно является объектом
- catch (const std::exception& ex)
- {
- std::cout << ex.what();
- }
- // Ловит любое исключение, не пойманное предыдущим блоком _catch_
- catch (...)
- {
- std::cout << "Unknown exception caught";
- throw; // Повторный выброс исключения
- }
- ///////
- // Получение ресурса есть инициализация (RAII)
- ///////
- // Программная идиома объектно-ориентированного программирования, смысл которой
- // заключается в том, что с помощью тех или иных программных механизмов получение
- // некоторого ресурса неразрывно совмещается с инициализацией, а освобождение -
- // с уничтожением объекта.
- // Чтобы понять, насколько это полезно,
- // рассмотрим функцию, которая использует обработчик файлов в С:
- void doSomethingWithAFile(const char* filename)
- {
- // Для начала, предположим, ничего не может потерпеть неудачу.
- FILE* fh = fopen(filename, "r"); // Открываем файл в режиме чтения.
- doSomethingWithTheFile(fh);
- doSomethingElseWithIt(fh);
- fclose(fh); // Закрываем обработчик файла.
- }
- // К сожалению, вещи быстро осложняются обработкой ошибок.
- // Предположим, fopen может потерпеть неудачу, тогда doSomethingWithTheFile и
- // doSomethingElseWithIt вернут коды ошибок, если потерпят неудачу.
- // (Исключения являются предпочтительным способом обработки ошибок,
- // но некоторые программисты, особенно те, кто имеет большой опыт работы с С,
- // не согласны с аргументами о полезности исключений).
- // Теперь мы должны проверить каждый вызов на наличие ошибок и закрыть обработчик
- // файла, если он есть.
- bool doSomethingWithAFile(const char* filename)
- {
- FILE* fh = fopen(filename, "r"); // Открывает файл в режиме чтения
- if (fh == nullptr) // В случае неудачи возвращаемый указатель принимает значение null.
- return false; // Сообщает о неудаче вызывающему.
- // Предположим, каждая функция возвращает false в случае неудачи
- if (!doSomethingWithTheFile(fh)) {
- fclose(fh); // Закрываем обработчик файла, чтобы не было утечек
- return false; // Сообщает об ошибке.
- }
- if (!doSomethingElseWithIt(fh)) {
- fclose(fh); // Закрываем обработчик файла, чтобы не было утечек
- return false; // Сообщает об ошибке.
- }
- fclose(fh); // Закрываем обработчик файла, чтобы не было утечек
- return true; // Указывает на успех
- }
- // C-программисты часто упорядочивают это с помощью goto:
- 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;
- fclose(fh); // Закрываем файл.
- return true; // Указывает на успех
- failure:
- fclose(fh);
- return false; // Сообщает об ошибке.
- }
- // Если функции указывают на ошибки с помощью исключений, вещи становятся проще,
- // но все еще не оптимальны.
- void doSomethingWithAFile(const char* filename)
- {
- FILE* fh = fopen(filename, "r"); // Открываем файл в режиме чтения
- if (fh == nullptr)
- throw std::runtime_error("Could not open the file.");
- try {
- doSomethingWithTheFile(fh);
- doSomethingElseWithIt(fh);
- }
- catch (...) {
- fclose(fh); // Убедитесь, что закрываете файл, если происходит ошибка.
- throw; // Затем повторно бросает исключение.
- }
- fclose(fh); // Закрываем файл.
- // Успех
- }
- // Сравните это с использованием класса потока файла (fstream) в С++, который
- // использует свой деструктор, чтобы закрыть файл. Еще раз взгляните выше,
- // деструктор вызывается автоматически, когда объект выпадает из области видимости.
- void doSomethingWithAFile(const std::string& filename)
- {
- // ifstream определяет файловый поток
- std::ifstream fh(filename); // Открыть файл
- // Что-то делать с файлом
- doSomethingWithTheFile(fh);
- doSomethingElseWithIt(fh);
- } // Здесь файл автоматически закрывается в деструкторе.
- // Это имеет _огромнейшие_ преимущества:
- // 1. Неважно, что произойдет,
- // ресурсы (в данном случае дескриптор файла) будут очищены.
- // После того, как вы правильно напишете деструктор,
- // Больше будет _невозможно_ закрыть обработчик файлов или допустить утечку.
- // 2. Обратите внимание, что код намного проще.
- // Деструктор закрывает файловый поток "за кулисами", и вам больше не нужно об
- // этом беспокоиться.
- // 3. Код устойчив к исключениям.
- // Исключение может быть брошено в любом месте в функции, и это никак не повлияет
- // на очистку.
- // Весь идиоматический код на С++ широко использует RAII для всех ресурсов.
- // Дополнительные примеры включат:
- // - Использование памяти unique_ptr и shared_ptr
- // - Контейнеры - стандартная библиотека связанных списков, векторы
- // (т.е. самоизменяемые массивы), хэш-таблицы и все остальное автоматически
- // уничтожается сразу же, когда выходит за пределы области видимости.
- // - Использование мьютексов lock_guard и unique_lock
- // Контейнеры с пользовательскими классами в качестве ключей требуют
- // сравнивающих функций в самом объекте или как указатель на функцию. Примитивы
- // имеют компараторы по умолчанию, но вы можете перегрузить их.
- 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;
- }
- };
- // это не допускается (хотя это может варьироваться в зависимости от компилятора)
- // std::map<Foo, int> fooMap;
- std::map<Foo, int, compareFunction> fooMap;
- fooMap[Foo(1)] = 1;
- fooMap.find(Foo(1)); //true
- /////////////////////
- // Веселые вещи
- /////////////////////
- // Аспекты С++, которые могут быть удивительными для новичков (и даже для некоторых
- // ветеранов). Этот раздел, к сожалению, очень неполон. С++ является одним из самых
- // простых языков, где очень легко выстрелить себе в ногу.
- // Вы можете перегрузить приватные методы!
- class Foo {
- virtual void bar();
- };
- class FooSub : public Foo {
- virtual void bar(); // Перегружает Foo::bar!
- };
- // 0 == false == NULL (в основном)!
- bool* pt = new bool;
- *pt = 0; // Устанавливает значение указателя 'pt' в false.
- pt = 0; // Устанавливает значение 'pt' в нулевой указатель. Обе строки проходят
- // компиляцию без ошибок.
- // nullptr приходит на помощь:
- int* pt2 = new int;
- *pt2 = nullptr; // Не пройдет компиляцию
- pt2 = nullptr; // Устанавливает pt2 в null.
- // Существует исключение для булевых значений.
- // Это позволит вам проверить указатели с помощью if(!ptr),
- // но как следствие вы можете установить nullptr в bool напрямую!
- *pt = nullptr; // Это по прежнему проходит компиляцию, даже если '*pt' - bool!
- // '=' != '=' != '='!
- // Вызывает Foo::Foo(const Foo&) или некий вариант (смотрите "move semantics")
- // конструктора копирования.
- Foo f2;
- Foo f1 = f2;
- // Вызывает Foo::Foo(const Foo&) или вариант, но копирует только часть 'Foo' из
- // 'fooSub'. Любые другие члены 'fooSub' пропускаются. Иногда это ужасное поведение
- // называют "object slicing."
- FooSub fooSub;
- Foo f1 = fooSub;
- // Вызывает Foo::operator=(Foo&) или вариант.
- Foo f1;
- f1 = f2;
- // Как по-настоящему очистить контейнер:
- class Foo { ... };
- vector<Foo> v;
- for (int i = 0; i < 10; ++i)
- v.push_back(Foo());
- // В следующей точке размер v устанавливается в 0, но деструктор не вызывается
- // и не происходит очистка ресурсов!
- v.empty();
- v.push_back(Foo()); // Новые значения копируются в первый вставленный Foo
- // Настоящее уничтожение всех значений v. Смотрите раздел о временном объекте
- // для объяснения того, как это работает.
- v.swap(vector<Foo>());
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement