Форум программистов, компьютерный форум, киберфорум
Наши страницы
PHP
Войти
Регистрация
Восстановить пароль
 
Рейтинг 4.62/186: Рейтинг темы: голосов - 186, средняя оценка - 4.62
Vovan-VE
13154 / 6538 / 1038
Регистрация: 10.01.2008
Сообщений: 15,070
#1

Как работают ссылки в PHP

05.08.2010, 20:27. Просмотров 33545. Ответов 3

Обсуждение статьи

Как работают ссылки в PHP

От автора

Данная статья предназначена для тех программистов, кто имеет опыт программирования в других языках и только начал изучать PHP, и для тех из начинающих, кто уже уверенно понимает суть языка и его основные примитивы (синтаксис, переменные, функции, типы данных).

Многие программисты, которые пришли в PHP из компилируемых языков (таких, как C и Pascal), невольно путают ссылки в PHP с указателями. В то же время, начинающие программисты без опыта вообще не понимают, что такое ссылки и как их использовать.

Терминология в статье

В данной статье используются следующие термины:
  • Переменная - Переменная PHP ($foo) или обращение к элементу массива ($foo['bar'][3]). Другими словами - это то, что можно передать в синтаксические конструкии isset(), unset(), empty() и т.п.
  • Значение - значение, которое хранится в Переменной.
  • Таблица переменных, Таблица - некий логический объект для визуального представления происходящего.

Таблица переменных

Где-то в недрах ядра PHP существует Таблица переменных. В этой Таблице в том или ином виде хранятся Переменные, Значения, и связь между ними. Для наглядности Таблицу переменных можно представить следующим образом:

Название: 01.png
Просмотров: 7142

Размер: 1.3 Кб
Рис.1

В левой половине - Переменные, в правой - Значения. Стрелки показывают связь Переменной со Значением. В данной примере изображены две переменные:
PHP
1
2
$foo = 2;
$bar = 'lol';
Важно
  1. Стрелки символизируют одностороннюю связь: Переменной важно знать свое Значение, но Значению совершенно все равно, к какой(-им) Переменной оно принадлежит.
  2. Связь (стрелка на изображении) может идти только от Переменной только к Значению.
  3. Каждая Переменная всегда имет ровно одну связь (ссылается на одно Значение).
Итак, давайте разбираться.

Простейшая ситуация

Самая простая ситуация со ссылками записывается кодом так:
PHP
1
2
$foo = 5;
$bar = &$foo;
После выполнения 1-й строки в Таблицу переменных заносится Переменная $foo и ее Значение 5. Выполнение 2-й строки создает Переменную $bar и устанавливает связь со Значением Переменной $foo, как показано на Рис.2 красной стрелкой.

Название: 02.png
Просмотров: 7148

Размер: 1.3 Кб
Рис.2

Что это значит? Операция присваивания по ссылке заставляет Переменную ссылаться на уже существующее Значение другой Переменной.
Обычное присваивание в существующую Переменную изменяет ее существующее Значение.
Обычное присваивание в несуществующую Переменную создает новое Значение в Таблице.

А теперь вполне логичный вопрос: Чем отличаются между собой связи, изображенные на Рис.2? Ответ: ничем, они абсолютно одинаковы. Если в коде выше поменять переменные местами:
PHP
1
2
$bar = 5;
$foo = &$bar;
то Таблица переменных будет выглядеть абсолютно так же (если не брать в расчет порядок создания Переменных).

Более того, каждое Значение знает, сколько Переменных на него ссылаются (количество стрелок на рисунке). В литературе это число называется счетчик ссылок (references counter).

Удаление Переменных

Продолжаем манипуляции над Таблица переменных, изображенной на Рис.2. Удаляем первую переменную после создания ссылки:
PHP
1
2
3
$foo = 5;
$bar = &$foo;
unset($foo);
Это изменение отобразится в Таблице следующим образом:

Название: 03.png
Просмотров: 7140

Размер: 1.4 Кб
Рис.3

Оператор unset() удаляет Переменную и исходящую от нее связь. Почему же при этом не удаляется Значение 5? Потому что еще остались другие Переменные, которые на него ссылаются.

Далее удаляем вторую переменную:
PHP
1
unset($bar);
Аналогично, из Таблицы удаляется Переменная $bar и ее связь со значением. После этого оказывается, что у Значения количество ссылок стало равным нулю, а это значит, что данное Значение тоже удаляется (Рис.4).

Название: 04.png
Просмотров: 7161

Размер: 1.3 Кб
Рис.4

Массивы

Более сложные для понимания вещи поджидают нас при махинациях с массивами. Итак, создаем простой массив с тремя элементами:
PHP
1
2
3
4
5
$arr = array(
    1 => 10,
    2 => 20,
    3 => 30,
);
Как его отобразить в Таблице переменных? Весь массив целиком - это Значение, ибо на него ссылается Переменная $arr. Также, можно заставить другую Переменную ссылаться на Значение любого элемента массива, поэтому 10, 20 и 30 - также должны быть отдельными Значениями.

