Показаны сообщения с ярлыком CPP. Показать все сообщения
Показаны сообщения с ярлыком CPP. Показать все сообщения

Ввод-вывод в C++

Использование библиотеки <iostream>.
#include <iostream>
using namespace std;


Ввод

int a = 0;
int b = 0;
cin >> a >> b;


#include <iostream>

int main()
{
  int i = 42;
  double d = 3.14;

  std::cout << "Enter an integer and a double:\n";
  std::cin >> i >> d;
  std::cout << "Your input is " << i << ", " << d << "\n";

  return 0;
}


Оператор чтения из потока ввода (>>), обычно, игнорирует пробельные символы (это поведение можно изменить с помощью std::noskipws). Иногда это нежелательно. Для организации посимвольного ввода можно использовать метод get потока ввода.
char c = '\0';
while (cin.get(c)) { // на каждой итерации считываем один символ в переменную c
    /* здесь можно пользоваться значением прочитанным в переменную c */
    if (c != 'a')
        cout << c; // выводим символ, если он не равен 'a'
}
Метод get принимает на вход ссылку на переменную типа char. Метод get изменит значение переменной c, если чтение символа прошло удачно. Значение, которое возвращает метод get может быть интерпретировано как логическое значение. Это значение интерпретируется как false, если при чтении произошла ошибка или достигнут конец потока ввода. Т.е. цикл в примере выше прочитает весь ввод программы по одному символу за раз.

Что такое конец потока ввода? Если программа читает свой ввод из файла (это легко организовать средствами командной оболочки), то программа достигнет конца потока ввода, когда прочитает весь файл целиком. Если же программа читает ввод с клавиатуры, то конец потока ввода определяется специальным нажатием клавиш, например, в Linux это, как правило, сочетание Ctrl+D, а в Windows — Ctrl-Z.


Вывод

cout << "a + b = " << (a + b) << endl;


#include <iostream>

int main()
{
  int i = 42;
  double d = 3.14;
  const char *s = "C-style string";

  std::cout << "This is an integer " << i << "\n";
  std::cout << "This is a double " << d << "\n";
  std::cout << "This is a \"" << s << "\"\n";

  return 0;
}


Инструкции в C++

Выполнение программы состоит из последовательности инструкций. Инструкции выполняются одна за другой.

Порядок вычислений внутри инструкций не определён.
/* unspecified behavior */
int i = 10;
i = ( i += 5) + ( i * 4);

Блоки имеют вложенную область видимости:
int k = 10;
{
 int k = 5 * i; // не видна за пределами блока
 i = ( k += 5) + 5;
}
k = k + 1;


Операции в C++


Оператор присваивания: =

int i = 10;


Арифметические

Бинарные: + - * / %
i = (20 * 3) % 7;

Унарные операции инкремента и декремента: ++ --
int k = i++;
int l = --i;


int a = 10; // a = 10
int b = ++a; // префиксный инкремент возвращает новое значение => b = 11 и a = 11
int c = a++; // постфиксный инкремент возвращает старое значение => с = 11 и a = 12


Логические

Бинарные: && ||
b = (a == 0) || (1 / a < 1);
Важным отличием логических бинарных операций заключается в том, что выполнение эти операций происходит "лениво".

Унарные: !
bool b = !(k == l);


Сравнения: == != > < >= <=



Приведения типов: (type)

double d = 3.1415;
float f = (int)d; // округление до ближайшего снизу целого числа


