Skip to content

go-maxus-go/CppFacts

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 

Repository files navigation

Что-то о C++

Тип результата

auto a = int{} / float{1} // a is float
auto b = int{} * float{} // b is float
auto c = 1.0 // c is double
auto d = 1.f // d is float

Аргументы по-умолчанию для виртуальных функций

struct A { virtual void Do(int = 0){ std::cout<<"A"; }};
struct B : A { virtual void Do(int){ std::cout<<"B"; }};
B b;
(A&)(b).Do(); // Будет вызов B.Do(0)

emplace vs push

struct A { explicit A(int){} };
std::vector<A> arr;
arr.emplace_back(42); // Создаст элемент в векторе, push_back не создаст.

Явное преобразование, также можно применять к операторам

struct A { explicit bool operator() { return true; } };
std::cout << A(); // error, without explicit prints 1

Немного формулировок

std:: string str = "hello" // литерал
char c = 'A' // символьная константа

Немного о виртуальных функциях и override

struct A { virtual Do() {std::cout << "A"; }};
struct B : A { Do() {std::cout << "B"; }};
struct C : B { Do() override {std::cout << "C"; }};
B * b = new C;
A * a = b;
a->Do(); // C
b->Do(); // C

Перемещение вектора

std::vector<int> v1(10);
auto v2 = std::move(v1);
assert(v1.empty());
assert(v2.size() == 10);

std::string to c string

std::string().c_str(); // получение C - строки (ограниченной нулем)

[] - массив векторов, () - вектор с 1000 пустых элементов

std::vector<int> v1[1000];
std::vector<int> v2(1000);

Названия типов

bool, int, char // интегральные типы
bool, int, char, float, double // арифметические типы
class, struct, enum, enum class // пользовательские
void, void *, int &, float && // встроенные типы

bool -> int

bool преобразуется в int // false => 0, true => 1
и наоборот // false => 0, true => 1

Префикс L и char

// MSVS: error C2220: warning treated as error - no 'object' file generated
// gcc OK
auto a = L'ab'; // wchar_t
auto b = 'ba'; // int
auto c = 'a'; // char

char практически всегда 8бит, но могут быть исключения

Литералы

0x0123abc // шестнадцатиричный литерал
01234567 // восьмеричный литерал
123456789 // десятичный литерал
0.1f // float 0.1
0.1 // double 0.1
1.23e-1f // float
1.23e8L // long double

U, L - суффиксы определяют unsigned и long

1L // long 1
2U // unsigned int 2
3UL // unsigned long

все размеры типов измеряются в char, char берется за 1

фундаментальные типы преобразуются без потери информации, также могут преобразовываться и с потерей.

Об enum

enum { Red, Green, Blue}; // можно записывать без названия
enum State { Invalid = 1, Valid = 2, Downloaded = 4};
State s = State(Valid | Downloaded); // можно записать как флаг
enum Smth { A = -5, B = -10, C = 8}; // можно задавать отрицательные значения
// у енума есть диапазон, такой чтобы влезли все возможные флаги, в плюс и в минус относительно нуля.

Предекларация глобальной переменной

// A.h
extern int gl; // означает, что глобальная переменная gl объявлена в другом месте
struct A{ int v = gl; }

// B.h
int gl;

C++ проверяет только корректность выражений, даже если в них нет смысла

// успешно скомпилируется
class A {};
int main() {
	class A;
	class A;
}

Объявление в котором указывается значение является определением

Объявления в одной строке

int* p, a; // *p, a
int a[10], *p[]; // a[10], *p[]

extern "C" void f();

Отключает механизм декорации имени, чтобы фукнцию можно было запаковать в *.obj файл и сохранить при это обратную совместимость с языками C и прочими древними. Переопредилить такую функцию нельзя, потому что возникнет конфликт имен (будет две функции с одинаковым именем, а mangling отключен)

Массив можно неавно привести к указателью на его первый элемент, размер массива будет утерян

int d[] = {0, 1, 2, 3};
int * p = d; // ok
d = p; // error

Операция сложения указателей смысла не имеет и поэтому запрещена.

int *a = new int{1};
int *b = new int{2};
auto c = a + b; // error!

Шаблонные методы генерируются, только если они вызываются.

