Маша С++
104 subscribers
54 photos
2 files
65 links
Учу C++ и не только вместе с вами, мои котятки)
Download Telegram
Добью тему std::bind для закрепления. В примере ниже сортируем вектор строк по длине (кажется, сортировка не стабильная). А затем переиспользуем функцию isShorter, переставив параметры местами с помощью bind, так что у нас получается "обратная" сортировка - по убыванию длины.

#include <iostream>
#include <string>
#include <vector>
#include <functional>
#include <algorithm>

bool isShorter(const std::string& str1, const std::string& str2)
{
return str1.size() < str2.size();
}

int main()
{
std::vector<std::string> v = { "ACGTTAC", "GTTA", "CCATAT" };

std::sort(v.begin(), v.end(), isShorter);

for (const auto& str : v) {
std::cout << str << " "; // GTTA CCATAT ACGTTAC
}
std::cout << std::endl;

std::sort(v.begin(), v.end(),
std::bind(isShorter, std::placeholders::_2, std::placeholders::_1));

for (const auto& str : v) {
std::cout << str << " "; // ACGTTAC CCATAT GTTA
}

return 0;
}
🔥2🏆1
Встроенные пространства имен (inline namespace) неявно вызывают директиву using для вложенных неймспейсов. Когда осуществляется поиск имени, члены встроенного пространства используются вперед, даже если такое имя уже объявлено.

В примере ниже, если скомпилировать с USE_INLINE_B, то выведется 1, иначе 2:

namespace A {
#if USE_INLINE_B
inline
#endif
namespace B {
int foo(bool) { return 1; }
}
int foo(int) { return 2; }
}

int main(void) {
return A::foo(true);
}

Inline namespaces обычно используются для версионности, например в библиотеках (ABI).
Еще пример, в котором доступны outer::inner::foo() и outer::foo():
namespace outer {
inline namespace inner {
void foo();
} // namespace inner
} // namespace outer
👍4
Увидела на реддит список (довольно базовых?) вопросов с одного собеседования на C++ разработчика. Может кому-то будет полезно/интересно.

Основы C++:
* Разница между ссылкой и указателем?
* Разница между выделением памяти на куче и стеке?
* Какие типы умных указателей существуют?
* Как реализован unique_ptr? Как сделать, чтоб у объекта был только один владелец?
* Как работает shared_ptr ? Как синхронизируется подсчет ссылок?
* Как можно скопировать unique_ptr или передать от одного объекта другому?
* Что такое rvalue и lvalue?
* Что такое std::move и std::forward() ?

ООП:
* Способы получить доступ к приватным полям некоторого класса?
* Может ли класс наследовать множество классов?
* Инициализируются ли статические поля в конструкторе класса?
* Может ли исключение быть выброшенным в конструкторе/деструкторе? Как это предотвратить?
* Что такое виртуальный метод?
* Зачем нужен виртуальный деструктор?
* Разница между абстрактным классом и интерфейсом?
* Может ли конструктор быть виртуальным?
* Как ключевое слово const используется для методов класса?
* Как защитить объект от копирования?

STL контейнеры:
* Разница между vector и list?
* Разница между map и unordered_map?
* Когда вызов push_back() инвалидирует итератор vector?
* Как изменить свой класс, чтоб использовать с map/unordered_map?

Потоки:
* Разница между процессом и потоком?
* Может ли поток быть запущен дважды?
* Способы синхронизации потоков?
* Что такое дедлок?

#cpp #собеседования
👍7
Адаптер вставки - адаптер итератора, получающий контейнер и возвращающий итератор, позволяющий вставлять элементы в указанный контейнер.
Адаптер back_inserter создает итератор, использующий функцию push_back, тогда как front_inserter использует push_front. Пример:

#include <iostream>
#include <vector>
#include <list>
#include <algorithm>

int main()
{
std::vector<int> v = { 1, 2, 3, 4, 5 };
std::list<int> list1, list2;

copy(v.cbegin(), v.cend(), std::front_inserter(list1));
copy(v.cbegin(), v.cend(), std::back_inserter(list2));

for (auto i : list1)
std::cout << i << " "; // 5 4 3 2 1

std::cout << std::endl;

for (auto i : list2)
std::cout << i << " "; // 1 2 3 4 5

return 0;
}
👍4
Время жизни объектов в C++ можно разделить на 4 категории:

