Как передавать - по значению или по указателю - целиком зависит от природы объекта (ссылки - отдельная история, отвечу ниже).
Существует две важнейших категории обрабатываемых в программе данных, которые редко рассматриваются в явном виде, но от которых, как говорится, не убежишь, по кр. мере в нефункциональном языке.
Первая категория - это "значения". Я не готов вам дать сейчас точное определение, но можете представлять себе эту категорию как те значения, что вы знаете из математики. Особенностью этих "значений" является то, что они несут ровно столько информации, сколько в них самих есть (в их представлении в ЭВМ). Ну то есть если вы видите строку "Василий", и другую строку "Василий" в другом месте, то как "значения" они ничем не отличаются. Также, одно целочисленное значение 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*.
Ответ большой и сумбурный, но вам нужно еще со многим разобраться. Если что не понятно - спрашивайте в комментах.