Название: 05.png
Просмотров: 7191

Размер: 1.6 Кб
Рис.5

Здесь #1 - это некий идентификатор массива для внутреннего представления.

Чтобы прочитать значение элемента массива
PHP
1
echo $arr[2];
необходимо пройти через две связи в Таблице.

Ссылки и Массивы

Итак, к только что созданному массиву начинаем применять ссылки:
PHP
1
2
3
4
5
6
7
$arr = array(
    1 => 10,
    2 => 20,
    3 => 30,
);
$foo = &$arr;
$bar = &$arr[2];
Поскольку мы уже знаем, как расположить массив в Таблице переменных, добавить две Переменные со ссылками на существующие Значения не составит труда.

Название: 06.png
Просмотров: 7506

Размер: 2.4 Кб
Рис.6

Затем выполняем обычное присваивание:
PHP
1
$arr[1] = $arr[2];
Порядок действий таков:
  1. Для правой части присваивания в Таблице ищем Переменную $arr - находим массив #1.
  2. В массиве #1 ищем элемент с ключем 2 - находим Значение 20.
  3. Для левой части аналогично ищем Переменную $arr - находим массив #1.
  4. Аналогично в массиве #1 ищем элемент с ключем 1 - находим Значение 10.
  5. В найденное в п.4 Значение 10 копируем найденное в п.2 Значение 20.

Перечисленным действиям соответствуют оранжевые цифры на Рисунке 7.

Название: 07.png
Просмотров: 7613

Размер: 2.6 Кб
Рис.7

Далее делаем присваивание по ссылке:
PHP
1
$arr[2] = &$arr[3];
Существующая связь от Переменной #1[2] разрывается и создается новая связь до Значения 30.

Название: 08.png
Просмотров: 7595

Размер: 2.5 Кб
Рис.8

Если теперь уничтожить переменную $arr
PHP
1
unset($arr);
то из Таблицы будет удалена соответвтсующая Переменная и ее связь. Поскольку на Значение #1 все еще ссылается другая Переменная, то оно не будет уничтожено.

Название: 09.png
Просмотров: 7642

Размер: 2.6 Кб
Рис.9

Последний шаг порождает более интересные вещи:
PHP
1
$foo = 42;
Значение #1, на которое ссылается Переменная $foo, заменяется новым Значением 42. Что значит "заменяется"? Это значит, что старое #1 удаляется. Это приводит к удалению Переменных #1[1], #1[2] и #1[3] вместе с их связями. Тут же оказываются бесхозными два Значения, поэтому они тоже удаляются.

Название: 10.png
Просмотров: 7680

Размер: 2.8 Кб
Рис.10

В результате остается две простых пары Переменная - Значение.
32
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
05.08.2010, 20:27
Ответы с готовыми решениями:

Как с php отправить данные ссылки обратно
Не могу вникнуть как передать с помощью данных json и второй момент как парсить...

PHP функции file_exists, is_file, is_readable не работают с кириллицей. Как быть?
PHP функции file_exists, is_file, is_readable не работают с кириллицей. Как...

Не работают символы к php
Есть вот такой вот скрипт: bobr.php Я делаю к нему что-то вроде этого...

Cеансы в PHP,не работают скрипты
задание такое: 1й скрипт: установить с помощью сеансовых переменных координаты...

PHP и HTML ссылки
Привет всем! Есть такое что создать гипер ссылка при нажатие на него...

3
Vovan-VE
13154 / 6538 / 1038
Регистрация: 10.01.2008
Сообщений: 15,070
05.08.2010, 20:32  [ТС] #2
Неявные ссылки

Помимо явного использования ссылок (присваивание по ссылке и передача аргументов по ссылке) существуют также несколько вариантов неявного использования ссылок.

Доступ к глобальным переменным из функции

Как Вам известно, глобальные переменные сами по себе недоступны внутри функций. Например, в приведенном коде:
PHP
1
2
3
4
5
6
7
8
9
$foo = 5;
 
function bar() {
    $foo = 10;
    echo $foo;
}
 
bar();
echo $foo;
в 4-й строке идет работа с локальной переменной $foo, а не с глобальной, поэтому последний echo выведет 5, а не 10.

Чтобы получить доступ к глобальной переменной внутри функции, необходимо использовать оператор global:
PHP
1
2
3
4
5
6
7
8
9
10
$foo = 5;
 
function bar() {
    global $foo;
    $foo = 10;
    echo $foo;
}
 
bar();
echo $foo;
Теперь последний echo выведет 10, потому что внутри функции была изменена глобальная переменная $foo.

