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