Страницы

Поиск по вопросам

воскресенье, 29 декабря 2019 г.

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

#cpp #функции #указатели


В чём различие между двумя инструкциями?

 void func() {}
 int main()
 {
    func(); // <--
    func;   // <--
 }

    


Ответы

Ответ 1



Первая строчка вызывает функцию, а вторая ничего не делает. Вторую строчку можно сравнить со следующим кодом: int n = 0; n; Т.е. это выражение, результатом которого является само выражение. В случае с n это значение переменной n, а в Вашем случае это адрес функции func. Можно Ваш пример дописать следующим образом: auto f = func; // <-- f(); Здесь, мы скопировали адрес func(этот то, что было у Вас, но мы дополнили присвоением), а потом мы вызываем func, косвенно, через f.

Ответ 2



Чтобы было более ясно, давайте разберемся последовательно с каждым предложением программы. Данное предложение void func() {} является объявлением функции с типом void(), то есть функции, которая не имеет параметров и имеет тип возвращаемого значения void. Данное предложение func(); // <-- вызывает эту функцию. Функция ничего не делает и не имеет побочных эффектов. Поэтому компилятор может выкинуть это предложение из сгенерированного объектного кода. Данное предложение func; // <-- состоит из выражения, в котором используется имя функции. Оно преобразуется в указатель на функцию типа void ( * )(). И опять-таки данное предложени не имеет побочных эффектов и может быть проигнорировано компилятором при генерации объектного кода. Вы могли бы получить более наглядный пример, если бы функция выполняла какие-то действия. Например, если бы вы объявили функцию как void func() { std::cout << "I'm called" << std::cout; } то результатом выполнения данного предложения func(); // <-- был бы вывод на консоль строки I'm called А результат этого предложения func; // <-- не изменился бы, так как это выражение ничего не вычисляет и, как я уже сказал, не имеет побочных эффектов. Другой наглядный пример. Рассмотрите программу #include int f1() { return 10; } int f2() { return 20; } int main() { int ( *pf[] )() = { f1, f2 }; int a1[] = { f1(), f2() }; int a2[] = { pf[0](), pf[1]() }; for ( int x : a1 ) std::cout << x << ' '; std::cout << std::endl; for ( int x : a2 ) std::cout << x << ' '; std::cout << std::endl; } В этой программе объявляются две функции f1 и f2. Затем объявляется массив указателей на эти функции int ( *pf[] )() = { f1, f2 }; В качестве инициализаторов массива используются выражения f1 и f2, которые преобразуются в указатели на эти функции. То есть сами функции не вызываются и не выполняются. Берутся их адреса, и этими адресами инициализируются элементы массива pf. Затем объявляется массив целых чисел a1. int a1[] = { f1(), f2() }; В качестве инициализаторов массива используются вызовы функций f1 и f2. Результатом вызова первой функции будет значение 10, а вызова второй функции - значение 20, Поэтому массив a1 инициализируется значениями 10 и 20. Затем объявляется второй массив int a2[] = { pf[0](), pf[1]() }; Он инициализируется результатами вызовов тех же самых функций, но с использованием указателей на эти функции, которые были занесены в элементы массива pf. Эти вызовы эквивалентны что для инициализации первого массива a1, и что для инициализации второго массива a2. Поэтому вывод на консоль будет одним и тем же для обоих циклов 10 20 10 20 Еще один пример программы, которая наглядно демонстрирует преобразование имени функции, используемой в выражениях, в указатель на функцию, и связанный с этим эффект, о котором даже квалифицированные программисты не подозревают. Итак, как было сказано, если у вас есть какая-то функция, как, например, void f() { std::cout << "I'm called!" << std::endl; } то использование ее имени в выражении f; приводит к преобразованию ее в указатель на нее саму. То есть это выражение имеет тип void ( * )() Разыменование указателя приводит к ссылке на саму функцию. То есть если вы напишите *f; То оператор разыменования, примененный к выражению f , снова даст, грубо говоря, саму функцию (lvalue на f) . И сразу же это выражение снова преобразуется в указатель на функцию, так как выражение *f не используется в операции, где требуется сама функция. Поэтому данные записи f; *f; **f; и т.д. все они являются выражениями, которые имеют тип указателей на функцию f Вот соответствующая программа, которая это демонстрирует #include void f() { std::cout << "I'm called!" << std::endl; } int main() { f(); ( *f )(); ( **f )(); ( ***f )(); ( ****f )(); } Ее вывод на консоль будет I'm called! I'm called! I'm called! I'm called! I'm called! Количество допустимых разыменований, то есть применения оператора * ограничивается лишь возможностями компилятора. Поэтому если у вас на работе производительность программиста оценивается количеством знаков написанного им кода, то вы можете использовать этот прием вызова функции для увеличения этого значения.:)

Комментариев нет:

Отправить комментарий