struct A { void Do() {} };
template<class T>
struct B
{
void Do() { t.Do(); }
void Bo() { t.Bo(); } // T может не иметь данного метода
T t;
};
int main()
{
B<A> b; // компилируется и работает нормально
b.Do();
}

Нельзя вызывать чисты виртуальные методы в конструкторе, даже косвенно

struct A {
// компилятор может запретить вызов чисто виртуального метода в конструкторе
A(){ PureVirtualFunctionCall(); }
virtual void Do() = 0;
void PureVirtualFunctionCall() { Do(); } // вызов чисто абстрактного метода
};

struct B : A { void Do() override {} };

int main() {B b;} // получаем ошибку рантайма Pure virtual function call

Еще о виртульаных методах

struct A
{
void a() {std::cout << "A::a" << std::endl;}
virtual void b() {std::cout <<"A::b" << std::endl;}
};
struct B : A
{
virtual void a() {std::cout << "B::a" << std::endl;}
void b() {std::cout <<"B::b" << std::endl;}
};
int main(int argc, char *argv[])
{
B b;
A &a = b;
a.a(); // A::a
a.b(); // B::b
b.a(); // B::a
b.b(); // B::b
}

Размеры структур

// sizeof(int) = 4
// sizeof(void*) = 8
struct A {}; // sizeof(A) = 1, в пустой структуре считается, что есть 1 char
struct B { char v; }; // sizeof(A) = 1
struct C { char v; char vv; }; // sizeof(A) = 2
struct D { char v; int vv; }; // sizeof(A) = 8
struct E { char v; int vv; char vvv; }; // sizeof(A) = 12
struct F { int v; char vv; char vvv; }; // sizeof(F) = 8

struct G { char v; short vv; char vvv; }; // sizeof(G) = 12
struct H { char v; double vv; char vvv; }; // sizeof(H) = 24

struct VA { virtual ~VA() = default; }; // sizeof(VA = 8
struct VB { virtual ~VB() = default; }; // sizeof(VB) = 8
struct VC: VA { virtual ~VC() = default; }; // sizeof(VC) = 8
struct VD: VA, VB { virtual ~VD() = default; }; // sizeof(VD) = 16

Значения указателя на таблицу виртуальных функций

При создании A ее указатель на таблицу виртуальных функций указывает на собственную таблицу, когда А будет полностью инициализирована указатель будет указывать на таблицу виртуальных функций B.

struct A {virtual ~A = default;}
struct B: A {};
B b;

Исключение в конструкторе

Тело деструктора не будет вызвано, потому что объект А не будет считаться созданным. Все его поля будут уничтожаться в порядке LIFO.

struct A
{
A() { std::cout << "A"; throw std::exception(); }
~A() { std::cout << "~A"; }
};

Полиморфность

Если где-то в иерархии наследования есть класс с виртуальными функциями, то все его потомки тоже будут обладать указателем на эту таблицу виртуальных функций. При множественном наследовании класс может иметь несколько указателей на таблицы виртуальных функций

Читать такие выражения справа налево:

char * const p; // p - константный указатель на char
char const * p; // p - указатель на константный char
const char * p; // p - указатель на константный char
// 2 и 3 - эквивалентны!

модификатор const - изменяет тип, накладывая на него ограничения

обращение к функции по ее имени эквивалентно обращению по указателю

обычный указатель можно присвоить константному, а наоборот нельзя.

chat * p;
const char * pc;
pc = p; // ok
p = pc; // compile time error

ссылки обязаны быть инициализированы при объявлении, поэтому нет массивов из ссылок.

ссылка - это альтернативное имя переменной, может быть реализовано через константный указатель, за исключением того, что над ссылкой нельзя проводить никакие операции.

константные ссылки, продлевают жизнь временным объектам.

double & dr = 1; // error, нужен lvalue
const double & cdr = 1; // ok, создается временный объект типа double, а единица используется как инициализатор

Просто необычная запись

int & f(int & v) { return v; }
int v = 0;
f(v)++;
std::cout << v; // v = 1, в целом очевидно, просто чтобы знать что так можно записать.

?

В void * можно преобразовать любой тип, из void * только через static_cast разыменовать нельзя, никакие операции проводить нельзя. Указатели на члены и на функции не могут быть присвоены переменным void * ? Мне компилятор разрешил.

