@JustSokol
newby programmer

Для чего нужна передача объекта в функцию по значению и по указателю в С++?

Прочитал более менее про различные варианты передачи объекта в функцию, вроде понял как работает.
Но не понятно когда на практике это реально может пригодится (передача по значению и передача по указателю)?

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

По указателю только 1 пример знаю, когда передается указатель на начало массива - тогда указателем можно орудовать как итератором, например.
Также нужно если в функции нужно менять значение указателя - тогда передается указатель на указатель. Хотя вещь сомнительная, т.к. если указатель изменится, то предыдущий адрес окажется утерянным. И соответственно старый объект может быть потерян, как и память, выделенная под него.
А другие применения на практике в хороших больших серьезных проектах какое?

По значению вообще не понимаю когда нужно если есть "const &".

Обращаю внимание что речь идет именно о двух конкретных случаях (передача в функцию объекта по значению, передача в функцию указателя на один объект), именно в рамках хорошей/большой программы/проекта.
  • Вопрос задан
  • 3096 просмотров
Решения вопроса 1
@Alexander1705
Указатели действительно не очень востребованы в языке C++, так как в большинстве случаев лучше использовать ссылки. Однако в языке С ссылок нет, и поэтому для совместимости иногда используют указатели.
Передача по значению создаёт копию передаваемого объекта. Это используется тогда, когда в функции (или методе) вам может понадобится изменить объект, однако эти изменения не должны затронуть тот объект, который вы передавали.

Например, поиск наибольшего общего делителя:
int gcd(int a, int b) {
   int c;
   while (b) {
      c = a % b;
      a = b;
      b = c;
   }
   return abs(a);
 }

В функции изменяются копии переданных объектов, а не сами объекты.
int a = 16;
int b = 4;
gcd(a, b);
cout << a << ' ' << b; // Выведет "16 4", а не "4 0"


Upd.1
Указатели используются в многих структурах данных. Например, связный список:
template<T> struct LinkedListItem
{
    T value;
    LinkedListItem* next;
};

Значение здесь использовать нельзя, так как объект не может содержать внутри объект того же типа (получается бесконечная рекурсия). Ссылку использовать также нельзя, так как ссылка должна быть сразу привязана к объекту (переменной), а в случае со списком, элементы могут быть добавлены/изменены в любое время.

Upd.2
Можно создавать указатели на функции. Функцию нельзя передать по значению или по ссылке.
#include <iostream>

float f1(float x)
{
    return 2 * x + 6;
}

float f2(float x)
{
    return -3 * x * x + 8 * x - 6;
}

void printTable(float(*f)(float), float beg=0.f, float end=10.f, float step = 1.f)
{
    for(float x = beg; x <= end; x += step)
    {
        std::cout << "x = " << x << "\ty = " << f(x) << '\n';
    }
}

int main(int argc, char** argv)
{
    printTable(f1);
    printTable(f2);
}
Ответ написан
Пригласить эксперта
Ответы на вопрос 1
Nipheris
@Nipheris Куратор тега C++
Как передавать - по значению или по указателю - целиком зависит от природы объекта (ссылки - отдельная история, отвечу ниже).
Существует две важнейших категории обрабатываемых в программе данных, которые редко рассматриваются в явном виде, но от которых, как говорится, не убежишь, по кр. мере в нефункциональном языке.
Первая категория - это "значения". Я не готов вам дать сейчас точное определение, но можете представлять себе эту категорию как те значения, что вы знаете из математики. Особенностью этих "значений" является то, что они несут ровно столько информации, сколько в них самих есть (в их представлении в ЭВМ). Ну то есть если вы видите строку "Василий", и другую строку "Василий" в другом месте, то как "значения" они ничем не отличаются. Также, одно целочисленное значение 5 ничем не отличается от другого 5. Кроме того, у значения нет жизненного цикла - у него нет прошлого и будущего, оно есть такое, каким оно было создано. Иными словами, у значения нет понятия изменяемости, оно не привязывается ко времени. Значение 6 - это уже другое значение, и неважно, как оно было получено. Важно, что значения не обязательно должны быть примитивными: комплексное число (5, 7) это такое же значение, как и его составляющие 5 и 7.
Вторая категория - это "переменные". Это уже совсем другие сущности в программе. Это такие контейнеры, куда кладутся значения, причем под "кладутся" понимается хорошо знакомая вам операция присвоения. Важно, что несмотря на то, что переменная как контейнер может содержать различные значения в разные промежутки времени, у нее всегда есть некоторый неизменный идентификатор. В языках с прямым доступом к памяти, таких как C++, это указатель. Неважно, где находится ваша переменная - на стеке или в куче - важно, что это контейнер, и у него есть уникальный идентификатор. Т.к. содержимое контейнера может изменяться - то мы можем говорить о прошлом и будущем этого контейнера, зная его идентификатор.
Если сузить понятие объекта до такой сущности, которая может изменять свое состояние, то объект в проекции на язык C++ - ничто иное, как переменная с четко определенным интерфейсом изменения состояния. Отсюда вытекает, что в плюсах identity объекта - это есть указатель на участок памяти, где этот объект находится.
Когда вы сравниваете значения, вы должны сравнивать их "начинку". Понятно, что ваши значения технически лежат в каких-то переменных, но 5 и 5 будут равны вне зависимости от того, где каждая из них находятся.
Другое дело объекты. Их природа такова, что один Студент не эквивалентен другому Студенту, если их identity, т.е. указатели, различаются (соотв-но, если указатели совпадают, то это, безусловно, один и тот же Студент). Это в общем-то очень хорошо моделирует реальный мир: даже если у студентов совпадают ФИО и дата рождения (и вообще все данные, что мы о них имеем) это не значит, что это один и тот же человек.
Когда вы плотнее займетесь ООП в C++ вы поймете, что именно адрес объекта в памяти - первичная информация для начала манипуляций с объектом. Адрес можно передать не только указателем, но и ссылкой, однако именно указатели, например, удобно сравнивать друг с другом. Список из студентов - это std::list<Student*>, а не std::list<Student>. Безусловно, вам никто не мешает использовать второй вариант, но тогда ваши студенты будут значениями (!), а не объектами (кстати, именно во втором случае std::list потребует от вас оператора сравнения для студентов).
Т.к. в С++ все значения копируются по умолчанию (перемещение сейчас не рассматриваем), то для моделирования "объектов", как самостоятельных сущностей с собственным жизненным циклом, нам нужны указатели и ручное управление памятью.
Ссылки - это лишь обертка, которая превращает указатель либо в l-value, либо в r-value, в зависимости от ее типа. const string& - это экономный способ смоделировать передачу "значения", а Student& - это по сути то же самое, что и Student*.
Ответ большой и сумбурный, но вам нужно еще со многим разобраться. Если что не понятно - спрашивайте в комментах.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы