Проблема:
// math.h
double cos(double x);
// ваш код
double cos(double x);
// ваш код
double fastCos(double x);
namespace fast
{
double cos(double x);
}
fast::cos(x);
cos(x); // вызов из math.h
- Проверка в текущем namespace
- Если имени нет и текущий namespace глобальный - ошибка
- Рекурсивно поиск в родительском namespace
void foo() {} // ::foo
namespace A
{
void foo() {} // A::foo
namespace B
{
void bar() // A::B::foo
{
foo(); // A::foo
::foo(); // foo()
}
}
}
Добавляет имена из указанного namespace в текущий namespace.
void foo()
{
using namespace A;
// видимо все из A
}
void foo()
{
using namespace A::foo;
// видима только A::foo()
}
void foo()
{
namespace ab = A::B;
ab::bar(); // A::B::bar()
}
using namespace fast;
cos(x); // ???
cos(x); // ???
Не используйте using namespace в заголовочных файлах!
int rollDice()
{
return 4;
}
int square(int x)
{
int tmp = x * x;
return tmp;
}
square(int):
; Пролог
push ebp
mov ebp, esp
; Выделение памяти
sub esp, 16
; Тело
mov eax, DWORD PTR [ebp+8]
imul eax, eax
mov DWORD PTR [ebp-4], eax
; Возврат результата
mov eax, DWORD PTR [ebp-4]
; Эпилог
mov esp, ebp
pop ebp
ret
Исторически принятое соглашение для языка С.
Аргументы функций передаются через стек, справа налево. Аргументы, размер которых меньше 4-х байт, расширяются до 4-х байт. Очистку стека производит вызывающая программа. integer-like результат возвращается через регистр EAX.
Перед вызовом функции вставляется код, называемый прологом (prolog) и выполняющий следующие действия:
- сохранение значений регистров, используемых внутри функции
- запись в стек аргументов функции
После вызова функции вставляется код, называемый эпилогом (epilog) и выполняющий следующие действия:
- восстановление значений регистров, сохранённых кодом пролога
- очистка стека (от локальных переменных функции)
Соглашение о вызовах, используемое компиляторами для языка C++ при вызове методов классов.
Отличается от cdecl соглашения только тем, что указатель на объект, для которого вызывается метод (указатель this), записывается в регистр ecx.
Передача параметров через регистры: если для сохранения всех параметров и промежуточных результатов регистров не достаточно, используется стек (в gcc через регистры ecx и edx передаются первые 2 параметра).
[[gnu::fastcall]]
void foo1(int x, int y, int z, int a)
{
}
void foo2(int x, int y, int z, int a)
{
}
void bar1()
{
foo1(1, 2, 3, 4);
}
void bar2()
{
foo2(5, 6, 7, 8);
}
g++ -c test.cpp -o test.o -O0 -m32
objdump -d test.o
000005c8 <_Z4bar1v>:
5c8: 6a 04 push $0x4
5ca: 6a 03 push $0x3
5cc: ba 02 00 00 00 mov $0x2,%edx
5d1: b9 01 00 00 00 mov $0x1,%ecx
5d6: e8 b5 ff ff ff call 590 <_Z4foo1iiii>
5dd: c3 ret
000005eb <_Z4bar2v>:
5eb: 6a 08 push $0x8
5ed: 6a 07 push $0x7
5ef: 6a 06 push $0x6
5f1: 6a 05 push $0x5
5f3: e8 b3 ff ff ff call 5ab <_Z4foo2iiii>
5fd: c3 ret
- 6 регистров (RDI, RSI, RDX, RCX, R8, R9) для передачи integer-like аргументов
- 8 регистров (XMM0-XMM7) для передачи double/float
- если аргументов больше, они передаются через стек
- для возврата integer-like значений используются RAX и RDX (64 бит + 64 бит)
Иногда вызова функции не будет - оптимизирующий компилятор выполнит ее встраивание по месту вызова.
Можно подсказать компилятору выполнить встраивание:
inline void foo()
{
}
Но, компилятор умный и скорее всего проигнорирует inline, но можно попросить настойчивей:
// ms vc
__forceinline void foo()
{
}
// gcc
__attribute__((always_inline)) void foo()
{
}
Все равно нет гарантий.
#ifdef __GNUC__
#define __forceinline __attribute__((always_inline))
#endif
Ссылка - псевдоним объекта.
Главное отличие от указателя - ссылка должна быть проинициализирована при объявлении и до конца своей жизни ссылается только на один объект.
int a = 1;
int b = 2;
int* ptr = nullptr;
ptr = &a;
ptr = &b;
int& ref; // Ошибка
int& ref = a; // ref ссылается на a
ref = 5; // Теперь a == 5
ref = b; // ref не стала указывать на b,
// мы просто скопировали значение из b в a
ref = 7; // a == 7, b == 2
int a = 2;
int* ptr = nullptr;
int*& ptrRef = ptr; // ptrRef ссылается на ptr
ptrRef = &a; // теперь ptr хранит адрес a
void foo(int x)
{
x = 3;
}
int x = 1;
foo(x);
// x == 1
void bar(BigObject o) { ... }
- В функции окажется копия объекта, ее изменение не отразится на оригинальном объекте
- Копировать большие объекты может оказаться накладно
void foo(int& x)
{
x = 3;
}
int x = 1;
foo(x);
// x == 3
void bar(BigObject& o) { ... }
- Копирования не происходит, все изменения объекта внутри функции отражаются на объекте
- Следует использовать, если надо изменить объект внутри функции
void swap(int& x, int& y)
{
int tmp = x;
x = y;
y = tmp;
}
void foo(const int& x)
{
x = 3; // ошибка компиляции
}
void bar(const BigObject& o) { ... }
- Копирования не происходит, при попытке изменения объекта будет ошибка
- Большие объекты выгодней передавать по ссылке, маленькие - наоборот
void foo(int* x)
{
*x = 3;
}
void bar(BigObject* o) { ... }
void foo(const int* x)
{
*x = 3; // ошибка компиляции
}
void bar(const BigObject* o) { ... }
- Копирования не происходит
- Если указатель на константный объект, то при попытке изменения объекта будет ошибка
- Есть дополнительный уровень косвенности, возможно придется что-то подгрузить в кеш из дальнего участка памяти
- Реализуется optional-концепция
int countObject(time_t* fromDate, time_t* toDate)
{
const auto begin =
fromDate == nullptr
? objects_.begin()
: objects_.findFirst(fromDate);
}
void foo(int&& x) { ... }
void bar(BigObject&& o) { ... }
Поговорим в отдельной лекции.
void print(int x) // 1
{
std::cout << x << std::endl;
}
void print(bool x) // 2
{
std::cout << (x ? "true" : "false") << std::endl;
}
print(10); // 1
print(true); // 2
void print(const std::string& x) // 3
{
std::cout << "string" << std::endl;
}
print("hello!"); // 2 const char* приводится к bool
Перегруженная функция - декорированная функция
void foo(int x)
{
}
typedef void (*FooPtr)(int);
FooPtr ptr = foo;
ptr(5);
using FooPtr = void(*)(int);
Функция высшего порядка — функция, принимающая в качестве аргументов другие функции или возвращающая другую функцию в качестве результата.
void sort(int* data, size_t size, bool (*compare)(int x, int y));
bool less(int x, int y)
{
return x < y;
}
sort(data, 100, less);
using OnMailReceived = void (*)(const Mail& newMail);
void addOnMailReceivedObserver(OnMailReceived handler);
void onMailReceivedHandler(const Mail& newMail)
{
...
}
addOnMailReceivedObserver(onMailReceivedHandler);
using MoveFunctionPtr = void (*)(int& x, int& y);
void moveLeft(int& x, int& y) { ... }
void moveRight(int& x, int& y) { ... }
std::vector<MoveFunctionPtr> trajectory =
{
moveLeft,
moveLeft,
moveRight,
};
int x = 0;
int y = 0;
for (auto& func : trajectory)
{
func(x, y);
}
auto lessThen3 = [](int x) { return x < 3; };
if (lessThen3(x)) { ... }
[список_захвата](список_параметров) { тело_функции }
[список_захвата](список_параметров) -> тип_возвращаемого_значения
{ тело_функции }
int x = 5;
int y = 7;
auto foo = [x, &y]() { y = 2 * x };
foo();
Если не указать &, то будет захват по значению, то есть копирование объекта; если указать, то по ссылке (нет копирования, можификации внутри функции отразяться на оригинальном объекте).
// Захват всех переменных в области видимости по значению
auto foo = [=]() {};
// Захват всех переменных в области видимости по ссылке
auto foo = [&]() {};
Использование переменных, определённых в той же области видимости, что и лямбда-функция, называют замыканием.
[] // без захвата переменных из внешней области видимости
[=] // все переменные захватываются по значению
[&] // все переменные захватываются по ссылке
[x, y] // захват x и y по значению
[&x, &y] // захват x и y по ссылке
[in, &out] // захват in по значению, а out — по ссылке
[=, &out1, &out2] // захват всех переменных по значению,
// кроме out1 и out2, которые захватываются по ссылке
[&, x, &y] // захват всех переменных по ссылке, кроме x,
// которая захватывается по значению
int x = 3;
auto foo = [x]() mutable
{
x += 3;
...
}
#include <functional>
using MoveFunction =
std::function<void (int& x, int& y)>;
MoveFunction getRandomDirection() { ... }
std::vector<MoveFunction> trajectory =
{
moveLeft,
moveLeft,
moveRight,
[](int& x, int& y)
{
...
},
moveLeft,
getRandomDirection()
};
int x = 0;
int y = 0;
for (auto& func : trajectory)
{
func(x, y);
}
Информация о пользователе:
- Имя
std::string name;
std::string email;
Для упрощения программы, логически связанные данные можно объединить.
struct User
{
std::string name;
std::string email;
};
const User user =
{ "Bob", "bob@mail.ru" };
std::cout << user.name;
name, email - поля структуры
User users[N];
struct Users
{
std::string name[N];
std::string email[N];
};
struct A
{
public:
int x; // Доступно всем
protected:
int y; // Наследникам и объектам класса
private:
int z; // Только объектам класса
};
A a;
a.x = 1; // ок
a.y = 1; // ошибка
a.z = 1; // ошибка
Объект - сущность в адресном пространстве компьютера, появляющаяся при создании класса.
В С++ struct от class отличаются только модификатором доступа по умолчанию. По умолчанию содержимое struct доступно извне (public), а содержимое class - нет (private).
class A
{
int x; // private
};
struct B
{
int x; // public
}
struct User
{
void serialize(Stream& out)
{
out.write(name);
out.write(email);
}
private:
std::string name;
std::string email;
};
serialize - метод класса
Методы класса, доступные для использования другими классами, представляют его интерфейс
struct File
{
int descriptor;
char buffer[BufferSize];
};
File* openFile(const char* fileName)
{
File* file = (File*) malloc(sizeof(File));
file->descriptor = open(fileName, O_CREAT);
return file;
}
void write(File* file, const char* data, size_t size)
{
...
}
void close(File* file)
{
close(file->descriptor);
free(file);
}
File* file = openFile("some_file.dat");
write(file, data, size);
close(file);
class File
{
public:
File(const char* fileName)
{
descriptor = open(fileName, O_CREAT);
}
void write(const char* data, size_t size)
{
...
}
~File()
{
close(descriptor);
}
private:
int descriptor;
char buffer[BufferSize];
};
File file("some_file.dat");
file.write(data, size);
struct A
{
void foo(); // _ZN1A3fooEv
};
void bar(); // _Z3barv
void write([File* this], const char* data, size_t size)
{
this->descriptor ...
}
Метод класса - обычная функция, которая неявно получает указатель на объект класса (this)
struct A
{
void foo() { std::cout << "ok"; }
void bar() { std::cout << x; }
int x;
};
A* a = nullptr;
a->foo(); // Ок
a->bar(); // Разыменование нулевого указателя
void foo([A* this])
{
std::cout << "ok";
}
void bar([A* this])
{
std::cout << [this->]x;
}
Написать библиотеку-парсер строк состоящих из следующих токенов:
- строка
- число
Число состоит из символов от 0 до 9, строка - все остальные символы. Токены разделены пробелами, символами табуляции и первода строки.
Пользователь библиотеки должен иметь возможность зарегистрировать колбек-функцию вызываемую каждый раз, когда найден токен, функция получает токен. Должно быть возможно зарегистрировать свой обработчик под каждый тип токена. Также должны быть колбеки вызываемые перед началом парсинга и по его окончанию.
EOF