# 08 лекция - C++, подробно > [TOC] ## VLA и const [VLA - Variable-length array](https://en.wikipedia.org/wiki/Variable-length_array), массив переменной длинны (определяет свою длинну в момент исполнения программы) Переменная внутри скобочек при создании массива, `int a[b];` - это всегда VLA, даже если `b` - `const` переменная, в си. [Подробнее про это в си](https://stackoverflow.com/questions/18435302/variable-length-array-folded-to-constant-array)\ (`const` по переменной всё что делает - запрещает её изменять и требует иниц. при создании, => такие переменные могут быть инициализированны в момент запуска программы, например - другими переменными, которые зависят от ввода) **В си++ нет, не было и не ожидается VLA вообще** (в стандарте, в некоторых компиляторах включенно в качестве расширения компилятора)\ Следовательно `const` переменные которые могут быть инициализированны в момент компиляции - подходят для создания **обычных** массивов. Если мы попытаемся в си++ сделать массив длинны которая определяется в момент исполнения прграммы - по стандарту оно не должно скомпилироваться, однако **некоторые компиляторы поддерживают VLA и в си++!**\ Ещё в си++ есть специальное слово - `constexpr`, оно говорит, что эта переменная обязана быть иниц. в момент компиляции. ## Про разделение на файлы **`#include` не подключает никуда никакие файлы!** > a.cpp ```cpp int g(int a, int b) { return a + b; } ``` > main.cpp ```cpp int g(int, int); //прототип функции, что она где-то существует int main() { int result = g(3, 5); //result == 8 } ``` **Чтобы функция g() была доступна в main.cpp нужно написать ей прототип в main.cpp!**. Прототип позволяет вызвать функцию реализованную в другом файле, не инклуды. **`#include<...>` - это простая текстовая вставка всего содержимого файла внутри <>, чтобы не писать прототипы ручками в каждом новом .сpp файле!** Инклуды не подключают никаких библиотек, функций и прочего, это делают прототипы! ## За жизнь > [Динамические `dll` библиотеки](https://ru.wikipedia.org/wiki/%D0%94%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8_%D0%BF%D0%BE%D0%B4%D0%BA%D0%BB%D1%8E%D1%87%D0%B0%D0%B5%D0%BC%D0%B0%D1%8F_%D0%B1%D0%B8%D0%B1%D0%BB%D0%B8%D0%BE%D1%82%D0%B5%D0%BA%D0%B0) требуются в момент запуска, а статические (вшиваются в программу) в момент компиляции --- В си++ есть три типа переменных: 1) Локальные 2) Внутри класса, принято выделять именем 3) Глобальные, принято называть с `g_` Так же принято по разному выделять `public` и `private` поля класса, так же константы времени компиляции (обычно заглавными буквами, как `#define`) --- На **си** в функции пустые скобки - `g()` означает, что **у функции неизвестное количество, и типы аргументов**(её можно вызвать с любым количеством любых аргументов) Отсутвие аргументов можно указать `void` в скобках - `g(void)`\ На **c++** отсутвие аргументов в скобках эквивалент `void`, что **у функции нет аргументов!** В си так по историческим причинам, в первых версиях языка вообще не было прототипов, и когда мы пишем функцию без прототипа - компилятор считает, эта функция существует, принимает те параметры, которые мы ей передали и возвращает `int`, тогда были только `int`, **прототип это способ чтобы компилятор проверил что бы всё совпало** (вызов с реализцией). Язык *C* наследник языка *B*, в которых вообще не было типов. --- ## Глобальные обьекты У `static` есть 2 значения: 1) `static` по глобальному обьекту - ограничивает видимость этого обьекта этим файлом, время жизни останется тем же (по умолчанию видимость глобального обекта - вся программа) (глобальная переменная, функция, структура) (так можно писать функции в `.h` файлов, но она будет в копии у каждого `.cpp` который подключил `.h`) 2) `static` по переменной в функции - означет, что эта переменная одна на все функции ```cpp int b = 0; //глобальная переменная b int f(int c) { static a = c + 5; // иниц. происходит один раз только в первый вызов a++; // будет исполняться каждый вызов f() return a; } int main() { int res; res = f(1); //res == 7 res = f(5); //res == 8 } ``` Пременная `a` живет **всю** программу, но область видимости (имени `a`) у неё только в функции **f()**, переменная `b` живет всю программу и области видимости у неё вся программа. (глобальные переменные можно называть как статические в функции, но тогда их не будет видно в этой функции) --- В СИ глобально живущие обьекты (так же `static` переменные) могут быть инициализированны только ПРОСТЫМИ выражением вычисляемым в момент компиляции, НЕ зависящими от других глобальных обьектов: ```c int a = 3; //скомпилируется int b = 3*4; //скомпилируется //int с = a + 2; //НЕ скомпилируется int f(void) { static a = 3; //скомпилируется static b = 3*4; //скомпилируется //static с = a + 2; //НЕ скомпилируется } int main (void) { return 0; } ``` В C++ разрешили иниц. глобальные переменные сложными выражениями, например: ```cpp int f() { printf("Call f\n"); return 3; } int a = f(); //a == 3 int main() { printf("Hello world!\n"); return 0; } ``` Вывод после запуска: ``` Call f Hello world! ``` **Глобальные переменные иниц ДО `main()`!** Настоящее начало программы происходит в: **недрах OC -> С runtime (даже в C++) -> main** С runtime: * иниц себя * вычисляет argc, argv (аргументы main) * иниц глобальные переменные * вызывает main() * после main вызывает деструкторы глобальных обьектов (в си++) --- В C++ также разрешили иниц. глобальные переменные глобальными обьектыми: (разделим код на файлы) > a.cpp ```cpp extern int a; //обьявление переменной (прототип) int f() { return a + 5; } ``` > main.cpp ```cpp int f(); //обьявление функции (прототип) //int b = f(); //так НЕ скомпилируется, потому что a ещё не иниц. int a = 5; int b = f(); //так скомпилируется ``` --- Ещё один случай: > a.cpp ```cpp int a = 5; ``` > main.cpp ```cpp extern int a; int b = 3 + a; //так НЕЛЬЗЯ //тк порядок инициализации между РАЗНЫМИ файлыми неопределен ``` > порядок можно опредлелить, но оч сложно --- Давайте вернемся к первому примеру: ```cpp int f(int a) { static b = a + 2; // иниц. происходит один раз только в первый вызов return b; } int main() { int res; res = f(1); //res == 7 } ``` Распаралеллим этот код, тогда у кучи тредов будет неиниц глобальная (по времени жизни) переменная, которую оин должны иниц каким-то своим значением, они одновременно начнут писать туда свои переменные и может получиться мусор, однако - по стандарту `b` должно быть иниц только **один** раз, что означает, что это место образует **критическую секцию**, которая сольет все треды в один и выстроит их в порядок очереди, этот код очень трудоемкий и содержит в себе кучи вызовов системных функций, ожиданиями, вызовами ядра и т.д.\ Если бы тут было просто `static b = 2;` этого всего бы не было, то, что нам разрешили писать `static b = a + 2;` выглядит как почти ничего не изменилось, а на самом деле всё радикально изменилось! (Это задача консенсуса - договориться какая переменная, из какого треда должна иниц переменную `b`)\ В этом вся идея C++, что тут более тяжелые вещи, но эти вещи могут быть значительно дольше исполняться. ## Namespace ```cpp #include <math.h> namespace my //без него не скомпилируется //тк функция пересечется с стандартной { unsigned int abs(int a) { if(a >= 0) return a; else { unsigned int abs_a = a; // нужно тк в стандарте //знаковое переполнение не определенно! //(a = -a; - эквивалент инверт биты + 1) abs_a = -abs_a; //эквивалент инверт биты + 1, //только без Undefined Behavior //(случался из-за переполнения) return abs_a; } } } int main() { abs(-5); // вызовется стандартная my::abs(-5); // вызовется наша return 0; } ``` Чтобы не писать постоянно свои `namespace`, в стандартной библиотеки си++ все функции убрали в **namespace std** (в том числе сишные, вместо `math.h` стало `cmath` и также с остальными). Теперь каждый вызов стандартной функции надо начинать с `std::`, что заметно бесит, так что разработчики добавили возможность не писать постоянно название namespace - **using namespace std;** (их можно написать несколько, главное чтобы не пресеклись функции (имя + те же аргументы)) Анонимные namespace: ```cpp namespace { int f() {return 1;} } ``` Делает магическое имя namespace и сразу же пишет для него `using namespace`\ Таким образом пытались избавиться от `static` по глобальным обьектам, и даже задиплекейтили его в стандарте 2003, но [андипликейтили](https://ru.wikipedia.org/wiki/Deprecation) в стандарте 2011, поняв абсурдность, [подробнее](https://www.open-std.org/jtc1/sc22/wg21/docs/cwg_closed.html#174 ). namespace единственное, что делает - ещё больше заворачивает имена функции в name mangling namespace можно обернуть в другой namespace ## Шаблоны Посмотрим на наши функции `abs`: ```cpp float abs(float a) { if(a >= 0) return a; else return -a; } int abs(int a) { if(a >= 0) return a; else return -a; } ``` Они выглядят как дословная копипаста тела функции, различие только в типах\ Для таких случаев придумали шаблоны: ```cpp template <typename T> T abs(T a) //здесь не создается никакого кода, вообще { if(a >= 0) return a; else return -a; } int main() { int a = abs(-5); float b = abs(-5.0); //будет созданна функция double return 0; } ``` В `<>` указываются аргументы шаблона, типы или целочисленные константы времени компиляции. Шаблонные функции не создают код **совсем!** Компилятор создает копию функции только при обнаружении вызова с новым типом. (создает функцию подставив вместо шаблона какой-то тип) Шаблоны - способ автокопипасты когда разница только в типе. Вместо `typename` можно писать `class` - `template <class T>` ```cpp template <typename T> T max(T a, T b) { return a > b ? a : b; } // template <typename T, typename G> T max(T a, G b) // { // return a > b ? a : b; // } плохое решение проблеммы int main() { //int a = max(5, 10.0); //не скомпилируется //тк непонятно какую функции нужно создать, int или double int a = max(5, (int)10.0); //скомпилируется, но не интерестно int a = max<int>(5, 10.0); //явное указание аргументов шаблона return 0; } ``` `T` можно использовать внутри шаблона в любом месте, где можно использовать тип. Можно сделать специальные типы шаблоннов для конкретных типов: ```cpp template <typename T> T abs(T a) //общая версия { if(a >= 0) return a; else return -a; } template <typename int> unsigned int abs(int a) //спец версия для int { if(a >= 0) return a; else return -((unsigned int)a); } ``` **(Напомни мне добавить то, что я знаю про шаблонны)** ## Ссылки ```cpp void f(int a, int *b, int &c) //a - локальная копия, b - указатель на int (хранит адрес), //с - ссылка, синтаксический сахар на указатель { a = 3; *b = 5; с = 7; } int main() { int a = 1, b = 2, c = 3; f(a, &b, c); //a == 1, b == 5, с == 7 //&b - адрес переменной b //f(a, &b, 5); //не скомпилируется, //тк ссылка должна быть привязанна к какому-то обьекту //f(a, &b, с + 1); //не скомпилируется, //тк у с + 1 нет адреса } ``` Ссылки - синтаксический сахар на указатели, выглядит как обьект, но на самом деле указатель, не может быть привязанна к NULL, не может быть привязанна к константе, **НЕ** меняется после создания. ```cpp int main() { int x = 4; int &y = x; y = 5; //x == 5 int &z = y; //ссылается на x } ``` Нельзя создать ссылку на ссылку, будет указывать на то, что указывает первая ссылка Это были так называемые `l-value` ссылки, умеют связываться с обьектами, не с значениями! `int &&y` - `r-value` ссылка, то что умеет связываться со значениями, но не с обьектами! Если коротко: `l-value` - то, что может стоят слева от знака `=` `r-value` - то, что может стоять справа от знака `=` `const int &y` - константная ссылка может связаться с обьектом и со значением, но через неё нельзя менять обьект! `const int &&y` тоже имеет некоторый смысл, [подробнее](https://stackoverflow.com/questions/4938875/do-rvalue-references-to-const-have-any-use) ## auto auto (в старом значении) - это место размещения данных, по локальным переменным в стандарте 3 возможных варианта, где размещать обьект:\ 1) auto - в секции данных в стеке (по умолчанию) 2) static - в секции данных в сегменте иниц данных 3) регистр - в регистре процессора (по желанию компилятора, сегодня компиляторы почти никогда так не делают) `auto` в новом значении - тип этого обьекта такой, который в него присваивается. (то есть можно писать перед иеменем переменной только в момент иниц.) (взять тип справа от знака `=`) (придумали чтобы не писать длинный типы посл шаблонов) ```cpp auto f() //вычислить возвращаемый тип по return { return 0; } ``` auto вычисляется в момент компиляции и подставляет нужный тип # Оглавление Full - C_C++, 8 лекция, 16.04.2022, Скаков > [TOC]