Передача параметров в функцию по указателю, ссылке и значению

Программирование Программирование на С++ Уроки по С++ для чайников Передача параметров в функцию по указателю, ссылке и значению

В этой теме 0 ответов, 1 участник, последнее обновление  Васильев Владимир Сергеевич 1 год, 6 мес. назад.

  • Автор
    Сообщения
  • #3714

    Говорят, что передача по указателю и ссылке эффективнее, чем передача по значению, а передача по ссылке — безопаснее, чем по указателю. Тогда почему вообще используются какие-либо виды передачи, кроме как по ссылке?

    При передаче параметра по значению происходит вызов конструктора копирования (создается новый объект), но при передаче по указателю и по ссылке этого не происходит — поэтому они эффективнее.

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

    int foo(Bar *bar) {
      if (bar != nullptr) { // тут может быть ошибка, если
        return bar->foo(); // указатель не был инициализирован
      }
      return 0;
    }

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

    state_pattern

    void DiagramItem::setText(string text) {
      state->setText(this, text);
    }
    
    void DiagramItem::move(Position pos) {
      state->move(this, pos);
    }
    
    void DiagramItem::select() {
      if (state)
        delete state;
      state = DiagramEditingItemSelectedState();
    }

    Дополнительные сведения

    ссылочные аргументы

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

    #include <iostream>
    using namespace std; 
    
    // обмен значений аргументов типа int
    void swap_copy(int a,int b) {int c=a; a=b; b=a;}
    void swap_ref(int &a, int &b) {int c=a; a=b; b=c;}
    
    int main() {
    int A=10,B=20;
    cout<<"A="<<A<<" "<<"B="<<B<<endl;
    
    // реально значения переменных не изменится
    swap_copy(A,B);
    cout<<"after swap_copy A="<<A<<" "<<"B="<<B<<endl;	
    
    // значения переменных изменились
    swap_ref(A,B);
    cout<<"after swap_ref A="<<A<<" "<<"B="<<B<<endl;	
    
    return 0;
    }

    функция как аргумент

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

    double add(double a, double b){return a+b;}
    double mul(double a, double b){return a*b;}
    
    // чтобы по короче
    typedef double (*OPfunc)(double,double); 
    
    double op(OPfunc f, double a, double b){return f(a,b);}
    ...
    cout << op(add,23.5,23.2);

    аргументы с атрибутом const

    Нужно помнить, что передавая аргументы по ссылке или указателю, мы даем возможность изменить объекты внутри функции. В одних случаях это нужно, в других нет. Для предотвращения изменения аргумента используется атрибут const.

    //объекты на которые ссылаются a и b
    //внутри функции доступны только для чтения
    void anyfunc(const int *a, const int &b);

    значения по умолчанию

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

    // обычно значения задают в объявлениях функциях
    int anyfunc(int a, int b, float c=3.14, bool d=true);

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

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

    Реализуется перегрузка генерацией нового имени функции на основе числа и типов аргументов. У каждого компилятора собственный алгоритм генерации имен. Поэтому при разработки библиотек для их переносимости используют только C функции. А по стандарту языка С, к именам функции добавляется только символ подчеркивания. Ниже приведены имена функций, которые сгенерировал компилятор MinGw. Посмотреть имена можно в объектных файлах (*.obj или *.o)

    //__Z4funci
    void func(int a){ a*456;}
    
    //__Z4funcdPKc
    int func(double a,const char* b){return a+456;}
    
    // для статических (*.lib,*.a) или 
    // динамических (*.dll для windows) библиотек
    // _func
    extern "C" double func(int*a){return 456;}

    переменное число аргументов

    В С++ разрешены функции с переменным числом аргументов. Например, библиотечная функция консольного вывода printf. Для указания переменного числа аргументов служит троеточие. При этом должен быть хотя бы один именованный аргумент, обычно определяющий число неименованных аргументов.

    Во-вторых, для большинства компиляторов способ передачи аргументов нельзя изменять (т.е. не использовать атрибуты функций подобные __fastcall). Для доступа к неименованным аргументам служат макросы определенные в stdarg.h.

    #include <iostream>
    #include <stdarg.h>
    using namespace std;
    
    int maxof(int, ...);
    
    int main() {
        cout<<"maximum="<<maxof(5, 1, 10, 34, 23, 12)<<endl;
        return 0;
    }
    
    int maxof(int n_args, ...) {
        int max, a;
        va_list ap; // список аргументов
    
        va_start(ap, n_args); // открываем список неименованных аргументов
        
        max = va_arg(ap, int); // берем первый аргумент
        for (int i = 2; i <= n_args; i++) {
            a = va_arg(ap, int); // берем следующий аргумент
            if (a > max)
                max = a;        
        }
    
        va_end(ap); // закрываем список
        return max;
    }

Для ответа в этой теме необходимо авторизоваться.