А теперь самое интересное. Задумывались ли Вы, как это работает? Что будет, если перед оператором global использовать такую же локальную переменную? Что будет, если внутри функции после оператора global удалить переменную?

Ответ на первый вопрос очень прост и описан в документации PHP. Оператор global создает ссылку на значение одноименной глобальной переменной. Иными словами, это эквивалентно присваиванию по ссылке из глобальной переменной в локальную.

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

Если после оператора global удалить эту переменную, то удалится всего лишь локальная переменная со своей ссылкой, а глобальная переменная никуда не денется. После этого с переменной можно работать, как с локальной:
PHP
1
2
3
4
5
6
7
8
9
10
11
$foo = 5;
 
function bar() {
    global $foo; // создаем ссылку на глобальную $foo
    $foo = 10;   // изменяем значение глобальной $foo
    unset($foo); // удаляем локальную копию и ссылку
    $foo = 20;   // создаем уже локальную $foo
}
 
bar();
echo $foo;
Этот код выведет 10, а не 20, потому что в 6-й строке мы удаляем локальную $foo вместе со ссылкой на глобальную $foo. В 7-й строке мы работаем уже с новой локальной переменной.

Статические переменные

Статические переменные видны только внутри функции, как локальные переменные, но их значение сохраняется между вызовами функции. Чтобы декларировать статическую переменную, необходимо использовать оператор static.
PHP
1
2
3
4
5
6
7
function bar() {
    static $foo = 0;
    echo $foo;
    $foo++;
}
bar();
bar();
При первом вызове в переменную $foo запишется указанное значение по умолчанию (0), а при последующих вызовах получившееся значение будет сохраняться.

На самом деле оператор static при первом вызове создает Значение, которое не уничтожается, когда на него больше не ссылается ни одна Переменная. Указанная переменная $foo становится ссылкой на это Значение.

Если перед оператором static уже была одноименная локальная переменная, то, аналогично оператору global, ее связь со старым значением разрывается. Если после оператора static удалить переменную, то ее статическое Значение останется в памяти на своем месте (даже если удалить все ссылающиеся на него Переменные).
PHP
1
2
3
4
5
6
7
8
function bar() {
    $foo = 42;       // локальная $foo
    static $foo = 0; // заставляем локальную $foo ссылаться на свое статическое Значение
    echo $foo;       // выводим значение статической $foo
    $foo++;          // изменяем статическое $foo
    unset($foo);     // удаляем $foo со ссылкой на статическое значение
    $foo = 21;       // снова создаем обычную локальную $foo
}
Здесь переменная $foo имеет статус статической переменной только от строки 3 до строки 6.
22
Vovan-VE
13154 / 6538 / 1038
Регистрация: 10.01.2008
Сообщений: 15,070
04.09.2010, 13:34  [ТС] #3
Ссылки и объекты
Важно! Нижеизложенное справедливо только для PHP версии 5 и выше.
При работе с ООП в PHP мы сталкиваемся со ссылками другого рода: ссылками на объект. Здесь важно не запутаться в терминологии. Для ясности небольшой пример:
PHP
1
2
3
4
5
6
7
class Foo {
    public $bar;
}
$a = new Foo();
$b = $a;
$b->bar = 42;
echo $a->bar; // 42
В строке 4 создаем объект класса Foo и присваиваем ссылку на него в переменную $a . Если объяснять на пальцах, то объект класса Foo можно сравнить с курткой в гардеробе. Присваивание в 4й строке кладет в переменную $a номерок от этой куртки, а не саму куртку. Далее в строке 5 мы копируем в переменную $b ссылку на тот же самый объект (номерок от той же самой куртки). Так мы получаем две одинаковых ссылки на один и тот же объект (два одинаковых номерка от одной и той же куртки). Следующий шаг: в строке 6, используя копию ссылки (копию номерка) в переменной $b, изменяем свойство того же самого объекта (кладем спички в карман той же самой куртки). Чтобы убедиться, что работа идет с одним и тем же объектом (с одной и той же курткой), считываем свойство объекта через оригинальную ссылку в переменной $a (проверяем карман куртки через оригинальный номерок).

Заметьте, что в данном примере идет обычное присваивание, и нет присваивания по ссылке.

Дополнительная терминология

Итак, появились два разных по смыслу термина "Ссылка". Чтобы избежать путаницы, будем называть "Ссылкой" именно те ссылки, о которых рассказывает вся статья. А ссылки на объекты (те самые номерки от курток) будем называть "Ссылками на объект" или "Ссылками объектов".

Ссылка на Ссылку объекта

Работая с объектами, при обычном присваивании мы и так копируем Ссылки объектов (номерки от курток) вместо копирования самих объектов. Возникает вопрос: зачем здесь вообще Ссылки, если мы и так работаем со Ссылками объектов?

