Проведение технического интервью: краткий обзор новых возможностей C++ 11/14.

C++
Проведение технического интервью: краткий обзор новых возможностей C++ 11/14.

Introduction

Тот же C++ инженер, кто-то пишет C с объектом, кто-то пишет C++ 98, мода немного пишет C++ 11/14, а некоторые программисты используют Tan++.

Это всего лишь шутка. Многие студенты понимают C++ только в классе и не обращают внимания на последние разработки C++, на самом деле многие новые функции C++ могут значительно повысить эффективность разработки, эффективность выполнения программ и безопасность кода, а также стабильность. , и т.д.

Эта статья в основном оlvalue rvalue, автоматическое ключевое слово, интеллектуальный указатель, по умолчанию, удаление, переопределение, окончательное, {} инициализация, использование псевдонима, цикл for на основе диапазона, лямбда-выражениеСодержание длинное, пожалуйста, наберитесь терпения, чтобы прочитать и провести больше экспериментов.

lvalue rvalue

Все выражения и переменные в C++ (включая C) являются либо lvalue, либо rvalue. Популярное определение lvalue — это невременный объект, объект, который можно использовать в нескольких операторах. Все переменные удовлетворяют этому определению и могут использоваться в нескольких строках кода, все они являются lvalue. rvalue — это временные объекты, которые действительны только в рамках текущего оператора.Ссылки IBM rvalue и семантика передачи

Один из способов определить, является ли выражение lvalue, состоит в том, чтобы проверить, можно ли получить адрес выражения; если он может быть получен, то это в основном выражение lvalue; если он не может быть получен, обычно это rvalue.

Все значения в C++ относятся либо к lvalue, либо к rvalue; если разделить, rvalue можно разделить на: чистое rvalue и xvalue.

ссылочный тип rvalue

Мы можем понять, что значение r — это временный объект.Например, объект, возвращаемый некоторыми функциями, является временным объектом, и пространство временного объекта будет освобождено после выполнения предложения, поэтому ссылка на значение r была бесполезна до .

C++11 предлагает ссылки rvalue, которые могут продлить жизненный цикл временных объектов.type && vb = xx;Символ жизни соответствующей ссылки lvalue:&.

#include <iostream>
#include <string>
 
int main()
{
    std::string s1 = "Test";
//  std::string&& r1 = s1;           // 错误:不能绑定到左值
 
    const std::string& r2 = s1 + s1; // okay:到 const 的左值引用延长生存期
//  r2 += "Test";                    // 错误:不能通过到 const 的引用修改
 
    std::string&& r3 = s1 + s1;      // okay:右值引用延长生存期
    r3 += "Test";                    // okay:能通过到非 const 的引用修改
    std::cout << r3 << '\n';
}

Что еще более важно, когда функция имеет перегрузки как ссылки rvalue, так и ссылки lvalue, перегрузки ссылки rvalue привязываются к rvalues ​​(как prvalue, так и xvalue), а перегрузки ссылки lvalue привязываются к lvalue:

#include <iostream>
#include <utility>
 
void f(int& x) {
    std::cout << "lvalue reference overload f(" << x << ")\n";
}
 
void f(const int& x) {
    std::cout << "lvalue reference to const overload f(" << x << ")\n";
}
 
void f(int&& x) {
    std::cout << "rvalue reference overload f(" << x << ")\n";
}
 
int main() {
    int i = 1;
    const int ci = 2;
    f(i);  // 调用 f(int&)
    f(ci); // 调用 f(const int&)
    f(3);  // 调用 f(int&&)
           // 若不提供 f(int&&) 重载则会调用 f(const int&)
    f(std::move(i)); // 调用 f(int&&)
 
    // 右值引用变量在用于表达式时是左值
    int&& x = 1;
    f(x);            // calls f(int& x)
    f(std::move(x)); // calls f(int&& x)
}

Move Sementics и Perfect Forwarding