Выражениям так же как и значениям в C++ приписывается некоторые типы. Например, если a и b — это переменные типа int, то выражения (a + b), (a - b), (a * b) и (a / b) тоже будут иметь тип int. Важно всегда понимать, какой тип у выражения, которое вы написали в программе. Давайте проиллюстрируем это на следующем примере:
int a = 20;
int b = 50;
double d = a / b; // d = 0, оба аргумента целочисленные, а значит деление целочисленное 
Как исправить этот код, чтобы получить вещественное значение в переменной d? Для этого хотя бы один из аргументов оператора деления должен иметь типа double. Этого можно добиться при помощи уже известного нам оператора приведения типов:
double d = (double)a / b; // d = 0.4
Почему это сработало? Дело в том, что операторы для встроенных типов C++ всегда работают с одинаковыми типами аргументов. Если аргументы имеют разные типы, то происходит преобразование типов (promotion).


Правило преобразования встроенных типов в операторах
Рассмотрим выражение (a + b), где вместо '+' может стоять любой другой подходящий оператор.
  • Если один из аргументов имеет числовой тип с плавающей точкой, то второй аргумент приводится к этому типу (например, при сложении double и int значение типа int приводится к double). 
  • Если оба аргумента имеют числовой тип с плавающей точкой, то выбирается наибольший из этих типов (например, при сложении double и float значение типа float приводится к double). 
  • Если оба аргумента целочисленные, но их типы меньше int, то оба аргумента приводятся к типу int (например, при сложении двух значений типа char они оба сначала приводятся к int).
  • Если оба аргумента целочисленные, то аргумент с меньшим типом приводится к типу второго аргумента (например, при сложении long и int значение типа int приводится к long).
  • Если оба аргумента целочисленные и имеют тип одного размера, то предпочтение отдаётся беззнаковому типу (например, при сложении int и unsigned int значение типа int приводится к unsigned int).
Несколько важных следствий
  • Следите за тем, какие типы участвуют в выражении, от этого может зависеть его значение.
  • Не стоит использовать целочисленные типы меньше int в арифметических выражениях, они всё равно будут приведены к int.
  • Не стоит смешивать unsigned и signed типы в одном выражении, это может привести к неприятным последствиям.
Для иллюстрации последнего следствия давайте рассмотрим следующий пример:
unsigned from = 100;
unsigned to = 0;
for (int i = from; i >= to; --i) {  ....  }
Сколько итераций сделает этот цикл? На самом деле этот цикл — бесконечный: в условии цикла проверяется i >= to, где тип i — int, а тип to — unsigned int. Так как операторы всегда применяются к одинаковым встроенным типам, то в данном случае значение переменной i (в соответствии с правилом 4) будет преобразовано к unsigned int, а значения этого типа всегда неотрицательны, т.е. >= 0. Другими словами, когда пременная i станет равной -1, то в условии будет проверяться (unsigned)-1 >= 0, где  (unsigned)-1 = UINT_MAX (UINT_MAX — максимальное значение, которое может принимать переменная типа unsigned int).


Сокращённые версии бинарных операторов: += -= *= /= %=

// d = d * (i + k)
d *= i + k;


Примитивные типы данных в C++

Целочисленные

char (символьный тип данных)
Размер соответствует минимальной адресуемой ячейке (обычно это 1 байт).
Основное применение: хранение символьных данных.
Т.к. 1 байт соответствует 1 символу, то строке соответствует набор символов типа char.
Строки задаются в двойных кавычках: "Text string".

Тип char в языке C++ является особенным: стандарт не оговаривает является ли тип char знаковым или беззнаковым — это зависит от компилятора. Если вам нужна именно беззнаковая версия используйте unsigned char явно, если же вам нужна знаковая используйте signed char. При этом все три типа (char, unsigned char, signed char) являются тремя различными типами.


short int, int и long int
Про размер типа int и его производных в стандарте ничего не указано, однако указано, что short int должен быть меньше int, а int должен быть меньше long int. Это означает, что эти типы могут занимать различное число байт в зависимости от настроек компилятора, ОС, архитектуры процессора.
В большинстве настольных систем тип int занимает 4 байта.


