В Java все переменные, которые вы объявляете, на самом деле являются «ссылками» на объекты (или примитивы), а не самими объектами.
При попытке выполнить один метод объекта , ссылка просит живой объект выполнить этот метод. Но если ссылка ссылается на NULL (ничего, нуль, void, nada), то нет способа, которым метод будет выполнен. Тогда runtime сообщит вам об этом, выбросив исключение NullPointerException.
Ваша ссылка «указывает» на нуль, таким образом, «Null -> Pointer».
Объект живет в памяти виртуальной машины пространство и единственный способ доступа к нему - использовать ссылки this
. Возьмем этот пример:
public class Some {
private int id;
public int getId(){
return this.id;
}
public setId( int newId ) {
this.id = newId;
}
}
И в другом месте вашего кода:
Some reference = new Some(); // Point to a new object of type Some()
Some otherReference = null; // Initiallly this points to NULL
reference.setId( 1 ); // Execute setId method, now private var id is 1
System.out.println( reference.getId() ); // Prints 1 to the console
otherReference = reference // Now they both point to the only object.
reference = null; // "reference" now point to null.
// But "otherReference" still point to the "real" object so this print 1 too...
System.out.println( otherReference.getId() );
// Guess what will happen
System.out.println( reference.getId() ); // :S Throws NullPointerException because "reference" is pointing to NULL remember...
Это важно знать - когда больше нет ссылок на объект (в пример выше, когда reference
и otherReference
оба указывают на null), тогда объект «недоступен». Мы не можем работать с ним, поэтому этот объект готов к сбору мусора, и в какой-то момент VM освободит память, используемую этим объектом, и выделит другую.
Причина в том, что у компилятора уже есть много дел, которые к тому же не являются полноценным интерпретатором, способным оценивать произвольный код C ++.
Если они придерживаются отдельных выражений, они резко ограничивают количество случаев, которые необходимо рассмотреть. Грубо говоря, это очень упрощает то, что в частности нет точек с запятой.
Каждый раз, когда встречается ;
, это означает, что компилятор должен иметь дело с побочными эффектами. Это означает, что в предыдущем утверждении было изменено какое-то локальное состояние, на которое будет опираться следующий оператор.Это означает, что оцениваемый код больше не является просто серией простых операций, каждая из которых принимает в качестве входных данных результат предыдущей операции, но также требует доступа к памяти, что гораздо труднее рассуждать.
Вкратце, это:
7 * 2 + 4 * 3
просто вычислить. Вы можете построить синтаксическое дерево, которое выглядит следующим образом:
+
/\
/ \
* *
/\ /\
7 2 4 3
и компилятор может просто пройти по этому дереву, выполняя эти примитивные операции на каждом узле, а корневой узел неявно является возвращаемым значением выражения.
Если бы мы записали одно и то же вычисление, используя несколько строк, мы могли бы сделать это так:
int i0 = 7;
int i1 = 2;
int i2 = 4;
int i3 = 3;
int i4 = i0 * i1;
int i5 = i2 * i3;
int i6 = i4 + i5;
return i6;
что гораздо труднее интерпретировать. Нам нужно обрабатывать операции чтения и записи в память, и мы должны обрабатывать операторы возврата. Наше синтаксическое дерево стало намного сложнее. Нам нужно обрабатывать объявления переменных. Нам нужно обрабатывать операторы, которые не имеют возвращаемого значения (например, цикл или запись в память), но которые просто где-то модифицируют некоторую память. Какая память? Где? Что, если он случайно перезапишет часть собственной памяти компилятора? Что, если произойдет ошибка?
Даже без всех мерзких «а что, если» код, который компилятор должен интерпретировать, стал намного более сложным. Синтаксическое дерево теперь может выглядеть примерно так: ( LD
и ST
- это операции загрузки и сохранения соответственно)
;
/\
ST \
/\ \
i0 3 \
;
/\
ST \
/\ \
i1 4 \
;
/\
ST \
/ \ \
i2 2 \
;
/\
ST \
/\ \
i3 7 \
;
/\
ST \
/\ \
i4 * \
/\ \
LD LD \
| | \
i0 i1 \
;
/\
ST \
/\ \
i5 * \
/\ \
LD LD \
| | \
i2 i3 \
;
/\
ST \
/\ \
i6 + \
/\ \
LD LD \
| | \
i4 i5 \
LD
|
i6
Мало того, что оно выглядит намного больше сложный, теперь он также требует состояния. Раньше каждое поддерево можно было интерпретировать изолированно. Теперь все они зависят от остальной части программы.Одна из листовых операций LD не имеет смысла , если она не помещена в дерево так, чтобы операция ST
выполнялась в том же месте ранее .
РЕДАКТИРОВАТЬ: игнорировать этот ответ. Ссылочная статья устарела. Стандарт допускает ограниченную рекурсию (см. Комментарии).
Обе формы являются незаконными. Рекурсия не разрешена в функциях constexpr из-за ограничения, что функция constexpr не может быть вызвана, пока она не определена. Ссылка, предоставленная OP, утверждает это в явном виде:
constexpr int twice(int x);
enum { bufsz = twice(256) }; // error: twice() isn’t (yet) defined
constexpr int fac(int x)
{ return x > 2 ? x * fac(x - 1) : 1; } // error: fac() not defined
// before use
Несколько строчек ниже:
Требование, чтобы функция с постоянным выражением могла вызывать только ранее определенные функции с постоянным выражением гарантирует, что у нас не возникнет никаких проблем, связанных с рекурсией.
...
Мы (до сих пор) запрещаем рекурсию во всех ее формах в константных выражениях.
Без этих ограничений вы впутываетесь в проблему остановки (спасибо @Grant за потрясение моей памятью с вашим комментарием к моему другому ответу). Вместо того, чтобы устанавливать произвольные пределы рекурсии, дизайнеры посчитали более простым просто сказать «Нет».
Как я понимаю, они сделали все как можно проще, чтобы не усложнять язык (на самом деле, я помню время, когда рекурсивные вызовы были запрещены, но это уже не тот случай). Это объясняется тем, что гораздо проще смягчить правила в будущих стандартах, чем ограничивать их.
Вероятно, плохо сформирован, потому что его слишком сложно реализовать. Аналогичное решение было принято в первой версии стандарта в отношении закрытия функций-членов (т. Е. Возможности передать obj.func
как вызываемую функцию). Возможно, более поздняя версия стандарта предоставит больше возможностей.
На случай, если здесь возникнет какая-то путаница, вы знаете, что функции / выражения constexpr
вычисляются во время времени компиляции . Здесь нет проблем с производительностью во время выполнения.
Зная это, причина того, что они разрешают только отдельные операторы возврата в функциях constexpr
, заключается в том, что разработчикам компилятора не нужно писать виртуальную машину для вычисления постоянного значения.
Однако меня беспокоят проблемы с QoI. Интересно, будут ли разработчики компилятора достаточно умны, чтобы выполнить мемоизацию?
constexpr fib(int n) { return < 2 ? 1 : fib(n-1) + fib(n-2); }
Без мемоизации вышеуказанная функция имеет сложность O (2 n ) , что, конечно, не то, что я ' хочу пощупать, даже во время компиляции.