Семантика перемещения заключается в том, чтобы украсть пространство переменной памяти умирающего значения, сначала убедиться, что эта часть пространства не будет использоваться позже, а затем занять место для себя, что выглядит как операция копирования. Семантика перемещения находится в заголовочном файле#include<algorithm>, имя функцииstd::move.

#include <iostream>
#include <vector>
#include <list>
#include <iterator>
#include <thread>
#include <chrono>
 
void f(int n)
{
    std::this_thread::sleep_for(std::chrono::seconds(n));
    std::cout << "thread " << n << " ended" << '\n';
}
 
int main() 
{
    std::vector<std::thread> v;
    v.emplace_back(f, 1);
    v.emplace_back(f, 2);
    v.emplace_back(f, 3);
    std::list<std::thread> l;
    // copy() 无法编译,因为 std::thread 不可复制
 
    std::move(v.begin(), v.end(), std::back_inserter(l)); 
    for (auto& t : l) t.join();
}

автоматический вывод типа

autoиdecltypeКлючевые слова — это недавно добавленные ключевые слова.Мы знаем, что C++ — строго типизированный язык, но, используя эти два ключевых слова, вы можете позволить компилятору вывести реальный тип без записи полного типа.

autoИспользование очень простое, пример следующий:

#include <iostream>
#include <utility>
 
template<class T, class U>
auto add(T t, U u) { return t + u; } // 返回类型是 operator+(T, U) 的类型
 
// 在其所调用的函数返回引用的情况下
// 函数调用的完美转发必须用 decltype(auto)
template<class F, class... Args>
decltype(auto) PerfectForward(F fun, Args&&... args) 
{ 
    return fun(std::forward<Args>(args)...); 
}
 
template<auto n> // C++17 auto 形参声明
auto f() -> std::pair<decltype(n), decltype(n)> // auto 不能从花括号初始化器列表推导
{
    return {n, n};
}
 
int main()
{
    auto a = 1 + 2;            // a 的类型是 int
    auto b = add(1, 1.2);      // b 的类型是 double
    static_assert(std::is_same_v<decltype(a), int>);
    static_assert(std::is_same_v<decltype(b), double>);
 
    auto c0 = a;             // c0 的类型是 int,保有 a 的副本
    decltype(auto) c1 = a;   // c1 的类型是 int,保有 a 的副本
    decltype(auto) c2 = (a); // c2 的类型是 int&,为 a 的别名
    std::cout << "a, before modification through c2 = " << a << '\n';
    ++c2;
    std::cout << "a, after modification through c2 = " << a << '\n';
 
    auto [v, w] = f<0>(); // 结构化绑定声明
 
    auto d = {1, 2}; // OK:d 的类型是 std::initializer_list<int>
    auto n = {5};    // OK:n 的类型是 std::initializer_list<int>
//  auto e{1, 2};    // C++17 起错误,之前为 std::initializer_list<int>
    auto m{5};       // OK:C++17 起 m 的类型为 int,之前为 initializer_list<int>
//  decltype(auto) z = { 1, 2 } // 错误:{1, 2} 不是表达式
 
    // auto 常用于无名类型,例如 lambda 表达式的类型
    auto lambda = [](int x) { return x + 3; };
 
//  auto int x; // 于 C++98 合法,C++11 起错误
//  auto x;     // 于 C 合法,于 C++ 错误
}

decltypeполезен для проверки объявленного типа объекта или типа выражения и типа значения. Использование заключается в следующем:decltype(实体/表达式). Новые переменные могут быть определены с использованием типа другого объекта.

#include <iostream>
 
struct A { double x; };
const A* a;
 
decltype(a->x) y;       // y 的类型是 double(其声明类型)
decltype((a->x)) z = y; // z 的类型是 const double&(左值表达式)
 
template<typename T, typename U>
auto add(T t, U u) -> decltype(t + u) // 返回类型依赖于模板形参
{                                     // C++14 开始可以推导返回类型
    return t+u;
}
 