1. Статическое время жизни
Все время работы приложения, выделяются в начале запуска и освобождаются при завершении. Сюда входят глобальные переменные, статические члены, статические локальные переменные.

2. Потоковое время жизни
Объекты существуют в течение жизни потока, к которому они принадлежат. Создаются при запуске потока и разрушаются при выходе потока. Указываются ключевым словом thread_local:
thread_local int my_var;   // Thread storage duration


3. Автоматическое время жизни
Создаются в момент определения и разрушаются при выходе из области видимости. Более известны как локальные объекты/объекты на стеке. Например локальные нестатические переменные и параметры функции.

4. Динамическое время жизни
Создаются в рантайме с использованием функци выделения памяти как new, malloc и тп. Временем жизни таких объектов нужно управлять вручную.
👍1👏1
Не моя вина, но сегодня у меня день рожденюшко. Сделоть подарок можно денюжным переводом (н-р через TG Wallet).

TON: UQCinAVCbNfOxvOfsyBjtmKrZoQr6t1tQ5A-XLS3XwJanTwr

USDT: TCyFuko2mxtduRc9SgPkZtPWUk7cPz3vZt

BTC:
1Ce8eJmJNDUxHdnqQGfgBVkLzixSBhEsaC
🎉6
Давайте рассмотрим на первый раз простую задачу - итерирование вектора в обратном порядке, то есть от конца к началу. Для начала возьмем просто индекс:
    std::vector<int> v = {1,2,3,4,5};
const std::vector<int>::size_type size = v.size();

int i;
for (i = size - 1; i != -1; --i) {
std::cout << v[i]; // 54321
}
std::cout << std::endl << i << std::endl; // -1

Вроде бы все хорошо, но на моей машине в int максимум поместится 2147483647, тогда как в size_type вектора (обычно size_t) уже 18446744073709551615.
Давайте исправим ситуацию и возьмем size_t в качестве индекса:

    size_t j;
for (j = size - 1; j != -1; --j) {
std::cout << v[j]; // 54321
}
std::cout << std::endl << j << std::endl; // 18446744073709551615


А есть ли способ лучше, через итераторы? Да - реверсивный итератор перебирает контейнер с конца в начало. Для этого доступны rbegin/rend:

    for (auto it = v.rbegin(); it != v.rend(); ++it) {
std::cout << *it;
}

Хочу отметить, что здесь нет ошибки и реверсивный итератор действительно инкрементируется, то есть продвигается наоборот. Почему, расскажу в другой раз😉.
Так как реверсивный итератор перебирает контейнер от последнего элемента к первому, то смысл инкремента инвертирован: ++it переводит итератор на предыдущий элемент, а --it на следующий.

Для получения реверсивного итератора существуют rbegin()/rend(), а также константные аналоги crbegin()/crend(). rbegin указывает на последний элемент контейнера, а rend на элемент перед контейнером, аналогично обычным итераторам, просто наоборот. Изображение пояснит соотношение на векторе.

Возможно, вам покажется неправильным, что ++it идет на предыдущий элемент, но это позволяет использовать все стандартные алгоритмы задом наперед. Например, отсортируем вектор в порядке убывания:
std::sort(v.rbegin(), v.rend())
👍4
Заканчиваем с реверсивными итераторами. Допустим, что нам нужно найти первое и последнее слово в строке. С первым всё понятно, используем std::find с начала:
    std::string str = "One Two Three";
auto space = std::find(str.cbegin(), str.cend(), ' ');
std::cout
<< std::string(str.cbegin(), space) // One
<< std::endl;

Для поиска с конца ищем реверсивно с конца по начало, итератор правильно находится на 8-й позиции, но при получении подстроки слово будет задом наперед:
    auto rspace = std::find(str.crbegin(), str.crend(), ' ');

std::cout
<< str.crend() - rspace // at 8, correct
<< std::endl;

std::cout
<< std::string(str.crbegin(), rspace) // eerhT
<< std::endl;

Почему? Потому что итератор идет в обратном направлении. Для перехода на прямое направление используем вызов base() у реверсивного итератора:
    std::cout
<< std::string(rspace.base(), str.cend()) // Three
<< std::endl;

Обратите внимание, что в паре с base() идет обычный итератор.
1👍1
std::list определяет несколько алгоритмов в качестве членов, например sort(), merge(), reverse(), unique(). Обобщенная версия std::sort (та, которая принимает итераторы) требует итераторы произвольного доступа, которых нет в list, поэтому std::sort нельзя использовать с list. Вместо этого следует вызывать член sort() у самого list.

