Много вычислений в числах с плавающей точкой - накапливают большую погрешность, немного разные реализации могут давать существо разную точность
Сравнивать можно только через eps
Массивы - способ обьединения однородных данных (одного типа)
Структуры - способ обьединения разнородных данных (разных типов)
Структуры - это новый тип данных, его описание
Обычное использование:
Внутри структур могут быть массивы, другие структуры. Можно делать массивы структур и т.д.
Здесь не создается никакой обьект, как с typedef
(создавал новый тип), создаем новый тип, который можно использовать для создания новых переменных. Такой тип использовать немного неудобно, тк нужно всегда указывать struct
(только в си) перед именем структуры.
Обратим внимание на ;
после структуры, она тут нужна потому что после структуры можно сразу содать обьекты типа нашей структуры.
Структуру можно даже не называть, не рекомендуется тк есть спец эффекты, например при отладке имя структуры не покажется
Часто можно встретить: (Чтобы не писать struct
)
ИЛИ
У структур нет модификаторов доступа, в си такого нет, появляется только в си++
Структуры можно скопировать поэлементно, что существенно отличает структуры от массивов. Если мы напишем так про массивы (a = b
, где a
и b
массивы) - то ничего хорошего не произойдет, массивы будут интерпретированны как указатели и при компиляции нам скажут "что за ужас здесь творится". Чтобы скопировать массивы надо либо писать цикл либо вызвать специальную функцию копирования памяти (из одного места в другое). (либо же обернуть их в структуры)
Массивы внутри структур ведут себя как структуры (копируются полностью)
Сразу ничем нельзя инициализировать, тк структура это не создание обьекта, описание структуры - описание формата данных в памяти. Описание расположения ничего не говорит о содержимом.
Можно иниц обьект при создании:
A a = {.y=2, .x=1};
Начиная с C99, нет в C++
A d = {};
- компилятор инициализирует всё нулям, некоторые компиляторы не любят пустые {}
Элементы структуры в памяти хранятся ровно в том порядке в котором их описали в структуре. Переставлять их компилятор не имеет права. Но это не означает что они лежат в памяти плотно.
Это так называемое Выравнивание на родную границу. Железкам очень нравится когда данные храняться по адресу кратному размеру данных
Для x86 архитектуры железок (x64 частный случай x86) практически не важно есть выразвнивание или нет (иногда небольшое замедление). В других железках программа может даже упасть, тк железо может не уметь читать невыравненные данные, либо может быть дикое замедление.
В си по умолчанию все данные выравниваются на родную границу, для этого копилятору может прийтись напихать дырок.
Первый int
начаниется в начале структуры и начало структуры достаточно хорошо выразвненно для ВСЕХ элементов (можно считать что начинается с 0 адреса)
Чтобы избежать дырок в структуре - рекомендуется описывать структуры в порядке уменьшения рамера базового типа (double -> int -> char[4]
), но можно обойтись и без этого
Очень часто есть хвостовое выравнивание для массивов из структур
Чтобы избежать свостового выравнивания при создании массива струкр - можно передалать логику работы алгоритма на структуру массивов! (вместо массива структур)
Зачем - например, для работы с файлами можно описать структуру чтобы её внутреннее устройство соответствовало один в один расположению данных в файлике. Читать так файли можно не поэлементно а одной операйией чтения. (Расположение данных должно точно совпадать) (Быстрее обычного способа)
В таких случаях вырвнивание можно отключить с помощью флагов компиляции (компиляторозависимо) (код может начать работать существенно медленее)
Для VS (Microsoft)
Для Clang/GCC
Размер какого-то обьекта - типа, переменной, массива в C/C++ можно узнать с помощью оператора sizeof
, например: (в байтах)
Скобки можно иногда не писать, это не функция!
Полученное число - это размер типа выражения в скобках в байтах!
К слову:
Тип выражения - typeof
, есть в некоторых компиляторах, предлагают добавить в стандарт C23. По сути typeof
уже существует, тк до того как посчитать размер в памяти нужно почитать тип выражения.
Синтаксически - тоже самое, что и структура
Разница только в одном - в том, как он хранится в памяти.
Это структура в которой всё элементы побайтово лежат поверх друг друга. Размер union - sizeof максимального элемента. Может использоваться для создания переменных с разными типами. (обычно вместе со структорой, которая хранит активный тип) (ещё можно прочитать битики floata, напрмер))
Все элементы начинаются с адреса 0, массив считается одним элементом, структура тоже будет считаться одним элементом, со всеми своими дырками
Нужны не очень часто, но там где их нет, но они нужны - уровень извращений зашкаливает, и он будет гораздо выше чем здесь.
В интернете есть много картинок про сравнение перехода с C на Python (например, и наоборот). Во многом легкость изучения других языков после C/С++ обуславливается тем, что здесь приходится вручную управлять памятью (=> существуют указатели).
Когда говорят что на Java можно писать эффективный код - имею ввиду, что для этого программист должен обладать высшей калификацией, так как чтобы писать эффектывный код нужно понимать что язык прячет от вас и как он это длает, C, меньше С++ прячут значительно меньше чем Java => присать быстрый код на них проще (несмотря на то, что писать просто код на них сложнее)
В последнее время почему-то чем быстрее удаётся накодить программу - тем лучше (лучше выпустить на неделю раньше, чем исправить пару багов), такой подход как раз и провоцирует использование простых, однако неэффективных языков, например Python, Java
Указатели - идейно они довольно похожи на ярлыки в windows, если создать кучу ярлыков и один удалить, данные никак не поменяются. Ярлыки не содержат данные, только информацию как эти данные найти. На си тоже самое, только указатели строго типизированны, в указателе зашито то, на что он указывает. Указатель на int
может указывать только на int
. Сами обьекты не содержат информации о типе обьекта, int
- это просто 4 байта в памяти. Информация о типе есть только в момент компиляции. По этому в C/C++ в момент компиляции указатель знает информация о типе.
Занимают в памяти столько, какая разрядность программы (32 или 64 бита)
Система обычно позволяет запускать меньшей и равной битности программы (иногда строго равной)
Есть религиозный момент, где ставить *
рядом с int
или рядом с именем переменной. Тк при создании переменной указатель относиться только к этой пеменной, а не типу, то следует её ставить рядом с переменной.
*
может относиться к типу, но это редкость (в основном при создании указателя на функцию)
От того, где в C/C++ стоят пробелы - ничего не меняется!
Указатель - чтобы работать с данными, другой смысл *
: (унарный)
Тип указателя позволяет их коректно разименовывать
В си ссылок нет! (int &a;
) (есть в си++)
Пример пользы указателей - scanf
, мы даем адрес переменной, а функция туда записывает данные.
Обычные переменные, аргументы функции и т.д. создаются на стеке (храняться на стеке)
Выделение памяти - проиходит на куче.
Если привысить стек - программа упадет, возможно молча, тк для вывода ошибки тоже нужно место на стеке.
Опишем переменную вне функции:
g
будет глобальной переменной. Такие переменные глобальные:
int g;
- Cextern int g;
- C++,::g
- обратиться в С++ (когда имя скрыто локальной переменной)Минусы глобальных переменных:
Глобальные переменные лучше не использовать без Острой необходимости
Константы рекомендуется размещать в глобальной области
static
по глобальной переменной ограничивает видимость текущим файлом.
Выглядит как локальная:
Тоже самое, что и глобальная переменная.
Из минусов осталось:
Три секции:
static
локальные тоже)И всё это лежит в оперативной памяти.
Для выделения памяти нам и нужны указатели:
malloc
, возвращает void *
, принимает количество байт которые нужно попросить выделить систему
Религиозный вопрос про проверку. Проверять рекомендуется при выделении большого размера в памяти, тк при выделении маленького - есть огромная веротность что уже всё остальное тоже сломалось.
В си НЕ нужно кастовать указатель из void *
к int *
(в ++ нужно, по религиозным причинам)
Освобождать память нужно точно таким же способом (семейство функций), каким и выделили (на будущее). Так проиходит из-за того, что у разных функций выделения - разные вспомогательные данные, из-за этого ещё если мы попросим выделить 0 байт, то место в оперативной памяти такая штука займет больше 0.
В системе существует много разных способов (функций) выделить память, для разных нужд:
- просто выделение
- выделить очень много
- чтобы была возможность отдать другому процессу
- и т.д.
Доступ к выделенной памяти такой же как и у одномерных массивов, тк синтаксис []
это ни что иное, как разьменование и перемещение указателя. Обычные одномерные массивы почти тоже самое, что указатель, за исключением того, что:
Массивы в аргументах функций никогда не бывают массивами, они автоматически преобразуются до указателя. (альтернативная запись указателя - int x[]
)
=> массивы никогда не копируются в функцию, любое изменение которые мы сдлаем с массивом внутри функции будет видно там, где этот массив создавали.
int **m;
)
int m[h][w];
)
Разницу нужно понимать, тк обращение к этим двум обьектам выглядит идентично - m[i][j]
В двумерном массиве нужный указатель вычисляется с помощью умножение, фактически, двумерный массив - это одномерный массив
Массив массивов может выделяться 3мя разными способами:
Двумерный массив выделяется так:
Есть ещё один способ сделать двумерный массив ТОЛЬКО В СИ, выделить на куче, и с нормальным доступом: (трудный для понимания):
В этом примере мы использовали понятие массива с неконстантной длинной, VLA - variable length array Не все компиляторы это умеют. Дело в том, что добавили это только в стандарт C99, но убрали из обязательной части стандарта в оптиональную в C11, те компиляторы, которые не поддерживают стандарт C99, не умеют в VLA (например microsoft), даже в VLA указатели (данный пример)
VLA массивом называют локальный или глобальный массив с неконстантным размером. Многие утверждаю, что это зло - так и есть, потому что такие массивы
VLA называю злом из-за того, что "Тупые хомячки" используют его для создания динамического массива на стеке (VLA массив), вместо создания хитрого типа переменного размера (VLA указатель). На самом деле идея очень хорошая, но только в качестве VLA указателей.
В 23 стандарте хотят вернуть часть VLA (указатели) в обязательную часть стандарта
Как НЕ надо писать
Нужно сначала передать в функцию размер массива:
Full - C/C++, 4 лекция, 05.03.2022, Скаков