Такая перегрузка имен возможна, уходит корнями в Си. Не рекомендуется так делать.

struct stat {};
int stat();
enum stat {...};
union stat {...};
Такая перегрузка имен возможна, уходит корнями в Си. Не рекомендуется так делать.
  1. char (&r)[10]; // ссылка на массив из 10-ти char'ов char *(data)[]; // указатель на массив строк(char) typedef char (tt)[8]; // tt - указатель на массив из 8-ми char typedef int ((tt[8]))[7]; // tt - массив из 8-ми массивов указателей на целое

  2. когда имеет смысл воспользоваться именем в его собственном инициализаторе? struct A { int v; A(int v): v(v) {} };

  3. С не есть подмножество С++, большинство программ С будет совместимо с С++, но есть часть которая не будет. int virtual = 0; // virtual - ключевое слово int class = 0; // class - ключевое слово int * data = malloc(100 * sizeof(int)); // void * нельзя неявно привести к другому указателю в С++

  4. C++11 если объявлено одно из следующих: деструктор/копи-конструктор/мув-конструктор/оператор присвоения/оператор перемещения то рекомендуется объявить остальные операции, часть из них не будет сгенерировано. Некоторые могут быть сгенерированы, но это устаревшее поведение. Если объявить оператор копирования, то перемещение будет запрещено и конструктор копирования должен быть запрещен. Если объявить оператор перемещения, то оператор копирования будет запрещен, и конструктор копирования тоже должен быть запрещен. Если объявить деструктор, то перемещение и копирование запрещено.

  5. C++11 enum class : int { A, B}; В новых классах перечислений можно указывать от какого они типа, чтобы использовать их как флаги нужно самостоятельно определять операторы |=, &=

  6. const vs constexpt const - говорит о том, что объект не может быть изменен через данный интерфейс, компилятор может оптимизировать константные переменные, помещая их в свои таблицы, и не создавая эти переменные в коде constexpr - говорит о том что данное выражение может быть вычислено на этапе компиляции, и его значение компилятору нужно разместить в таблице. очевидно, что не каждое выражение может быть вычислено на этапе компиляции.

  7. decltype(1 * 1.0) a; // a типа double decltype - декларация типа как результат некоторого выражения, часто можно заменить словом auto. decltype принимает только объекты, нельзя через него задать тип через другие типы.

  8. std::initializer_list - список, с помощью которого можно инициализировать объект. std::vector v {0, 1, 2}; v = {4, 5}; void f(const std::list &); f({7, 8, 9});

  9. список инициализации предотвращает сужение типов. char c = {256}; // ошибка, 256 вне диапазона char vector v = {1, 2.5}; // сужение int -> double, должна быть ошибка компиляции, но я видел просто ворнинг Похоже что из-за того что список инициализации шаблонный возможна такая проверка.

  10. Если в наследнике функция перекрывается переменной, то через using нельзя обратиться к функции struct A { void f(){} }; class B : A { int f{0}; public: using B::f; }; int main() { B().f(); } Если функция перекрывается функцией, то можно обратиться. В С++ 11 теперь можно таким же образом объявлять конструктор базового класса в наследнике.

  11. argc - количество переданных параметров. argv - аргументы, первый - имя программы argv[argc] == 0; // это выполняется всегда.

  12. std::cout, std::cerr, std::clog - разные стримы, первый для вывода программы, второй для вывода ошибок, третий для логов. Каждым из них можно управлять независимо. вывод первого можно направить в файл, второго оставить в консоль, вывод третьего в другой файл. std::cerr пишет без буфера, то есть сразу же в конечный вывод.

  13. #include typeid - возвращает структуру type_info, которая содержит имя типа и хеш-сумму для выявления типа идет обращение к статичной структуре, которая содержит эту инфу. Для полиморфных объектов возвращается тип реального объекта, видимо эта информация берется из обращения к таблице виртуальных функций. struct Base { virtual void foo() {} }; // polymorphic struct Derived : Base {}; Derived d; Base& b = d; std::cout << typeid(b).name(); // Derived, из-за vtable!!!

  14. int v = 15; std::cout << ~v; // -16, инверсия каждого бита

  15. operator ->* and .* - вызов функции члена класса #include

class A { int f() { return 1; } public: A() : x(&A::f) {} int (A::*x)(); };

