C: база

/*

  • Вывод типов это процесс определения типа переменной во время компиляции (необходимо для статической типизации языка) в с++ вывод типов происходит при: - присваивании - вызове функции - в вызовах шаблонов функций

    ВЫВОД ТИПОВ В ШАБЛОННЫХ ФУНКЦИЯХ:
    template<typename Т>        int x = 0;
    void f(Т param);                при вызове f(x), T приводится к int, НО НЕ ВСЕГДА!!!
    
    В процессе компиляции компилятор использует агументы фукнции для вывода двух типов:
        - типа Т из <typename Т>
        - типа, который указан в параметре функции
    
    Тип, выведенный для T, зависит не только от типа аргумента функции, но и от вида
    ParamType.
    
    - ParamType - указатель или ссылка
    В C++ функция объявленная как шаблонная функция с параметром типа T,
    означает, что компилятор будет генерировать отдельную версию функции
    для каждого уникального типа T, который будет передан в качестве аргумента.
    Когда мы вызываем функцию с разными аргументами, компилятор пытается
    определить тип T на основе типа аргумента. Это происходит в процессе
    вывода типов:
    
    1.  template<typename Т>
            void f(T param);
            f(х);    // Т - int, тип param - int                                | int х = 27;
            f(cx); // Т - const int, тип param - const int      | const int сх = х;
            f(rx); // Т - const int&, тип param - const int&    | const int& rx = х;
    
            в случае если - void f(T param) - T без ссылок и без const, то все
            типы выводятся по обычному
    
    2.  template<typename Т>
            void f(T& param);
            f(х);    // Т - int, тип param - int                                | int х = 27;
            f(cx); // Т - const int, тип param - const int      | const int сх = х;
            f(rx); // Т - const int, тип param - const int&     | const int& rx = х;
    
            когда используем ссылки на типы в шаблонных функциях, происходит слияние
            ссылок (reference collapsing). Это означает, что если тип T является
            ссылкой на тип, который уже является ссылкой, то ссылки сливаются в одну.
            В этом случае квалификаторы const и volatile могут быть опущены.
    
    3.  template<typename Т>
            void f(const T& param);
            f(х);    // Т - int, тип param - const int&     | int х = 27;
            f(cx); // Т - int, тип param - const int&       | const int сх = х;
            f(rx); // Т - int, тип param - const int&       | const int& rx = х;
    
            тоесть если const T& - то ссылочность const и volatile опускается 
            если T& - то ссылочность опускается
    
    - ParamType - ни указатель, ни ссылка
    Когда тип параметра ни ссылка ни указатель - то это передача по значению,
    это означает, что параметр функции будет копией аргумента (новым объектом)
    в этом случае отбрасывается и const и ссылочность и volatile
    так параметр, который представляет собой новый объект полностью независим,
    и не выжно что он при передаче аргумента там был const или что-то еще
    тот факт, что аргументы не могут быть модифицированы, ничего не говорит о том,
    может ли быть модифицирован параметр (новая копия), поэтому все игнорируется
    при выводе типа
    
    ВЫВОД ТИПА AUTO
    
    Вывод типа auto обычно такой же, как и вывод типа шаблона, но вывод типа auto,
    в отличие от вывода типа шаблона, предполагает, что инициализатор в фигурных
    скобках представляет std::initializer_list
    auto в возвращаемом типе функции или параметре лямбда-выражения влечет
    применение вывода типа шаблона, а не вывода типа auto.
    
  • R-value & L-value ссылки L-value: Объект, имеющий адрес в памяти. L-value ссылки объявляются с использованием одного амперсанда (&). Они могут ссылаться только на l-value. ----- int x = 10; int& ref = x; ----- L-value ссылки используются для передачи больших объектов в функции, чтобы избежать копирования: void printValue(int& val) { }

    R-value:
        Временное значение, которое не имеет собственного адреса в памяти.
        -----
        int x = 10;    // x - это l-value
        x = 20;        // l-value может быть на левой стороне присваивания
        int y = x + 1; // (x + 1) - это r-value
        -----
        R-value ссылки объявляются с использованием двух амперсандов (&&).
        Они могут ссылаться только на r-value. Это позволяет "захватить" временные
        объекты, что полезно для оптимизации (например, перемещения).
        -----
        int&& rref = 10;  // rref - это r-value ссылка на временное значение 10
      -----
        R-value ссылки часто используются в контексте (move semantics),
        что позволяет эффективно передавать временные объекты:
        -----
        class MyClass {
        public:
                MyClass() { std::cout << "Constructor\n"; }
                MyClass(const MyClass&) { std::cout << "Copy Constructor\n"; }
                MyClass(MyClass&&) noexcept { std::cout << "Move Constructor\n"; }
        };
        -----
    
    Универсальные ссылки:
        Может быть как l-value так и r-value в зависимости от переданного аргумента:
        -----
        void func(T&& arg) { }
        func(x);  // x передается как l-value, T выводится как int&
        func(20); // 20 передается как r-value, T выводится как int
        -----
    
  • decltype для получения типа выражения, предоставляет возможность узнать тип переменной или выражения на этапе компиляции: ----- int x; double y; decltype(x) type_name; // type_name имеет тип Int decltype(x+y) type_name; // type_name имеет тип Double ----- decltype анализирует выражение и выводит его тип, при этом не выполняя само выражение -> эффективно

        полезен при написании шаблонных функций и классов, когда необходимо
        узнать тип возвращаемого значения функции или выражения:
        -----
        template <typename T, typename U>
        auto add(T t, U u) -> decltype(t + u) {
                return t + u;
        }
        int a = 5;
        double b = 4.5;
        auto result = add(a, b);  // result имеет тип double (тип выражения a + b)
        -----
    
  • static 1. Статические переменные в функции Переменные, объявленные с ключевым словом static внутри функции, сохраняют свое значение между вызовами функции. Они инициализируются только один раз, при первом вызове функции, и их значение сохраняется между вызовами. ----- void staticVariableExample() { static int count = 0; // Инициализируется только один раз count++; } staticVariableExample(); // Выводит: Count: 1 staticVariableExample(); // Выводит: Count: 2 staticVariableExample(); // Выводит: Count: 3 ----- Статические переменные внутри функции хранятся в статической области памяти (обычно это раздел данных в памяти программы), а не в стеке.

        2. Статические переменные класса
        Статические переменные-члены класса принадлежат классу,
        а не отдельным его объектам. Это означает, что все экземпляры класса
        разделяют одну и ту же копию статической переменной.
        -----
        class MyClass {
                static int staticMember;
                void increment() { staticMember++; }
        };
        int MyClass::staticMember = 0;
        MyClass obj1, obj2;
        obj1.increment();
        obj2.increment();
        std::cout << "Static Member: " << MyClass::staticMember << std::endl;
        // Выводит: 2
        -----
        Статические члены класса хранятся в статической области памяти,
        подобно глобальным переменным. Они инициализируются до выполнения
        функции main() и существуют на протяжении всего времени выполнения программы.
    
        3. Статические функции класса
        Статические функции-члены класса могут вызываться без создания экземпляра
        класса. Они имеют доступ только к статическим переменным и другим
        статическим функциям класса.
        -----
        MyClass::staticFunction();
        -----
        Статические функции класса вызываются без указателя на объект
        (т.е., без this). Они компилируются как глобальные функции с дополнительным
        параметром, который указывает на конкретный класс, к которому они принадлежат.
    
        4. Статические переменные в пространстве имен или в файле
        Переменные, объявленные с ключевым словом static в пространстве имен или
        на уровне файла, имеют внутреннее связывание (internal linkage).
        Это означает, что они видимы только в пределах того файла, в котором они
        объявлены.
        -----
        static int internalVariable = 42;
        -----
        Статические переменные, объявленные на уровне файла, имеют внутреннее
        связывание. Это достигается путем добавления специальной информации
        в объектный файл, которая указывает линкеру, что эта переменная не должна
        быть видима за пределами файла.
    
  • inline для подсказки компилятору, что он должен заменять вызов функции ее телом непосредственно в месте вызова. Это может улучшить производительность, особенно для небольших, часто вызываемых функций, поскольку устраняет накладные расходы на вызов функции, но выше расход памяти

        -Компиляторы склонны встраивать только небольшие функции, поскольку большие
            функции могут привести к раздуванию кода и увеличению размера бинарника.
        -Встраивание рекурсивных функций обычно не выполняется, так как это приведет
            к бесконечному разрастанию кода
        -Компиляторы часто самостоятельно решают, какие функции следует встраивать,
            основываясь на оптимизационных параметрах, даже без указания inline
    
  • extern используется для объявления переменных и функций, которые определены в другом месте. Оно позволяет использовать одну и ту же переменную или функцию в разных файлах, избегая дублирования определения и обеспечивая возможность разделения кода на модули. ----- extern int var; // Объявление переменной extern void func(); // Объявление функции ----- var и func в других файлах

        Уведомляет компилятор о существовании переменной или функции.
        Не резервирует память под нее.
        Каждый исходный файл компилируется в объектный файл.
        Компилятор проверяет объявления extern на предмет правильности типов сигнатур.
        Линкер объединяет объектные файлы. Он использует информацию extern для
        разрешения символов (имён переменных и функций), обеспечивая, чтобы все
        ссылки на одну и ту же переменную или функцию указывали на одно и то же
        место в памяти.
    
  • explicit используется для предотвращения неявного преобразования типов при инициализации объектов и вызове конструкторов. Оно особенно полезно для улучшения читаемости и безопасности кода, предотвращая потенциальные ошибки, вызванные неявными преобразованиями.

        1. Конструкторы
        Когда конструктор помечен как explicit, это означает, что он не может
        быть использован для неявного преобразования типов.
        -----
        class MyClass {
                MyClass(int x) { }
        };
        void f(MyClass obj) {}
        MyClass obj1 = 42;  // Неявное преобразование int в MyClass
        f(42);                          // Неявное преобразование int в MyClass
        -----
        В этом примере конструктор MyClass(int x) может быть вызван неявно
    
        ИСПОЛЬЗУЮ explicit:
        -----
        class MyClass {
                explicit MyClass(int x) { }
        };
        void f(MyClass obj) {}
        MyClass obj1 = 42;  // Ошибка: неявное преобразование запрещено
        f(42);                          // Ошибка: неявное преобразование запрещено
        MyClass obj1(42);   // Явный вызов конструктора
        f(MyClass(42));         // Явный вызов конструктора
        -----
        explicit предотвращает неявное преобразование типа int в MyClass
    
        На уровне компиляции explicit влияет на то, как компилятор рассматривает
        допустимые преобразования типов. Без explicit компилятор может автоматически
        вставлять вызовы конструктора для преобразования типов, где это возможно.
        С explicit компилятор требует явного указания на вызов конструктора,
        что предотвращает неявные преобразования.
    
        ГЛАВНОЕ:
        -----
        MyClass obj1 = {1, 2, 3};       // Ошибка: неявное преобразование запрещено
        MyClass obj2( {1, 2, 3} );  // Явный вызов конструктора
        -----
    
  • noexcept используется для указания компилятору, что функция не генерирует исключений. Это позволяет компилятору оптимизировать код и улучшить производительность, так как он может сделать предположения о том, что функция не будет прервана исключением. ----- void f() noexcept { } void f() noexcept(true) { } // тоже самое что и первое void f() noexcept(false) { } // гарантированно генерирует исключение ----- Оптимизация кода: Компилятор может сгенерировать более эффективный машинный код для функций, объявленных как noexcept, так как он знает, что эти функции не будут выбрасывать исключения. Безопасность: noexcept может использоваться для документирования и обеспечения безопасности кода, так как позволяет программистам указывать, какие функции генерируют исключения, а какие нет.

        ЕСЛИ ФУНКЦИЯ ПОМЕЧЕНА КАК noexcept И ОНА выкинула исключение, то будет
        ошибкак компиляции.
    
  • НЕЯВНЫЕ КОПИРОВАНИЕ И ИХ ИЗБЕЖАНИЕ ----- for (const auto& x : collection) перебор x в коллекции будет происходить непосредственно с элементами коллекции void func(int& a) при передаче не будет копирования, операции будут неспосредственно с этим объектом

        void f(const std::string& s) { }    функция принимает ссылку на объект строки
                                                                            типа std::string, который не может быть
                                                                            изменен внутри функции. Это предпочтительный
                                                                            способ передачи строк в C++
    
        void f(const char* s) { }       В C++ строки могут быть представлены в виде
                                                                массивов символов, заканчивающихся нулевым
                                                                символом ('\0'). Этот способ часто используется
                                                                в старом коде на C или при работе с
                                                                низкоуровневыми функциями или библиотеками,
                                                                которые ожидают строки в формате const char*.
        Разница между этими подходами в том, что std::string предоставляет более
        безопасный и удобный способ работы со строками в C++, в то время как
        const char* используется чаще всего для совместимости с C или при работе
        с функциями, которые ожидают строки в формате C-style. Однако использование
        const char* может быть менее безопасным из-за отсутствия проверок на нулевой
        указатель и более громоздким в использовании без дополнительных утилит для
        работы со строками.
        -----
    
        1. Возвращаемое значение функций
        -----
        // Копируется временный объект std::string
        std::string func() { return "John"; }
    
        // Использование ссылки или rvalue reference для возвращаемого значения
        const std::string& func() {
                static std::string name = "John";
                return name;
        }
    
        // Использование std::move для перемещения временного объекта
        std::string func() { return std::move("John"); }
        -----
    
        2. Конструкторы и операторы присваивания
        -----
        class MyClass {
        public:
                // Копирование данных из другого объекта MyClass
                MyClass(const MyClass& other) { }
    
                // Копирование данных из другого объекта MyClass
                MyClass& operator=(const MyClass& other) { return *this; }
        };
        -----
    
        3. Передача аргументов в функции
        -----
        // Обработка вектора
        void processVector(std::vector<int> vec) { }
    
        // Использование константной ссылки для передачи аргумента,
        // чтобы избежать копирования
        void processVector(const std::vector<int>& vec) { }
    
        // Передача rvalue reference для перемещения вектора
        void processVector(std::vector<int>&& vec) { }
        -----
    
        4. Использование умных указателей
        -----
        // Создание объекта MyClass
        std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();
        // Копирование shared_ptr, увеличивается счетчик ссылок
        std::shared_ptr<MyClass> ptr2 = ptr1;
    
        // Создание объекта MyClass
        std::unique_ptr<MyClass> ptr3 = std::make_unique<MyClass>();
        // Перемещение unique_ptr, ptr3 теперь пустой
        std::unique_ptr<MyClass> ptr4 = std::move(ptr3);
        -----
    
        Избегание копирования:
        Используйте ссылки или rvalue reference для передачи и возвращаемых значений
        Используйте const перед ссылками, чтобы гарантировать, что данные не будут изменены
        Используйте std::move, чтобы переместить временные объекты или ресурсы вместо копирования.
        Предпочтительно передавайте большие объекты по константной ссылке или rvalue reference.
        Используйте умные указатели для управления ресурсами и избегания лишних копий владеемых объектов.
    
  • РАБОТА С ПАМЯТЬЮ В С++ Динамическое выделение памяти используется для создания объектов, которые должны существовать в течение длительного времени или должны быть доступны из разных частей программы. Однако это требует более аккуратного управления памятью, чтобы избежать утечек памяти или доступа к недопустимой памяти. Создание объекта на стеке более эффективно в плане производительности и менее склонно к ошибкам управления памятью, но объекты, созданные таким образом, будут существовать только в рамках текущей области видимости.

        СТЕК:
        -----
        A* ptr_a = &a;
        -----
        Объекты, созданные на стеке, автоматически освобождаются при выходе из
        области видимости, в которой они были созданы.
        Создание объектов на стеке быстрее, чем в куче, и их удаление происходит
        автоматически, без необходимости вручную освобождать память.
        (Создание локальных переменных в функциях,
        Создание объектов внутри блоков кода)
        Если вы вышли из области видимости и снова вернулись в нее, локальные
        переменные, созданные на стеке, не будут восстановлены. Каждый раз при
        входе в область видимости эти переменные будут создаваться заново.
    
        КУЧА:
        -----
        A* a = new A();
        -----
        Объекты, созданные в куче, не освобождаются автоматически, они существуют
        до тех пор, пока не будут явно удалены с помощью оператора delete.
        В куче можно создавать объекты переменного размера и динамически
        управлять памятью.
        Создание и удаление объектов в куче требует дополнительных ресурсов и
        времени по сравнению со стеком.
        (Создание объектов с долгим временем жизни,
        Переменных, чей размер известен только во время выполнения программы,
        объектов, которые должны быть доступны в нескольких частях программы)
    
        ДРУГИЕ ВАРИАНТЫ (область данных программы):
        Объекты, созданные как статические переменные И КАК ГЛОБАЛЬНЕ, хранятся
        в специальной области памяти, которая существует на протяжении всего времени
        выполнения программы. Они создаются до запуска программы и уничтожаются
        после ее завершения.
        Иногда объекты создаются в специально выделенной области памяти,
        известной как "пул объектов". Это используется для управления памятью и
        повышения производительности в некоторых сценариях, например,
        при создании большого количества объектов одного типа.
    
  • использование USING NAMESPACE STD; Использование директивы using namespace std; влияет на производительность в крайне незначительной степени, если вообще влияет. Это потому, что компилятору приходится искать имена из пространства имен std, если они не были явно указаны. Однако более существенным аспектом, который следует учитывать, является возможное введение конфликтов имен. Если несколько пространств имен содержат одинаковые имена, то использование директивы using namespace может привести к неоднозначности и ошибкам в коде. В целях читаемости и ясности кода рекомендуется явно указывать пространство имен перед каждым использованием имени из этого пространства. Это делает ваш код более понятным для других разработчиков и предотвращает потенциальные конфликты имен. Итак, хотя влияние на производительность от использования using namespace крайне незначительно, хорошая практика состоит в явном указании пространства имен перед каждым именем, чтобы избежать конфликтов имен и сделать ваш код более ясным и поддерживаемым.


Когда пользователи сложной системы не знают, как она работает, но их устраивает то, что она делает, это говорит об удачном проектировании системы. (Скотт Майерс)

*/