C : использование оператора

#c #pass-by-reference

#c #передача по ссылке

Вопрос:

Я сам изучаю C , и в процессе я пишу простые небольшие программы, чтобы изучить основные идеи. Что касается «передачи по ссылке», я не понимаю, почему работает следующий фрагмент кода (часть кода предназначена только для практики перегрузки конструкторов):

 #include <iostream>
#include <string>
using namespace std;

class Dude
{
public:
  string x;
  Dude();                    // Constructor 1
  Dude(const string amp;a);     // Constructor 2
};

Dude::Dude() : x("hi") {}
Dude::Dude(const string amp;a) : x(a) {}

int main()
{
  Dude d1;
  Dude d2 = Dude("bye");

  cout << d1.x << endl;
  cout << d2.x << endl;

  return 0;
}
  

В «main()» я создаю объект «d2» типа «Чувак» и использую конструктор 2, чтобы установить «x» в качестве строки «bye».

Но в объявлении конструктора 2 я сказал ему принять адрес строки, а не саму строку. Итак, почему я могу передать его «bye» (который является строкой). Почему мне не нужно создавать переменную string , а затем передавать адрес этой строки конструктору 2 из Dude?

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

1. Кто сказал, что ссылочный параметр означает передачу адреса? Они должны быть выполнены.

2. Хотя ссылка не является адресом для C , в реальной жизни она почти всегда реализуется таким образом. Но ссылки вообще не действуют как указатели. Ну, вряд ли.

3. Я изменил заголовок, чтобы лучше отразить вопрос / ответы.

4. Отредактированный код для перемещения инициализации конструктора за пределы определения класса.

Ответ №1:

На самом деле это иллюстрирует одну из самых крутых и полезных функций C : временные переменные. Поскольку вы указали, что ссылка на строку const равна , компилятор позволяет вам передавать ссылку на временное значение этой функции. Итак, вот что происходит за кулисами с Dude d2 = Dude("bye"); :

  • Компилятор определяет, что наилучшим конструктором для использования является Dude::Dude(const string amp;) . Как делается этот выбор — это совершенно другая тема.
  • Однако для использования этого конструктора вам нужно string значение. Теперь "bye" это a const char[4] , но компилятор может тривиально преобразовать это в a const char * , и это можно превратить в a string . Итак, создается анонимная временная переменная (вызовите ее temp1 ).
  • string::string(const char *) вызывается с "bye" помощью , и результат сохраняется в temp1
  • Dude::Dude(const stringamp;) вызывается со ссылкой на temp1 . Результат присваивается d2 (на самом деле, он присваивается другой временной переменной, а конструктор копирования for Dude вызывается с постоянной ссылкой на нее, и она присваивается d2. Но в этом случае результат тот же.)
  • temp1 отбрасывается. Именно здесь запускается строковый деструктор string::~string() temp1
  • Управление переходит к следующему оператору

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

1. «пока» — это не a const char * , это a const char[4] . В приведенном выше контексте он распадается на указатель.

2. Тонкое, но важное различие, особенно для тех, кто изучает язык и действительно хочет знать, как работает система типов. Ответ отредактирован, чтобы включить это.

Ответ №2:

Я думаю, вы неправильно понимаете, что amp; делает оператор в этом контексте. Получение адреса переменной ( amp;var ) отличается от указания того, что параметр должен быть передан в качестве ссылки (как у вас, в const string amp;a ).

На самом деле ваш код неявно создает новый string объект, который инициализируется строкой "bye" , а затем этот объект передается по ссылке на Dude конструктор. То есть ваш код по сути:

 Dude d2 = Dude(string("bye"));
  

и затем конструктор получает этот строковый объект по ссылке и присваивает его x через конструктор копирования.

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

1. Думаю, теперь я понимаю. В аргументе функции оператор amp; имеет другое значение, чем, скажем, при инициализации указателя. Кроме того, здесь многое происходит за кулисами, чего я не осознавал.

2. Точно, amp; оператор имеет два разных значения в этих контекстах. Перед, скажем, переменной он принимает ее адрес. Перед идентификатором при просмотре аргумента или после типа при объявлении (например: intamp; i = j ), это создает ссылку, а ссылка просто присваивает новое имя тому же значению. И это имя не может быть изменено.

Ответ №3:

В этом случае string имеет конструктор, который принимает const char* и не объявлен explicit , поэтому компилятор создаст временный string (созданный с string("bye") помощью вышеупомянутого конструктора), а затем ваш const stringamp; будет настроен на ссылку на этот временный.

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

1. Тот, о котором идет речь, создается из «bye».

2. @MooingDuck упс, эти два слова так близки и в то же время так далеки. Спасибо, также не стесняйтесь редактировать мой ответ на подобные вещи в будущем.

