# 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]