int main() {
	A a;
std::cout << (a.*a.x)();
return 0;

}

  1. long long - по меньшей мере 64 бита

  2. в чем проблема данного выражения? template<class T, class U> decltype(xy) mul(T x, U y) // Проблема с видимостью! { return xy; } нужно заменить decltype на auto // C++17 нужно добавить суффикс указывающий на возвращаемое значение // C++11

  3. как в С++ 11 можно решать проблему области видимости struct A { struct B {}; B give(); };

auto A::give() -> B // так можно писать вместо A::B A::give() { return B(); }

  1. C++ 11 template alias
template<class T>
using myVector = std::vector<T>;
  1. variadic templates - шаблоны со списком аргументов. Для обработки этих аргументов нужно определить общий класс или метод, а затем вызывать себя рекурсивно до тех пор, пока не закончатся аргументы. Когда закончатся аргументы должен произойти последний вызов. Напоминает функциональное программирование. Пример сумматора: #include

template<class VALUE, class... ARGS > VALUE sum(VALUE value, ARGS... args) { return value + sum(args...); }

int sum() { return 0; }

int main()

{ std::cout << sum(1, 2, 3, 4, 5) << std::endl; } ARGS - состоит из пар тип + значение, при рекурсивном вызове доходит до нуля.

на этом же принципе построен tuple, только он производит наследование сам от себя рекурсивно. Чем больше аргументов тем больше уровень наследования. так-как большинство типов можно будет вывести для создания tuple используется функция make_tuple, она принимает на вход переменное число аргументов и создает по ним tuple.

  1. Цель стандартных арифметических преобразований типов сводится к получению "наибольшего" типа. Например float * int будет float. Операнды которые по размеру меньше int до исполнения оператора переводятся в int. как правило если на вход оператору предоставляется lvalue, то результат будет тоже lvalue. int x = 0; int y = 0; int p = x = y; int * p1 = &(x++); // результатом будет временный объект, нельзя брать его ссылку. int * p2 = &(x > y ? x : y);

  2. если число становится больше своего максимума, то поведение не определено!!! тоже самое если делить на ноль. при этом программа аварийно завершается, без генерации стандартных исключений.

  3. порядок вычисления подвыражений внутри выражения не определен. int i = 1; v[i] = i++; получистся либо v[1] = 1; v[2] = 2; неизвестно какая часть выражения выполнится раньше только операторы , && || гарантируют что сначала вычислится выражение слева, а потом справа.

  4. std::vector - упакован для экономии памяти, с ним нужно быть аккуратным напрямую доступиться к элементы такого вектора нельзя, для этого есть специальные сылки. но эти ссылки лишь ведут себя похожим образом. поэтому с ним не будут работать стандартные алгоритмы и много чего другого. std::bitset - массив из битов, упакован для экономии памяти

  5. оператор new выделяет памяти чуть больше, как правило больше на одно слово, чтобы знать размер выделенной памяти. если был создан объект, то его нужно удалять через delete, если массив, то через delete[]

  6. операторы new, new[] при неудачном выделении памяти кидают исключение bad_alloc, можно задать обработчик для такой ситуации, типа set_new_handler({ throw std::bad_alloc; }) в этом обработчике можно делать дополнительные действия. Есть возможность переопределить операторы new, new[] таким образом чтобы не было необходимости вызова операторов delete, delete[] (реализовать сборку мусора.)

  7. знает ли компилятор тип на который указывает void* ? нет не знает.

  8. const_cast - убирает const и volatile, очевидно на этапе компиляции. static_cast - на этапе компиляции позволяет делать преобразования арифметических типов например float в int или long в int. или преобразования типов в одной иерархии наследования. reinterpret_cast - адрес в памяти считается выбранного типа, например int в void*, результирующим типом можно корректно пользоваться только если он есть тип которым была инициализирована переменная. dynamic_cast - преобразует полиморфные типы в рантайме, если не получается преобразовать возвращает nullptr (T)e - выражение доставшееся в наследство от С, означает комбинацию static_cast, reinterpret_cast и const_cast - чтобы получить заданный тип T.

