C++ Course
  • Описание курса
  • Материалы лекций
  • Задания
  • Об авторе
  • Гайды

On this page

  • Переменные, ввод-вывод, операции
    • Немного о языке
    • Hello, world!
    • Переменные
      • Типы переменных
      • Целые числа
      • Целые числа
      • Способы создания переменной
      • Область видимости переменной
    • Ввод-вывод
      • Составное форматирование
    • Операции
      • Арифметические операции
    • Задача о нахождении гипотенузы
    • Задача о разрядах числа
    • Представление чисел в памяти компьютера
      • Представление целых чисел

Циклы

Переменные, ввод-вывод, операции

Немного о языке

На этом курсе будет рассматриваться язык C++, который был создан в 1980-х как расширение языка C, в котором не было доступно ООП (объектно-ориентированное программирование). Автор C++ — Бьёрн Страуструп.

Этот язык является:

  • компилируемым — по тексту программы создается исполняемый файл, который затем можно запустить

  • статически типизируемым — у каждой переменной есть свой тип, который определен еще в коде до компиляции

  • универсальным по сфере применения

  • а также инструментом для прямого управления памятью

Hello, world!

#include <iostream>

int main() {
    std::cout << "Hello, world!\n";
}

В примере выше мы “подключаем” нужный модуль директивой #include , затем в функции main (с нее начинается исполнение программы) отправляем нужную строку в поток вывода.

Переменные

Программу можно рассматривать как последовательность действий, где точно известно следующее действие, если мы знаем состояние программы — значения всех переменных.

Переменная — объект определенного типа, у нее может быть определено значение. Тип переменной должен быть известен на этапе компиляции.

#include <iostream>


int main() {
    int number = 0;
    std::cout << "Our number is " << number << std::endl;
}

Программа выше создает переменную типа int — целое число (integer) — инициализирует его нулем, а затем выводит его с подписью на стандартный поток ввода.

Типы переменных

Полный список встроенных типов

// #include <string> должен быть написан выше для корректной работы с std::string


char c = '1';                // символ
std::string s = "text";      // строка, не является встроенным типом, состоит из символов (char)


bool b = true;               // boolean, булевая, логическая переменная, принимает значения false и true


int i = 42;                  // integer, целое число (как правило, 4 байта)
short int si = 17;           // короткое целое (занимает 2 байта)
long li = 12321321312;       // длинное целое (как правило, 8 байт)


float f = 2.71828;           // дробное число с плавающей запятой (4 байта)
double d = 3.141592;         // дробное число двойной точности (8 байт)
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; // объявление без инициализации
    a = 10; // оператор присваивания
    
    // вариант с одновременной инициализацией является предпочтительным
    int new_num_1 = 10; // создаем переменную со значением 10
    int new_num_2{10}; // альтернатива
}

Область видимости переменной

Области видимости — тот участок кода, где переменная существует, как правило, выделяется фигурными скобками. Например, функция main — области видимости большинства наших переменных.

// глобальная переменная -- доступна везде
bool global_flag = false;


int main() {
    bool external_flag = false;
    {
        // это внутренний scope -- область видимости
        // здесь external_flag доступен
        bool internal_flag = true;
    }
    // здесь, за пределами скоупа 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() {
    std::string name;  // объявляем переменную name
    std::cout << "What is your name?\n";
    std::cin >> name;  // считываем её значение с клавиатуры
    std::cout << "Hello, " << name << "!\n";


    // объявляем нужные нам переменные
    // не инициализируем, потому что сразу будем в них читать значения
    int age, height;
    std::cout << "What is your age?\n";
    std::cin >> age;
    std::cout << "How tall are you?" << std::endl;
    std::cin >> height;
    
    std::cout << "Hm, it seems " << name << " is " << height << "cm tall at " << age << " years old!\n";
}

Стоит отметить, что подобное обращение к 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() {
    std::string name;  // объявляем переменную name
    printf("What is your name?\n");
    getline(std::cin, name);
    
    printf("Hello, %s!\n", name.c_str());


    // объявляем нужные нам переменные
    // не инициализируем, потому что сразу будем в них читать значения
    int age, height;
    printf("What is your age?\n");
    scanf("%d", &age);
    printf("How tall are you?\n");
    scanf("%d", &height);


    int day, month, year;
    scanf("%d-%d-%d", &day, &month, &year);    


    printf("Hm, it seems %s is %dcm tall at %d years olf!\n", name.c_str(), height, age);
}

Операции

Арифметические операции

Работают стандартным образом.

  • при выполнении операций результат будет являться наибольшим из типов — например, long long + int = long long, а также float + double = double

  • операция взятия остатка — %, целочисленное деление применяется по умолчанию в целых типах (см. пример)

int main() {
    int a = 7, b = 3;
    int q = a / b;  // 2
    int r = a % b;  // 1
    
    // целочисленное и точное деление
    int c = 6, d = 4;
    c / d; // 1
    static_cast<float>(c) / d; // 1.5
    1. * c / d; // 1.5
}

Стоит обратить внимание на две последние строки:

  1. static_cast<T>(object)  приводит объект к типу T , если это возможно

  2. 1. * c / d  — сначала выполнится умножение (float * int = float), затем точно деление, так как в нем участвует float : float / int = float

Если необходимо не создать новую, а изменить уже созданную переменную, есть сокращенный синтаксис:

int main() {
    int a = 2;
    a += 5;
    std::cout << a << '\n';
    a /= 2; // целочисленное деление на 2
}

Показанные выше операции являются бинарными — в них участвует 2 операнда — объекта, с которыми они взаимодействуют.

Есть и унарные операции, например, — инкремент и декремент — увеличение и уменьшение переменной на 1 соответственно.

#include <iostream>


int main() {
    int a = 2;
    ++a;
    std::cout << a << '\n'; // 3
    std::cout << ++a << '\n'; // 4
    std::cout << a++ << '\n'; // 4
    std::cout << a << '\n'; // 5
}

Почему в последней строки было выведено 4, а не 5? Такой инкремент (когда ++ стоит после переменной) выполняет операцию, но возвращает свое старое значение, до увеличения.

Важно помнить о приоритете операций

Задача о нахождении гипотенузы

Дано два числа a и b. Найдите гипотенузу треугольника с заданными катетами.

Входные данные

В двух строках вводятся два числа (числа целые, положительные, не превышают 1000).

Выходные данные

Выведите ответ на задачу.

#include <iostream>
#include <cmath>


int main() {
    int a, b;
    std::cin >> a >> b;
    float hypot = std::sqrt(a*a + b*b);


    std::cout << hypot << '\n';
}

Задача о разрядах числа

Подумаем над задачей о нахождении цифры в разряде единиц для системы счисление с основанием \(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() {
    int num = 12345;
    std::cout << num % 10 << '\n'; // 5
    std::cout << num / 10 % 10 << '\n'; // 4
    std::cout << num / 100 % 10 << '\n'; // 3
}

Представление чисел в памяти компьютера

Представление целых чисел

Есть различные способы хранить знаковые целые числа в памяти. Один из них — 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
Denis Bakin ©

Build on Quart Academic Website Template adapted by Dr. Gang He