int main() 
{
    int i = 33;
    decltype(i) j = i * 2;
 
    std::cout << "i = " << i << ", "
              << "j = " << j << '\n';
 
    auto f = [](int a, int b) -> int
    {
        return a * b;
    };
 
    decltype(f) g = f; // lambda 的类型是独有且无名的
    i = f(2, 2);
    j = g(3, 3);
 
    std::cout << "i = " << i << ", "
              << "j = " << j << '\n';
}

возвращаемое значение функции

Таким образом, объявление возвращаемого значения функции может быть помещено в конец объявления функции;auto function_name( 形参 ) (属性,如 override等) (异常说明,可选) -> 返回值类型. Честно говоря, такой способ написания заставляет меня чувствовать, что я не пишу на C++, и я думаю, что в большинстве случаев я не вернусь к использованию этой функции. . .

// 返回指向 f0 的指针的函数
auto fp11() -> void(*)(const std::string&)
{
    return f0;
}

принуждение

Приведения в стиле C устарели, начиная с C++ 11. Рекомендуется использоватьstatic_cast、const_cast、reinterpret_cast、dynamic_castи другие методы преобразования типов.

Ключевые слова инструкция
static_cast(обычно используется) Он используется для доброкачественной конверсии, обычно не вызывает случайного возникновения, и риск очень низок.
const_cast Используется для преобразования между постоянными и непостоянными, изменчивыми и энергонезависимыми.
reinterpret_cast Крайне опасное преобразование, которое представляет собой просто переинтерпретацию двоичных битов и не корректирует данные с помощью существующих правил преобразования, но позволяет добиться наиболее гибкого преобразования типа C++.
dynamic_cast Используйте RTTI для безопасного преобразования типов.

C++ четыре оператора преобразования типов: static_cast, dynamic_cast, const_cast и reinterpret_cast

умный указатель

видетьУмные указатели C++

некоторые новые ключевые слова

nullptr

Говорят, что NULL обычно определяется в заголовочных файлах C++ как#define NULL 0, так по существуNULLТипint,использоватьNULLПредставление нулевого указателя является очень неуместным поведением, поэтому C++11 переопределяет неintтип и применяется к ключевым словам нулевого указателя.

Ключевое слово nullptr представляет литерал указателя. Это значение типа std::nullptr_t. Существуют неявные преобразования из nullptr в любой тип указателя и в любой тип указателя-члена. Такое же преобразование существует для любой константы нулевого указателя, включая значение std::nullptr_t и макрос NULL.nullptr, литерал указателя

специальная функция-член

  1. конструктор по умолчанию
  2. конструктор копирования
  3. конструктор перемещения (начиная с C++11)
  4. копировать оператор присваивания
  5. оператор присваивания перемещения (начиная с C++11)
  6. деструктор

default

мы знаемdefaultсам по себеswitchКлючевое слово оператора, которое было расширено до нового использования в C++ 11, может использоваться, чтобы сказать компилятору сгенерировать функцию-член по умолчанию (конструктор по умолчанию и т. д.). Специальные функции-члены и операторы сравнения (начиная с C++20) — единственные функции, которые могут быть предварительно заданы, т. е. определены с помощью = default вместо тела функции (подробности см. на соответствующих страницах).

Например: конструктор по умолчанию может использовать类名 ( ) = default ; (C++11 起)путь объявления, а затем не может быть использован в*.cppНапишите реализацию тела функции в файле, эта функция будет сгенерирована компилятором по умолчанию.

удалить функцию удаления

Новое использование удаления - устаревшая функция, вместо того, чтобы делать конструктор в объекте закрытым, теперь есть способ удалить функцию.