По умолчанию целочисленные типы могут хранить как положительные так и отрицательные значения. В этом случае множество представимых значений вычисляется по формуле -2n-1...(2n-1-1), где n - это число бит. Такая асимметрия происходит из того, что в положительные значения записывается 0.
Целочисленные типа данных могут быть беззнаковыми (unsigned). В этом случае множество представимых значений изменится на 0...(2n-1).

Все целочисленные типы (кроме char) являются знаковыми. Беззнаковые версии типов определяется с ключевым словом unsigned, например: unsigned short int, unsigned int или unsigned long int. Для симметрии в языке предусмотрено явное указание того, что тип является знаковым — ключевое слово signed (используется редко). Более того, C++ допускает использование следующих сокращений:
  • unsigned вместо unsigned int,
  • short вместо short int,
  • long вместо long int.


Значения встроенных типов C++ задаются с помощью литералов. Целочисленные значения можно задавать следующим образом:
  • ’a’ — код буквы ’a’, тип char,
  • 42 — все целые числа по умолчанию типа int,
  • 1234567890L — суффикс ’L’ соответствует типу long,
  • 1703U — суффикс ’U’ соответствует типу unsigned int,
  • 2128506UL — соответствует типу unsigned long.


При определении переменной указывается её тип. При определении можно сразу задать начальное значение (инициализация).
int i = 10; // тип переменной совпадает с типом литерала
short j = 20; // здесь произойдет преобразование встроенного типа, число 20 из типа int преобразуется в тип short (если число окажется больше, чем может вместить тип short, то преобразование может оказаться не специфицированным)
unsigned long l = 123123; // значение типа int и при присваивании оно преобразуется к беззнаковому типу long


Числа с плавающей точкой

float, 4 байта, 7 значащих цифр.
Тип float используют в тех случаях, когда нужно значительно сэкономить на памяти. Например, если нужно хранить массив из миллиарда вещественных чисел, то для нас уже имеет значение размер.


double, 8 байт, 15 значащих цифр.
На практике в большинстве случаев разумно использовать тип double, т.к. погрешность при его использовании будет меньше.


Числа с плавающей точкой:
  • 3.14 — все числа с точкой по умолчанию типа double,
  • 2.71F — суффикс ’F’ соответствует типу float,
  • 3.0E8 — соответствует 3.0 · 108

Определение переменной:
double x = 13.5, y = 3.1415; // не очень удобно использоваться, потому что редактирование кода идет построчно и лучше определять каждую переменную на своей строчке
float z; // плохой тон, т.к. никто не знает какое значение будет у переменной
Нужно всегда инициализировать переменные.


Логический тип данных

bool
Умеет хранить значения типа "ложь" или "истина".
Обычно используется в условных выражениях.
true и false — значения типа bool.

Определение переменной:
bool b = false;


Пустой тип

void
Не хранит никакого значения и используется в тех случаях, когда надо указать тип, но никакого значения нет. Например, если функция не возвращает никакого значения, то тогда указывается тип void.

Нельзя определить переменную пустого типа void.

Структура кода на С++


Разбиение программы на файлы

Зачем разбивать программу на файлы?
  • С небольшими файлами удобнее работать.
  • Разбиение на файлы структурирует код, отделяя одно подсистему от другой. 
  • Позволяет нескольким программистам разрабатывать приложение одновременно.
  • Ускорение повторной компиляции при небольших изменениях в отдельных частях программы.
Файлы с кодом на C++ бывают двух типов:
  1. файлы с исходным кодом (расширение .cpp, иногда .C);
  2. заголовочные файлы (расширение .hpp или .h).

Компиляция и исполнение кода на C++


Что такое компиляция?



Компиляция — преобразование одностороннее, нельзя восстановить исходный код.

Для того, чтобы скомпилировать программу на C++ для некоторой архитектуры X, необязательно устанавливать компилятор С++ на компьютер с архитектурой X.

Не каждая программа, написанная на компилируемом языке, переносима. Т.е. не любая программа, написанная на компилируемом языке, будет работать везде одинаково.