Давайте ради интереса проверим скорость сортировки вектора и списка из 100 тысяч int-ов:

#include <iostream>
#include <list>
#include <vector>
#include <algorithm>
#include <chrono>

constexpr int sortSize = 100'000;

int main()
{
std::list<int> list;
for (int i = sortSize; i > 0 ; --i) {
list.push_back(i);
}

auto begin1 = std::chrono::steady_clock::now();

//std::sort(list.begin(), list.end()); // не скомпилится
list.sort(); // скомпилится

auto end1 = std::chrono::steady_clock::now();

std::vector<int> vector;
vector.resize(sortSize);
for (int i = sortSize; i > 0; --i) {
vector[sortSize - i] = i;
}

auto begin2 = std::chrono::steady_clock::now();

std::sort(vector.begin(), vector.end());

auto end2 = std::chrono::steady_clock::now();

auto it_l = list.begin();
for (size_t i = 0; i < 10; ++i) {
std::cout << *it_l++ << " "; // 1 2 3 4 5 6 7 8 9 10
}

std::cout << std::endl;

auto it_v = vector.begin();
for (size_t i = 0; i < 10; ++i) {
std::cout << *it_v++ << " "; // 1 2 3 4 5 6 7 8 9 10
}

std::cout << std::endl;

std::cout << "list: "
<< std::chrono::duration_cast<std::chrono::microseconds>(end1 - begin1).count()
<< " mcs"
<< std::endl;

std::cout << "vector: "
<< std::chrono::duration_cast<std::chrono::microseconds>(end2 - begin2).count()
<< " mcs"
<< std::endl;

// list: 1717 mcs
// vector : 958 mcs

return 0;
}

На MSVC на моей тачке (x86_64) получилось, что сортировка списка в 1.8 раза медленней сортировки вектора (если я нигде не обосралась). А почему так, думаю догадаются многие 😁.
Начиная с C++17 if может быть с инициализатором`if (init; condition)` (подобно for). Переменная доступна только внутри if :

#include <iostream>
#include <cstdlib>
#include <vector>

int main()
{
std::vector<int> v = { 1, 2, 3, 4, 5, 6 };

// C++17 if statement with initializer
// if (init; condition) {
if (auto it = std::find(v.begin(), v.end(), 5); it != v.end()) {
std::cout << *it << std::endl;
}

srand(time(NULL));

if (int random = rand(); random % 2 == 0) {
std::cout << random << " is an even number" << std::endl;
} else {
std::cout << random << " is an odd number" << std::endl;
}

return 0;
}

Во втором примере показано, что переменная также доступна в else.

#cpp17
👍5🤔1
Классический пример использования ассоциативного контейнера std::map - подсчет числа слов в строке.
#include <iostream>
#include <map>
#include <string>
#include <sstream>
#include <iomanip>

int main()
{
std::string str =
"the only person with whom you have to"
" compare yourself is you in the past";

std::stringstream stream(str);

// счетчик числа слов: string - ключ, size_t - значение
std::map<std::string, size_t> wordCount;

std::string word;
while (stream >> word) { // разделяем слова по пробелам
++wordCount[word]; // добавляем слово или берем уже сущ.
}

std::cout << wordCount["the"] << std::endl; // 2

for (auto w : wordCount) { // w - pair
std::cout << std::setw(10) << w.first << ": "
<< w.second << std::endl;
}

// with: 1
// you: 2
// ...

return 0;
}

Для ассоциативного контейнера нужно задать типы ключа (строка - слово) и значения (число слов).
Вызов wordCount[word] добавит слово в мапу со значением 0, если такого слова нет.
++wordCount[word] увеличит значение в мапе на 1.
wordCount["the"] вернет количество the.

При итерации по словарю будет возвращен std::pair, гдe first - это ключ, а second - значение.
👍1
На собеседованиях по алгоритмам часто спрашивают, как обратить список. Ответ: вызывать метод reverse() 😆
#include <iostream>
#include <list>

int main()
{
std::list<int> l;
for (int i = 0; i < 5; ++i) l.push_back(i);

// 0 1 2 3 4
for (int i : l) std::cout << i << " ";
std::cout << std::endl;

l.reverse();

// 4 3 2 1 0
for (int i : l) std::cout << i << " ";
std::cout << std::endl;

return 0;
}

