II Flashcards
Операторы “.”, “→”, ключевое слово this
- как обратиться к полю (аналогично к методу):
объект.поле
- стрелочка это то же самое, но не для самих объектов, а указателей для них
(*p).f( ); или же p -> f( );
- this это указатель на тот объект, в котором мы “сейчас” находимся
Пример:
class String {
public:
String (const char* str, int size) {
this str -> new char [size];
….
}
~String () {delete [] this str}
private:
char* str;
int size;
}
};
Оператор присваивания
- оператор присваивания нужно определять “руками” так как по умолчанию оператор просто копирует поля
- это метод, возвращает ссылку на класс на котором его определяют
String& operator = (const String& s) {
if (&s == this) return *this; // проверка, что объект не равен самому себе
delete[] str;
str = new char [s.size];
for ( … ) {…};
return *this;
}
Правило трех (the rule of three)
Эмпирическое/эвристическое правило, которое говорит, что если класс требует определения одного из трех (хотя бы одного из трех):
- Конструктора копировани
- Оператора присваивания
- Деструктора
То значит он требует определения всех этих вещей (иначе крах)
В С++11 вместо “Правила трех появилось “Правило пяти”
Списки инициализации в конструкторах
- Как в конструкторе инициализировать поля, которые нельзя изменять внутри тела конструктора? Для этого существуют списки инициализации в конструкторах.
int z = 10;
Class C {
const int x = 3;
int& y = z;
C (const int x, int& y) : x (x); y (y) {} // название поля (значение, которым его надо проинициализировать)
}
};
- ! Поля инициализируются ДО входа в тело конструктора
- Лучше всегда пользоваться списками инициализации, а не обычным присваиванием (эффективнее с точки зрения управления памятью)
Ключевое слово explicit
String s = s1 + ‘a’; // a имеет тип char и сложение строки с ним не определено
- Можно из типа char сделать строку и потом производить действия
- Неявная конверсия в int даст некорректный результат, поэтому конверсию нужно делать в явном виде. Поэтому возникает желание запретить неявные преобразования типов - для этого существует ключевое слово explicit, которое пишется перед конструктором
explicit String (int size) { … }
- Если это не конструктор копирования, то рекомендуется везде делать explicit конструкторы
Константные методы
Пусть есть метод в конструкторе int size () {return size_;}
- По умолчанию считается, что каждый метод класса имеет право менять поля этого класса
сonst String s =”abc”;
s. size (); // вызовет ошибку компиляции так как метод size потенциально может менять поля, которые мы задали как неизменяемые (константы)
* чтобы метод класса можно было вызывать над константным объектом нужно явно пометить, что он не меняет поля класса, то есть допускает вызов от константного объекта.
Что нужно сделать:
int size () const {return size_;}
- сам метод является константным по отношению к объекту, которым вызывается. Вот тогда можно будет этот метод вызывать от константных объектов, но все поля внутри этого метода считаются константными (то есть нельзя из этого метода вызвать модифицирующие операции над полями объектов)
- жизненный метод, нужно не забывать ставить метку там, где это нужно
Ключевое слово mutable
- допустим есть объект, который служит для логирования и в нём есть поле, в котором считается, сколько раз методы были вызваны - при каждом вызове некоторого метода счетчик увеличивается на 1
- проблема: допустим мы создали константный объект и у него начинаем вызывать методы, но это затруднительно, так как поля не могут меняться (но мы хотим менять одно конкретное поле)
- для этого есть специальное ключевое слово, которое разрешает менять отдельные поля в константном методе (mutable)
определяем поле класса следующим образом:
mutable int x;
Ключевое слово friend
- Допустим имеется класс и в нём есть некоторое внутреннее приватное представление, а вы хотите определить оператор ввода этого в поток (cin)
- Проблема: вы не можете этого сделать, так как поля приватные и их нельзя трогать извне, а оператор ввода в поток вы не можете определить внутри класса как член (потому что у него левый операнд это поток, а внутри класса вы можете определять только те операторы, у которых левый операнд это объект вашего класса)
- Таким образом, нужен оператор, который не является членом класса, но может иметь допступ к его приватным полям (исключительный). Для этого есть ключевой слово friend, которое ставится перед оператором
friend f ( … )
- не рекомендуется делать много друзей - нарушается принцип инкапсуляции
- друзьями можно объявлять целые классы, это будет означать, что все методы одного класса имеют доступ к приватной части другого класса
- друзья не взаимны (если А это друг Б, то обратное может быть не верно)
Делегирующие конструкторы
- пишем конструктор класса String от двух параметров: String (const char* str, int size) и вы хотите написать другой конструктор, который делает всё то же самое, что и первый, но кроме этого еще что-то делает (доп параметры)
- Проблема: не хочется переписывать наполнение старого конструктора
- Допустим мы хотим написать конструктор, в котором переменные принимаются в обратном порядке (int, const chat*). Для этого применяются делегирующие конструкторы (появились в C++11).
String (int size, const chat* str) : String (str, size) { };
Ключевое слово static
- представьте, что у вас есть класс в котором помимо прочего хранится какая-то информация (например, сколько было создано объектов этого класса и существует сейчас). То есть должно быть в классе поле про сам класс, которое не принадлежит ни одному объекту. Для этого некоторое поле класса можно объявить static
static int x;
- это поле будет видимо для всех объектов (на общих основаниях)
- static может быть не только в классе, но и в функциях
- переменные static удалятся после завершения программы
- представьте, что у вас есть класс в котором помимо прочего хранится какая-то информация (например, сколько было создано объектов этого класса и существует сейчас). То есть должно быть в классе поле про сам класс, которое не принадлежит ни одному объекту. Для этого некоторое поле класса можно объявить static
static int x;
- это поле будет видимо для всех объектов (на общих основаниях)
- static может быть не только в классе, но и в функциях
- переменные static удалятся после завершения программы
Указатели на члены
class C {public: int a; char b;}
- Пусть есть класс с некоторыми полями и вы хотите объявить указатель не на сам объект типа C, а на некоторое поле этого объекта
int main ( ) {
int C::* p = &C::a;
C c;
c.*p = 5; // можно менять значение параметра а
}
- аналогично можно вызывать методы класса
Перегрузка операторов, арифметические операторы
- пусть есть класс BigInteger - сколько угодно большое число, записанное в виде массивов и мы хотим определить операции над ним
Сложение:
BigInteger operator + (const BigInteger& x) const { // этот оператор возвращает некоторый новый объект, не меняя старый, поэтому нужно добавить константный метод
// const BigInteger& мы пишем для того, чтобы каждый раз не создавалась лишняя копия BigInteger
BigInteger sum = * this;
return sum += x;
}
Оператор += :
BigInteger& operator += (const BigInteger& x) {
}
Операция И и ИЛИ
- Логическая операция И выполняется с помощью оператора &&.
- Логическая операция ИЛИ выполняется с помощью оператора | | .
Синтаксис цикла for
Синтаксис цикла for таков:
for (Выражение инициализации, выполняемое только р а з ;
Условие выхода, проверяемое в начале каждой итерации;
Выражение цикла, выполняемое в конце каждой итерации)
{
Блок инструкций;
}
Синтаксис цикла while
Синтаксис этого цикла имеет следующий вид:
whilе (Выражение)
{
// Если Выражение == true
Блок_Инструкций;
}
Основные этапы создания приложений C++
Основные этапы создания приложений C++ приведены ниже.
- Написание (программирование) кода C++ с использованием текстового редактора.
- Компиляция кода с помощью компилятора C++, который преобразовывает исходный текст в команды машинного языка и записывает их в объектный файл (object file).
- Компоновка результатов работы компилятора с помощью компоновщика и по лучение окончательного выполнимого файла (.ехе в Windows, например).
Компиляция (compilation) представляет собой этап, на котором код C++, содержащийся обычно в текстовых файлах с расширением . срр, преобразуется в бинарный код, который может быть выполнен процессором. Компилятор (compiler) преобразует по одному файлу кода за раз, создавая объектный файл с расширением .о или .obj и игнорируя связи, которые код в этом файле может иметь с кодом в другом файле.
Распознавание этих связей и объединение кода в одно целое является задачей компоновщика (linker). Кроме объединения различных объектных файлов, он разрешает имеющиеся связи и в случае успешной компоновки создает выполнимый файл, который можно выполнять и в конечном счете распространять среди пользователей. Весь процесс в целом называется построением выполнимого файла.
В дополнение к перечислен ным выше трем этапам (программирование, компиляция и компоновка) разработка зачастую подразумевает этап отладки (debugging), на котором программист анализирует ошибки в приложении и исправляет их.
IDE
Большинство программистов предпочитают использовать интегрированную среду разработки (Integrated Development Environment — IDE), объединяющую этапы программирования, компиляции и компоновки в пределах единого пользовательского интерфейса, предоставляющего также средства отладки, облегчающие обнаружение ошибок и устранение проблем.
Директива препроцессора #include
include “…путь к файлу FileA\FileA”
Как и предполагает его название, препроцессор (preprocessor) — это инструмент, запускающийся перед фактическим началом компиляции.
Директивы препроцессора (preprocessor directive) — это команды препроцессору, которые всегда начинаются со
знака “диез” (#).
В профессиональных приложениях C++ включаются не только стандартные заголовочные файлы, но и разработанные программистом. Сложные приложения, как правило, состоят из нескольких исходных файлов, причем одни из них должны включать другие. Так, если некоторый объект, объявленный в файле FileA, должен использоваться в файле FileB, то первый файл необходимо включить в последний. Обычно для этого в файл FileB помещают директиву #include:
При включении самодельного заголовочного файла мы используем кавычки, а не угловые скобки. Угловые скобки ( о ) обычно используются при включении стандартных заголовочных файлов.
Функция main( ) и аргументы командной строки, оператор вывода в поток, строковый литерал
Выполнение программ на языке C++ всегда начинается с функции main ( ). Согласно стандарту перед функцией main ( ) указывается тип int.
Во многих приложениях C++ можно найти вариант функции main ( ), выглядящий следующим образом:
int main( int argc, char* argv [])
Это объявление совместимо со стандартом и вполне приемлемо, поскольку функция main ( ) возвращает тип int, а содержимое круглых скобок - это аргументы (argument), передаваемые программе. Такая программа позволяет пользователю запускать ее с аргументами командной строки, например как:
program.exe /DoSomethingSpecific
/DoSomethingSpecific - это аргумент данной программы, передаваемый операционной системой в качестве параметра для обработки в функции main ( ).
cout — это поток, определенный в пространстве имен std (поэтому и std : : cout), а то, что мы делаем, — это помещение текстовой строки Hello World в данный поток с использованием оператора вывода (или вставки) в поток <<.
Оператор вывода в поток (stream insertion operator) используется каждый раз, когда в поток нужно вывести новый элемент.
Преимущество потоков C++ заключается в одинаковой семантике, используемой потоками разного типа. В результате различные операции, осуществляемые с одним и тем же текстом, например вывод в файл, а не на консоль, выглядят одинаково и используют один и тот же оператор << , только для std : : fstream вместо std : :cout.
Фактический текст, заключенный в кавычки (“Hello World”), называется строковым литералом (string literal).
Возврат значения функцией main ( )
main () — это функция, всегда и обязательно возвращающая целое число.
Это целочисленное значение возвращается операционной системе и, в зависимости от характера вашего приложения, может быть очень полезным, поскольку большинство операционных систем предусматривает для других приложений возможность обра
титься к возвращенному значению.
Не так уж и редко одно приложение запускает другое, и родительскому приложению (запустившему дочернее) желательно знать, закончило ли дочернее приложение свою задачу успешно. Программист может использовать возвращаемое значение функции main ( ) для передачи родительскому приложению сообщения об успехе или неудаче.
Чувствительность языка С++ к регистру
Язык C++ чувствителен к регистру
Использование ключевого слова using
Многие программисты находят утомительным регулярный ввод при наборе исходного текста спецификатора std при использовании имени cout и других подобных средств, содержащихся в том же пространстве имен. Объявление using namespace, позволит избежать этого повторения.
Комментарии в коде
■ Символ / / означает, что следующая далее строка — комментарий. Например:
/ / Это комментарий
■ Текст, содержащийся между символами /* и * /, также является комментарием, даже если он занимает несколько строк:
/* Это комментарий,
занимающий две строки * /
Применение потока cin
Применение потока cin очень простое; он работает в паре с переменной, в которую следует поместить вводимые данные:
std : : cin >> Переменная;
Таким образом, за потоком cin следуют оператор извлечения значения >> (данные извлекаются из входного потока) и переменная, в которую следует поместить считываемые данные. Если вводимые данные разделены пробелом, и их следует сохранить в двух разных переменных, можно использовать цепочку операторов:
std : : cin >> Переменная1 >> Переменная2;
Обратите внимание на то, что поток cin применяется для ввода как текстовых, так и числовых данных.