Если вместо тела функции используется специальный синтаксис= delete ;, функция определяется как удаленная. Любое использование устаревшей функции является некорректным (программа не компилируется). Сюда входят вызовы, как явные (вызов оператора как функции), так и неявные (вызов устаревшего перегруженного оператора, специальной функции-члена, функции распределения и т. д.), представляющие собой указатель или указатель-член на устаревшую функцию, или даже в отбросить функцию в невычисленном выражении. Однако разрешено неявное использование ODR нечистых виртуальных функций-членов, которые только что устарели.

struct sometype
{
    void* operator new(std::size_t) = delete;
    void* operator new[](std::size_t) = delete;
};
sometype* p = new sometype; // 错误:尝试调用弃置的 sometype::operator new

override

Это ключевое слово переводится как перезапись и используется при указании виртуальной функции для переопределения другой виртуальной функции.Effective Modern C++В книге рекомендуется, чтобы это ключевое слово было добавлено в этом случае, чтобы компилятор мог помочь нам проверить, правильно ли мы определили переопределяемую функцию (если нет, то он скомпилирует ошибку).

Эта часть кода не скомпилируется корректно, так как после добавления переопределения компилятор найдет для нас соответствующую виртуальную функцию в унаследованном базовом классе, и здесь мы можем найти некоторые ошибки в объявлении нашей функции. И если вы не добавите переопределение, он будет успешно скомпилирован здесь, но это определенно не тот результат компиляции, который нам нужен.

/*
 * Key idea:
 *
 *   The below code won't compile, but, when written this way, compilers will
 *   kvetch about all the overriding-related problems.
 */

class Base {
public:
  virtual void mf1() const;
  virtual void mf2(int x);
  virtual void mf3() &;
  void mf4() const;
};

// Uncomment this, compile and see the compiler errors.
//class Derived: public Base {
//public:
//  virtual void mf1() override;
//  virtual void mf2(unsigned int x) override;
//  virtual void mf3() && override;
//  void mf4() const override;
//};

Успешная компиляция возможна только в том случае, если объявление функции, измененное переопределением, верно.

/*
 * Key idea:
 *
 *   This the code-example that uses override and is correct.
 */

class Base {
public:
  virtual void mf1() const;
  virtual void mf2(int x);
  virtual void mf3() &;
  virtual void mf4() const;
};

class Derived: public Base {
public:
  virtual void mf1() const override;
  virtual void mf2(int x) override;
  virtual void mf3() & override;
  void mf4() const override;         // adding "virtual" is OK,
};                                   // but not necessary

final

Объявите, что виртуальная функция не может быть переопределена.

( ), { } инициализация

Есть больше способов инициализировать объект, напримерСписок инициализации в фигурных скобкахПримеры следующие:

/*
 * Key idea:
 *
 *   The treatment of braced initializers is the only way in which auto type
 *   deduction and template type deduction differ.
 */

#include <initializer_list>

template<typename T>  // template with parameter
void f(T param) {}    // declaration equivalent to
                      // x's declaration

template<typename T>
void f2(std::initializer_list<T> initList) {}

int main()
{
  {
    int x1 = 27;
    int x2(27);
    int x3 = {27};
    int x4{27};
  }

  {
    auto x1 = 27;    // type is int, value is 27
    auto x2(27);     // ditto
    auto x3 = {27};  // type is std::initializer_list<int>,
                     // value is {27}
    auto x4{27};     // ditto

    //auto x5 = {1, 2, 3.0};  // error! can't deduce T for
    //                        // std::initializer_list<T>
  }

  {
    auto x = { 11, 23, 9 };  // x's type is
                             // std::initializer_list<int>

    //f({ 11, 23, 9 });        // error! can't deduce type for T

    f2({ 11, 23, 9 });        // T deduced as int, and initList's
                              // type is std::initializer_list<int>
  }
}

используя псевдоним

Кромеtypedefключевые слова, вы также можете использоватьusingКлючевые слова создают псевдонимы,Effective Modern C++В книге рекомендуется использовать объявления псевдонимов.

/*
 * Key Idea:
 *
 *   Using alias declarations is easier to read than function pointers.
 */

#include <string>

