воскресенье, 31 января 2010 г.

Ссылки и указатели C++ как входные параметры

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

void do_some_calc(int arg)
{
    int count = arg;
    int prev = 1, prev_prev =0;
    for(int i = 0; i < count; i++)
    {
        if(i < 1)
        {
            prev_prev = prev;
            prev = arg;
        }
        arg = prev + prev_prev;
    }
}

Эта программка должна вычислять arg-е число Фибоначчи и записывать его в тот же arg (да, способ не самый адекватный, проехали это).

Допустим, мы создали переменную int number = 3 и отправили её в процедуру do_some_calc(number). Но по выходу из процедуры переменная number не сможет подтвердить, что мы действительно что-то вычисляли, потому что значение её не изменится.

А всё потому, что передавалась не сама переменная, а её копия. И вычисленное значение, сохранённое в копии переменной number с именем arg, по выходу из процедуры просто удалилось вместе со всем, что использовалось в её теле.

Этого можно избежать, передавая параметр по ссылке или указателю. В таком случае, мы будем иметь дело не с копией переменной, а с адресом оригинала переменной. А доступ к значению и всё прочее описано парой постов ранее. Соответственным образом и не составляет труда переписать код процедуры, чтобы она таки начала работать на нас.

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

void allocate_memory_for_ptr(int* ptr)
{
    ptr = new int[1000];
}


Да, передаётся указатель int* tricky_pointer. Да, выделяется память для него allocate_memory_for_ptr(tricky_pointer). Но по выходу из процедуры доступ к tricky_pointer[0]..tricky_pointer[whatever] получить не удастся. Более того, в процедуре только что нашими руками была создана утечка памяти.
Как ни странно, а в allocate_memory_for_ptr(...) была передана копия указателя tricky_pointer, которой повезло так же, как и переменной arg из прошлого примера. Можно заключить, что всё (абсолютно всё, включая указатели) передаваемое параметром в функцию, просто копируется. И этого эффекта будет незаметно, если в процедуре меняется только содержимое указателя или не меняется вообще ничего, как в примере с суммой элементов массива (для этого, в общем-то, придумана константность, но о ней как-нибудь попозже). Когда же меняется сам адрес, лучше поступить вот так:

void allocate_memory_for_ptr(int*& ptr)
{
    ptr = new int[1000];
}

А если говорить непосредственно о ссылках, то вот самая типичная процедура:

void change_ref(int& ref)
{
    int p = 5;
    ref = p;
}

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

int i = 0;
int& r = i;
change_ref(r);

с самой ссылкой ничего не станет, изменится лишь значение i. Ссылки предоставляют меньше свободы в работе с адресом, и вся процедура change_ref(...) по сути может быть  расписана как получение указателя на i в качестве параметра (по ссылке, по значению - без разницы, мы ведь меняем лишь значение) и изменения содержимого указателя. А вот поиграться непосредственно с адресом ссылки, как мы это проделывали в примере allocate_memory_for_ptr(...) не получится из-за ограничений на операции со ссылками.

Следовательно, ссылки, в общем-то, удобнее в работе, нежели указатели, но все различия между ними не заканчиваются на операции разыменовывания. И, как стало понятно, параметр, передаваемый в функцию, не может быть в неё "засунут" извне - он обязательно копируется. Другое дело уже в том, что иногда эта копия бывает нежелательна.

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

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