Я не программировал на C ++ в течение ряда лет, поэтому я решил освежить свои воспоминания об указателях.
В классическом примере переключения между двумя числами, пример
void swapPointersClassic(double *num1, double *num2)
{
double temp;
temp = *num1;
*num1 = *num2;
*num2 = temp;
}
Это позволяет нам сделать вызов функции как swapPointersClassic(&foo, &bar);
и поскольку мы передаем в память адреса обеих переменных foo и bar, функция извлечет значения и выполнит обмен. Но я начал задаваться вопросом, почему я не могу сделать следующее?
void swapPointers(double *num1, double *num2)
{
double *temp;
temp = num1;
num1 = num2;
num2 = temp;
}
Мне кажется, это имеет больше смысла, потому что нам нужно только создать достаточно временного хранилища для хранения адреса памяти num1 (вместо полного временного хранилища для хранения двойного значения * num1). Однако, похоже, что область действия функции ограничивает эффект замены указателя. После звонка swapPointers(&foo, &bar);
Я вижу, что внутри функции swapPointers, foo & бар действительно поменялся местами. После выхода из функции swapPointers foo и bar больше не меняются местами. Может кто-нибудь помочь мне понять, почему это так? Такое поведение напоминает мне о типичном подходе «проход за ценностью», но здесь мы передаем указатели. Таким образом, это означает, что мы можем касаться только значений, указанных этими указателями, но не самих указателей?
На самом деле вы не проходите мимо указателя. Вы передаете два указателя по значению. Передача указателей сама по себе не достаточно — вы должны разыменовать их. Акт разыменования (копий) указателей вызывает магию, и это фактическое место, где достигается обход области действия.
Изменение аргументов функции является локальным для функции, даже если сами аргументы являются указателями. Вы должен разыменовать их для доступа к памяти, на которую они указывают (опять же, «передача по указателю» — это больше, чем передача указателей — это передача указателей) а также используя их правильно).
Также учтите следующее. Если ваш второй подход сработал, и два указателя поменялись местами, это будет означать, что адреса переменных меняются. Это не совсем имеет смысла.
Между прочим, в C ++ у нас есть настоящее соглашение о передаче вызовов по ссылке, так что есть
незачем связываться с указателями:
void swap(double &a, double &b)
{
double temp = a;
a = b;
b = temp;
}
float a = 1, b = 2;
swap(a, b);
(Это просто вопрос реализации, что компилятор, скорее всего, реализует это поведение, фактически используя указатели, но по крайней мере у вас нет головной боли.)
Если вы хотите поменять местами указатели, а не их значения, вам нужно передать указатель указателя:
void swapPointers(double** num1, double** num2)
{
double* temp = *num1;
*num1 = *num2;
*num2 = temp;
}
Вы можете увидеть пример в: http://ideone.com/iklCta
Вы можете работать даже со ссылками:
void swapPointers(double*& num1, double*& num2) {
double* temp = num1;
num1 = num2;
num2 = temp;
}
Пример: http://ideone.com/w4k9mV
Это полезно, когда вы работаете с огромными объектами (например, данными изображения), и вы не хотите перемещать много памяти, а только ссылки.
Это хороший вопрос, но как только вы разобрались, пожалуйста, используйте std::swap
или же std::iter_swap
вместо того, чтобы писать свой собственный.
Если foo
а также bar
указатели, звонящие std::swap(foo, bar)
обменял бы адреса между двумя указателями. призвание std::swap(*foo, *bar)
или же std::iter_swap(foo, bar)
разыменовывает указатели и обменивается объекты указатели указывают на.
Вы передаете указатели сами по значению; num1
а также num2
являются локальные копии указателей, с которыми вызывающая сторона вызывает функцию. Таким образом, перестановка не видна в месте вызова при выходе из функции, поскольку изменения были сделаны только для переменных, которые являются локальными для функции.
Вместо этого измените функцию, чтобы она брала ссылки на указатели, и ваш код обмена будет работать так, как задумано.
void swapPointers(double*& num1, double*& num2) { ... }
Теперь ваши параметры функции являются псевдонимами для указателей, которые вызывающая сторона передает в функцию, и что бы вы ни делали с ними, это также повлияет на указатели в контексте вызывающей стороны.
Когда вы звоните
swapPointersClassic(&foo, &bar)
Вы берете адрес двух предметов на стек. Эти значения передаются как временные и копируются по значению. В своем теле функции вы меняете местами, куда указывают эти временные значения, но не двигаетесь туда, куда указывают foo и bar.
Самое главное, фу и бар живут на стек. Вы не можете изменить место, где переменные живут в стеке в C ++. Значение отображается на место в стеке и живет там. Вы можете получить указатель на то, где это значение живет, чтобы выполнить передачу по указателю (аналогично передаче по ссылке), но просто изменив место, куда указывает этот временный указатель, вы не меняете место, где находится указанный объект.
Когда вы проходите double *num1
в функцию, вы передаете указатель по значению. Это создает переменную в области действия функции, которая содержит адрес типа double. Когда вы назначаете это, вы изменяете значение указателя в области действия функции.
Так же, как вы должны передать указатель на double, разыменовать его и назначить для замены двойного, вы должны передать указатель на указатель, разыменовать его и назначить для замены указателя.
Указатели 101: указатель представляет собой лист бумаги с адресом дома на нем.
Письменно &bar, вы берете дом под названием bar и записываете его адрес на анонимном клочке бумаги.
Затем вы вызываете функцию с указателем в качестве аргумента. Здесь происходит то, что функция делает еще одну копию листа бумаги.
Затем ваша функция подкачки берет две такие локальные копии и меняет, какой адрес написан на них.
Так что это ничего не делает за пределами функции. Очистить?
Теперь принципиально, даже если внутри функции адреса не было копии, все, с чем вы работаете, — это макулатура с домашними адресами на них. Никакие изменения в такой бумаге не могут переместить данные, хранящиеся в домашней панели или в foo.
Поскольку надлежащий ответ (ы) на этот вопрос уже был опубликован, я просто поясню некоторые вещи.
Обмен значениями двух переменных с использованием ссылок в целом является плохой практикой, и ее следует избегать. причина в том, что ссылки не должны быть переназначены. Посмотрите на следующий код:
1. void swap(double &a, double &b)
2. {
3. double temp = a;
4. a = b; //Not what you think
5. b = temp;
6. }
7.
8. float a = 1, b = 2;
9. swap(a, b);
в строках 4 и 5 вы явно нарушаете общее правило: «Никогда не назначайте ссылки заново».
Если вам действительно нужно изменить значение переменной (ов), всегда предпочитайте «передавать по указателям», а не «передавать по ссылке».
Следующий код имеет смысл и является хорошей практикой IMO.
1. void swapPointersClassic(double *num1, double *num2)
2. {
3. double temp;
4. temp = *num1;
5. *num1 = *num2;
6. *num2 = temp;
7. }
8.
9. float a = 1, b = 2;
10. swap(&a, &b);
Согласились, что ссылки чище и проще в использовании, и они лучше скрывают информацию, как вы видели в первом примере.
Однако ссылки не могут быть переназначены. Если вам нужно указать сначала на один объект, а затем на другой, вы должны использовать указатель.
Практическое правило: