C++ — Возвращение функции bool и использование его в main()


Содержание

Метод класса возвращает bool, но компилятор обрабатывает его как возврат vo >

Все еще не знаю, в чем проблема. Выглядело так, как будто у меня было несколько файлов, но я уверен, что нет. В любом случае, я переименовал свой класс .h и .cc в поле и переименовал сами классы. На самом деле никакого кода не изменилось, но теперь проблема исчезла.

Я хотел бы избавиться от этого вопроса, поскольку не вижу, чтобы он помогал кому-либо в будущем. Очевидно, это была моя локальная ошибка.

Я вызываю метод из моей основной функции и использую логическое возвращение в операторе if. Метод объявлен как возврат bool, но компилятор считает его возвращением void. Я пытался перейти от bool к int и изменить оценку if, но компилятор все еще считает его пустой функцией. Я понятия не имею, что не так.

замедление метода внутри определения класса (как public)

определение метода в файле cc класса

вызывая метод в main.

Я понимаю, что означает ошибка компилятора, но я не понимаю, почему ее выдают. Эта функция явно является возвращением типа bool. Любая помощь?

лабы по информатике, егэ

лабораторные работы и задачи по программированию и информатике, егэ по информатике

Урок 2. C++ функции

C++ пользовательские функции

    Создать проект с функцией для вычисления Евклидова расстояния между двумя точками на плоскости.

AB = √(x2-x1) 2 + (y2-y1) 2

  • Реализовать тестирующую программу.
  • Подробно:
    Создать функцию:

    double distance ( double x1, double y1, double x2, double y2 )

    которая вычисляет Евклидово расстояние между двумя точками на плоскости.
    Параметры функции:
    x1, y1, x2, y2 – вещественные координаты точек.
    Возвращаемое значение:
    расстояние между точками ( x1; y1 ), ( x2; y2 ) .

    1. Создайте пустой проект (см. урок 1). Главный файл назовите main.cpp, заголовочный файл — func1.h
    2. В заголовочный файл добавьте необходимую директиву для работы с математическими функциями:

    #include #include using namespace std;

    . double distance(double x1, double y1, double x2, double y2); double distance(double x1, double y1, double x2, double y2)

    #include #include // для system(«pause»); #include «func1.h» using namespace std; int main()

    . int main() < double x1, y1, x2, y2; cout >x1 >> y1 >> x2 >> y2; system(«pause»); return 0; >

    Для подключения кириллицы в начале функции main() вызовите функцию:

    . cout Заголовочные файлы -> Добавить -> Создать элемент -> Файл заголовка)
    Подключите заголовочный файл в код main.cpp, перед подключением добавьте директиву #define NDEBUG :

    . #define NDEBUG #include «test.h» .

    Функция assert анализирует аргумент, переданный ей. Если аргумент-выражение равен нулю (т.е. выражение ложно), сообщение записывается на стандартное устройство вывода ошибок и вызывается функция abort , работа программы прекращается.

    . #include #include using namespace std;

    . const double precision = 1E-16; .

    . assert ( abs ( distance (x1,y1,x2,y2) — result) > x1 >> y1 >> x2 >> y2; cout S = √r * (r — a)*(r — b)*(r — c)
    где:
    r — полупериметр
    a, b, c — стороны

    double square ( double a, double b, double c );

    a, b, c – вещественные стороны треугольника

    Использовать формулу S = √r * (r — a)*(r — b)*(r — c)

    где:
    r — полупериметр
    a, b, c — стороны

    Для получения сторон использовать функцию, созданную в лабораторной работе №1 данного урока (double distance(…)).

    double square ( double xa, double ya, double xb, double yb, double xc, double yc );

    xa, ya, xb, yb, xc, yc – вещественные координаты точек A ( xa, ya ), B ( xb, yb ), C ( xc, yc ).

    площадь треугольника ABC.

    • Из Лабораторной работы №1 текущего урока имеем описание функции вычисления расстояния между двумя точками:

    . double distance(double x1, double y1, double x2, double y2); double distance(double x1, double y1, double x2, double y2)

    . double square(double xa, double ya, double xb, double yb, double xc, double yc);

    . double x1, x2, y1, y2, xa, ya, xb, yb, xc, yc; cin >> xa >> ya >> xb >> yb >> xc >> yc; .

    . r = (ab + bc + ac) / 2; S = sqrt(r*(r — ab)*(r — bc)*(r — ac)); return S; .

    . cout Поделитесь уроком с коллегами и друзьями:

    тип bool в C

    Не могу понять, что происходит с типом bool. пустая прога:

    при компиляции gcc выдает:


    test.c: in function ‘main’: test.c:3: ‘bool’ undeclared (first use in this function) test.c:3: (Each undeclared identifier is reported only once test.c:3: for each function it appears in.) test.c:3: parse error before ‘bval’

    Что, нет такого типа ‘bool’.

    Re: тип bool в C

    Прикинь, и в самом деле нет.

    Re: тип bool в C

    Типа bool в С действительно нет. Точнее, в версиях стандарта до C99 его действительно нет. В С99 он есть, но называется _Bool. Кроме того, определен заголовочный файл в котором определен `bool’ как синоним для _Bool. Сделано это было, чтобы не нарушать совместимость с существующим кодом. GCC поддерживает С99 в этом отношении. Однако особого смысла в использовании _Bool/bool я не вижу, т.к. в плане проверки типов это обычный целочисленный тип (совместимый по присваиванию с int и так далее). Единственная особенность — явное приведение к _Bool дает всегда либо 0 либо 1 (это, однако, не означает, что переменная типа _Bool будет занимать в памяти один бит) — точно такой же эффект может быть достигнут при помощи `!!’

    Re: тип bool в C

    А не проще писать так?
    #define TRUE 1
    #define FALSE 0

    Ну и для полной радости
    typedef unsigned short bool;

    Re: Re: тип bool в C

    short занимает 2 байта, может быть вместо него использовать char?

    Re: тип bool в C

    Компилятор при сравнениях и прочих операциях будет приводить char к int, так что ты выиграешь (может быть) в памяти, но проиграешь (какие-то копейки) в скорости.

    Конструкция
    typedef enum bool;

    будет лучше (IMHO), чем #define.

    Re: Re: тип bool в C

    я понял, спасибо за ответы

    Re: тип bool в C

    Re: тип bool в C

    Ну здрасьте, всегда в условиях проверяли числа и указатели на 0 просто if(!number) а тут оказывается false!=0. приехали.

    Re: тип bool в C

    > enum не обязан начинаться с 0
    Согласно стандарту языка Си, первый элемент перечисления по умолчанию равен 0.

    > я не уверен что 0 это на самом деле false
    Если использовать конструкцию
    enum ;
    то слово false будет синонимом числа 0.
    Во всяком случае, компилятор, встретив false, будет заменять его на 0 (а true — на 1).
    Тип у обоих будет int.
    Вот пример:

    Функция, написанная программером:
    bool fn(bool arg)
    <
    if (arg) do_something();
    else do_nothing();
    return true;
    >

    Та же функция, но с точки зрения компилятора:
    int fn(int arg)
    <
    if (arg != 0) do_something();
    else do_nothing();
    return 1;
    >

    Вобщем, разница между использованием int в диапазоне от 0 до 1 и использованием bool от false до true заключается только в читабельности программы. Если в программе есть немалое количество функций, возвращающих только успешность выполнения (да/нет), то есть смысл (IMHO) использовать для этих целей bool — для лучшей читаемости. То же самое справедливо и для аргументов функций.

    Re: тип bool в C

    Плохо не то что false == 0, а то что true == 1; Значит проверки на x == true будут проваливаться для большинства случаев.

    В C нет булевского типа — лучше с этим смириться и вести себя соответсвенно, а не смущать народ.

    Re: тип bool в C

    > Значит проверки на x == true будут проваливаться
    > для большинства случаев

    int main()
    <
    if (fn() != true) puts(«anonymous прав»);
    return 0;
    >

    Если функция возвращает либо false, либо true, все будет работать.
    Если функция возвращает char *, то fn() == true работать не будет.
    Но то же самое верно и для Си++.
    При чем здесь «В C нет булевского типа — лучше с этим смириться и вести себя соответсвенно, а не смущать народ»?

    Re: тип bool в C

    > Если функция возвращает char *, то fn() == true работать не будет.

    Именно. Догадайтесь почему. (Правильный ответ — нет встроенного преобразования char* -> bool, т.к.»В C нет булевского типа» )

    > Но то же самое верно и для Си++.

    При чем здесь это? Кстати и в С++ bool не слишком нужен. (Я не имею в виду vector ).

    > При чем здесь «В C нет булевского типа — лучше с этим смириться и вести себя соответсвенно, а не смущать народ»?

    Проверки типа ‘!= NULL’, ‘== FALSE’, ‘== TRUE’ в С не только бесполезны, но и вредны. Тот кто их применяет — напрашивается на неприятности сам и делает код менее читаемым для окружающих.

    Re: тип bool в C

    Re: тип bool в C

    false == 0
    frue == не 0 (любое число != 0)

    Re: тип bool в C

    Предпоследнему anonymous.
    Похоже, имеет место непонимание друг друга.
    Пример.

    /* При успешном выполнении функция возвращает 1. */
    /* При ошибке — 0. */
    int fn(const char *filename)
    <
    if (some_condition) return 0;
    /*. */
    if (another_condition) return 0;
    /*. */
    return 1;
    >

    int main(int argc, char *argv[])
    <
    if (!fn(argv[1])) return 1;
    /*. */
    return 0;
    >

    Так вот, функция fn может в данном случае выглядеть так:

    bool fn(const char *filename)
    <
    if (some_condition) return false;
    /*. */
    if (another_condition) return false;
    /*. */
    return true;
    >

    При этом отпадает необходимость такого комментария:

    /* При успешном выполнении функция возвращает 1. */
    /* При ошибке — 0. */

    для каждой подобной функции.
    Если такая функция одна, то не стоит и огород городить.
    А если их два десятка, то программа (IMHO) будет читаться легче — потому что достаточно взглянуть на объявление функции в файле .h, чтобы понять, что она возвращает. Если же функция объявлена как возвращающая int, то придется лезть в файл .c, чтобы почитать комментарий — ведь функция, возвращающая int, может вернуть не только признак успешного/ошибочного выполнения, но и вообще все, что угодно — например, дескриптор открытого файла. Функция же, возвращающая bool, может вернуть только признак успешности своего выполнения — во всяком случае, так подразумевается читающим программу.

    А ты в своем примере (с fn() == 45) нарушаешь общепринятые правила: объявляешь функцию так, будто она возвращает значение перечислимого типа, а на самом деле она возвращает другое.
    Вот поясняющий пример:

    int main()
    <
    static const char c[] = «123»;
    putchar(c[fn()]);
    puts(«segfault почему-то не случился»);
    return 0;
    >

    Функции (C++) Functions (C++)

    Функции — это блоки кода, выполняющие определенные операции. A function is a block of code that performs some operation. Если требуется, функция может определять входные параметры, позволяющие вызывающим объектам передавать ей аргументы. A function can optionally define input parameters that enable callers to pass arguments into the function. При необходимости функция также может возвращать значение как выходное. A function can optionally return a value as output. Функции полезны для инкапсуляции основных операций в едином блоке, который может многократно использоваться. В идеальном случае имя этого блока должно четко описывать назначение функции. Functions are useful for encapsulating common operations in a single reusable block, ideally with a name that clearly describes what the function does. Следующая функция принимает два целых числа от вызывающего объекта и возвращает их сумму. a и b являются параметры типа int. The following function accepts two integers from a caller and returns their sum; a and b are parameters of type int.

    Эту функцию можно вызывать, или вызывается, из любого количества адресов в программе. The function can be invoked, or called, from any number of places in the program. Значения, которые передаются в функцию аргументы, типы которых должны быть совместимы с типами параметров в определении функции. The values that are passed to the function are the arguments, whose types must be compatible with the parameter types in the function definition.

    Длина функции практически не ограничена, однако для максимальной эффективности кода целесообразно использовать функции, каждая из которых выполняет одиночную, четко определенную задачу. There is no practical limit to function length, but good design aims for functions that perform a single well-defined task. Сложные алгоритмы лучше разбивать на более короткие и простые для понимания функции, если это возможно. Complex algorithms should be broken up into easy-to-understand simpler functions whenever possible.

    Функции, определенные в области видимости класса, называются функциями-членами. Functions that are defined at class scope are called member functions. В C++, в отличие от других языков, функции можно также определять в области видимости пространства имен (включая неявное глобальное пространство имен). In C++, unlike other languages, a function can also be defined at namespace scope (including the implicit global namespace). Такие функции называются свободные функции или не являющиеся членами функции; они широко используются в стандартной библиотеке. Such functions are called free functions or non-member functions; they are used extensively in the Standard Library.


    Функции могут быть перегружены, что означает разные версии функции могут совместно использовать тем же именем, если они отличаются по количество или типы формальных параметров. Functions may be overloaded, which means different versions of a function may share the same name if they differ by the number and/or type of formal parameters. Дополнительные сведения см. в разделе перегрузка функций. For more information, see Function Overloading.

    Части объявления функции Parts of a function declaration

    Функции, как минимум объявление состоит из возвращаемого типа, имя функции и список параметров (может быть пустым), а также необязательные ключевые слова, которые предоставляют дополнительные инструкции компилятору. A minimal function declaration consists of the return type, function name, and parameter list (which may be empty), along with optional keywords that provide additional instructions to the compiler. Следующий пример — объявление функции: The following example is a function declaration:

    Определение функции включает объявления, плюс текст, который является весь код между фигурными скобками: A function definition consists of a declaration, plus the body, which is all the code between the curly braces:

    Объявление функции, за которым следует точка с запятой, может многократно встречаться в разных местах кода программы. A function declaration followed by a semicolon may appear in multiple places in a program. Оно необходимо перед любыми вызовами этой функции в каждой записи преобразования. It must appear prior to any calls to that function in each translation unit. По правилу одного определения, определение функции должно фигурировать в коде программы лишь один раз. The function definition must appear only once in the program, according to the One Definition Rule (ODR).

    При объявлении функции необходимо указать: The required parts of a function declaration are:

    Возвращаемый тип, который задает тип значения, возвращаемого значения, или void Если значение не возвращается. The return type, which specifies the type of the value that the function returns, or void if no value is returned. В C ++ 11 автоматически является допустимым типом возвращаемого значения, которое предписывает компилятору логически распознавать тип с оператором return. In C++11, auto is a valid return type that instructs the compiler to infer the type from the return statement. Тип decltype (auto) также используется в C++14. In C++14, decltype(auto) is also allowed. Дополнительные сведения см. в подразделе «Выведение возвращаемых типов» ниже. For more information, see Type Deduction in Return Types below.

    Имя функции, которое должно начинаться с буквы или символа подчеркивания и не должно содержать пробелов. The function name, which must begin with a letter or underscore and cannot contain spaces. В стандартной библиотеке со знака подчеркивания обычно начинаются имена закрытых функций-членов или функций, не являющихся членами и не предназначенных для использования в вашем коде. In general, leading underscores in the Standard Library function names indicate private member functions, or non-member functions that are not intended for use by your code.

    Список параметров, заключенный в скобки. В этом списке через запятую указывается нужное (возможно, нулевое) число параметров, задающих тип и, при необходимости, локальное имя, по которому к значениям можно получить доступ в теле функции. The parameter list, a brace delimited, comma-separated set of zero or more parameters that specify the type and optionally a local name by which the values may be accessed inside the function body.

    Необязательные элементы объявления функции: Optional parts of a function declaration are:

    constexpr — указывает, что возвращаемое значение функции является константой, значение которой может быть определено во время компиляции. constexpr , which indicates that the return value of the function is a constant value can be computed at compile time.

    Его спецификация компоновки extern или статических. Its linkage specification, extern or static.

    Дополнительные сведения см. в разделе программа и компоновка. For more information, see Program and Linkage.

    встроенный, предписывающее компилятору команду заменять каждый вызов функции с код самой функции. inline, which instructs the compiler to replace every call to the function with the function code itself. Подстановка может улучшить эффективность кода в сценариях, где функция выполняется быстро и многократно вызывается во фрагментах, являющихся критическими для производительности программы. inlining can help performance in scenarios where a function executes quickly and is invoked repeatedly in a performance-critical section of code.

    Дополнительные сведения см. в разделе встраиваемые функции. For more information, see Inline Functions.

    Объект noexcept выражение, которое указывает ли функция может создавать исключения. A noexcept expression, which specifies whether or not the function can throw an exception. В следующем примере функция не вызывает исключение, если is_pod выражение, результатом которого является true. In the following example, the function does not throw an exception if the is_pod expression evaluates to true.

    Дополнительные сведения см. в разделе noexcept. For more information, see noexcept.

    (Только функции-члены) Cv квалификаторы, которые определяют, является ли эта функция const или volatile. (Member functions only) The cv-qualifiers, which specify whether the function is const or volatile.

    Цукерберг рекомендует:  Форма обратной связи - Форма обратной связи php

    (Только функции-члены) виртуального, override , или final . (Member functions only) virtual, override , or final . виртуальный указывает, что функция может быть переопределена в производном классе. virtual specifies that a function can be overridden in a derived class. override — означает, что функция в производном классе переопределяет виртуальную функцию. override means that a function in a derived class is overriding a virtual function. final — означает, что функция не может быть переопределена ни в одном из последующих производных классов. final means a function cannot be overridden in any further derived class. Дополнительные сведения см. в разделе виртуальные функции. For more information, see Virtual Functions.

    (только функции-члены) статический применяется к члену функция означает, что функция не связана с любой экземпляры объектов класса. (member functions only) static applied to a member function means that the function is not associated with any object instances of the class.

    (Только для не являющегося статическим члена функции) Ref квалификатор, который указывает компилятору, какую перегрузку функции следует выбрать, если неявный объект-параметр (*это) является ссылкой rvalue или ссылкой lvalue. (Non-static member functions only) The ref-qualifier, which specifies to the compiler which overload of a function to choose when the implicit object parameter (*this) is an rvalue reference vs. an lvalue reference. Дополнительные сведения см. в разделе перегрузка функций. For more information, see Function Overloading.

    На следующем рисунке показаны компоненты определения функции. The following figure shows the parts of a function definition. Затененная область является телом функции. The shaded area is the function body.

    Части определения функции Parts of a function definition

    Определения функций Function definitions

    Объект определение функции состоит из объявления и тело функции, заключенных в фигурные скобки, который содержит объявления переменных, операторы и выражения. A function definition consists of the declaration and the function body, enclosed in curly braces, which contains variable declarations, statements and expressions. В следующем примере показано определение функции при завершении: The following example shows a complete function definition:

    Переменные, объявленные в теле функции, называются локальными. Variables declared inside the body are called local variables or locals. Они исчезают из области видимости при выходе из функции, поэтому функция никогда не должна возвращать ссылку на локальную переменную. They go out of scope when the function exits; therefore, a function should never return a reference to a local!

    функции const и constexpr const and constexpr functions

    Можно объявить функцию-член как const для указания, что функция не разрешено изменять значения членов данных в классе. You can declare a member function as const to specify that the function is not allowed to change the values of any data members in the class. Объявив функцию-член как const, позволяет компилятору обеспечить const корректность. By declaring a member function as const, you help the compiler to enforce const-correctness. Если кто-то по ошибке предпринимается попытка изменить объект, используя функцию, объявленную как const, возникает ошибка компилятора. If someone mistakenly tries to modify the object by using a function declared as const, a compiler error is raised. Дополнительные сведения см. в разделе const. For more information, see const.

    Объявите функцию как constexpr когда он выдает значение возможно можно определить во время компиляции. Declare a function as constexpr when the value it produces can possibly be determined at compile time. Функция constexpr обычно выполняется быстрее, чем базовые функции. A constexpr function generally executes faster than a regular function. Дополнительные сведения см. в разделе constexpr. For more information, see constexpr.

    Шаблоны функций Function Templates

    Шаблоны функций подобны шаблонам классов. Их задача заключается в создании конкретных функций на основе аргументов шаблонов. A function template is similar to a class template; it generates concrete functions based on the template arguments. Во многих случаях шаблоны могут определять типы аргументов, поэтому их не требуется явно указывать. In many cases, the template is able to infer the type arguments and therefore it isn’t necessary to explicitly specify them.

    Дополнительные сведения см. в разделе шаблонов функций For more information, see Function Templates

    Параметры и аргументы функций Function parameters and arguments

    У функции имеется список параметров, в котором через запятую перечислено необходимое (возможно, нулевое) число типов. Каждому параметру присваивается имя, по которому к нему можно получить доступ в теле функции. A function has a comma-separated parameter list of zero or more types, each of which has a name by which it can be accessed inside the function body. В шаблоне функции могут указываться дополнительные типы или значения параметров. A function template may specify additional type or value parameters. Вызывающий объект передает аргументы, представляющие собой конкретные значения, типы которых совместимы со списком параметров. The caller passes arguments, which are concrete values whose types are compatible with the parameter list.

    По умолчанию аргументы передаются функции по значению, то есть функция получает копию передаваемого объекта. By default, arguments are passed to the function by value, which means the function receives a copy of the object being passed. Копирование крупных объектов может быть ресурсозатратным и неоправданным. For large objects, making a copy can be expensive and is not always necessary. Чтобы аргументы для передачи по ссылке (в частности ссылки lvalue), добавьте параметр квантификатор ссылки: To cause arguments to be passed by reference (specifically lvalue reference), add a reference quantifier to the parameter:

    Если функция изменяет аргумент, передаваемый по ссылке, изменяется исходный объект, а не его локальная копия. When a function modifies an argument that is passed by reference, it modifies the original object, not a local copy. Чтобы предотвратить изменение такого аргумента функции, следует определить как const &: To prevent a function from modifying such an argument, qualify the parameter as const&:

    C++ 11: Для явной обработки аргументов, которые передаются по ссылке rvalue или ссылку lvalue, используйте двойной амперсанд в параметре, чтобы указать универсальную ссылку: C++ 11: To explicitly handle arguments that are passed by rvalue-reference or lvalue-reference, use a double-ampersand on the parameter to indicate a universal reference:

    Функция, объявленная одним ключевым словом void в объявлении параметра списка не принимает аргументы, если ключевое слово void является первым и единственным членом списка объявления аргументов. A function declared with the single keyword void in the parameter declaration list takes no arguments, as long as the keyword void is the first and only member of the argument declaration list. Аргументы типа void в других местах списка создают ошибки. Arguments of type void elsewhere in the list produce errors. Пример: For example:

    Обратите внимание, что, хотя и существует нельзя указывать void аргумент Кроме описанного здесь, типы, производные от типа void (например, указатели на void и массивы void) может находиться в любом месте списка объявления аргументов. Note that, while it is illegal to specify a void argument except as outlined here, types derived from type void (such as pointers to void and arrays of void) can appear anywhere the argument declaration list.

    Аргументы по умолчанию Default Arguments

    Последним параметрам в сигнатуре функции можно назначить аргумент по умолчанию, т. е. вызывающий объект сможет опустить аргумент при вызове функции, если не требуется указать какое-либо другое значение. The last parameter or parameters in a function signature may be assigned a default argument, which means that the caller may leave out the argument when calling the function unless they want to specify some other value.

    Дополнительные сведения см. в разделе аргументы по умолчанию. For more information, see Default Arguments.

    типов возвращаемых функциями значений; Function return types

    Функция не может возвращать другие функции и встроенные массивы; Однако возможен возврат указателей на эти типы, или лямбда-выражение, создающий объект функции. A function may not return another function, or a built-in array; however it can return pointers to these types, or a lambda, which produces a function object. За исключением того, в этих случаях функция может вернуть значение любого типа, который находится в области, или он не возвращать никакого значения, в этом случае возвращаемый тип — void. Except for these cases, a function may return a value of any type that is in scope, or it may return no value, in which case the return type is void.

    Завершающие возвращаемые типы Trailing return types

    «Обычные» возвращаемые типы расположены слева от сигнатуры функции. An «ordinary» return type is located on the left side of the function signature. Объект завершающего возвращаемого типа находится в правой части подписи и предшествует оператор ->. A trailing return type is located on the right most side of the signature and is preceded by the -> operator. Завершающие возвращаемые типы особенно полезны в шаблонах функций, когда тип возвращаемого значения зависит от параметров шаблона. Trailing return types are especially useful in function templates when the type of the return value depends on template parameters.

    Когда автоматически используется в сочетании с завершающим возвращаемым типом, он просто служит в качестве заполнителя для любого действия выражения decltype и сам по себе не выполняет выведение типа. When auto is used in conjunction with a trailing return type, it just serves as a placeholder for whatever the decltype expression produces, and does not itself perform type deduction.

    Локальные переменные функции Function local variables

    Переменная, объявленная внутри тела другой функции, называется локальной переменной или просто локального. A variable that is declared inside a function body is called a local variable or simply a local. Нестатические локальные переменные видны только в теле функции. Если локальные переменные объявляются в стеке, они исчезают из области видимости при выходе из функции. Non-static locals are only visible inside the function body and, if they are declared on the stack go out of scope when the function exits. При создании локальной переменной и ее возвращении по значению компилятор обычно выполняет оптимизацию возвращаемого значения, чтобы избежать ненужных операций копирования. When you construct a local variable and return it by value, the compiler can usually perform the return value optimization to avoid unnecessary copy operations. Если локальная переменная возвращается по ссылке, компилятор выдаст предупреждение, поскольку любые попытки вызывающего объекта использовать эту ссылку произойдут после уничтожения локальной переменной. If you return a local variable by reference, the compiler will issue a warning because any attempt by the caller to use that reference will occur after the local has been destroyed.

    В C++ локальные переменные можно объявлять как статические. In C++ a local variable may be declared as static. Переменная является видимой только в теле функции, однако для всех экземпляров функции существует только одна копия переменной. The variable is only visible inside the function body, but a single copy of the variable exists for all instances of the function. Локальные статические объекты удаляются во время завершения, определенного директивой atexit . Local static objects are destroyed during termination specified by atexit . Если статический объект не был создан из-за того, что поток кода программы обошел соответствующее объявление, попытка уничтожения этого объект не предпринимается. If a static object was not constructed because the program’s flow of control bypassed its declaration, no attempt is made to destroy that object.

    Выведение возвращаемых типов (C ++ 14) Type deduction in return types (C++14)

    В C ++ 14, можно использовать автоматически можно указать компилятору вывести тип возвращаемого значения из тела функции без необходимости предоставления завершающего типа возвращаемого значения. In C++14, you can use auto to instruct the compiler to infer the return type from the function body without having to provide a trailing return type. Обратите внимание, что автоматически всегда выводит возврата по значению. Note that auto always deduces to a return-by-value. Используйте auto&& , чтобы дать компилятору команду выведения ссылки. Use auto&& to instruct the compiler to deduce a reference.

    В этом примере автоматически выведение как копии непостоянного значения суммы lhs и rhs. In this example, auto will be deduced as a non-const value copy of the sum of lhs and rhs.

    Обратите внимание, что автоматически не сохраняет постоянность выводимого типа, он выводит. Note that auto does not preserve the const-ness of the type it deduces. Для перенаправления, возвращаемое значение которой необходимо сохранить неизменность или привязанность (ref) аргументов функции, можно использовать decltype(auto) ключевое слово, которое использует decltype правила определения типов и сохраняет все сведения о типе. For forwarding functions whose return value needs to preserve the const-ness or ref-ness of its arguments, you can use the decltype(auto) keyword, which uses the decltype type inference rules and preserves all the type information. decltype(AUTO) может использоваться как обычное возвращаемое значение с левой стороны, или как завершающее возвращаемое значение. decltype(auto) may be used as an ordinary return value on the left side, or as a trailing return value.

    Приведенный ниже (на основе кода N3493), показывает decltype(auto) , используемая для включения точную пересылку аргументов функции в тип возвращаемого значения, неизвестный до шаблон создать экземпляр. The following example (based on code from N3493), shows decltype(auto) being used to enable perfect forwarding of function arguments in a return type that isn’t known until the template is instantiated.

    Возвращение нескольких значений из функции Returning multiple values from a function

    Существует несколько способов для возврата более одного значения из функции: There are various ways to return more than one value from a function:

    Инкапсуляция значения в объект с именем класса или структуры. Encapsulate the values in a named class or struct object. Требуется определение класса или структуры, чтобы быть видимым для вызывающего объекта: Requires the class or struct definition to be visible to the caller:

    Возвращает объект std::tuple или std::pair: Return a std::tuple or std::pair object:


    Visual Studio 2020 версии 15.3 и более поздние версии (состав /std: c ++ 17): Используйте структурированные привязки. Visual Studio 2020 version 15.3 and later (available with /std:c++17): Use structured bindings. Преимуществом структурированные привязки является то, что, в которых хранятся возвращаемые значения инициализации переменных в то же время, в которой они объявлены, что в некоторых случаях может быть значительно эффективнее. The advantage of structured bindings is that the variables that store the return values are initialized at the same time they are declared, which in some cases can be significantly more efficient. В этом операторе— auto[x, y, z] = f(); —квадратные скобки, вводят и инициализировать имена, которые находятся в области видимости блока всей функции. In this statement — auto[x, y, z] = f(); — the brackets introduce and intialize names that are in scope for the entire function block.

    Помимо использования самого возвращаемое значение, можно «вернуть» значения путем определения любое количество параметров для использования передаваемый по ссылке, чтобы ее можно изменить или инициализации значений объектов, которые вызывающий объект предоставляет. In addition to using the return value itself, you can «return» values by defining any number of parameters to use pass-by-reference so that the function can modify or initialize the values of objects that the caller provides. Дополнительные сведения см. в разделе аргументы функции ссылочного типа. For more information, see Reference-Type Function Arguments.

    Указатели функций Function pointers

    Как и в C, в C++ поддерживаются указатели на функции. C++ supports function pointers in the same manner as the C language. Однако более типобезопасной альтернативой обычно служит использование объекта-функции. However a more type-safe alternative is usually to use a function object.

    Рекомендуется typedef могут использоваться для объявления псевдонима для типа указателя функции, если объявление функции, возвращающей тип указателя на функцию. It is recommended that typedef be used to declare an alias for the function pointer type if declaring a function that returns a function pointer type. Пример For example

    Если оно не используется, то правильный синтаксис объявления функции можно вывести из синтаксиса декларатора для указателя на функцию, заменив идентификатор (в приведенном выше примере — fp ) на имя функции и список аргументов, как показано выше: If this is not done, the proper syntax for the function declaration may be deduced from the declarator syntax for the function pointer by replacing the identifier ( fp in the above example) with the functions name and argument list, as follows:

    Это объявление эквивалентно объявлению при помощи ключевого слова typedef, которое приводилось выше. The preceding declaration is equivalent to the declaration using typedef above.

    Функции

    Введение

    Ч ем дальше мы изучаем си, тем больше становятся программы. Мы собираем все действия в одну функцию main и по несколько раз копируем одни и те же действия, создаём десятки переменных с уникальными именами. Наши программы распухают и становятся всё менее и менее понятными, ветвления становятся всё длиннее и ветвистее.

    Но из сложившейся ситуации есть выход! Теперь мы научимся создавать функции на си. Функции, во-первых, помогут выделить в отдельные подпрограммы дублирующийся код, во-вторых, помогут логически разбить программу на части, в-третьих, с функциями в си связано много особенностей, которые позволят использовать новые подходы к структурированию приложений.

    Функция – это именованная часть программы, которая может быть многократно вызвана из другого участка программы (в котором эта функция видна). Функция может принимать фиксированное либо переменное число аргументов, а может не иметь аргументов. Функция может как возвращать значение, так и быть пустой (void) и ничего не возвращать.

    Мы уже знакомы с многими функциями и знаем, как их вызывать – это функции библиотек stdio, stdlib, string, conio и пр. Более того, main – это тоже функция. Она отличается от остальных только тем, что является точкой входа при запуске приложения.
    Функция в си определяется в глобальном контексте. Синтаксис функции:

    Самый простой пример – функция, которая принимает число типа float и возвращает квадрат этого числа

    Внутри функции sqr мы создали локальную переменную, которой присвоили значение аргумента. В качестве аргумента функции передали число 9,3. Служебное слово return возвращает значение переменной tmp. Можно переписать функцию следующим образом:

    В данном случае сначала будет выполнено умножение, а после этого возврат значения. В том случае, если функция ничего не возвращает, типом возвращаемого значения будет void. Например, функция, которая печатает квадрат числа:

    в данном случа return означает выход из функции. Если функция ничего не возвращает, то return можно не писать. Тогда функция доработает до конца и произойдёт возврат управления вызывающей функции.

    Если функция не принимает аргументов, то скобки оставляют пустыми. Можно также написать слово void:

    Формальные и фактические параметры

    П ри объявлении функции указываются формальные параметры, которые потом используются внутри самой функции. При вызове функции мы используем фактические параметры. Фактическими параметрами могут быть переменные любого подходящего типа или константы.

    Например, пусть есть функция, которая возвращает квадрат числа и функция, которая суммирует два числа.

    Обращаю внимание, что приведение типов просиходит неявно и только тогда, когда это возможно. Если функция получает число в качестве аргумента, то нельзя ей передать переменную строку, например «20» и т.д. Вообще, лучше всегда использовать верный тип или явно приводить тип к нужному.
    Если функция возвращает значение, то оно не обязательно должно быть сохранено. Например, мы пользуемся функцией getch, которая считывает символ и возвращает его.

    Передача аргументов

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

    Программы выведет
    200
    100
    200
    Понятно почему. Внутри функции мы работаем с переменной x, которая является копией переменной d. Мы изменяем локальную копию, но сама переменная d при этом не меняется. После выхода из функции локальная переменная будет уничтожена. Переменная d при этом никак не изменится.
    Каким образом тогда можно изменить переменную? Для этого нужно передать адрес этой переменной. Перепишем функцию, чтобы она принимала указатель типа int

    Цукерберг рекомендует:  Books - Книги начинающему

    Вот теперь программа выводит
    200
    100
    100
    Здесь также была создана локальная переменная, но так как передан был адрес, то мы изменили значение переменной d, используя её адрес в оперативной памяти.

    В программировании первый способ передачи параметров называют передачей по значению, второй – передачей по указателю. Запомните простое правило: если вы хотите изменить переменную, необходимо передавать функции указатель на эту переменную. Следовательно, чтобы изменить указатель, необходимо передавать указатель на указатель и т.д. Например, напишем функцию, которая будет принимать размер массива типа int и создавать его. С первого взгляда, функция должна выглядеть как-то так:

    Но эта функция выведет ERROR. Мы передали адрес переменной. Внутри функции init была создана локальная переменная a, которая хранит адрес массива. После выхода из функции эта локальная переменная была уничтожена. Кроме того, что мы не смогли добиться нужного результата, у нас обнаружилась утечка памяти: была выделена память на куче, но уже не существует переменной, которая бы хранила адрес этого участка.

    Для изменения объекта необходимо передавать указатель на него, в данном случае – указатель на указатель.

    Вот теперь всё работает как надо.
    Ещё подобный пример. Напишем функцию, которая принимает в качестве аргумента строку и возвращает указатель на область памяти, в которую скопирована эта строка.

    В этом примере утечки памяти не происходит. Мы выделили память с помощью функции malloc, скопировали туда строку, а после этого вернули указатель. Локальные переменные были удалены, но переменная test хранит адрес участка памяти на куче, поэтому можно его удалить с помощью функции free.

    Объявление функции и определение функции. Создание собственной библиотеки

    В си можно объявить функцию до её определения. Объявление функции, её прототип, состоит из возвращаемого значения, имени функции и типа аргументов. Имена аргументов можно не писать. Например

    Это смешанная рекурсия – функция odd возвращает 1, если число нечётное и 0, если чётное.

    Обычно объявление функции помещают отдельно, в .h файл, а определение функций в .c файл. Таким образом, заголовочный файл представляет собой интерфейс библиотеки и показывает, как с ней работать, не вдаваясь в содержимое кода.

    Давайте создадим простую библиотеку. Для этого нужно будет создать два файла – один с расширением .h и поместить туда прототипы функций, а другой с расширением .c и поместить туда определения этих функций. Если вы работаете с IDE, то .h файл необходимо создавать в папке Заголовочные файлы, а файлы кода в папке Файлы исходного кода. Пусть файлы называются File1.h и File1.c
    Перепишем предыдущий код. Вот так будет выглядеть заголовочный файл File1.h

    Содержимое файла исходного кода File1.c

    Наша функция main

    Рассмотрим особенности каждого файла. Наш файл, который содержит функцию main, подключает необходимые ему библиотеки а также заголовочный файл File1.h. Теперь компилятору известны прототипы функций, то есть он знает возвращаемый тип, количество и тип аргументов и имена функций.

    Заголовочный файл, как и оговаривалось ранее, содержит прототип функций. Также здесь могут быть подключены используемые библиотеки. Макрозащита #define _FILE1_H_ и т.д. используется для предотвращения повторного копирования кода библиотеки при компиляции. Эти строчки можно заменить одной

    Файл File1.c исходного кода подключает свой заголовочный файл. Всё как обычно логично и просто. В заголовочные файлах принято кроме прототипов функций выносить константы, макроподстановки и определять новые типы данных. Кроме того, именно в заголовочных файлах можно обширно комментировать код и писать примеры его использования.

    Передача массива в качестве аргумента

    К ак уже говорилось ранее, имя массива подменяется на указатель, поэтому передача одномерного массива эквивалентна передаче указателя. Пример: функция получает массив и его размер и выводит на печать:

    В этом примере функция может иметь следующий вид

    Также напомню, что правило подмены массива на указатель не рекурсивное. Это значит, что необходимо указывать размерность двумерного массива при передаче

    Либо, можно писать

    Если двумерный массив создан динамически, то можно передавать указатель на указатель. Например функция, которая получает массив слов и возвращает массив целых, равных длине каждого слова:

    Можно вместо того, чтобы возвращать указатель на массив, передавать массив, который необходимо заполнить

    На этом первое знакомство с функциями заканчивается: тема очень большая и разбита на несколько статей.

    C++. Как происходит возвращение локального объекта функциями?

    Читаю про конструктор копии. Про передачу аргументов в функцию разобрался, а вот с возвращением локального объекта функцией, запутался.
    Есть простой код:

    Конструктор стработал!
    Конструктор стработал!
    Деструктор стработал!
    Деструктор стработал!
    Для продолжения нажмите любую клавишу . . .
    Деструктор стработал!

    Сначала создается локальный объект в main, конструктор сработал.
    Вызывается функция, которая создает свой объект, конструктор сработал.
    После присваивания значения переменной, создается временный объект, который хранит это значение, а локальный объект удаляется, срабатывает деструктор!
    Затем, переменная в main, получает это значения, затирая предыдущее, а временный объект удаляется, деструктор сработал!
    Пауза.
    По завершению цикла временный объект уничтожается, деструктор так же срабатывает.

    Вроде все нормально.

    Но если поменять строчки:

    Результат становится таким:

    Конструктор стработал!
    Деструктор стработал!
    Деструктор стработал!
    Для продолжения нажмите любую клавишу . . .

    Объект создается только в функции, конструктор сработал.
    Создается временный объект, а локальный удаляется, деструктор сработал.
    Указатель получает адрес..
    Стоп.. А какой адрес он получил, временного объекта?
    Временный объект уничтожается, деструктор срабатывает.

    Выходит, указатель получил адрес на освобожденный временный объект?
    Я пробовал записывать в этот объект данные ob->a = 100;
    ОС никаких ошибок не выдавала.
    Вообщем, помогите пожалуйста разобратся, я запутался :(


    • Вопрос задан более двух лет назад
    • 673 просмотра

    Для локальных переменных всегда вызывается деструктор, поэтому есть техники использования этого свойства для некоторых нужд, типа лока мьютекса в конструкторе, и анлока в деструкторе.

    Обычные указатели хранят адрес. Для обычных указателей деструктор ничего не делает.
    А вот если локальная переменная представляет из себя не указатель, или умный указатель, то в его деструкторе может что-то быть, тем самым объект может высвободить память, либо произвести какие-либо другие манипуляции.

    Умные указатели позволяют более гибко управлять памятью. (shared_ptr, unique_ptr. )
    Они с технической точки зрения ничем не отличаются от обычных классов (структур).
    Им просто заранее ради вашего удобства написали подходящие реализации конструкторов, деструкторов, и других операторов.

    После высвобождения памяти адрес не перестаёт быть адресом, поэтому все манипуляции остаются возможными, кроме чтения и записи. Это уже как повезёт. Другое дело, что это не нормальное поведение — читать / писать высвобожденную память. Она уже может быть занята чем-то другим, либо изменена.

    Поэтому: никогда не возвращайте ни ссылки, ни адреса локальных переменных, за исключением тех, которые по факту глобальные: например те, которые объявлены как static.
    Однако такая static переменная находится в единственном экземпляре, а значит к ней нельзя будет одновременно обратиться из нескольких потоков. Точнее можно, но это не нормально, приведёт к ошибкам.

    Ах да, забыл ответить на заголовок.
    Точно не знаю, сначала опишу как можно это понимать, а потом уже укажу какую точность не знаю.

    Можно это понимать так: вы объявляете в вызывающей функции (caller) локальную переменную, и потом результат вызова присваиваете этой переменной. Эта переменная находится в стеке вызывающей функции (caller).
    В функции которую вы вызываете (callee) тоже есть локальная переменная результата.
    И после того как вызов закончился, caller присваивает результат от callee через оператор копирования.
    То есть

    1. Конструктор переменной в которую будет возвращён результат в функции из которой происходит вызов (caller). Кроме случая когда вызов является одним из параметров конструктора.
    2. Конструктор результата в функции которая вызвана (callee)
    3. Манипуляции с результатом (если есть таковые)
    4. Оператор копирования, кроме случая когда вызов является одним из параметров конструктора.

    Что за конструктор с параметром вызова? Ну например так:

    В таком случае результат используется прямо в конструкторе, поэтому оператор копирования не нужен. В таком случае конструктор называется конструктором копирования.

    По поводу точностей. Не знаю как стандарт регламентирует где должен и как передаваться результат. А так же не знаю, есть ли оптимизации которые пишут из callee прямо в caller.
    На StackOverflow пишут что есть у компиляторов оптимизации которые передают результат по ссылке или указателю, чего-бы это не значило.

    И наконец: ссылки ничем не отличаются от указателей в техническом плане. Это просто синтаксический сахар.

    Поправьте меня если я где-либо не прав. Буду рад.

    [C++] Вывод значения из функции в main

    Тема в разделе «Таверна», создана пользователем dofeeQQAAAA, 07 Jun 2020 в 09:42 .

    Оценить пост #

    dofeeQQAAAA

    #include «stdafx.h»
    #include
    #include
    using namespace std;
    #define N 15

    int Mas(int *arr, int size) <
    int i = 0;
    int chet = 1;
    int nechet = 0;
    for (i = 0; i

    Mae Borowski

    Спасибо, что представил код в таком удобоваримом виде. Читаю его прямо как стихи Маяковского.

    dofeeQQAAAA

    Mae Borowski

    Что-то мне подсказывает, что таким образом нельзя возвращать значения. В интернете по этому поводу рекомендуют создать структуру, которая будет хранить оба значения и которая будет возвращаться.

    Errathy

    1) почему для нечетных ты суммируешь, а для четных перемножаешь, это точно то, что нужно?
    2) с каких пор в плюсах можно возвращать несколько значений? Чтобы выводить в Main тебе нужно вернуть значения. Либо ты возвращаешь эти значения через аргументы — т.е. передаешь в функцию два инта по ссылкам/указателям и записываешь их внутри нее, либо явно возвращаешь что-нибудь вроде std::pair . В твоем коде ты возвращаешь только результат нечетных, так как оператор «,» возвращает правый аргумент.

    Ну и собственно, если значения ты возвращаешь, то проблема вывода очевидным образом решается — ты просто через cout выводишь их.

    #include «stdafx.h»
    #include
    #include

    using namespace std;

    Mae Borowski

    Вот кстати да. Просто делаешь функцию войдом и вместо return делаешь cout.

    nipple22

    Когда ты возврашаешь, тебе cout надо писать функцию, а не чет нечет. И добавь проверку, что возвращать, а не вместе. P.S непонятный код

    nubideus

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

    Legatus Legionis

    1) Ты пытаешься вернуть сразу 2 значения из функцииNotLikeThis
    2) Но ты их даже нигде не сохраняешьNotLikeThis
    3) Используешь потоки и переопределенный оператор, хотя не уверен, что понимаешь этоNotLikeThis
    В общем, предлагаю 3 варианта для получения этих значений из функции.
    1. Создать класс из двух переменных и вернуть объект этого класса.
    2. Вернуть массив из двух переменных (похоже это тебе и подойдет).
    3. Передать указатели на эти 2 переменные в функцию и спокойно их там менять. Даже возвращать ничего не придется.

    dofeeQQAAAA

    Чот вообще ничего не понял.

    1) почему для нечетных ты суммируешь, а для четных перемножаешь, это точно то, что нужно?
    2) с каких пор в плюсах можно возвращать несколько значений? Чтобы выводить в Main тебе нужно вернуть значения. Либо ты возвращаешь эти значения через аргументы — т.е. передаешь в функцию два инта по ссылкам/указателям и записываешь их внутри нее, либо явно возвращаешь что-нибудь вроде std::pair . В твоем коде ты возвращаешь только результат нечетных, так как оператор «,» возвращает правый аргумент.

    Ну и собственно, если значения ты возвращаешь, то проблема вывода очевидным образом решается — ты просто через cout выводишь их.

    #include «stdafx.h»
    #include
    #include

    using namespace std;

    void Mas( int *arr, int size, int& chet, int& nechet )
    <
    int i = 0;
    chet = 1;
    for( i = 0; i

    всё , да спасибо, очень помог) только надо было ещё chet = 1 и nechet = 0 убрать с функции и в мейне их обьявить и присвоить значения

    §5 Функции возвращающие значение. Перегрузка функций. Шаблоны

    Функции возвращающие значение

    Функции возвращающие значение имеют тип, который соответствует типу возвращаемого значения. Эти функции возвращают значение в точку возврата операцией обращения. Соответственно функция должна выступать как операнд в выражениях с присваиванием или выводом. Это отличает синтаксис функций возвращающих и не возвращающих значение. В определении функции должна быть инструкция return , которая принимает выражение для определения возвращаемого значения. Ниже приводится схема размещения в программе функции возвращающей значение.

    Функция в программе

    Объявление функции. Прототип

    Вызов функции

    Определение функции

    Инструкция return

    Инструкция return завершает выполнение функции и возвращает её значение. Инструкция return возвращает только одно значение (переменной или выражения). В некоторых случаях оператор return может применяться и в функциях не возвращающих значение (программа 13.1 стр. 23). Но в этих функциях пустая инструкция return применяется для выхода из функции и возврата управления в вызывающую программу.
    Приведем пример программы с функцией возвращающей значение.
    Составим программу с функцией возвращающей случайное значение на отрезке [a, b] . Назовем ее myRand() . Эта функция двух аргументов может применяться, например, для заполнения массива.

    В теле функции может быть несколько инструкций return . В таком случае только одна инструкция return вернет значение. Это можно запрограммировать с помощью условной инструкции. Приведем пример такой программы. Дано двузначное целое число n. Если это число четное получить произведение его цифр, иначе получить сумму его цифр.

    Данную функцию можно было определить и более лаконично, если вместо условной инструкции применить тернарную операцию:


    Использование тернарной операции в сочетании с инструкцией return находит довольно частое применение.
    Если требуется возвращать функцией более одного значения, то необходимо прибегать к передаче параметров по ссылке или указателю. Рассмотрим пример. Разработать программу в которой используется функция возвращающая разряды двузначного числа.

    Перегрузка функций

    Перегрузка функций позволяет использовать несколько функций с одним и тем же именем, но различными параметрами. Каждая такая функция должна иметь свой прототип и свое определение. Вызов той или иной перегруженной функции будет определять набор аргументов (сигнатура). Сигнатуры перегруженных функций должны различаться либо по типу, либо по количеству, либо и по тому, и по другому. Рассмотрим простой пример. Требуется составить программу которая выводит половину произведения аргументов, если в сигнатуре два аргумента и среднее арифметическое аргументов, если в сигнатуре три аргумента.

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

    Шаблоны функций

    Шаблоны функций (function template) предоставляют инструменты для проектирования функции. Шаблон позволяет отвлечься от конкретного типа данных, с которыми функция будет работать. Шаблон — является руководством для создания реального объекта. Это значит, что шаблон не создает функцию, а указывает компилятору как создать определение функции для соответствующего типа данных, который будут иметь, в определенный момент работы программы, аргументы создаваемой функции. Шаблоны применяются в тех случаях, когда для разных типов данных должен использоваться один и тот же алгоритм.
    Определение шаблона:

    где T1 , T2 — произвольные имена параметров типа шаблона. Каждому параметру типа должно предшествовать ключевое слово typename . Эти параметры применяются для указания типов различных объектов в заголовке и в теле функции. Компилятор свяжет эти параметры с соответствующим типом аргументов для определения типа параметров функции.
    Приведем пример задачи. Составить программу в которой необходимо сравнивать два объекта одного типа. Продемонстрировать использование созданной шаблонной функции для сравнения объектов разного типа.

    Особенности вызова функций в С++

    Не так давно у меня произошёл очередной разговор с коллегой на извечную тему: «по ссылке, или по значению». В результате возникла данная статья. В ней я хочу изложить результаты моего исследования по этой и смежным темам. Далее будут рассмотрены:

    • Регистры и их назначение при вызове функций.
    • Передача и возврат простых типов и структур.
    • Как передача по ссылке и по значению влияют на оптимизации тела функции компилятором.
    • Как используется место при многочисленных вызовах функций.
    • Механизм виртуальных вызовов.
    • Оптимизация хвостовых вызовов и рекурсии.
    • Инициализация структур, массивов и векторов.

    Осторожно! Статья содержит большое количество кода на C++ и ассемблере (Intel ASM с комментариями), а также множество таблиц с оценками производительности. Всё написанное актуально для x86-64 System V ABI, который используется во всех современных Unix операционных системах, к примеру, в Linux и macOS.

    Информация бралась из документа System V Application Binary Interface for x86-64. Ассемблерные листинги получены для clang 5.0.0 x86-64 с флагами -O3 -std=c++1z -march=sandybridge (с помощью сайта https://godbolt.org). Оценки производительности были сделаны для процессора Intel® Xeon® CPU E5-2660 2.20GHz.

    Содержание

    Регистры в x86-64

    Все данные хранятся в оперативной памяти. Для ускорения работы с ней используются многоуровневые кэши. Но для изменения данных, так или иначе, используются регистры (обсуждение в комментариях). Ниже дано очень краткое описание наиболее используемых регистров в архитектуре x86-64.

    • 16 регистров общего назначения: rax, rbx, rcx, rdx, rbp, rsi, rdi, rsp а также r8-r15 . Размер каждого из них равен 64 битам (8 байтам). Для доступа к младшим 32 битам (4 байтам) используется префикс e вместо r ( rax → eax ). Поддерживают только не векторные целочисленные операции.
    • rip (instruction pointer) указывает на инструкцию, которая будет исполнена следующей. Различные константные данные, лежащие в разделе памяти с инструкциями, могут считываться по смещению относительно rip .
    • rsp (stack pointer) указывает на последний элемент в стеке. Стек растёт в сторону меньших адресов. Запихивание чего-то в стек уменьшает значение rsp .
    • 16 SSE регистров размером 128 бит: xmm0 — xmm15 . Если поддерживается режим AVX , то они ссылаются на младшие 128 бит регистров ymm0 — ymm15 каждый из которых имеет размер 256 бит. Для векторных, или не целочисленных операций, данные необходимо предварительно загрузить в эти регистры.

    Передача параметров

    В этом параграфе приведено несколько сокращённое и упрощённое описание алгоритма распределения аргументов по регистрам/стеку. Полное описание можно увидеть на странице 17 «System V ABI».

    Введём несколько классов объектов:

    • INTEGER – интегральные типы, помещающиеся в регистры общего пользования. Это bool , char , int и так далее.
    • SSE – числа с плавающей точкой, вмещающиеся в векторный регистр. Это float и double .
    • MEMORY – объекты, передаваемые через стек.

    Для унификации описания, типы __int128 и complex представляются как структуры из двух полей:

    В начале каждый аргумент функции классифицируется:

    1. Если тип больше 128 бит, или имеет не выровненные поля, то он MEMORY.
    2. Если есть нетривиальные деструктор, конструктор копирования, виртуальные методы, виртуальные базовые классы, то он передаётся через «прозрачную ссылку». Объект заменяется указателем, который имеет тип INTEGER.
    3. Агрегаты, а это структуры и массивы, анализируются кусками по 8 байт.
      1. Если в куске есть поле типа MEMORY, то весь кусок MEMORY.
      2. Если есть поле типа INTEGER, то весь кусок INTEGER.
      3. Иначе весь кусок SSE .
    4. Если есть кусок типа MEMORY, то весь аргумент MEMORY.
    5. Типы long double и complex long double используют специальный набор x87 FPU регистров и имеют тип MEMORY.
    6. Типы __m256 , __m128 и __float128 имеют тип SSE .

    После классификации, все 8 байтные куски (в одном куске может быть несколько полей структуры, или элементов массива) распределяются по регистрам:

    1. MEMORY передаются через стек.
    2. INTEGER передаются через следующий свободный регистр rdi, rsi, rdx, rcx, r8, r9 в именно таком порядке.
    3. SSE передаются через следующий свободный регистр xmm0 — xmm7 .

    Аргументы рассматриваются слева направо. Те аргументы, которым не хватило регистров, передаются через стек. Если какому-либо куску аргумента не хватило регистра, то весь аргумент передаётся через стек.

    Возвращение значений производится следующим образом:

    1. MEMORY типы возвращаются через стек. Место на нём предоставляется вызывающей функцией и адрес его начала передаётся через rdi как будто бы это первый аргумент функции. При возврате это адрес должен быть возвращён через rax . Первый оригинальный аргумент будет передан, соответственно, как второй, и так далее.
    2. INTEGER кусок возвращается через следующий свободный регистр rax, rdx .
    3. SSE кусок возвращается через следующий свободный регистр xmm0, xmm1 . Эти регистры используются как для приёма, так и для возврата значений.
    Цукерберг рекомендует:  PHP и ООП. От новичка до профессионала. II часть

    Сводная таблица с регистрами и их назначением, очень полезна при чтении ассемблера:

    Регистр Назначение
    rax Временный регистр, возврат первого (ret 1) INTEGER результата.
    rbx Принадлежит вызывающей функции, не должен быть изменён на момент возврата.
    rcx Передача четвёртого (4) INTEGER аргумента.
    rdx Передача третьего (3) INTEGER аргумента, возврат второго (ret 2) INTEGER результата.
    rsp Указатель на стек.
    rbp Принадлежит вызывающей функции, не должен быть изменён на момент возврата.
    rsi Передача второго (2) INTEGER аргумента.
    rdi Передача первого (1) INTEGER аргумента.
    r8 Передача пятого (5) INTEGER аргумента.
    r9 Передача шестого (6) INTEGER аргумента.
    r10-r11 Временные регистры.
    r12-r15 Принадлежит вызывающей функции, не должны быть изменены на момент возврата.
    xmm0-xmm1 Передача и возврат первого и второго SSE аргументов.
    xmm2-xmm7 Передача с третьего по шестой SSE аргументов.
    xmm8-xmm15 Временные регистры.

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

    Простые примеры

    Если явно не указано обратное, то все используемые функции были помечены как NOINLINE . Делаем вид, что тело функции расположено в cpp файле, а LTO отключено. Также все результаты функций передаются в пустую NOINLINE функцию, чтобы предотвратить удаление всего кода оптимизатором.

    Рассмотрим что-нибудь простое.

    Параметры передаются так:

    Имя Регистр Имя Регистр Результат
    a rdi d rcx xmm0
    b rsi x xmm0
    c rdx y xmm1

    Рассмотрим сгенерированный код подробнее.

    Если в функцию передавать параметры простых типов, то нужно сильно постараться, чтобы они были переданы не через регистры.

    Рассмотрим различные примеры агрегатов. Массивы можно рассматривать как структуры с несколькими полями.

    Имя Регистр Имя Регистр Результат
    s.a xmm0 s.b xmm1 xmm0

    Казалось бы, ничто не мешает запихнуть сразу два double в один xmm регистр. Но увы, алгоритм распределения оперирует только восьмибайтными кусками.

    Если добавить ещё одно double поле, то вся структура будет передана через стек, так как её размер превысит 128 байт.

    Посмотрим, что будет, если заменить double на uint64_t .

    Имя Регистр Имя Регистр Результат
    s.a rdi s.b rsi rax

    Результат заметно компактнее. Подробнее про то, почему используется инструкция lea а не add , можно почитать, к примеру, тут: https://stackoverflow.com/a/6328441/1418863

    Если добавить ещё одно поле, то, как и в примере с double , структура будет передана через стек. Код, кстати, будет почти идентичен, даже загрузка на стек будет производиться через xmm регистры.

    Рассмотрим что-нибудь более интересное.

    Имя Регистр Имя Регистр Результат
    s1.a xmm0 s1.b xmm0 xmm0, xmm1
    s1.c xmm1 s1.d xmm1
    s2.a xmm2 s2.b xmm2
    s2.c xmm3 s2.d xmm3


    В каждый xmm регистр запихиваются по два float поля.

    Если бы в структуре было не 4, а три поля, то код функции был бы аналогичен, за исключением замены второй инструкции vaddps на vaddss , которая складывает только первые 64 бита регистра.

    Имя Регистр Имя Регистр Результат
    s1.a rdi s1.b rdi rax, rdx
    s1.c rsi s1.d rsi
    s2.a rdx s2.b rdx
    s2.c rcx s2.d rcx

    Внутри функции происходит битовая магия, но принцип примерно ясен. Каждая пара 32 битных чисел запаковывается в один 64 битный регистр. Возврат производится таким же образом.

    Посмотрим, что будет, если начать смешивать типы полей, но так, чтобы в пределах 8 байтовых кусков они были одинакового класса.

    Имя Регистр Имя Регистр Результат
    s1.a rdi s1.b rdi rax, xmm0
    s1.c xmm0 s1.d xmm0
    s2.a rsi s2.b rsi
    s2.c xmm1 s2.d xmm1

    Но это не интересно, так как типы полей в каждом 8 байтном куске совпадают. Перемешаем поля.

    Имя Регистр Имя Регистр Результат
    s1.a rdi s1.b rdi rax, rdx
    s1.c rsi s1.d rsi
    s2.a rdx s2.b rdx
    s2.c rcx s2.d rcx

    Смотрим пункт 3.2. Так как в 8 байтном куске есть и float , и int , то весь кусок будет иметь тип INTEGER и будет передан в регистрах общего назначения.

    Тут можно видеть 6 операций сдвига для извлечения float полей и их запихивания в регистр с результатом. А также отсутствие каких-либо векторных операций. В общем, лучше не мешать типы полей в пределах 8 байтовых кусков структуры.

    Передача по ссылке

    Передача параметров по константной ссылке аналогично передаче указателя на объект. Если объект не помещается в регистрах, то он передаётся и возвращается через стек. Посмотрим, как это происходит. Для реалистичности рассмотрим структуру для трёхмерной точки.

    Сравним код функций. Использоваться будут преимущественно новые xmm регистры, поэтому логика вполне понятна.

    Теперь посмотрим на место вызова этих функций.

    Посмотрим, что если у нас много полей, но структура всё-таки влезает в регистры. Тут начинается самое интересное.

    Код для функции, принимающей аргументы по значению.

    Имя Регистр Имя Регистр Результат
    s1.d[1:8] rdi s1.d[8:16] rsi rax, rdx
    s2.d[1:8] rdx s2.d[8:16] rcx

    Да, тут все аргументы копируются на стек, после чего из него извлекается и складывается по одному байту за раз. Как можно посчитать, в функции ровно 16 инструкций add . GCC, кстати, в данном примере выдаёт гораздо более компактный код, но всё так же с копированием через стек. Можно ли что-то улучшить? Передадим структуру по ссылке.

    Имя Регистр Имя Регистр Результат
    s1 rdi s2 rsi rax, rdx

    О да! Это выглядит гораздо лучше. Мы можем загрузить сразу 16 однобайтовых элементов в xmm регистр и вызывать vpaddb которая их все сложит за одну операцию. После этого результат копируется в выходные регистры через стек. Можно подумать, что от этой последней операции можно избавиться, заменив первый аргумент на не константную ссылку.

    Имя Регистр Имя Регистр Результат
    s1 rdi s2 rsi

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

    В этом случае, на каждой итерации вычисляется buff[i+1] += buff[i] , то есть имеется pointer aliasing. Для того, чтобы указать компилятору, что такого странного использования функции не предвидится, существует ключевое слово __restrict.

    Что и даёт желаемый результат.

    Изменении сигнатуры на void fooR3(St &__restrict s1, St s2) , тоже приведёт к раздутому ассемблерному коду, похожему на первый пример с St foo(St, St) .

    Кстати, если размер массива заранее не известен, то void foo(char* __restrict s1, const char* s2, int size) генерирует примерно в полтора раза меньше строк кода, чем версия без __restrict .

    Сравнение производительности

    Будем четыре раза прибавлять b к a , к примеру, для foo это будет выглядеть так:

    Code Cycles per iteration
    St a, b; st(a, b); 7.6
    4 x foo no reuse 121.9
    4 x foo 117.7
    4 x fooR no reuse 66.3
    4 x fooR 64.6
    4 x fooR1 84.5
    4 x fooR2 20.6
    4 x foo inline 51.9
    4 x fooR inline 30.5
    4 x fooR1 inline 8.8
    4 x fooR2 inline 8.8

    ‘no reuse’ указывает на то, что для хранения каждого результата используется новая переменная. auto a2 = foo(a, b); auto a3 = foo(a2, b); . ‘inline’ означает, что функции помечены как INLINE, а не NOINLINE.

    Если посмотреть на листинг fooR1 inline / fooR2 inline , то в нём будет всего пара инструкций, но в случае, когда структура передаётся, или возвращается через регистры, foo inline / fooR inline , компилятор сходит с ума и выдаёт сотни строк кода. Видимо, встраивание происходит после распределения всех полей по регистрам, после чего компилятор запутывается в происходящем и уже не может нормально упростить результат.

    Прозрачные указатели

    Посмотрим, что будет, если добавить немного деструктора.

    Первым параметром в rdi передаётся адрес, по которому нужно записывать результат. Вторым, в rsi , передаётся указатель на первый аргумент функции.

    Как видно, загрузка, умножение (через сложение) и сохранение производится по одному полю за раз. Компилятор не очень хочет оптимизировать не POD типы. Версия функции с константной ссылкой Point3f scaleR(const Point3f&) даст идентичный код. Посмотрим на место вызова.

    Если сделать деструктор NOINLINE, то всё будет гораздо запутаннее.

    Если параметр p явно передавать по ссылке, то кода станет несколько меньше и будет произведено только два вызова деструктора.

    Переиспользование стека

    Посмотрим, как компилятор переиспользует регистры и стек между вызовами. Будут использоваться функции из параграфа про ссылки.

    Видно, что в случае с передачей через регистры, кода значительно меньше. Если аргумент не помещается в регистры, то всё точно наоборот.

    Видно, что при передаче аргументов, помещающихся в регистры, код значительно короче, если передавать по значению. Если же они не помещаются – то лучше передавать по ссылке. В этом случае компилятор может удалить лишние копирования и передавать сразу указатель на стек с результатом предыдущего вызова. Причём он не может сделать так же, если параметр передаётся по значению через стек, и ему приходится выполнять лишние копирования.

    Сравнение производительности

    Посмотрим, какое влияние всё это оказывает на быстродействие. Функции для получения точек были объявлены в отдельном файле, чтобы предотвратить оптимизации для константных данных. Напомню, что при возврате из функции и передаче по значению, Point3f будет передаваться через регистры, а Point3d – через стек.

    Code Cycles per iteration
    auto r = pf(); 6.7
    auto r = scale(pf()); 11.1
    auto r = scaleR(pf()); 12.6
    auto r = scale(scale(pf())); 18.2
    auto r = scaleR(scaleR(pf())); 18.3
    auto r = scale(scale(scale(pf()))); 16.8
    auto r = scaleR(scaleR(scaleR(pf()))); 20.2
    auto r = pd(); 7.3
    auto r = scale(pd()); 11.7
    auto r = scaleR(pd()); 11.0
    auto r = scale(scale(pd())); 16.9
    auto r = scaleR(scaleR(pd())); 14.1
    auto r = scale(scale(scale(pd()))); 21.2
    auto r = scaleR(scaleR(scaleR(pd()))); 17.2
    Если функции пометить INLINE 8.1 — 8.9

    Если заменить Point3f на struct Point3i < int32_t x, y, z; >; и Point3d на struct Point3ll < int64_t x, y, z; >; , то различия в производительности будет менее выраженными. Видимо, значительное время уходит на распаковку параметров из регистров, вспомним, что в один 64 битный регистры обычно запаковывается сразу два int, а векторные операции они не поддерживают. С другой стороны, если заменить Point3f на struct Point2ll < int64_t x, y; >; и Point3d на struct Point4ll < int64_t x, y, z, a; >; , то цифры будут примерно такие-же.

    Что можно заметить:

    • Небольшие параметры лучше передавать по значению. Передача по ссылке только вредит.
    • Параметры, не влезающие в регистры, лучше передавать по ссылке.
    • Если есть возможность сделать функцию inline и поместить её в заголовочный файл, то это стоит сделать. При этом способ передачи параметров, по ссылке, или значению, не играет особой роли. По крайней мере для небольших простых типов.

    Тип optional

    Стандартный тип std::optional , а также boost::optional , в данный момент имеет кривую реализацию, которая исправлена только в «x86-64 clang (experimental concepts)» и, вроде бы, MSVC последних версий, поэтому

    будет совсем не эквивалентно

    Несмотря на одинаковые размер и выравнивание, OptPoint1 будет передаваться через стек, а OptPoint2 – через регистры.

    Впрочем, если пометить функции foo как inline , то генерируемый в месте использования код будет примерно одинаков.

    Видно, что в случае с простой структурой, компилятор смог соптимизировать код чуть лучше, но в остальном всё одинаково.

    Вывод: если функция не inline , то лучше всегда передавать std::optional по ссылке.

    Виртуальные функции

    Кратко рассмотрим, как у классов вызываются методы, в том числе виртуальные. Для реалистичности придумаем следующий пример. Допустим, мы делаем математическую библиотеку. Есть базовый класс функции, какие-то общие методы для работы с ним и наследники, реализующие, собственно, функции. В начале рассмотрим реализацию через наследование и виртуальные методы.

    В результат получим что-то вроде такого.

    Если сделать деструктор protected и не виртуальным, то весь код связанный с обработкой исключений исчезнет (строки 34-37). Если же оставить деструктор виртуальным и убрать NOINLINE, то компилятор встроит все вызовы функций и методов и запишет на стек готовый результат ( false в данном случае). Если пометить деструктор NOINLINE, то добавится просто куча кода с его вызовами. Для интереса переделаем пример с использованием шаблонов.


    Как видно, код заметно компактнее даже с NOINLINE.

    Сравнение производительности

    В данном случае была будет проводится итерация по вектору с 1000 элементов типа Add и вызова isFixedPoint для каждого из них.

    Code Cycles per iteration
    Виртуальный call и деструктор, без вызова isFixedPoint и call 5267
    Виртуальный call и деструктор, NOINLINE isFixedPoint 10721
    Виртуальный call и деструктор, INLINE isFixedPoint 8291
    Виртуальный только call , NOINLINE isFixedPoint 10571
    Без виртуальных методов, NOINLINE call , шаблонный NOINLINE isFixedPoint 10536
    Без виртуальных методов, без вызова isFixedPoint и call 4505
    Без виртуальных методов, INLINE call , шаблонный INLINE isFixedPoint 4531

    Что можно заметить:

    • Виртуальные функций вызываются очень быстро.
    • Указатель на таблицу виртуальных функций увеличивает размер класса, что может сказаться на производительности.
    • Даже функции, принимающие аргументы по указателю на базовый класс, лучше объявлять, как inline. Компилятор может их встроить и девиртуализировать.
    • Не inline шаблоны не дают особого выигрыша в производительности. Под не inline я имею ввиду что-то с определением в cpp файле и последующим явным инстанцированием, или явно помеченные как NOINLINE.
    • inline шаблоны дают нулевой оверхед при встраивании.

    Хвостовые вызовы

    Немного рассмотрим особенности вызова функции не через call , а через jmp без изменения размера стека.

    Немного оффтоп. Компилятор clang умеет оптимизировать хвостовую рекурсию. К примеру, возьмём функцию быстрого возведения в степень:

    Как видно, нет ни одного рекурсивного вызова, лишь циклы. Код этой же функции, реализованной через цикл, будет примерно аналогичен. Причём рекурсивный даже будет на

    Но я хотел рассмотреть особенности не рекурсивных вызовов функций непосредственно перед возвратом. Сравним три версии одной и той же функции по прибавлению единицы к аргументу.

    Как видно, если функция вызывается перед выходом и её результат никак не изменяется, то компилятор использует не call , а более быстрый jmp . Отличие в том, что адрес возврата не меняется, и после завершения функции sum , управление передаётся сразу в функцию, взывавшую add .

    Если пример немного усложнить и замерить производительность, то разница между скоростью выполнения функций будет в районе 10%.

    • Если из функции вызываются другие функции, то лучше всего их вызывать непосредственно перед выходом без манипуляций с результатом.
    • Аргументы лучше располагать везде в одном порядке.

    Инициализация

    В данном разделе я хочу рассмотреть особенности инициализации структур и массивов. Рассмотрим классы для 2D точки с различными состояниями по умолчанию:

    Структура Point не инициализируется. ZeroPoint заполняется нулями. По стандарту IEEE 754-1985:

    The number zero is represented specially: sign = 0 for positive zero, 1 for negative zero; biased exponent = 0; fraction = 0;

    Так что их можно смело обнулять с помощью memset . NanPoint заполняется значениями numeric_limits ::quiet_NaN(); Да, я сам видел реализацию с такими значения по умолчанию для точки.

    Один элемент

    Просто выделяем место на стеке без какой-либо инициализации.

    Обе строки дают идентичный результат.

    Выделяем место на стеке. Обнуляем регистр xmm0 . Это делается через XOR так как vxorps работает быстрее записи нуля. Копируем значение из регистра в память стека.

    То же самое, но в регистр загружаем значение из раздела с константными данными.

    Небольшой массив

    Здесь и далее будут использоваться следующие константы:

    Рассмотрим простой массив на стеке.

    Как и раньше – просто перемещение указателя на стек.

    Как и в случае с одной точкой, но размер стека пропорционально больше.

    Заметим явный вызов конструктора по умолчанию в третьей строке.

    Память очищается кусками по 256 бит, или 2 точки.

    Тут каждая точка инициализируется по отдельности.

    Большой массив

    Это было предсказуемо.

    После выделения места на стеке, вызывается memset . Указатель на начало, ноль в качестве заполнителся и размер, передаются через регистры rdi, esi, edx соответственно. Префикс e вместо d используется для манипуляции младшими 32 битами 64-битного регистра.

    Тут уже используется цикл. В каждой итерации копируются сразу три элемента, так как число 321 делится на 3 без остатка. В регистрах rax, rcx хранятся адреса начала и конца массива.

    А вот тут результат довольно неожиданный. Присутствует как цикл, так и вызов memset . Это логично, так как в общем случае, в структуре могут быть зазоры между элементами. Но в данном случае это откровенно лишняя работа.

    Динамический массив

    Вызываем new и обнуляем все элементы. По умолчанию, контейнеры инициализируют все элементы значением T<> , это поведение можно переопределить, предоставив собственный аллокатор. Листинги для ZeroPoint и NanPoint практически аналогичны. Увлеичим количество элементов:

    Как и в случае с массивом, инициализация происходит пачками через цикл. В случае с NanPoint листинг примерно аналогичен.

    A вот при использовании ZeroPoint код заметно отличается.

    Только в этом случае вызывается memset . Вспомним, что в случае с массивом, memset вызывался и при использовании Point . Посмотрим, что будет, если значение bigUnknownSize не известно на момент компиляции.

    Можно увидеть два цикла. Первый, LBB0_9 заполняет по одной точке до тех пор, пока количество оставшихся элементов не станет кратно 8. После чего идёт заполнение сразу 8 точек за итерацию.

    Как и в прошлый раз, листинг с Point примерно такой же, а при ZeroPoint опять используется memset :

    произведёт больше 250 строк ассемблера, что заметно больше случая с передачей размера непосредственно в конструктор. Но operator new вызывается только один раз, так как при создании пустого вектора он не используется.

    Сравнение производительности

    Теперь сравним скорость выполенения всех вышерассмотренных примеров.

    Code Cycles per iteration
    Point p; 4.5
    ZeroPoint p; 5.2
    NanPoint p; 4.5
    array

    p;

    4.5
    array p; 6.7
    array p; 6.7
    array

    p;

    4.5
    array p; 296.0
    array p; 391.0
    array

    p<>;

    292.0
    array p<>; 657.0
    vector

    p(smallSize);

    32.3
    vector p(smallSize); 33.8
    vector p(smallSize); 33.8
    vector

    p(bigSize);

    323.0
    vector p(bigSize); 308.0
    vector p(bigSize); 281.0
    vector p(smallUnknownSize); 44.1
    vector p(smallUnknownSize); 37.6
    vector

    p(bigUnknownSize);

    311.0
    vector p(bigUnknownSize); 315.0
    vector p(bigUnknownSize); 290.0
    vector p; p.resize(bigUnknownSize); 315.0

    • Создание массива на стеке без инициализации ничего не стоит.
    • Инициализация небольших массивов на стеке очень быстра.
    • Инициализация массивов на стеке через memset работает ощутимо быстрее, чем через явный цикл с инициализацией каждого элемента.
    • Не вызывайте конструктор у массива с элементами со сложной инициализацией.
    • Для небольших векторов выделение памяти более затратно, чем инициализация. Различия становятся малозаметны при размерах уже в несколько десятков элементов.
    • Инициализация элементов вектора происходит даже при отсутствии конструктора. Для изменения подобного поведения можно использовать свой аллокатор.
    • У векторов с большим количеством элементов, значительное количество времени уходит на инициализацию. Причём знание размера на момент компиляции не так важно.

    Возвращение true или что-то в функции bool

    Я хочу знать, имеет ли, например, эту функцию:

    Я знаю, что он возвращает true но возвращает ли он его, не оценивая другую часть утверждения, или он снова оценивает функцию?

    Нет. Это не приведет к вызову func().

    ЛОГИЧЕСКИЙ ИЛИ оценивает «Влево-Вправо» и останавливается, когда выражение возвращает true

    вы можете проверить его в любом случае. Вы проверяете его?

    протестируйте его с помощью следующей программы:

    он просто выполнит, не будет ничего печатать на консоли.

    Если вы имеете в виду другую часть кода в той же функции над оператором return, то да, он будет выполнен, даже если возвращаемое значение истинно внизу. если вы имеете в виду рекурсивную часть, то нет, она не будет выполнена, так как перед вызовом в этом выражении будет истинно. Если было return false||func() , только тогда здесь будет рекурсия.

    Понравилась статья? Поделиться с друзьями:
    Все языки программирования для начинающих