# 03 лекция - C, подробно
> [TOC]
## Типы данных с плавующей точкой:
[IEEE 754-2008](https://ru.wikipedia.org/wiki/IEEE_754-2008) - как представляются числа с плавающей точкой в современном компьютере (=> в C/C++)\
[Вольный пересказ](https://www.softelectro.ru/ieee754.html)\
(стандарт IEEE-754 (не стандарт языка, стандарт железа, некоторые моменты перенял си))
Их всего 3 типа
1. `float` = (32 бита) соответствует single precision IEEE 754-2008 10^(+- 38)
2. `double` = (64 бита) соответствует double precision IEEE 754-2008 10^(+- 308)
3. `long double`: (не договорились, про совместимость между компиляторами можно не думать)
* может совпадать с `double`
* может быть (128 бит) quad precision IEEE 754-2008, есть GCC, в x86 реализованно программно (очень медленно)
---
* может быть (80 бит) extended precision (не входит в стандарт IEEE 754-2008, аппаратно реализован в x86, (быстрее любой программной реализации))
Диапазон в 10^(+- 308) НЕ означает что будут храниться 308 десятичных знаков - означает, что максимальное значение по экспоненте будет 308 и оно будет неточным (будут храниться первые цифры)
> Обычно мы не хотим использовать `long double`
`double` как минимум вдвое медленнее чем `float` (может быть сильно медленнее)
## Константы
Целые константы реализуются как `int`, то есть `10` будет константной типа `int` (можно подправить тип написав `10u` или `10llu`, тогда она будет `unsigned int` и `unsigned long long` соответсвенно)
```c
long long a = 1000000 * 1000000; //получиться черти что, вообще UB
//тк переполнение не определенно для signed
long long b = 1000000ll * 1000000ll; //приведеться в long long и правильно посчитается
```
Переполнение знакового (signed) int - [Undefined Behavior](https://https://ru.wikipedia.org/wiki/%D0%9D%D0%B5%D0%BE%D0%BF%D1%80%D0%B5%D0%B4%D0%B5%D0%BB%D1%91%D0%BD%D0%BD%D0%BE%D0%B5_%D0%BF%D0%BE%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5)
Дробные константы реализуются как `double`, то есть `10.9` будет константной типа `double` (можно подправить тип написав 10.9f тогда она будет `float`)
Си **всю** арфметику считает в **одинаковых** типах! Нельзя к int прибавить unsigned. В случае, когда типы разные - копилятор их приводит к одному, например в случае `int + unsigned int` победит `unsigned int`
`float a = 5.7f; a += 5.6;` В этом случае `a` сначала сконвертируется в `double` сложится с константой и обратно сконвертируется в `float`.
Можно создать 16ричные и 8ричные костанты в тексте, 16ричные константы с плавающей точкой. (`0x43534P234` - 16ричное с точкой, после p экспанента)
## Ввод/Вывод
Форматы ввода семейства функций `printf/scanf`:
* `%i` - десятичные, также 16/8ричные с префиксом (0x/0) (int)
* `%u` - десятичные беззнаковые (unsigned int)
* `%x` - HEX (16рич), без префикса
Форматы вывода немного другие, в часности `%i` и `%d` с `printf` работают одинакого, выводят десятичное число, чтобы вывести число в HEX надо использовать `%x`
---
Дробные:
* float - `%f`
* double - `%lf` (сокр long float)
Однако, если мы перпутаем в `printf` - `%f` и `%lf` (скормим в `%f` double или в `%lf` float) то всё отработает хорошо, тк `printf` - `vararg` функция, а `vararg` автоматически расширяет `float` до `double`. Это только в `printf`. (типы которые короче int (например char) автоматически расширяются до int)
### Файловый ввод/вывод
`fopen` - открыть файл, принимает 2 аргумента
1) путь (просто имя файла если в тек папке - то, **откуда запускали исполняемый файл** .exe), или абсолютный/относительный путь, только надо не забывать, что в пути надолибо удвоить обратный слэш - `\\`, либо писать путь через прямые - `/`
2) режим (как открыть файл):
* `"r"` - на чтение, если файла нет - вернет NULL.
* `"w"` - на запись, если файла нет - создает, если есть - обнуляет файл.
* `"a"` - на дозапись, если файла нет - создаст, если есть - откроет на конце файла.
[Подробнее](https://en.cppreference.com/w/cpp/io/c/fopen)
У режима есть модификаторы - `t` или `b`, текстовый и двоичный (бинарный) соответственно, применяется после режима - `"rt"`, `"wb"`, по умолчанию всегда текстовый режим.
Можно открыть на запись/чтение одновременно - добавить `+` к режиму (до модификатора), например: `r+b` или `w+`, *использовать очень аккуратно*
Если происходит какая либо ошибка, нет файла/не удалось создать/нет прав - `fopen` вернет `NULL`, специальное значение (`NULL == 0`). Рекомендуется **всегда** проверять, тк легко получить [null pointer exeption](https://stackoverflow.com/questions/1823721/how-to-catch-the-null-pointer-exception#:~:text=There's%20no%20such%20thing%20as,other%20exceptions%20in%20C%2B%2B) - падение программы.
`fclose` - закрыть файл, принимает указатель на `FILE` (необходимо всегда закрывать файл, в противном случае, в лучшем случае, файл будет держаться до конца нашей программы (а возможно до перезагрузки компьютера))
**Файлы обязательно надо закрывать!** Желательно когда он перестал быть нужным, а не в конце программы. Тк этот файл блокируется пока он открыт нашей программой, так же под него выделенна память
Чтобы работать с файлами - мы можем использовать функции семейства printf/scanf, добавив `f` к названию функции, и ещё один первый аргумент - указатель на файл (то, что возвращает `fopen`)? напрмер: `fscanf(in, "%i", &a);`
```c
int read_and_write_sum_file()
{
int a, b;
FILE *in = fopen("in.txt", "r"); //"r" - на чтение
FILE *out = fopen("out.txt", "w"); // "w" - на запись
if(in == NULL) // проверка на открытие
{
printf("I can't open the input file\n");
return 1;
}
if(out == NULL)
{
printf("I can't open the output file\n");
return 1;
}
fscanf(in, "%i %i", &a, &b); //считать два числа
fprintf(out, "%i\n", a+b); //записать их сумму
//code//
fclose(in); // закрытие файлов
fclose(out);
return 0;
}
```
В более низкоуровневых функциях (с - `open`, win - `create file`) можно указать какие операции мы хотим оставить другим пользователям.
> C++ файловым (и не только) стандартным вводом и выводом лучше не пользоваться, тк под ними скрываются большие нетривиальные куски кода, которые медленно работают и плохо распаралеливаются (из-за глобальных переменных) (лучше в С++ использовать сишные функции ввода-вывода)
Модификатор `b` двоичный - это про переводы строк
Какие бывают переводы строк:
1) Windows - `0x0D 0x0A` (13 10) (`\r` `\n`)
2) Unix - `0x0A` (10) (`\n`) (Linux) (new MacOC)
3) Old MacOC - `0x0D` (13) (`\r`)
> По [ASCII](https://en.wikipedia.org/wiki/ASCII#Control_code_chart), на самом деле:
> * `0x0A` - [Line Feed](https://en.wikipedia.org/wiki/Newline) (перевод строки)
> * `0x0D` - [Carriage Return](https://en.wikipedia.org/wiki/Carriage_return) (возврат каретки)
Когда мы пишем `\n` в текстовом режиме - `с run time` автоматически приобразует их в нужный перевод строки (под систему в которой мы сейчас), мы не хотим так в двоичном режиме (например, когда открываем картинку)
Если мы работаем с двоичными данными, мы всегда хотим открыть в режиме с модификатором `b`. (часто это забывают сделать пользователи linux, тк у них совпадет `\n` и системный перевод)
### Потоки
> Стандартный ввод/вывод на самом деле тоже файловый, можно перенаправить из консоли в файл (`freeopen`)
> Есть три стандартных потока:
> 1) stdin - поток ввода
> 2) stdout - поток вывода
> 3) stderr - поток ошибок (пример - `fprintf(stderr, "Hi");`)
## Про ветвление
```c
if(out == NULL) //можно писать без {} (блока) если ОДНА строчка
printf("I can't open the output file\n");
else // else - если if не отработал - условие в скобочках ложное
printf("OK");
//если строчек будет несколько (без блока) в if будет выполненна
//ТОЛЬКО ПЕРВАЯ, вне зависимости от отступа
```
`;` посреди текста - это пустой оператор, её любят ставить после `}`, однако она не всегда там нужна и её не стоит пихать куда попало, например:
```c
if(out == NULL)
{
printf("I can't open the output file\n");
}; //в данном случа означает, что цепочка if закончилась
else // НЕ скомпилируется, тк не найдет свой if
printf("OK");
```
Так же можно создавать длинные цепочки if с помощью `else if` - отрицает все предыдущие if и принимает новое утверждение в скобках, например:
```c
if(a == 5)
{
printf("a == 5\n");
}
else if(b == 3)
{
printf("a != 5, but b == 3\n");
}
else
{
printf("a != 5, and b != 3\n");
}
```
If в скобках принимает логическое значение, то есть 0 или 1 (false или true), исполняет код под под if когда значение в скобках принимает логическую единицу, в связи с этим, и тем, что NULL ассоциируется с 0, например, можно короче записать проверку на неоткрытие файла:
```c
if(!in) //! - инвертировать логическое значение, 0 -> 1, 1 -> 0
//in указатель на файл (результат fopen)
{
printf("I can't open the input file\n");
}
//ТОЖЕ САМОЕ
if(in == NULL)
{
printf("I can't open the input file\n");
}
```
## Циклы
1. while
```c
int a = 4;
while(a < 10) //цикл будет выполняться
//пока условие в скобках - логическая единица
{
a++;
}
// a == 10;
```
---
2. do while
```c
int a = 10;
do //блок под do выполниться один раз
{
a++;
} while(a < 10); //затем цикл будет выполняться
//пока условие в скобках - логическая единица
//a == 11;
```
> рекомедуется писать `while` на строке с `{`, для большей понятности
---
3. for
```c
for(int i = 0; i < 10; i++) // int i = 0 происходит до цикла
// i < 10 условие как в while
// i++ происходит на каждом шаге в конце
{
printf("%i\n", i);
}
//напечатно: 0 1 2 3 4 5 6 7 8 9
```
Эквивалент:
```c
for(A; B; C)
{
D;
}
// ТОЖЕ САМОЕ (ПОЧТИ)
{
A; //происходит до цикла, обычно создание переменной
while(B) //условие
{
D;
C; //Происходит на каждом шаге
}
}
```
В таком эквиваленте есть только одно расхождение, при использовании слова `continue;`
`continue;` - ключевое слово для цикла, означает пропустить всё до конца следующего шага (Оставляет `С` в `for`)
`break;` - ключевое слово для цикла, означает выйти из цикла
## Массивы
```c
int q[10]; // завести массив длиной 10 элементов и названием q
q[0]; // доступ к 1му (0му) элементу массива q
```
Индекс в массиве - любая **целочисленная** переменная (значение)
**Индексироваться double и float нельзя!**\
Синтаксически можно управлять циклом (итерироваться) с помощью переменных с плавающей точкой, но так делать **НЕ надо!** Нпример, потому что число `1/10` непредставимо в двоичной системе, значение всегда будет чуть больше или чуть меньше, но никогда не `1/10`, то есть `1/10 * 10 != 1;`, более того умножить `1/10` и сложить `1/10` с высокой вероятностью тоже окажется не равным, тк в float и double постояноо происходят окугления. И это всё мы не хотим видеть при управление циклом!
```c
int q[10];
```
Означает, что последний адрес в массиве 9!
Чем больше программируешь - тем больше убеждаешься, что индексация с 0 - оочень удобно
Завести массив мы можем любого размера, однако надо помнить, что в 32 битных системах указатели размерности 32 бита, то есть ~4 миллиарда значений (байтиков, 4 гб), и мы не сможем создать массив больше 4х миллиардов, тк просто не хватить указателей (в 64 битных системах количество указателей в 4млрд раз больше - фактически, гораздо больше чем можно поставить оператичной памяти в компьютер)
Когда мы обращаемся по индексам си НЕ проверяет, что мы уместились в диапазон, например
```c
int q[10];
q[100]; // не так страшно, когда мы читаем её, получим мусор
q[100] = 0; //страшно здесь
```
Потому что мы пишем в рандомную ячейку памяти, она может хранить всё что угодно: другая переменная, служебные переменные, даже кусок кода => здесь может ничего не произойти, а могут дикие спец эффекты
(стандарт си не гарантирует ничего в этом случае)
Одна из сложностей языка "C" - нужно следить что ты не вышел за границы массива.
Сложность в том, что когда мы бажим в Undefine believer, нам стандарт ничего не обещает, полный undefine, по этому у нас программа может работать правильно, а у пользователя, у другого программиста или просто на том же компьютере в другое время - отработать неправильно или вообще упасть.
### Многомерные массивы
```c
int q[10][3]; // завести двумерный массив длиной 10 на 3
// и названием q
q[1][2] // доступ к 2 элементу 1 строки массива q
```
`q[z][y][x]` принято так индексировать многомерные массивы, тк это больше всего похоже на то, как они храняться в памяти
### Про размер
Размеры связанны с тем, где мы создаем переменные. Если мы создаем массив как обычную локальную переменную - общее ограничение на размер стека (пара мегабайт для каждой программы). Если мы попытаемся выделить массив больше, либо программа скажет нам stack overflow, либо просто тихо исчезнет, тк чтобы что-то сказать (сделать) программе нужно свободное место на стеке
stackoverflow.com - одноименный шикарный сайт (форум), вопросов и ответов на темы программирования.
# Оглавление
Full - C/C++, 3 лекция, 26.02.2022, Скаков
> [TOC]