struct A { int val() { return val; } int do() { return val++;} int val = 0; }; A a; std::cout << a.val() << a.do() << a.val; Результат вывода не определен, потому что промежуточные значения в выражении могут вычисляться в угодном компилятору порядке.

  1. запись T(e) может быть конструктором, но для арифметических типов она равнозначна записи (T)e, что есть очень печально, потому что это получается уже Сишное приведение типов.

  2. нельзя преобразовать int в указатель: char*(2), но это можно обойти через typedef КАК ЭТО МОЖНО ОБОЙТИ? НАПИСАТЬ ПРИМЕР! для арифметических типов дефолтное значение 0, поэтому можно написать int a = int(); переменная a инициализируется нулем, может быть полезно для использования в шаблонах, где не известно пользовательский тип или арифметический будет передан на вход.

  3. вся инициализация в одном стиле через фигурные скобки! знак =(равно) необязателен int a = {0}; struct A{ int a[3] A(int x, int y, int z):a{x,y,x}()};

  4. rvalue - временные объекты, которые жизненный цикл которых - одно выражение lvalue - все объекты которые живут дольше чем в одном выражении.

  5. можно объявить rvalue как X && x = getX(); причем нельзя передать rvalue в ссылку для lvalue. нужно помнить про операторы перемещения и про функцию std::move, которая перемещает значение из одной переменной в другую X a; X f(); X& r1 = a; // связывает r1 с a (lvalue) X& r2 = f(); // ОШИБКА: rvalue X&& rr1 = f(); // ok: связывает rr1 с временным объектом X&& rr2 = a; // ошибка: a – это lvalue

  6. правило 3-х: сделал кастомный деструктор/конструктор копирования/оператор присваивания, добавь оставшиеся из этих 3-х. суть в том, что если стандартная реализация чего-то одного не подошло, скорее всего реализации остальных тоже не подойдут. С++ 11, повилось правило 5-ти, стоит также добавить конструктор и оператор перемещения.

  7. union - набор данных который был сделан с целью экономии памяти. union хранит в себе несколько типов, но каждый из них начинается с одного и того же адреса в памяти. таким образом записав значение в один из типов, потеряются значения в других. есть проблемы связанные с пользовательскими конструкторами и деструкторами. Возможна ситуация когда несколько конструкторов переопределены и непонятно какой из них вызывать при инициализации union. такая же ситуация с деструктором. в С++ 11 ввели возможность добавлять подобные типы в объединения и в них нужно будет явно вызывать деструктор. ЕЩЕ РАЗ НАЙТИ КАКИЕ ТИПЫ МОЖНО ЗАПИХАТЬ В ЮНИОНЫ!!!

