//============================================================================
// Name        : cpp-unit-3.cpp
// Author      : 
// Version     :
// Copyright   : Your copyright notice
// Description : Hello World in C++, Ansi-style
//============================================================================

#include <iostream>
// #include <cstdint>
#include <iomanip> // для std::setprecision()
#include "constants.h"

using namespace std;

//Тип void — это самый простой тип данных, который означает отсутствие любого типа данных. Следовательно, переменные не могут быть типа void:

//void value; // не будет работать, так как переменная не может иметь тип void

//Тип void, как правило, используется в 3-х случаях:

//1. Указать, что функция не возвращает значение:

void writeValue(int x) // здесь void означает, что функция не возвращает никакое значение
{
    std::cout << "The value of x is: " << x << std::endl;
    // Нет стейтмента return, так как тип функции - void
}


//2. Указать, что функция не имеет никаких параметров (перешло из языка Cи):

int getValue(void) // здесь void означает, что функция не имеет никаких параметров
{
    int x;
    std::cin >> x;
    return x;
}

//Указание типа void как никаких параметров является пережитком, сохранившимся еще со времен языка Cи. Следующий код равнозначен и более предпочтителен для использования в языке C++:

int getValues() // пустые скобки означают то же, что и void
{
    int x;
    std::cin >> x;
    return x;
}

void printSize()
	{
	    std::cout << "bool:\t\t" << sizeof(bool) << " bytes" << std::endl;
	    std::cout << "char:\t\t" << sizeof(char) << " bytes" << std::endl;
	    std::cout << "wchar_t:\t" << sizeof(wchar_t) << " bytes" << std::endl;
	    // std::cout << "char16_t:\t" << sizeof(char16_t) << " bytes" << std::endl;
	    // std::cout << "char32_t:\t" << sizeof(char32_t) << " bytes" << std::endl;
	    std::cout << "short:\t\t" << sizeof(short) << " bytes" << std::endl;
	    std::cout << "int:\t\t" << sizeof(int) << " bytes" << std::endl;
	    std::cout << "long:\t\t" << sizeof(long) << " bytes" << std::endl;
	    std::cout << "long long:\t" << sizeof(long long) << " bytes" << std::endl;
	    std::cout << "float:\t\t" << sizeof(float) << " bytes" << std::endl;
	    std::cout << "double:\t\t" << sizeof(double) << " bytes" << std::endl;
	    std::cout << "long double:\t" << sizeof(long double) << " bytes" << std::endl;
	}

// Возвращаем true, если x и y равны, в противном случае - возвращаем false
bool isEqual(int x, int y)
{
    return (x == y); // оператор == возвращает true, если x равно y, в противном случае - false
}

