Маша С++
104 subscribers
54 photos
2 files
65 links
Учу C++ и не только вместе с вами, мои котятки)
Download Telegram
На первый взгляд, разница в двух лямбдах ниже небольшая, во второй просто захватываем аргумент по ссылке:
    int i = 100;
auto f1 = [ i](){ return i; };
auto f2 = [&i](){ return i; };
i = 0;

std::cout << f1() << " " << f2() << std::endl;

Однако же выведутся разные значения: 100 0, так как первая лямбда скопирует i в момент создания, а не вызова. Вторая тоже "скопирует" ссылку в момент создания, но значение ссылки получит во время вызова.

Если же мы захотим изменить значение i, то в первую лямбду нужно добавить mutable, а для второй это делать необязательно:
    auto f1 = [ i]() mutable { return ++i; };
auto f2 = [&i]() { return ++i; };


#cpp #lambda
🔥4
Только что подумала, что пустую лямбду достаточно удобно набирать, так как клавиши расположены рядом и даже Shift надо один раз удерживать: [](){}

Чтоб пост не был таким коротким, пример подсчета числа элементов, удовлетворящих предикату:
    std::vector<std::string> v = {"abc", "a", "ab", "c", "sd", "sjc"};

std::cout << std::count_if(v.begin(), v.end(),
[](const std::string &str)
{ return str.size() == 3; }); // 2
Допустим у нас имеется некая функция, скажем isClose с двумя параметрами, и нам нужно использовать ее для некоего алгоритма, скажем find_if, который ожидает вызываемый объект с одним параметром. Альтернативой лямбде может стать использование std::bind:
#include <functional>

size_t distance(const std::string& str1, const std::string& str2)
{
// TODO: Левенштейн
return str1.size() - str2.size();
}

bool isClose(const std::string& str1, size_t n)
{
return distance(str1, "ATGA") <= n;
}

int main()
{
size_t maxDistance = 1;
auto isCloseToOne = std::bind(isClose, std::placeholders::_1, maxDistance);
maxDistance = -1; // не влияет на копию
std::cout << isCloseToOne("CACAC") << std::endl; // 1

std::vector<std::string> v = { "ACGTTA", "GTTA", "CCATAT" };

auto it = std::find_if(
v.cbegin(), v.cend(),
// isClose); // ошибка: too few arguments
isCloseToOne); // один аргумент

if (it != v.cend()) std::cout << *it; // GTTA

return 0;
}


bind получает вызываемый объект и создает новый вызываемый объект, адаптируя список параметров исходного. В данном случае, создастся isCloseToOne, который будет вызывать isClose, прокидывая первый аргумент (placeholders::_1) в isClose, а вторым аргументом будет скопированное значение maxDistance.
3👍1
std::bind можно использовать для перестановки аргументов. Допустим, мы имеем некую функцию func с шестью аргументами. Сделаем новый вызываемый объект, так что его первый аргумент _1 станет пятым, а второй _2 первым и шестым:
#include <functional>

void func(
const std::string& s1, // 1 => _2
int i, // 2
float f, // 3
double d, // 4
char c, // 5 => _1
const std::string& s2) // 6 => _2
{
std::cout << s1 << i << f << d << c << s2 << std::endl;
}

int main()
{
using namespace std::placeholders;

auto func2 = std::bind(func, _2, 1, 2.0f, 3.0, _1, _2);

func2('a', "___"); // ___123a___

func("___", 1, 2.0f, 3.0, 'a', "___"); // ___123a___

return 0;
}

Вызовы func2 и func приведут к одинаковым результатам. Знакоместа _1 и _2 займут указанные позиции в func. Как видим, мы можем менять не только порядок, но и повторять аргументы.
👍3🤯31
std::string::find возврашает позицию найденной подстроки. Если такой строки нет, то вернется std::string::npos, что видимо означает "no pos".
Причина, по которой это поле не назвали nopos, дабы повысить читаемость за счет +1 символа, далека от моего понимания.

Интересно, что беззнаковому npos присваивается знаковое -1:
cpp static constexpr auto npos{static_cast<size_type>(-1)};

Такой каст приводит к получению максимального значения для этого типа. На x86-64 у меня получилось 18446744073709551615. Давайте проверим:
#include <iostream>
#include <string>

int main()
{
std::string str("abc");
auto pos = str.find("c");
if (pos != str.npos) {
std::cout << "found at: " << pos << std::endl;
}

std::cout << std::numeric_limits<std::string::size_type>::max() << std::endl; // 18446744073709551615
std::cout << std::string::npos << std::endl; // 18446744073709551615
std::cout << static_cast<size_t>(-1) << std::endl; // 18446744073709551615

return 0;
}

Раз так можно в std, то можно и нам пихать -1 в size_t 😁
#cpp #string
🔥2
Добью тему 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;
}

#юмор