3. Я обычно исправляю опечатки только тогда, когда я абсолютно уверен , что у плаката есть правильная идея, и ошибаюсь в комментариях. Иногда я добавляю примеры кода, если это запрашивается, и плакат добавляет его недостаточно быстро.

4. Итак, создание объекта Dude «d2» происходит (выше) путем вызова конструктора 2, который неявно вызывает строковый конструктор для создания строки «bye» и указателя на эту строку, указатель передается конструктору 2, который сохраняет значение, сохраненное в этом указателе («bye») вd2.x?

5. @higgy не совсем. Создание d2 происходит путем создания временного string путем передачи "bye" string(const char*) конструктору для получения временного, а затем передачи этого временного (по ссылке) конструктору 2. После возврата конструктора временное значение уничтожается.

Ответ №4:

Две вещи:

1) В вашем коде нет такого понятия, как «адрес». const stringamp; означает «постоянную ссылку на a string «.

Возможно, вас смущает тот факт, что символ amp; также используется в совершенно другом контексте в качестве оператора «address-of» для создания указателя : T x; T * p = amp;x; . Но это не имеет никакого отношения к ссылкам.

2) На самом деле вы не обязательно используете конструктор, на который вы претендуете d2 ; скорее, вы создаете временный объект с помощью вашего конструктора #2, а затем создаете d2 с помощью конструктора копирования из временного. Прямая конструкция гласит Dude d2("bye"); .

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

1. Я согласен с вашим первым пунктом, но, возможно, вам следует объяснить, почему ссылка не является адресом.

Ответ №5:

Когда вы вызываете второй конструктор со строковым аргументом, создается временная переменная, которая ссылается на копию строки и передается конструктору.

Ответ №6:

Конструктор 2 не принимает адрес в строку, const stringamp; a означает постоянную ссылку на std::string объект. Причина, по которой вы можете передать конструктору строковый литерал, заключается в том, что std::string класс содержит неявный конструктор, который принимает const char * . Таким образом, компилятор неявно преобразует ваш строковый литерал в std::string первый перед вызовом конструктора 2.

Таким образом, следующие 2 строки эквивалентны

 Dude d2 = Dude("bye");
Dude d2 = Dude( std::string("bye") );
  

Кроме того, при написании конструкторов предпочитайте инициализировать переменные-члены в списке инициализаторов, а не в теле конструктора

 Dude(const string amp;a) : x(a) {}
  

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

1. Хорошо, итак, для конструктора 2 я бы поместил прототип Dude(const string amp;a) в определение класса, а затем инициализировал его с помощью Dude::Dude(const string amp;a) { x = a; } .

2. @higgy Нет, вы все равно должны использовать список инициализаторов. Dude::Dude(const string amp;a) : x(a) {}

Ответ №7:

временные файлы могут быть привязаны к постоянной ссылке, вероятно, по этой причине.

При вызове Dude("bye") компилятор проверяет, является ли это идеальным совпадением ( char[4] ) для любых конструкторов. Нет. Затем он проверяет, что определенные преобразования ( char* ) по-прежнему отсутствуют. Затем он проверяет пользовательские преобразования и обнаруживает, что std::string они могут быть неявно сконструированы из a char* , поэтому он создает a std::string из char* для вас и передает его по ссылке на Dude конструктор, который создает копию. В конце инструкции Dude d2 = Dude("bye"); временная строка автоматически уничтожается. Было бы раздражающе, если бы нам пришлось самим выполнять явные приведения для каждого отдельного параметра функции.

Переменные, переданные в ссылочный параметр, автоматически передадут вместо этого свой адрес. Это приятно, потому что позволяет нам обрабатывать объекты с семантикой значений. Мне не нужно думать о передаче ему экземпляра строки, я могу передать ему значение "bye" .

Ответ №8:

Конструктор #2 принимает ссылку на a const string . Это позволяет ему принимать ссылку либо на уже существующий объект, либо на временный объект (без const квалификатора ссылка на временный объект не будет принята).

std::string имеет конструктор, который принимает указатель на символ. Компилятор использует это для создания временного std::string объекта, а затем передает ссылку на этот временный объект вашему ctor.

Обратите внимание, что компилятор будет (неявно) выполнять только одно подобное преобразование для вас. Если вам нужно более одного преобразования для перехода от исходных данных к целевому типу, вам нужно явно указать все эти преобразования, кроме одного.

Ответ №9:

Хотя «amp;» является оператором addressof , когда он объявлен как часть определения / объявления метода, это означает, что ссылка передается на метод. Ссылкой в этом случае является d2 . Обратите внимание, что D2 — это не указатель, это ссылка. В конструкторе «a» представляет строковый объект с содержимым «hi». Это типичный пример передачи по ссылке на метод в C .