int main() {

	//	Язык C++ поддерживает 2 основных способа инициализации переменных.

	//	1. Копирующая инициализация (или «инициализация копированием») с помощью знака равенства =:
//	int nValue = 5; // копирующая инициализация
	//	Когда переменной присваивается значение после её объявления (не в момент объявления), то это копирующее присваивание (или просто присваивание):
	// nValue = 5; // копирующее присваивание

	//	2. Прямая инициализация с помощью круглых скобок ():
//	int mValue(5); // прямая инициализация
	// uniform-инициализация
	//	Прямая или копирующая инициализация работают не со всеми типами данных (например, вы не сможете использовать эти способы для инициализации списка значений).

	//	В попытке обеспечить единый механизм инициализации, который будет работать со всеми типами данных, в C++11 добавили новый способ инициализации, который называется uniform-инициализация:
//	int uIvalue{5};
	//	Инициализация переменной с пустыми фигурными скобками указывает на инициализацию по умолчанию (переменной присваивается 0):
//	int uvalue{}; // инициализация переменной по умолчанию значением 0 (ноль)
	//	В uniform-инициализации есть еще одно дополнительное преимущество: вы не сможете присвоить переменной значение, которое не поддерживает её тип данных — компилятор выдаст предупреждение или сообщение об ошибке:
//	double uDvalue{4.5}; // ошибка: целочисленная переменная не может содержать нецелочисленные значения
	//	## Присваивание переменных

	//	В языке C++ нет встроенной поддержки способов прямого/uniform-присваивания, есть только копирующее присваивание.

	//	В одном стейтменте можно объявить сразу несколько переменных одного и того же типа данных, разделяя их имена запятыми. Например, следующие 2 фрагмента кода выполняют одно и то же:

	//	int a, b;
	//	Кроме того, вы даже можете инициализировать несколько переменных в одной строке:

	//	int a1 = 5, b1 = 6;

	//	Хороший способ запомнить эту ошибку и не допускать в будущем — использовать прямую или uniform-инициализацию:
//	int a;
	int b(5);
	int a = 5;
	cout << "a = " << a << " b = " << b << endl;
	//	int c(7), d(8);
//	int c;
	int c = 5;
	int d(4);
	cout << "c = " << c << " d = " << d << endl;

	int e(9);
	int f1(10);
//	int f{10};
	cout << "e = " << e << " f = " << f1 << endl;

	// В языке С++ приоритетным является объявление (а также инициализация) переменных как можно ближе к их первому использованию:

	cout << "Enter a number: ";
	int x; // мы используем x в следующей строке, поэтому объявляем эту переменную здесь (как можно ближе к её первому использованию)
	cin >> x; // первое использование переменной x

	//	cout << x << endl;

	writeValue(x);

	writeValue(getValue());

	writeValue(getValues());

	// Оператор sizeof — это унарный оператор, который вычисляет и возвращает размер определенной переменной или определенного типа данных в байтах.

	printSize();

	// Обратите внимание, оператор sizeof не используется с типом void, так как последний не имеет размера.

	// sizeof — это один из 3-х операторов в языке C++, который является словом, а не символом (еще есть new и delete).

	// Вы также можете использовать оператор sizeof и с переменными:
	cout << "x is " << sizeof(x) << " bytes" << endl;

	// Целочисленные типы фиксированного размера
	// Чтобы решить вопрос кроссплатформенности, в язык С++ добавили набор целочисленных типов фиксированного размера, которые гарантированно имеют один и тот же размер на любой архитектуре:
	// Начиная с C++11 доступ к этим типам осуществляется через подключение заголовочного файла cstdint (находятся эти типы данных в пространстве имен std).
//	std::int32_t i(5); // прямая инициализация
//	cout << i << endl;

	double d1(5000.0);
	double d2(5e3); // другой способ присвоить значение 5000

	double d3(0.05);
	double d4(5e-2); // другой способ присвоить значение 0.05
	// Точность и диапазон типов с плавающей точкой
	float f;
	f = 9.87654321f;
	std::cout << f << std::endl;
	f = 987.654321f;
	std::cout << f << std::endl;
	f = 987654.321f;
	std::cout << f << std::endl;
	f = 9876543.21f;
	std::cout << f << std::endl;
	f = 0.0000987654321f;
	std::cout << f << std::endl;

	// Также мы можем переопределить точность cout, используя функцию std::setprecision(), которая находится в заголовочном файле iomanip:

	std::cout << std::setprecision(16); // задаем точность в 16 цифр
	float f2 = 3.33333333333333333333333333333333333333f;
	std::cout << f2 << std::endl;
	double d23 = 3.3333333333333333333333333333333333333;
	std::cout << d23 << std::endl;

	// Этот принцип относится не только к дробным числам, но и ко всем значениям, которые имеют слишком большое количество значащих цифр. Например:
	float ff(123456789.0f); // переменная f имеет 10 значащих цифр
	std::cout << std::setprecision(9); // задаем точность в 9 цифр
	std::cout << ff << std::endl;

	// Ошибки округления
	// Рассмотрим дробь 1/10. В десятичной системе счисления эту дробь можно представить, как 0.1. В двоичной системе счисления эта дробь представлена в виде бесконечной последовательности — 0.00011001100110011… Именно из-за подобных разногласий в представлении чисел в разных системах счисления, у нас могут возникать проблемы с точностью. Например:
	double dd(0.1);
	std::cout << dd << std::endl; // используем точность cout по умолчанию (6 цифр)
	std::cout << std::setprecision(17);
	std::cout << dd << std::endl;
	// Подобные ошибки могут иметь неожиданные последствия:
    std::cout << std::setprecision(17);

    double dd1(1.0);
    std::cout << dd1 << std::endl;

    double dd2(0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1); // должно получиться 1.0
    std::cout << dd2 << std::endl;

//    nan и inf
//
//    Есть две специальные категории чисел типа с плавающей запятой:
//    1. inf (или бесконечность, infinity), которая может быть либо положительной, либо отрицательной.
//    2. nan (или не число, not a number). Их есть несколько видов.
    double zero = 0.0;
    double posinf = 5.0 / zero; // положительная бесконечность
    std::cout << posinf << "\n";

    double neginf = -5.0 / zero; // отрицательная бесконечность
    std::cout << neginf << "\n";

    double nan = zero / zero; // не число (математически некорректно)
    std::cout << nan << "\n";

    // Логические переменные — это переменные, диапазон которых состоит только из двух возможных значений: true (1) и false (0).

    // Для объявления логической переменной используется ключевое слово bool:

    bool bo;


    // Инициализировать логическую переменную или выполнить операцию присваивания можно с помощью ключевых слов true или false:

    bool bo1 = true; // копирующая инициализация
    bool bo2(false); // прямая инициализация
    bool bo3 { true }; // uniform-инициализация (C++11)
    bo3 = false; // операция присваивания


    // Аналогично работе унарного оператора минус (-), с помощью которого мы можем сделать число отрицательным, с помощью логического оператора НЕ (!) мы можем изменить true на false и наоборот (false на true):

    bool boo1 = !true; // значение b1 - false
    bool boo2(!false); // значение b2 - true

    // Следовательно, если мы попытаемся вывести логические значения с помощью std::cout, то увидим либо 0, либо 1:

    std::cout << true << std::endl; // вместо true единица
    std::cout << !true << std::endl; // вместо !true ноль

    bool boo(false);
    std::cout << boo << std::endl; // b - false (0)
    std::cout << !boo << std::endl; // !b - true (1)

    // Если вы хотите, чтобы std::cout выводил true или false (вместо целых чисел), то тогда используйте манипулятор форматирования std::boolalpha:

    std::cout << true << std::endl;
    std::cout << false << std::endl;

    std::cout << std::boolalpha; // выводим логические значения как "true" или "false"

    std::cout << true << std::endl;
    std::cout << false << std::endl;

    // Логические значения часто используются в качестве возвращаемых значений в функциях. Названия таких функций очень часто начинаются со слов is (например, isEqual) или has (например, hasCommonDivisor).

    std::cout << "Enter an integer: ";
    int xx;
    std::cin >> xx;

    std::cout << "Enter another integer: ";
    int yy;
    std::cin >> yy;

    if (isEqual(xx, yy))
        std::cout << xx << " and " << yy << " are equal" << std::endl;
    else
        std::cout << xx << " and " << yy << " are not equal" << std::endl;

    //Символы от 32 до 127 используются для вывода. Это буквы, цифры, знаки препинания, которые большинство компьютеров использует для отображения текста (на английском языке).

    //Следующие два стейтмента выполняют одно и то же (присваивают переменным типа char целое число 97):

    char ch1(97); // инициализация переменной типа char целым числом 97
    char ch2('a'); // инициализация переменной типа char символом 'a' (97)

    // Будьте внимательны при использовании фактических чисел с числами, которые используются для представления символов (из ASCII-таблицы). Следующие два стейтмента выполняют не одно и то же:

    char ch(5); // инициализация переменной типа char целым числом 5
    char chch('5'); // инициализация переменной типа char символом '5' (53)
    // При выводе переменных типа char, объект cout выводит символы вместо цифр:

    char ch7(97); // несмотря на то, что мы инициализируем переменную ch целым числом
    std::cout << ch7 << std::endl; // cout выводит символ

    // Также вы можете выводить литералы типа char напрямую:
    std::cout << 'b' << std::endl;

    // Если вы хотите вывести символы в виде цифр, а не в виде букв, то вам нужно сообщить cout выводить переменные типа char в виде целочисленных значений. Не очень хороший способ это сделать — присвоить переменной типа int переменную типа char и вывести её:
    char ch9(97);
    int ic(ch9); // присваиваем значение переменной ch переменной типа int
    std::cout << ic << std::endl; // выводим значение переменной типа int
    // Лучшим способом является конвертация переменной из одного типа данных в другой с помощью оператора static_cast.
    char ch97(97);
    std::cout << ch97 << std::endl;
    std::cout << static_cast<int>(ch97) << std::endl;
    std::cout << ch97 << std::endl;

    // Ввод символов

    // Следующая программа просит пользователя ввести символ. Затем она выводит этот символ и его ASCII-код:

    std::cout << "Input a keyboard character: ";

    char ich;
    std::cin >> ich;
    std::cout << ich << " has ASCII code " << static_cast<int>(ich) << std::endl;

    // даже если cin позволит вам ввести несколько символов, переменная ch будет хранить только первый символ (именно он и помещается в переменную). Остальная часть пользовательского ввода останется во входном буфере, который использует cin, и будет доступна для использования последующим вызовам cin.

    std::cout << "Input a keyboard character: "; // предположим, что пользователь ввел abcd


    // Обратите внимание, следующий cin не просит пользователя что-либо ввести, данные берутся из входного буфера!
    std::cin >> ich; // ch = 'b', "cd" останется в буфере
    std::cout << ich << " has ASCII code " << static_cast<int>(ich) << std::endl;

    std::cout << "\"This is quoted text\"\n";
    std::cout << "This string contains a single backslash \\" << std::endl;
    std::cout << "6F in hex is char \'\x6F\'" << std::endl;

    // Вы можете использовать литералы типа string в коде:

    std::cout << "Hello, world!"; // "Hello, world!" - это литерал типа string
    // Литералы и магические числа
    bool myNameIsAlex = true; // true - это литеральная константа типа bool
    int xm = 5; // 5 - это литеральная константа типа int
    int ym = 2 * 3; // 2 и 3 - это литеральные константы типа int

    // С литералами типов bool и int всё понятно, а вот для литералов типа с плавающей точкой есть два способа объявления:

    double pi = 3.14159; // 3.14159 - это литерал типа double
    double avogadro = 6.02e23; // число avogadro - 6.02 x 10^23

    // Во втором способе объявления, число после экспонента может быть и отрицательным:

    double electron = 1.6e-19; // заряд электрона - 1.6 x 10^-19

    // Чтобы сделать переменную константой — используйте ключевое слово const перед типом переменной или после него:

    const double gravity { 9.8 }; // предпочтительнее использовать const перед типом данных
    int const sidesInSquare { 4 }; // ок, но вариант выше - лучше

    // Обратите внимание, константы могут быть инициализированы и с помощью неконстантных значений:
    std::cout << "Enter your age: ";
    int age;
    std::cin >> age;

    const int usersAge (age); // в дальнейшем значение переменной usersAge не может быть изменено
    int radius = 22;

    double circumference = 2 * radius * constants::PI;
    cout << "circumference:" << circumference << endl; // prints !!!Hello World!!!

	cout << "!!!Hello World!!!" << endl; // prints !!!Hello World!!!
	return 0;
}