Плюсы и минусы компилируемости в машинный код

Плюсы:
  • эффективность: программа компилируется и оптимизируется для конкретного процессора;
  • нет необходимости устанавливать сторонние приложения, такие как интерпретатор или виртуальная машина (т.е. для запуска программы, написаной на компилируемом языке, не требуется установка компилятора).
Минусы:
  • нужно компилировать для каждой платформы (т.е. программу, написанную на языке, который компилируется в машинный код, недостаточно скомпилировать однажды чтобы её можно было запускать на любой платформе);
  • сложность внесения изменения в программу — нужно перекомпилировать заново.

Общая схема



g++ - это такая обёртка над несколькими программами:
  1. над препроцессором;
  2. над непосредственно компилятором;
  3. и над линковщиком.
g++ может сама решать, что вызывать, если не просить что-то делать специально, а просто изначально дать ей файлы, то g++ сама догадается что с ними нужно сделать.
g++ main.cpp square.cpp -o program

Этап 1: препроцессор

Язык препроцессора – это специальный язык программирования, встроенный в C++. Препроцессор работает с кодом на C++ как с текстом.

Команды языка препроцессора называют директивами, все директивы начинаются со знака #. Директива #include позволяет подключать заголовочные файлы к файлам кода.
  • #include <foo.h> — библиотечный заголовочный файл,
  • #include "bar.h" — локальный заголовочный файл.
Препроцессор заменяет директиву #include "bar.h" на содержимое файла bar.h.

Можно попросить компилятор вызвать только препроцессор и посмотреть что получится. Для компилятора g++ можно использовать ключ -E.

Этап 2: компиляция

На вход компилятору поступает код на C++ после обработки препроцессором.

Каждый файл с кодом компилируется отдельно и независимо от других файлов с кодом. Компилируется только файлы с кодом (т.е. *.cpp).

Заголовочные файлы сами по себе ни во что не компилируются, только в составе файлов с кодом.

На выходе компилятора из каждого файла с кодом получается “объектный файл” — бинарный файл со скомпилированным кодом (с расширением .o или .obj).

Если в коде C++ вы вызывает не объявленную функцию, то это ошибка этапа компиляции.

Можно "скормить" файлы с кодом непосредственно компилятору. Для компилятора g++ можно использовать ключ -c.
На выходе получается файлы с расширением .o - это объектные файлы.
  • main.o
  • square.o

Можно попросить компилятор g++ показать некоторое содержимое объектных файлов, но сам по себе смотреть объектный файл не интересно (там бинарная информация), но можно посмотреть его ассемблированный вид.
g++ -S square.cpp
Получится файл square.s.

Этап 3: линковка (компоновка)

На этом этапе все объектные файлы объединяются в один исполняемый (или библиотечный) файл. При этом происходит подстановка адресов функций в места их вызова.

void foo()
{
 bar();
}

void bar() { }

По каждому объектному файлу строится таблица всех функций, которые в нём определены.

На этапе компоновки важно, что каждая функция имеет уникальное имя. В C++ может быть две функции с одним именем, но разными параметрами. Имена функций искажаются (mangle) таким образом, что в их имени кодируются их параметры.

Например, компилятор GCC превратит имя функции foo

void foo(int, double) {}

в _Z3fooid. Компилятор g++ также предоставляет возможность обратного преобразования.
c++filt -n _Z3fooid
foo(int, double)
Заметим, что в полученной сигнатуре не участвует возвращаемое значение, потому что в C++ не может быть двух функций с одинаковым именем и одинаковыми параметрами, но разными возвращаемыми значениями.

Аналогично функциям в линковке нуждаются глобальные переменные.

Точка входа — функция, вызываемая при запуске программы. По умолчанию — это функция main:

int main()
{
 return 0;
}

или

int main(int argc, char ** argv)
{
 return 0;
}