if (auto v = value()) { int a = 100 / v; } else { v += a; // здесь можно использовать v }

  1. Такой код можно написать, внутри switch каждый case - метка int a = 0; int b = 1; switch (value()) { case 0: do { case 1: a = b; case 2: b = a; } while (a != b); }

  2. static переменный инициализируются 1 раз внутри функции, а затем используются повторно сохраняя их последнее значение. компилятор позволяет не инициализировать static переменную, при ее объявлении

  3. В union можно добавлять разные типы, но каждый этот тип будет начинаться с одного и того же места в памяти. Есть проблема если у типов свои конструкторы и деструкторы, в таком случае не понятно как инициализировать тип. В 11-м стандарте если у типа есть пользовательский конструктор или деструктор или оператор присваивания, то данное переопределение удаляется из union. struct A { A(){} }; union { A a; } u; // ошибка так как теперь у union нет конструктора. По понятным причинам у union нельзя добавлять ссылки, полиморфные класс, те что подходят под разряд выше упомянутых.

union можно встроить в структуру и обращаться к нему явно вызывая деструкторы нужных типов.

  1. POD - plain old data, примитивный тип структур в которых данные располагаются попорядку. Такие структуры можно инициализировать через memset и копировать через memcpy. POD - тип содержит в себе только другие POD типы(включая наследование), не содержит виртуальных функций, виртуального наследования, ссылок, спецификаторов доступа. В С++11 объявление конструкторов и функций не влияет на расположение данных в памяти

  2. класс который наследуется от двух полиморфных классов получает 2 указателя на виртуальные таблицы. при создании полиморфного класса его указатель на таблицу виртуальных функций указывает на его собственную таблицу, когда базовый класс создан его указатель начинает указывать на таблицу виртуальных функций класса наследника. Данный пример это иллюстрирует. Будет работать на компиляторах, где указатели на таблицу виртуальных функций лежат в начале объекта. #include #include struct A { A() { const int * vtbl = (const int *)this; std::cout << "A * vtbl = " << *vtbl << "; vtbl = " << vtbl << std::endl; } virtual void DoA() { std::cout << "A::DoA" << std::endl; } }; struct B { B() { const int * vtbl = (const int *)this; std::cout << "B * vtbl = " << *vtbl << "; vtbl = " << vtbl << std::endl; } virtual void DoB() { std::cout << "B::DoB" << std::endl; } }; struct C : A, B { C() { const int * vtbl = (const int *)this; std::cout << "A * vtbl = " << *vtbl << "; vtbl = " << vtbl << std::endl;

    vtbl += 1;
    std::cout << "B * vtbl = " << *vtbl << "; vtbl = " << vtbl << std::endl;
    

    } void DoA() override { std::cout << "C::DoA" << std::endl; } void DoB() override { std::cout << "C::DoB" << std::endl; } }; int main() { C c; A & a = c; B & b = c; c.DoA(); c.DoB(); a.DoA(); b.DoB(); std::cout << sizeof(C) << std::endl; std::cout << sizeof(A) << std::endl; std::cout << sizeof(B) << std::endl; }

  3. вспоминаем про сырые(Raw) строковые литералы std::cout << R"(\s\r\n\)"; // \s\r\n\

  4. вспомним про стандартные типы 123 // int 1.2 // double 1.2F // float 'a' // char 1ULL // unsigned long long 0xD0 // UNSIGNED INT в шестнадцатеричном формате "as" // string

  5. В С++11 можно указать литерал для любого пользовательского типа, но нужно обязательно определить оператор вне класса со специальными входными параметрами, выбрать которые можно из списка: ( const char * ) (1) ( unsigned long long int ) (2) ( long double ) (3) ( char ) (4) ( wchar_t ) (5) ( char16_t ) (6) ( char32_t ) (7) ( const char * , std::size_t ) (8) ( const wchar_t * , std::size_t ) (9) ( const char16_t * , std::size_t ) (10) ( const char32_t * , std::size_t ) (11) std::string operator " _s (const char * str, std::size_t length) { return std::move(std::string(str, length)); } auto str = "Hello!"_s; // str имеет тип std::string

  6. лямбда - функциональный объект, предназначен для передачи простых функций. auto l = {}; // список захвата, аргументы, тело функции в список захвата можно передавать параметры по ссылки или по значению. [&a] [a] [=a] // по ссылке, по значению, по значению [&] [=] // захватить все переменные которые объявлены выше по ссылке или значению лямбды по-умолчанию не могут менять свои внутренние значение по значению, но могут если указать что они mutable auto l = a mutable { return ++a; }; если лямбда не должна модифицировать захваченные объекты, то можно объявить ее как const если лямбда не принимает аргументов, то можно определить ее как auto l = []{};

ЕЩЕ ПРОЧИТАТЬ ПРО ЛЯМБДЫ http://en.cppreference.com/w/cpp/language/lambda если лямбда захватит значение по указателю или ссылке на уже несуществующий объект и будет пытаться их модифицировать, то будет UB.

лямбды с auto могут вести себя следующим образом: void f1(int ()(int)) {} void f2(char ()(int)) {} void h(int ()(int)) {} // #1 void h(char ()(int)) {} // #2 auto glambda = [](auto a) { return a; }; f1(glambda); // ok f2(glambda); // error: not convertible h(glambda); // ok: calls #1 since #2 is not convertible

