Как все мы знаем, protected используется, чтоб предоставить наследникам доступ к защищенным членам, но закрыть от общего доступа. Но всегда ли производный класс может обратиться к protected членам? Давайте посмотрим на код ниже:
В первой реализации функции f() класс-наследник Derived спокойно может обратиться к защищенному и унаследованному полю _protMember через ссылку на Derived.
А вот во втором методе f() доступ к тому же полю через ссылку на Base уже не доступен - код не скомпилится.
Таким образом, производный член класса (или его friend) может обратиться к защищенным членам базового класса только через объект производного.
class Base
{
protected:
int _protMember;
};
class Derived : public Base
{
private:
void f(Derived& d);
void f(Base& b);
int _privMember;
};
void Derived::f(Derived& d)
{
d._privMember = 0;
d._protMember = 0; // OK
}
void Derived::f(Base& b)
{
b._protMember = 0; // Не скомпилится
}
В первой реализации функции f() класс-наследник Derived спокойно может обратиться к защищенному и унаследованному полю _protMember через ссылку на Derived.
А вот во втором методе f() доступ к тому же полю через ссылку на Base уже не доступен - код не скомпилится.
Таким образом, производный член класса (или его friend) может обратиться к защищенным членам базового класса только через объект производного.
Немного про закрытое (private) наследование.
Спецификатор доступа наследования никак не влияет на возможность производного класса обратиться к членам его собственного прямого базового класса. Приватное наследование на запретит доступ непосредственным (!) детям к protected, любое наследование не разрешит доступ к private. Класс сам определяет права доступа наследникам:
Это все очевидно, но интересен вопрос, а что если добавить в цепочку наследования еще классов с разными доступами наследования:
Как видим, protected так и остался доступен для второго уровня наследника при public наследовании, а вот исправить public наследованием от private наследования уже нельзя - protected член стал безвозвратно недоступным из-за первого private наследования.
Спецификатор доступа наследования никак не влияет на возможность производного класса обратиться к членам его собственного прямого базового класса. Приватное наследование на запретит доступ непосредственным (!) детям к protected, любое наследование не разрешит доступ к private. Класс сам определяет права доступа наследникам:
class Base
{
protected:
int _protMember;
};
class PublicDerived : public Base
{
public:
int f() { return _protMember; } // OK
};
class PrivateDerived : private Base
{
public:
int f() { return _protMember; } // OK
};
Это все очевидно, но интересен вопрос, а что если добавить в цепочку наследования еще классов с разными доступами наследования:
class DerivedFromPublic : public PublicDerived
{
public:
int f2() { return _protMember; } // OK
};
class DerivedFromPrivate : public PrivateDerived
{
public:
int f2() { return _protMember; } // NOT OK
};
Как видим, protected так и остался доступен для второго уровня наследника при public наследовании, а вот исправить public наследованием от private наследования уже нельзя - protected член стал безвозвратно недоступным из-за первого private наследования.
Член производного класса с тем же именем, что и в базовом, скрывает член базового класса (скрывает к нему доступ).
При этом важно, что "скрытие" не означает замену. Член базового класса продолжает существовать (см sizeof), а для доступа к члену базового класса можно использовать оператор области видимости (Base::).
#include <iostream>
struct Base
{
Base(int i) : _protMember(i) {}
int get() const { return _protMember; }
protected:
int _protMember;
};
struct Derived : public Base
{
Derived(int i) : _protMember(i), Base(0) {}
int get() const { return _protMember; }
int getBase() const { return Base::_protMember; }
protected:
int _protMember;
};
struct Derived2 : public Base
{
Derived2(int i) : Base(i) {}
};
int main()
{
Derived d(42);
std::cout << d.get() << " " << d.getBase() << std::endl; // 42 0
std::cout << sizeof(Derived) << " " << sizeof(Derived2) << std::endl; // 8 4
return 0;
}
При этом важно, что "скрытие" не означает замену. Член базового класса продолжает существовать (см sizeof), а для доступа к члену базового класса можно использовать оператор области видимости (Base::).
👍2
В С++17 в std::map добавили метод try_emplace, который похож на emplace, но он создает элемент, только если такого ключа еще нет (есть еще другие особенности). Обычный emplace создает элемент, даже если вставка не произойдет. Таким образом, try_emplace позволяет ускорить код, если создание объекта тяжелое. Пример:
#include <iostream>
#include <map>
struct Value
{
Value(int i = 0)
{
std::cout << "Value " << i << std::endl;
}
Value(const Value& other)
{
std::cout << "Value copy ctor" << std::endl;
}
Value& operator=(const Value& other)
{
std::cout << "Value operator=" << std::endl;
return *this;
}
};
struct Key
{
Key(int i = 0) : _i(i)
{
std::cout << "Key " << i << std::endl;
}
bool operator<(const Key& other) const
{
return _i < other._i;
}
int _i;
};
int main()
{
// Key 1
// Value 1
// Value copy ctor
std::map<Key, Value> map = {
{1, 1}
};
std::cout << std::endl;
// Value 2
// Key 2
// Value 0 - вызвался дефолтный конструктор, он требуется для operator[]
// Value operator=
map[2] = 2;
std::cout << std::endl;
// Key 3
// Value 3
// Value copy ctor
map.insert({3, 3});
std::cout << std::endl;
// Key 4
// Value 4 - экономим один вызов конструктора!
map.emplace(4, 4);
std::cout << std::endl;
// А теперь добавим элемент, который уже есть
// Key 3
// Value 3 - элемент уже есть, но мы его все равно создадим
map.emplace(3, 3);
std::cout << std::endl;
// Key 3 - элемент создастся, только если такого еще нет
const auto& [it, inserted] = map.try_emplace(3, 3);
// Not inserted
if (!inserted) std::cout << "Not inserted" << std::endl;
return 0;
}
👍7
Как определить, начинается ли строка с подстроки?
Если у вас есть C++20, то используем
Если же его нет, то можно использовать compare, который возвращает 0 если (под) строки равны:
Менее читаемое, чем первый вариант, но как-будто бы терпимо.
Если же вы хотите напрячь мозги своего коллеги-джуна, то используйте std::string::rfind, передав 0 в качестве позиции:
Давайте прочитаем доку: rfind ищет *последнюю* подстроку, которая совпадает с переданной последовательностью символов. Когда передан параметр pos (второй), то поиск включает символы которые начинаются в или до позиции pos, игнорируя все возможные совпадения начинающиеся после pos. Таким образом, передав 0 как второй аргумент, мы ищем строку, которая начинается не позже 0, то есть в начале. Обычный же find продолжит искать подстроки далее начала.
Если у вас есть C++20, то используем
starts_with и радуемся краткому и понятному коду.Если же его нет, то можно использовать compare, который возвращает 0 если (под) строки равны:
std::string prefix("Who");
if (str.compare(0, prefix.size(), prefix) == 0)Менее читаемое, чем первый вариант, но как-будто бы терпимо.
Если же вы хотите напрячь мозги своего коллеги-джуна, то используйте std::string::rfind, передав 0 в качестве позиции:
std::string str = "Who let the dogs out";
if (s.rfind("Who", 0) == 0) {
Давайте прочитаем доку: rfind ищет *последнюю* подстроку, которая совпадает с переданной последовательностью символов. Когда передан параметр pos (второй), то поиск включает символы которые начинаются в или до позиции pos, игнорируя все возможные совпадения начинающиеся после pos. Таким образом, передав 0 как второй аргумент, мы ищем строку, которая начинается не позже 0, то есть в начале. Обычный же find продолжит искать подстроки далее начала.
👀3
Рассмотрим следующий код, в котором определен конструктор копирования для производного класса:
Как видим, мы ожидали вывод
Так будет
Важно понимать, что конструктор копирования базового класса скопирует только члены базового класса, иначе говоря, если написать
То же самое верно и для конструктора перемещения наследника:
#include <iostream>
class Base
{
public:
Base() : bValue(3) {}
int bValue;
};
class Derived : public Base
{
public:
Derived() = default;
Derived(const Derived& other) : dValue(other.dValue) {}
int dValue = 0;
};
int main() {
Derived d1;
d1.dValue = 4;
d1.bValue = 7;
Derived d2(d1); // делаем копию
std::cout << d2.dValue << " " << d2.bValue << std::endl; // 4 3
}
Как видим, мы ожидали вывод
4 7, а получили 4 3, то есть конструктор копирования не скопировал значение из базового класса, а вызвал его конструктор по-умолчанию. Это ожидаемое поведение, ведь мы "забыли" *явно* вызвать базовый конструктор копирования. Исправим ситуацию:Derived(const Derived& other) : Base(other), dValue(other.dValue) {}Так будет
4 7Важно понимать, что конструктор копирования базового класса скопирует только члены базового класса, иначе говоря, если написать
Derived(const Derived& other) : Base(other) {}, то вывод будет неверным: 0 7То же самое верно и для конструктора перемещения наследника:
Derived (Derived && d) : Base (std::move(d)) {/* init Derived vars */}👍4👀3
Если не указать атрибут наследования, то для производного класса объявленного как class будет приватное наследование, а для struct - публичное:
#include <iostream>
#include <map>
class Base
{
public:
void f() {}
};
class Derived1 : Base // не указан атрибут наследования
{
};
struct Derived2 : Base // не указан атрибут наследования
{
};
int main()
{
Derived1 d1;
Derived2 d2;
d1.f(); // Ошибка, private inheritance
d2.f(); // OK, public inheritance
return 0;
}
🫡6
Начиная с С++11 параметр типа шаблона можно сделать дружественным, пример:
Класс Bob стал другом класса Alice<Bob> и получил доступ до его приватных членов.
#include <iostream>
template <typename T>
class Alice
{
friend T; // не friend class T
int priv_int = 4;
};
class Bob
{
public:
void print(const Alice<Bob>& alice) {
std::cout << alice.priv_int // Bob is friend of Alice
<< std::endl;
}
};
int main()
{
Alice<Bob> a;
Bob b;
b.print(a); // 4
return 0;
}
Класс Bob стал другом класса Alice<Bob> и получил доступ до его приватных членов.
🔥8
Можно ли вызывать виртуальные функции из конструкторов и деструкторов? Можно, но поведение может быть неожиданным (и такого лучше избегать). Пример:
Как видим при вызове конструтора производного класса Derived сперва вызовется конструктор базового. При этом пока выполняется конструктор базового класса, производная часть объекта остается неинициализированной. Поэтому конструктору базового класса при вызове виртуального метода было бы опасно вызывать переопределенную версию производного, так как такой метод мог бы обратиться к объектам производного класса. Поэтому C++ при создании (и удалении) объекта рассматривает его тип как изменяющийся. При вызове вртуального метода из базового конструктора вызовется метод базового класса, а при вызове из производного - этого производного. После окончания вызова конструктора базового класса вызовется конструктор производного.
При вызове деструктора будет похожая ситуация, но в обратном порядке. При вызове деструктора базового класса производная часть уже будет удалена то есть уже будет вызыван деструктор производного класса. При этом виртуальный метод в базовом деструкторе будет вызван для базового типа.
#include <iostream>
class Base
{
public:
Base() {
std::cout << "Base::Base" << std::endl;
f1(); // всегда вызов Base::f1, даже если он переопределен в наследнике
}
virtual ~Base() {
std::cout << "Base::~Base" << std::endl;
f2(); // всегда вызов Base::f2, даже если он переопределен в наследнике
};
virtual void f1() { std::cout << "Base::f1" << std::endl; }
virtual void f2() { std::cout << "Base::f2" << std::endl; }
};
class Derived : public Base
{
public:
Derived() {
std::cout << "Derived::Derived" << std::endl;
f1();
}
~Derived() override {
std::cout << "Derived::~Derived" << std::endl;
f2();
};
void f1() override { std::cout << "Derived::f1" << std::endl; }
void f2() override { std::cout << "Derived::f2" << std::endl; }
};
int main() {
Derived d;
std::cout << "============" << std::endl;
d.f2();
std::cout << "************" << std::endl;
}
// Output:
// Base::Base
// Base::f1
// Derived::Derived
// Derived::f1
// ============
// Derived::f2
// ************
// Derived::~Derived
// Derived::f2
// Base::~Base
// Base::f2
Как видим при вызове конструтора производного класса Derived сперва вызовется конструктор базового. При этом пока выполняется конструктор базового класса, производная часть объекта остается неинициализированной. Поэтому конструктору базового класса при вызове виртуального метода было бы опасно вызывать переопределенную версию производного, так как такой метод мог бы обратиться к объектам производного класса. Поэтому C++ при создании (и удалении) объекта рассматривает его тип как изменяющийся. При вызове вртуального метода из базового конструктора вызовется метод базового класса, а при вызове из производного - этого производного. После окончания вызова конструктора базового класса вызовется конструктор производного.
При вызове деструктора будет похожая ситуация, но в обратном порядке. При вызове деструктора базового класса производная часть уже будет удалена то есть уже будет вызыван деструктор производного класса. При этом виртуальный метод в базовом деструкторе будет вызван для базового типа.
👍4
У шаблона класса могут быть статические члены, при этом у каждого экземпляра для любого конкретного типа будет свой экземпляр:
Как видим у экземляра Class<float> свои экземпляры для статических членов size() и sz, а у Class<int> свои, отличные от них.
#include <iostream>
template <typename T>
class Class
{
public:
static int size() {
return sz;
}
static int sz;
T value;
};
template <typename T>
int Class<T>::sz = 0; // дефолтное значение для всех экземпляров шаблонов
int main() {
Class<float> fc1, fc2, fc3;
Class<int> ic;
fc1.sz = 3;
ic.sz = 4;
std::cout << fc1.size() << " " << fc2.size()
<< " " << Class<float>::size(); // 3 3 3
std::cout << std::endl;
std::cout << ic.size(); // 4
}
Как видим у экземляра Class<float> свои экземпляры для статических членов size() и sz, а у Class<int> свои, отличные от них.
❤🔥6
Давайте набросаем простой класс, который поможет отследить вызов конструктора и деструктора:
Далее мы просто создадим временное значение Xray:
Как видим, деструктор вызовется в той же строке кода, что и конструктор, то есть до вывода "Wait a sec", то есть до закрываюшей скобки.
Теперь же создадим ссылку (констанную lvalue) на временный объект:
Получили другой и возможно ожидаемый для первого случая вывод.
В чем же дело? Виноват механизм продления времени жизни временных объектов, который звучит так:
Прочитать подробнее можно в источнике.
struct Xray
{
Xray(std::string value)
: mValue(std::move(value))
{
std::cout << "Xray ctor, value is " << mValue << std::endl;
}
Xray(const Xray& other)
: mValue(other.mValue)
{
std::cout << "Xray const& ctor, value is " << mValue << std::endl;
}
~Xray()
{
std::cout << "~Xray dtor, value is " << mValue << std::endl;
}
std::string mValue;
};
Далее мы просто создадим временное значение Xray:
void main()
{
// Вывод: Xray ctor, value is 1
// Вывод: Xray dtor, value is 1
Xray{"1"};
std::cout << "Wait a sec" << std::endl;
}
Как видим, деструктор вызовется в той же строке кода, что и конструктор, то есть до вывода "Wait a sec", то есть до закрываюшей скобки.
Теперь же создадим ссылку (констанную lvalue) на временный объект:
void main()
{
// Вывод: Xray ctor, value is 1
const Xray& xrayRef = Xray{"1"};
// Вывод: xrayRef value is 1
std::cout << "xrayRef value is " << xrayRef.mValue << std::endl;
} // Вывод: Xray dtor, value is 1
Получили другой и возможно ожидаемый для первого случая вывод.
В чем же дело? Виноват механизм продления времени жизни временных объектов, который звучит так:
Если вы приняли временный объект по константной lvalue ссылке или rvalue ссылкe, то его время жизни будет продлено до времени жизни ссылки.
Прочитать подробнее можно в источнике.
PVS-Studio
Продление жизни временных значений в С++: рецепты и подводные камни
Прочитав эту статью, вы узнаете следующее: способы, которыми можно продлить время жизни временного объекта в С++; рекомендации и подводные камни этого механизма, с которыми может столкнуться С...
👍3
Прохожу курс, там сказано сравните время вставки в конец вектора и дека миллиона и 5 млн случайных чисел. Напишем код:
После запуска на 5 разных платформах получилось, что дек быстрее на 3, на одной примерно также, а на последней (MSVC) медленнее. По мнению авторов курса, дек быстрее, тк там нет копирования при ресайзе (reserve для вектора сказано не использовать).
Лично у меня два вывода: 1) берем вектор и не паримся, он все равно очень быстрый 2) такие вопросы без окружения в тестирование выносить - дурной тон.
#include <vector>
#include <deque>
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <chrono>
#include <string>
void fill_random_vector(size_t size)
{
std::vector<int> v;
for (size_t i = 0; i < size; ++i) {
v.push_back(rand());
}
std::cout << "v[" << size - 1 << "] = " << v[size - 1] << std::endl;
}
void fill_random_deque(size_t size)
{
std::deque<int> d;
for (size_t i = 0; i < size; ++i) {
d.push_back(rand());
}
std::cout << "d[" << size - 1 << "] = " << d[size - 1] << std::endl;
}
template<typename F>
void measure_time(F func, const std::string& text)
{
auto begin = std::chrono::steady_clock::now();
func();
auto end = std::chrono::steady_clock::now();
std::cout << text << " "
<< std::chrono::duration_cast<std::chrono::milliseconds>(end - begin).count()
<< " ms" << std::endl;
}
int main()
{
srand((unsigned)time(0));
measure_time([](){ fill_random_vector(1'000'000); }, "fill_random_vector 1'000'000");
measure_time([](){ fill_random_deque(1'000'000); }, "fill_random_deque 1'000'000");
measure_time([](){ fill_random_vector(5'000'000); }, "fill_random_vector 5'000'000");
measure_time([](){ fill_random_deque(5'000'000); }, "fill_random_deque 5'000'000");
return 0;
}
После запуска на 5 разных платформах получилось, что дек быстрее на 3, на одной примерно также, а на последней (MSVC) медленнее. По мнению авторов курса, дек быстрее, тк там нет копирования при ресайзе (reserve для вектора сказано не использовать).
Лично у меня два вывода: 1) берем вектор и не паримся, он все равно очень быстрый 2) такие вопросы без окружения в тестирование выносить - дурной тон.
👍4
Удаляет ли std::vector элементы (иначе, вызывает ли деструктор) при resize() и clear()? И если да, то в каком порядке?
Напишем код:
Проанализируем вывод:
Несмотря на то, что capacity не меняется, для элементов вызывается деструктор. При этом деструктор неожиданно вызывается в порядке от первого элемента к последнему, что противоречит обычному правилу: деструктор вызывается в порядке обратном конструктору. Хотя следует упомянуть, что порядок удаления для вектора не определен (FIXME) и может быть другим/любым. Однако же для обычного массива он будет ожидаемым - сперва удалится последний элемент.
Напишем код:
#include <iostream>
#include <vector>
struct DtorLog
{
DtorLog(int v = 0) : value(v) {}
~DtorLog() { std::cout << "~" << value << std::endl; }
int value;
};
int main()
{
{
std::vector<DtorLog> v;
v.reserve(10);
for (int i = 1; i <= 5; ++i) {
v.emplace_back(i);
}
std::cout << "resize " << v.capacity() << std::endl;
v.resize(3);
std::cout << "clear " << v.capacity() << std::endl;
v.clear();
std::cout << "shrink_to_fit " << v.capacity() << std::endl;
v.emplace_back(6);
v.emplace_back(7);
v.shrink_to_fit();
std::cout << "end " << v.capacity() << std::endl;
}
std::cout << "array" << std::endl;
DtorLog a[3];
for (int i = 0; i < 3; ++i) {
a[i].value = i;
}
return 0;
}
Проанализируем вывод:
resize 10
~4
~5
clear 10
~1
~2
~3
shrink_to_fit 10
~6
~7
end 2
~6
~7
array
~2
~1
~0
Несмотря на то, что capacity не меняется, для элементов вызывается деструктор. При этом деструктор неожиданно вызывается в порядке от первого элемента к последнему, что противоречит обычному правилу: деструктор вызывается в порядке обратном конструктору. Хотя следует упомянуть, что порядок удаления для вектора не определен (FIXME) и может быть другим/любым. Однако же для обычного массива он будет ожидаемым - сперва удалится последний элемент.
👍6
std::atexit регистрирует функцию для вызова при "нормальном" завершении программы (например выходе из main или вызова std::exit).Если программа завершается аварийно, например из-за
terminate(), то вызов не срабатывает. Давайте напишем программу, которая выведет в лог текст, если не "крэшнулась". Крэш сэмулируем с вероятностью 50% с помощью исключения:#include <cstdlib>
#include <iostream>
#include <time.h>
void atexit_handler()
{
std::cout << "Hooray, not crashed" << std::endl;
}
int main()
{
std::atexit(atexit_handler);
srand(time(NULL));
if (rand() % 2 == 0) {
throw std::runtime_error("Let's crash");
}
std::cout << "app end" << std::endl;
return 0;
}
В случае нормального завершения вывод будет:
app end
Hooray, not crashed
а при исключении - ни одна из строк.
При этом обработчиков-функций может быть несколько (как минимум 32).
👍7
В продолжении темы про аварийное завершение работы. Что делать, если статическая или thread_local переменная в конструкторе выбрасывает исключение? Ничего другого, кроме как вызвать std::terminate нет.
Следует отметить, что это верно и для исключения в деструкторе.
#include <iostream>
class ThrowsClass
{
public:
ThrowsClass()
{
throw std::runtime_error("oh shit");
}
~ThrowsClass()
{
throw std::runtime_error("dtor");
}
void print()
{
std::cout << "throws";
}
};
static ThrowsClass t; // std::terminate
thread_local ThrowsClass tl; // std::terminate
int main() {
tl.print();
std::cout << "end";
}
Следует отметить, что это верно и для исключения в деструкторе.
👍4
Рассмотрим шаблон функции, который задает не только типы аргументов, но и тип результата, например дробное или целочисленное деление:
Мы могли бы вызвать этот код так:
Код скомпилируется и верно отработает, выдав заказанное нами дробное значение. Типы int для аргументов он выведет автоматом и нам их не нужно писать в угловых скобках (double написать все же придется).
Ну и что такого, спросите вы, это же ожидаемо? А давайте посмотрим, что было бы, если бы мы написали эту же функцию, но с другим порядком типов:
Этот код не скомпилируется и нам пришлось бы писать все 3 параметра, чтоб уважить компилятор:
Как вы понимаете, порядок важен и пропускать типы можно только для крайних справа параметров.
#cpp #templates
template <typename T1, typename T2, typename T3>
T1 div(T2 a, T3 b) {
return (T1)a / b;
}
Мы могли бы вызвать этот код так:
int main()
{
auto res = div<double>(3, 2);
std::cout << res << std::endl; // 1.5
return 0;
}
Код скомпилируется и верно отработает, выдав заказанное нами дробное значение. Типы int для аргументов он выведет автоматом и нам их не нужно писать в угловых скобках (double написать все же придется).
Ну и что такого, спросите вы, это же ожидаемо? А давайте посмотрим, что было бы, если бы мы написали эту же функцию, но с другим порядком типов:
template <typename T1, typename T2, typename T3>
T3 div_v2(T1 a, T2 b) {
return (T3)a / b;
}
auto res2 = div_v2<int>(3, 2); // won't compile!
Этот код не скомпилируется и нам пришлось бы писать все 3 параметра, чтоб уважить компилятор:
auto res2 = div_v2<int, int, int>(3, 2); // OK
Как вы понимаете, порядок важен и пропускать типы можно только для крайних справа параметров.
#cpp #templates
👍4
Всем известно, что язык C++ является прямым потомком языка C и позаимствовал из него очень многое. Но работает ли это в обратную сторону? Судите сами. Изменения в стандарте C23 (не С++23!) среди прочего включают:
- изменения в auto для:
- поддержка синтаксиса [[attr]], например:
- добавлены ключевые слова
- добавлены nullptr_t и nullptr
- добавлен разделитель ':
- поддержка constexpr
Подробнее тут: https://www.opennet.ru/opennews/art.shtml?num=62278
- изменения в auto для:
auto y = cos(x);- поддержка синтаксиса [[attr]], например:
[[deprecated]]- добавлены ключевые слова
bool, static_assert и другие- добавлены nullptr_t и nullptr
- добавлен разделитель ':
1'000'000- поддержка constexpr
Подробнее тут: https://www.opennet.ru/opennews/art.shtml?num=62278
www.opennet.ru
GCC 15 будет использовать стандарт C23 по умолчанию
В кодовую базу, на основе которой формируется запланированный на весну следующего года выпуск набора компиляторов GCC 15, принято изменение, включающее по умолчанию использование стандарта С23 с расширениями GNU ("-std=gnu23") при компиляции программ на языке…
👍6
Как быстро и просто инвертировать вектор? Передадим reverse-итераторы в конструктор вектора, получим нужную копию:
Коротко, но может быть не самый эффективный вариант? Что ж, сравним с такой мамкиной реализацией:
После нескольких замеров оказалось, что более короткая реализация при N = 10М выполняется за 12 мс, а более длинная за 19мс. Уотак вот, можно пользоваться и радоваться мощи итераторов.
std::vector<int> reverse(const std::vector<int>& source) {
return std::vector(source.crbegin(), source.crend());
}Коротко, но может быть не самый эффективный вариант? Что ж, сравним с такой мамкиной реализацией:
std::vector<int> reverse2(const std::vector<int>& source) {
std::vector<int> res;
res.reserve(source.size());
for (auto it = source.crbegin(); it != source.crend(); ++it) {
res.push_back(*it);
}
return res;
}После нескольких замеров оказалось, что более короткая реализация при N = 10М выполняется за 12 мс, а более длинная за 19мс. Уотак вот, можно пользоваться и радоваться мощи итераторов.
👍3🔥2
Во тут народ извращается 🤣
https://t.me/grokaemcpp/577
Но что, если я вам скажу, что вы можете наследоваться от лямбды!
https://t.me/grokaemcpp/577
Please open Telegram to view this post
VIEW IN TELEGRAM
Telegram
Грокаем C++
Наследование? От лямбды? Ч2
#опытным
Лямбды - это функциональные объекты по своей сути. Объекты классов с перегруженным оператором(). Зачем вообще от такой сущности наследоваться? Можно же просто сделать обычный класс с такими же перегруженным оператором…
#опытным
Лямбды - это функциональные объекты по своей сути. Объекты классов с перегруженным оператором(). Зачем вообще от такой сущности наследоваться? Можно же просто сделать обычный класс с такими же перегруженным оператором…
😁6❤2🤯1
Проходили тут курс по python, и там конечно же сказано, что в python отступы в блоках обязательны, а во многих других языках - нет. И мне даже показалось, что это в недостатки других языков внесли. Типа вот глядите какую нечитаемую фигню можно на C написать, а на python всё замечательно. И приводят такой пример (кстати, рекомендую запустить код):
P.S. на телефоне разметка может разъехаться.
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <unistd.h>
k;double sin()
,cos();main(){float A=
0,B=0,i,j,z[1760];char b[
1760];printf("\x1b[2J");for(;;
){memset(b,32,1760);memset(z,0,7040)
;for(j=0;6.28>j;j+=0.07)for(i=0;6.28
>i;i+=0.02){float c=sin(i),d=cos(j),e=
sin(A),f=sin(j),g=cos(A),h=d+2,D=1/(c*
h*e+f*g+5),l=cos (i),m=cos(B),n=
sin(B),t=c*h*g-f* e;int x=40+30*D*
(l*h*m-t*n),y= 12+15*D*(l*h*n
+t*m),o=x+80*y, N=8*((f*e-c*d*g
)*m-c*d*e-f*g-l *d*n);if(22>y&&
y>0&&x>0&&80>x&&D>z[o]){z[o]=D;;;b[o]=
".,-~:;=!*#$@"[N>0?N:0];}}/*#****!!-*/
printf("\x1b[H");for(k=0;1761>k;k++)
putchar(k%80?b[k]:10);A+=0.04;B+=
0.02;}}/*****####*******!!=;:~
~::==!!!**********!!!==::-
.,~~;;;========;;;:~-.
..,--------,*/
P.S. на телефоне разметка может разъехаться.
😁9❤1