// FP is a synonym for a pointer to a function taking an int and
// a const std::string& and returning nothing
typedef void (*FP)(int, const std::string&);    // typedef

// same meaning as above
using FP = void (*)(int, const std::string&);   // alias
                                                // declaration

Тип перечисления с заданной областью

Область действия типа перечисления можно ограничить, добавив ключевое слово в определение типа перечисления.enum test-> enum class test;

/*
 * Key Idea:
 *
 *   In C++11, the names of scoped enums do not belong to the scope containing
 *   the enum.
 */

enum class Color { black, white, red };  // black, white, red
                                         // are scoped to Color

auto white = false;              // fine, no other
                                 // "white" in scope

//Color c1 = white;                 // error! no enumerator named
                                 // "white" is in this scope

Color c2 = Color::white;          // fine

auto c3 = Color::white;           // also fine (and in accord
                                 // with Item4's advice)

диапазон на основе цикла

C++ также может использовать циклы for на основе диапазона, такие как python. Диапазон, основанный на синтаксисе цикла, равенfor(范围声明:范围表达式). Среди них объявление области видимости: объявление именованной переменной, тип которой является типом элемента последовательности, представленной выражением области видимости, или ссылкой на этот тип, обычно со спецификатором auto для автоматического вывода типа; выражение области видимости: любое выражение подходящей последовательности (массив или объект, определяющий начальные и конечные функции-члены или свободные функции, см. ниже), или список инициализаторов в фигурных скобках, в основном несколько общих контейнеров в std, таких как: вектор, список и т. д. циклы for на основе диапазона.

#include <iostream>
#include <vector>
 
int main() {
    std::vector<int> v = {0, 1, 2, 3, 4, 5};
 
    for (const int& i : v) // 以 const 引用访问
        std::cout << i << ' ';
    std::cout << '\n';
 
    for (auto i : v) // 以值访问,i 的类型是 int
        std::cout << i << ' ';
    std::cout << '\n';
 
    for (auto& i : v) // 以引用访问,i 的类型是 int&
        std::cout << i << ' ';
    std::cout << '\n';
 
    for (int n : {0, 1, 2, 3, 4, 5}) // 初始化器可以是花括号初始化器列表
        std::cout << n << ' ';
    std::cout << '\n';
 
    int a[] = {0, 1, 2, 3, 4, 5};
    for (int n : a) // 初始化器可以是数组
        std::cout << n << ' ';
    std::cout << '\n';
 
    for (int n : a)  
        std::cout << 1 << ' '; // 不必使用循环变量
    std::cout << '\n';
 
}

лямбда-выражение

Лямбда-выражение - это анонимная функция, очень похожая на временную функцию в java (собирающая сильные стороны каждой, и ее сложнее использовать, чем другие...) Синтаксис лямбды следующий:

[ 俘获 ] <模板形参>(可选)(C++20) ( 形参 ) 说明符(可选) 异常说明 attr -> ret requires(可选)(C++20) { 函数体 }	
[ 俘获 ] ( 形参 ) -> ret { 函数体 }	
[ 俘获 ] ( 形参 ) { 函数体 }	
[ 俘获 ] { 函数体 }	

Есть более подробная информация о лямбда-выражениях. Можно написать отдельный блог, чтобы объяснить. Если вам интересно, вы можете сначала взглянуть.zh.cppreference.comэто описание.

Reference

  1. Effective Modern C++
  2. С++ 11/14 Расширенное программирование
  3. Ссылки IBM rvalue и семантика передачи
  4. справочное заявление cppreference.com
  5. cppreference.com auto
  6. C++ четыре оператора преобразования типов: static_cast, dynamic_cast, const_cast и reinterpret_cast
  7. cppreference.com nullptr, литерал указателя
  8. cppreference.com специальные функции-члены
  9. Устаревшие функции cppreference.com
  10. Список инициализации в фигурных скобках
  11. диапазон на основе цикла
  12. лямбда-выражение