int& (fpi)(int) = [](auto* a)->auto& { return *a; }; // ok

  1. вспомнить о возможном создании временного объекта при передаче по константной ссылке в функцию. struct B {}; struct A { A(const B &) { std::cout << "A(const B &)";} }; void Do(const A &){} int main(int argc, char *argv[]) { Do(B()); // выведется "A(const B &)" }

  2. При перегрузке функций вызывается та функция, которая полностью подходит по семантике, или которая подходит при помощи преобразований стандартных типов, или при помощи пользовательских преобразований, или при помощи списков аргументов (args ...) Если на каком-то этапе находится больше одной функции, то возникает ошибка выбора функции. void f(char){} void f(long){} f(0); // 0 - int, не понятно куда преобразовать в char или в long

  3. виртуальные методы могут быть перегружены таким образом чтобы возвращать дочерние объекты struct Base {}; struct Certain : Base {};

struct AbstractFactory { virtual ~AbstractFactory() = default; virtual Base * create() const = 0; }; struct Factory : AbstractFactory { Certain * create() const override { return new Certain; } };

int main() { Factory().create(); }

  1. int Do(int*=0) { return 42; } ошибка в том что между * и = нет пробела, интерпретируется как оператор *=

  2. Сишный способ работы с неизвестным числом аргументов #include #include void Do(int val ...) { va_list ap; va_start(ap, val); std::cout << val << std::endl; for (;;) { auto arg = va_arg(ap, int); if (arg == 0) break; std::cout << arg << std::endl; } va_end(ap); } int main() { Do(1, 2, 3, 4, 5, 0); }

  3. 5 способов объявления указателей и ссылок на функции Типы в функциях и в указателях на них ОБЯЗАНЫ СОВПАДАТЬ! #include void Do(int) { std::cout << "Do" << std::endl;} int main() { void (&f1)(int) = Do; void (&f2)(int) = *Do; void (*f3)(int) = Do; void (*f4)(int) = &Do; void (*f5)(int) = *Do; f1(0); f2(0); f3(0); f4(0); f5(0); } void (&f)(int) = &Do; // такой способ запрещен вообще лучше обращаться через указатель а не через ссылку, потому что указатели можно собрать в массив

  4. потренироваться в написании qsort, shared_ptr

  5. при множественном наследовании: проблемы с вызовом одинаковых функций:

  • нужно помнить про вызовы типа a.B::f(); проблемы с ромбовидным наследованием:
  1. расположение таблиц виртуальных функций в памяти

  2. виртуальное наследование

  3. вызовы различных конструкторов базовых функций при ромбовидном наследовании

  4. приведение указателя к разным потомкам и дальнейшие вызовы виртуальных функций.

  5. область видимости -локальная { int i = 0; }

  • область функции (метки) они видны в любом месте фукнции и не видны за ее пределами
  • Область файлов. Любое имя, объявленное за пределами всех блоков или классов. После объявления можно использовать.
  • Область классов, все что объявлено в классе Сокрытие имен:
  • в локальном блоке: { int a = 0; { int a = 1; } }
  • сокрытие имен классов class Account {}; int Account = 0;

доступ можно полчить к классу следующим образом: class Account * p = new class Account;

  • сокрытие имен в области видимости файла: int a = 0; int main() { int a = 1; }

  • сокрытие имен классов при наследовании: struct A {void f(){}} struct B : A {int f;} B().f(); // error!!!

namespace Lib { using namespace Private; // область видимости внутри Lib распространяется теперь и на Private }

  1. модель памяти

  2. псевдоними пространства имен: namespace Lib = Very_Important_Library_v_1_0; Lib::String;

  3. using Lib::F; включает все перегруженные функции F. Полезно чтобы не думать о деталях реализации.

  4. namespace A{ class Set; class Map; } namespace B {class Set; class Map; class List;} namespace C{ using namespace A; using namespace B; using A::Set; using B::Map; // использовать Map из B class List{}; // создать свой List внутри данного namespace }

  5. механизм перегрузки работает через различные области видимости. например в одном файле можно объявить функцию F и в другом. Если заинклюдить оба файла, то перегруженный вызов функции F будет работать. Если аргументы у функций F одинаковые, то через namespace можно разрешить этот конфликт.

  6. namespace A{ int f(); } // A.h int A::f(){return 0;} // A.cpp такая запись лучше, потому что компилятор найдет ошибку в том случае если в объявлении не будет функции f

struct A { void operator()(){} }; int main() { { std::thread t(A()); // error } { std::thread t((A())); // fine, really ?! } }

  1. std::ref в std::thread нельзя передать функцию по ссылке, но можно это сделать через std::ref.

  2. std::move vs A() - нужно посмотреть когда создаются копии а когда перемещение.

  3. std::thread::hardware_concurrency(); // сколько ядер у проца, сколько поток максимум можно создавать если потов больше чем ядер, то эта ситуация называется oversubscription.

  4. struct A { int val; void Do(void Smth(int a)) { Smth(a); } };

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published