#юмор
А давайте теперь сравним время работы (MSVC 64 bit) обобщенной версии remove_if и встроенной в std::list на примере удаления всех четных элементов из списка на 100 тысяч int-ов:

#include <iostream>
#include <list>
#include <vector>
#include <algorithm>
#include <chrono>

int main()
{
std::list<int> list;
for (int i = 0; i < 100'000; ++i) {
list.push_back(i);
}

// сделаем копии
std::list<int> list_copy1 = list;
std::list<int> list_copy2 = list;

auto begin1 = std::chrono::steady_clock::now();

std::remove_if(
list_copy1.begin(),
list_copy1.end(),
[](int n) { return n % 2 == 0; });

auto end1 = std::chrono::steady_clock::now();

auto begin2 = std::chrono::steady_clock::now();

list_copy2.remove_if([](int n) { return n % 2 == 0; });

auto end2 = std::chrono::steady_clock::now();

auto it1 = list_copy1.begin();
for (size_t i = 0; i < 10; ++i) {
std::cout << *it1++ << " "; // 1 3 5 7 9 11 13 15 17 19
}

std::cout << std::endl;

auto it2 = list_copy2.begin();
for (size_t i = 0; i < 10; ++i) {
std::cout << *it2++ << " "; // 1 3 5 7 9 11 13 15 17 19
}

std::cout << std::endl;

std::cout << "1: "
<< std::chrono::duration_cast<std::chrono::microseconds>(end1 - begin1).count()
<< " mcs"
<< std::endl;

std::cout << "2: "
<< std::chrono::duration_cast<std::chrono::microseconds>(end2 - begin2).count()
<< " mcs"
<< std::endl;

// 1: 710 mcs
// 2: 1631 mcs

return 0;
}

Обобщенный в 2 раза быстрее, что?😱
std::set - это ассоциативный контейнер, который хранит отсортированный набор уникальных объектов (то есть отсортированное множество).

Для проверки наличия элемента во множестве предназначен метод find(), который вернет end(), если такого элемента нет.

Однако начиная с c++20 доступен сахар в виде метода contains(), который более читаем:
#include <iostream>
#include <set>

int main()
{
std::set<int> setOfInts{ 1, 2, 3, 4, 5};

if (setOfInts.find(7) == setOfInts.end()) {
std::cout << "Set doesn't contain 7" << std::endl;
}

// Начиная с C++20:
if (setOfInts.contains(3)) {
std::cout << "Set contains 3" << std::endl;
} else {
std::cout << "Set doesn't contain 3" << std::endl;
}

return 0;
}

Оба метода — log(N)
#cpp20
std::set хранит ключи в отсортированном виде. Сортируются сами значения, а не порядок их добавления! Ниже показаны два примера с целочисленными и строковыми множествами.
std::unordered_set не хранит ключи в каком-то определенном порядке, разницу с std::set можно увидеть ниже (результат может отличаться).
#include <iostream>
#include <set>
#include <unordered_set>
#include <vector>
#include <string>

void print_int_set()
{
std::set<int> s{ 5, 3, 1, 4, 2, 1 };

s.insert(0);

for (auto i : s) { // 0 1 2 3 4 5
std::cout << i << " ";
}
std::cout << std::endl;
}

void print_str_set()
{
std::vector<std::string> v{ "b", "c", "a" };
std::set<std::string> sv(v.cbegin(), v.cend());

for (auto is : sv) { // a b c
std::cout << is << " ";
}
std::cout << std::endl;
}

void print_unordered_int_set()
{
std::unordered_set<int> us{ 5, 3, 1, 4, 2, 1 };
us.insert(0);

for (auto i : us) { // 5 3 1 4 2 0 - may differ!
std::cout << i << " ";
}
std::cout << std::endl;
}

int main()
{
print_int_set();
print_str_set();
print_unordered_int_set();

return 0;
}
Структурное связывавание (structured binding) - начания с C++17 позволяет распаковать сложные типы данных. Рассмотрим пример получения значений переменных кортежа (tuple) без использования std::tie:
#include <iostream>
#include <tuple>

std::tuple<char, int, bool> getTuple()
{
return std::make_tuple('a', 777, true);
}

int main()
{
auto [a, i, b] = getTuple();

std::cout << a << " " << i << " " << b; // a 777 1

return 0;
}


Кстати, определение функции можно также сократить через auto:
auto getTuple2()
{
return std::make_tuple('b', 123, false);
}

#cpp17
👍2