Даже для программы, состоящей всего из одного файла и из одной пустой функции int main() { return 0; } все равно требуется ликовка.

Если в коде C++ вы вызываете функцию, которая была объявлена, но не была определена, то это ошибка этапа линковки.

Для того чтобы собрать объектные файлы в один файл их нужно "скормить" компилятору и указать имя исполняемого файла:
g++ square.o main.o -o program

Характеристики языка C++


Сложность

Есть интересная фраза Бьёрна Страуструпа о языках C и C++:

C makes it easy to shoot yourself in the foot. C++ makes it harder, but when you do, it blows away your whole leg.
(В языке С легко прострелить себе ногу. В С++ это сложнее, но если вы сделаете это, то отстрелите всю ногу целиком.)

Такова цена мощности языка — программист получает больше возможностей, но вместе с тем и ответственность становится больше. На плечи программиста ложится много дополнительной
работы:
  • проверка корректности данных;
  • управление памятью;
  • обработка низкоуровневых ошибок.

Другая интересная цитата пришла из времен первой стандартизации языка и принадлежит Джиму Вальдо:

Every extension proposal should be required to be accompanied by a kidney. People would submit only serious proposals, and nobody would submit more than two.
(Нужно, чтобы к каждому предложению о расширении языка обязательно прилагалась почка. Тогда люди присылали бы только очень важные предложения, и никто не прислал бы более двух.)

C++ оснащен огромным количеством самых разнообразных средств. Описание стандарта занимает более 1300 страниц текста.

История языка C++

Язык программирования C++ создан на основе языка C. Синтаксис C++ унаследован от языка C. Один из принципов разработки стандарта C++ — это сохранение совместимости с C. C++ не является в строгом смысле надмножеством C. Можно писать программы на C так, чтобы они успешно компилировались на C++, но не все программы на языке C успешно компилируются C++ компилятором. C и C++ сильно отличаются как по сложности, так и по принятым архитектурным решениям, которые используются в обоих языках.

Разрабатывается с начала 1980-х годов. Создатель — сотрудник Bell Labs Бьёрн Страуструп. Изначально это было расширение языка C для поддержки работы с классами и объектами. Это позволило проектировать программы на более высоком уровне абстракции. Ранние версии языка назывались “C with classes”. Первый компилятор cfront, перерабатывающий исходный код “C с классами” в исходный код на C.

К 1983 году в язык было добавлено много новых возможностей:
  • виртуальные функции;
  • перегрузка функций и операторов;
  • ссылки;
  • константы.
Получившийся язык перестал быть просто дополненной версией классического C и был переименован из “C с классами” в C++. Имя языка, получившееся в итоге, происходит от оператора унарного постфиксного инкремента C ’++’ (увеличение значения переменной на единицу). Язык также не был назван D, поскольку “является расширением C и не пытается устранять проблемы путём удаления элементов C”.

В 1998 году впервые был стандартизован язык C++. В 1998 году был ратифицирован международный стандарт языка C++: ISO/IEC 14882:1998 “Standard for the C++ Programming Language”.

В 2003 году был опубликован стандарт языка ISO/IEC 14882:2003, где были исправлены выявленные ошибки и недочёты предыдущей версии стандарта.

В 2005 году был выпущен Library Technical Report 1 (TR1). С 2005 года началась работа над новой версией стандарта, которая получила кодовое название C++0x.

В конце концов в 2011 году стандарт был принят и получил название C++11 ISO/IEC 14882:2011.

В данный момент ведётся одновременная работа над двумя версиями стандарта: C++14 и C++17.

Наследование в C++

А может ли виртуальный метод быть статическим?

"Правила хорошего тона", при написании кода на C++


  • Аккуратно форматируйте код.
  • Давайте переменным, функциям и классам осмысленные имена.
  • Инициализируйте переменные при определении.
  • Не забывайте освобождать память.
  • Используйте средства самоограничения: модификаторы доступа, константность.