Для ответа на вопрос рассмотрим функцию:
PHP
1
2
3
4
5
function foo(Bar $bar) {
    $bar->lol();
}
$a = new Bar();
foo($a);
Функция принимает в аргумент $bar объект класса Bar. В функцию передается копия Ссылки на объект, поэтому функция работает с оригинальным объектом, а не с его копией. По той же причине функция не может изменить исходную Ссылку на объект, которая хранится в переменной $a.

Другой пример:
PHP
1
2
3
4
5
6
function foo(Bar &$bar) {
    $bar->lol();
    $bar = null;
}
$a = new Bar();
foo($a);
Здесь функция принимает по ссылке в аргумент $bar объект класса Bar. Т.е. аргумент $bar становится Ссылкой на Значение Переменной $a. Получается Ссылка на Ссылку объекта. От предыдущего примера отличие следующее: теперь функция может изменить саму Ссылку на объект, а не только работать с объектом, на который она ссылается.

Итак, Ссылка на Ссылку объекта имеет право на существование, если Вы четко понимаете, что делаете.
19
Vovan-VE
13154 / 6538 / 1038
Регистрация: 10.01.2008
Сообщений: 15,070
04.09.2010, 17:53  [ТС] #4
Копирование при записи (Copy on write)

Копирование при записи напрямую не связано со ссылками в PHP. Однако с этим явлением Вы косвенно сталкиваетесь очень часто не только в PHP, но и во многих других языках программирования.

Источник проблемы

Предположим, есть код:
PHP
1
2
$foo = 'строка';
$bar = $foo;
Формально в этом коде создаются две переменные, в которых содержатся одинаковые строки. Если теперь изменить одну из них, то вторая останется неизменной.
PHP
1
2
3
4
5
$foo = 'строка';
$bar = $foo;
$foo[1] = 'Т';
echo $foo; // сТрока
echo $bar; // строка
Теперь рассмотрим другой пример:
PHP
1
2
3
4
5
function foo($str) {
    echo $str;
}
$bar = 'строка';
foo($bar);
Функция принимает по значению строковый аргумент. Формально в функцию передается копия строки. Ведь если внутри функции изменить значение переменной $str, то это изменение никак не повлияет на исходную строку в переменной $bar.

А что, если функция не будет изменять значение переданного аргумента? Напрашивается вывод, что строка будет копироваться впустую и, таким образом, вхолостую тратить ресурсы. Вот здесь-то и вступает в силу оптимизация по имени Копирование при записи.

Как это работает?

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

Чтобы понять, как она работает, воспользуется PHP функцией memory_get_usage(), которая возвращает объем памяти, занимаемой интерпретатором PHP.
PHP
1
2
3
4
5
6
7
echo memory_get_usage() . "\n"; // 321568
$foo = str_repeat('foobar', 32768);
echo memory_get_usage() . "\n"; // 518296
$bar = $foo;
echo memory_get_usage() . "\n"; // 518344
$bar[1] = '!';
echo memory_get_usage() . "\n"; // 715000
В 1й строке выводим изначальный объем памяти. В строке 2 создает строку объемом в 192 КБ, после чего в 3й строке снова отображаем использование памяти. Видим, что объем памяти существенно увеличился. Далее в строке 4 пытаемся скопировать строку из переменной $foo в переменную $bar. Однако в строке 5 мы видим, что памяти на это потребовалось совсем немного. Благодаря копированию при записи реально строка еще не скопировалась, потому что в этом пока нет необходимости. Фактически обе переменные работают с одной и той же строкой в памяти. Формально же они работают с разными "копиями" строки. Теперь в строке 6 изменяем один символ в одной и "копий" строк. Только теперь выполняется реальное копирование строки, о чем сигнализирует вывод в 7й строке.

Причем здесь ссылки?

Предоложим, что Вы создаете функцию, которая должна принимать в аргумент строку (или массив), которая теоритически может иметь достаточно большой объем. Сама функция не изменяет переданную строку, а использует ее только для чтения. Если бы Вы задумались об оптимизации, чтобы избежать холостого копирования (о котором говорилось выше), и если бы Вы не знали о существовании копирования при записи, то наверняка Вы бы в своей функции определили аргумент для передачи по ссылке.
28
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
04.09.2010, 17:53

PHP редирект (скрытие внешней ссылки от поисковиков)
Здравствуйте. Подскажите, пожалуйста, как правильно можно реализовать...

Не работают некоторые запросы в БД из PHP через AJAX с HTML страницы
Делаю сайт отеля, где есть возможность сложного поиска по номерам отеля,...

Парсинг ссылки, для каждого ip своя ссылка php
Нужно спарсить эту страницу -...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
4
Закрытая тема Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2018, vBulletin Solutions, Inc.
Рейтинг@Mail.ru