Циклы
Переменные, ввод-вывод, операции
Немного о языке
На этом курсе будет рассматриваться язык C++, который был создан в 1980-х как расширение языка C, в котором не было доступно ООП (объектно-ориентированное программирование). Автор C++ — Бьёрн Страуструп.
Этот язык является:
компилируемым — по тексту программы создается исполняемый файл, который затем можно запустить
статически типизируемым — у каждой переменной есть свой тип, который определен еще в коде до компиляции
универсальным по сфере применения
а также инструментом для прямого управления памятью
Hello, world!
#include <iostream>
int main() {
<< "Hello, world!\n";
std::cout }
В примере выше мы “подключаем” нужный модуль директивой #include
, затем в функции main
(с нее начинается исполнение программы) отправляем нужную строку в поток вывода.
Переменные
Программу можно рассматривать как последовательность действий, где точно известно следующее действие, если мы знаем состояние программы — значения всех переменных.
Переменная — объект определенного типа, у нее может быть определено значение. Тип переменной должен быть известен на этапе компиляции.
#include <iostream>
int main() {
= 0;
int number << "Our number is " << number << std::endl;
std::cout }
Программа выше создает переменную типа int
— целое число (integer) — инициализирует его нулем, а затем выводит его с подписью на стандартный поток ввода.
Типы переменных
Полный список встроенных типов
// #include <string> должен быть написан выше для корректной работы с std::string
= '1'; // символ
char c = "text"; // строка, не является встроенным типом, состоит из символов (char)
std::string s
bool b = true; // boolean, булевая, логическая переменная, принимает значения false и true
int i = 42; // integer, целое число (как правило, 4 байта)
int si = 17; // короткое целое (занимает 2 байта)
short long li = 12321321312; // длинное целое (как правило, 8 байт)
float f = 2.71828; // дробное число с плавающей запятой (4 байта)
= 3.141592; // дробное число двойной точности (8 байт)
double d long double ld = 1e15; // длинное дробное (как правило, 16 байт)
Целые числа
Целые числа
Название типа | Размер в байтах на 64-bit системе Unix и минимальный размер по стандарту |
char |
\(1 (\geq1)\) |
short int |
\(2 (\geq2)\) |
int |
\(4 (\geq2)\) |
long int |
\(8 (\geq4)\) |
long long int |
\(8 (\geq8)\) |
Причем char
\(\leq\) short int
\(\leq\) int
\(\leq\) long int
\(\leq\) long long int
вне зависимости от архитектуры системы.
Подсчитаем границы множества возможных значений переменной типа char: \(-2^{7}\leq2^{7}-1\), то есть \(-128\leq var \leq 127\), так как переменная должна уметь принимать как отрицательные, так и положительные значения.
Важно отметить, что любой целый тип можно сделать беззнаковым, тогда бит, который обычно отводится на знак, удвоит количество принимаемых положительных значений. В этом случае: \(0 \leq var \leq 2^{8}-1=255\)
Способы создания переменной
int main() {
; // объявление без инициализации
int a, b, c= 10; // оператор присваивания
a
// вариант с одновременной инициализацией является предпочтительным
= 10; // создаем переменную со значением 10
int new_num_1 10}; // альтернатива
int new_num_2{ }
Область видимости переменной
Области видимости — тот участок кода, где переменная существует, как правило, выделяется фигурными скобками. Например, функция main
— области видимости большинства наших переменных.
// глобальная переменная -- доступна везде
bool global_flag = false;
int main() {
= false;
bool external_flag
{// это внутренний scope -- область видимости
// здесь external_flag доступен
= true;
bool internal_flag
}// здесь, за пределами скоупа internal_flag уже недоступен
// main.cpp: error: use of undeclared identifier 'internal_flag'
}
external_flag
и internal_flag
называются локальными, так как доступны только в своей области видимости (скоупе) — функции main
или скоупе{ }
. На предстоящих уроках мы узнаем, что множество конструкций в C++ используют { }
для обособления своей части кода — тела цикла или условия — для них также будет работать эта логика локальных переменных, который нельзя использовать извне.
Ввод-вывод
Мы уже сталкивались с выводом в консоль при написании самой первой программы, теперь же рассмотрим ввод и вывод чуть подробнее. Каждый исполняемый файл по умолчанию получает указание, откуда ему по умолчанию читать (стандартный поток ввода, std::cin
), а куда по умолчанию писать (стандартный поток вывода, std::cout
) данные в текстовом виде. При использовании чтения и записи с этими объектами форматирование для конкретного типа переменной произойдет автоматически.
Воспользуемся этими знаниями для создания простого диалогового бота:
#include <iostream>
#include <string>
int main() {
; // объявляем переменную name
std::string name<< "What is your name?\n";
std::cout >> name; // считываем её значение с клавиатуры
std::cin << "Hello, " << name << "!\n";
std::cout
// объявляем нужные нам переменные
// не инициализируем, потому что сразу будем в них читать значения
;
int age, height<< "What is your age?\n";
std::cout >> age;
std::cin << "How tall are you?" << std::endl;
std::cout >> height;
std::cin
<< "Hm, it seems " << name << " is " << height << "cm tall at " << age << " years old!\n";
std::cout }
Стоит отметить, что подобное обращение к std::cin
читает данные до первого пробельного символа (переноса строки, знака табуляции или пробела — \n, \t, “ ”
соответственно), то есть при вводе имени из нескольких слов через пробел, в переменную name
будет прочитано только первое слово. Следующее слово имени будет прочитано как рост age
, что вызовет ошибку по время работы программы — runtime error
Составное форматирование
Как еще можно читать и выводить данные с потоков ввода и вывода? Оказывается, что можно использовать более низкоуровневый функционал (который был унаследован у языка C) — функции prinf
и scanf
. Идея этих функций заключается в наличие формата, по которому эта функция будет либо читать в, либо подставлять указанные переменные.
scanf
не принято использовать для работы со строками, для этого лучше подходит функция getline(stream, string)
, которая читает из консоли строку целиком, не обращая внимания на пробельные символы до переноса строки.
Пример выше, переписанный с использованием prinf
и scanf
%d
— целое число%f
— число с плавающей точкой%s
— строка
#include <iostream>
#include <string>
int main() {
; // объявляем переменную name
std::string name"What is your name?\n");
printf(;
getline(std::cin, name)
"Hello, %s!\n", name.c_str());
printf(
// объявляем нужные нам переменные
// не инициализируем, потому что сразу будем в них читать значения
;
int age, height"What is your age?\n");
printf("%d", &age);
scanf("How tall are you?\n");
printf("%d", &height);
scanf(
;
int day, month, year"%d-%d-%d", &day, &month, &year);
scanf(
"Hm, it seems %s is %dcm tall at %d years olf!\n", name.c_str(), height, age);
printf( }
Операции
Арифметические операции
Работают стандартным образом.
при выполнении операций результат будет являться наибольшим из типов — например,
long long + int = long long
, а такжеfloat + double = double
операция взятия остатка —
%
, целочисленное деление применяется по умолчанию в целых типах (см. пример)
int main() {
= 7, b = 3;
int a = a / b; // 2
int q = a % b; // 1
int r
// целочисленное и точное деление
= 6, d = 4;
int c / d; // 1
c <float>(c) / d; // 1.5
static_cast1. * c / d; // 1.5
}
Стоит обратить внимание на две последние строки:
static_cast<T>(object)
приводит объект к типуT
, если это возможно1. * c / d
— сначала выполнится умножение (float * int = float
), затем точно деление, так как в нем участвуетfloat
:float / int = float
Если необходимо не создать новую, а изменить уже созданную переменную, есть сокращенный синтаксис:
int main() {
= 2;
int a += 5;
a << a << '\n';
std::cout /= 2; // целочисленное деление на 2
a }
Показанные выше операции являются бинарными — в них участвует 2 операнда — объекта, с которыми они взаимодействуют.
Есть и унарные операции, например, — инкремент и декремент — увеличение и уменьшение переменной на 1 соответственно.
#include <iostream>
int main() {
= 2;
int a ++a;
<< a << '\n'; // 3
std::cout << ++a << '\n'; // 4
std::cout << a++ << '\n'; // 4
std::cout << a << '\n'; // 5
std::cout }
Почему в последней строки было выведено 4, а не 5? Такой инкремент (когда ++ стоит после переменной) выполняет операцию, но возвращает свое старое значение, до увеличения.
Задача о нахождении гипотенузы
Дано два числа a и b. Найдите гипотенузу треугольника с заданными катетами.
Входные данные
В двух строках вводятся два числа (числа целые, положительные, не превышают 1000).
Выходные данные
Выведите ответ на задачу.
#include <iostream>
#include <cmath>
int main() {
;
int a, b>> a >> b;
std::cin = std::sqrt(a*a + b*b);
float hypot
<< hypot << '\n';
std::cout }
Задача о разрядах числа
Подумаем над задачей о нахождении цифры в разряде единиц для системы счисление с основанием \(d\)
\(\overline{a_{1}a_{2}...a_{n}}_{10}=\underbrace{b_{1}\cdot d^{m} + b_{2} \cdot d^{m -1} +\cdot\cdot\cdot + }_{\vdots d} d^{m} \cdot d^{0}= \overline{b_{1}b_{2}...b_{md}}\)
Раз мы хотим получить \(d_{m}\), а остальные слагаемые делятся на \(d\), то наш ответ — остаток от деления на \(d\). Чтобы получить цифру в разряде \(m-1\) достаточно целочисленно разделить на \(d\), а затем снова взять остаток.
Рассмотрим на примере более близкой нам, десятичной системы счисления:
#include <iostream>
#include <cmath>
int main() {
= 12345;
int num << num % 10 << '\n'; // 5
std::cout << num / 10 % 10 << '\n'; // 4
std::cout << num / 100 % 10 << '\n'; // 3
std::cout }
Представление чисел в памяти компьютера
Представление целых чисел
Есть различные способы хранить знаковые целые числа в памяти. Один из них — sign-magnitude, то есть первый бит отведен на знак, остальные — на модуль. Это кажется более естественным, но только не для компьютера.
Наиболее распространенным называется два-дополнительный код, его можно формализовать следующим образом: \(-x=\overline{x} +1\). Он позволяет упростить архитектуру ЭВМ за счет использование операции сложения вместо вычитания, а также применения одинаковых операций для знаковых и беззнаковых чисел.
В частности: \(-101_{2}=\overline{101} +1= 10_{2} + 1_{2}=2 + 1=3\Rightarrow 101_{2}\rightarrow-3\)
Рассмотрим на примере 3-битных чисел:
Биты | Число |
---|---|
0 | 0 |
1 | 1 |
10 | 2 |
11 | 3 |
100 | -4 |
101 | -3 |
110 | -2 |
111 | -1 |