Указатели: инициализация против объявления

17

Я C ++ noob, и я уверен, что это глупый вопрос, но я просто не совсем понимаю, почему возникает ошибка (не возникает) из следующего кода:

#include <iostream>
using namespace std;


int main() 
{
int a,*test; 

*test = &a;  // this error is clear to me, since an address cannot be 
             // asigned to an integer  


*(test = &a); // this works, which is also clear
return 0;
}

Но почему это тоже работает?

#include <iostream>
using namespace std;

int main() 
{
int a, *test= &a;  // Why no error here?, is this to be read as:
                   // *(test=&a),too? If this is the case, why is the 
                   // priority of * here lower than in the code above?

return 0;
}
    
задан maxE 17.06.2017 в 10:38
источник
  • int * x = y; означает, что y является инициализатором для x (не для * x). Это не выражение назначения –  M.M 17.06.2017 в 10:40
  • @ M.M ok, чем мне нужно прочитать int * test = & a как int * (test = & a)? –  maxE 17.06.2017 в 10:46
  • Нет, прочитайте его как объявление переменной с именем test, с типом int * и initializer & a. –  M.M 17.06.2017 в 10:57
  • * = & все имеют различное значение в декларации, чем в выражении –  M.M 17.06.2017 в 11:04
  • Как вы можете видеть, лучше избегать объявления более одной переменной в одной строке. –  Christian Hackl 17.06.2017 в 12:17

6 ответов

30

Основное различие между этими двумя строками

*test= &a; // 1
int a, *test= &a; // 2

- это первое выражение, состоящее из вызовов операторов с известными правилами приоритета:

       operator=
          /\
        /    \
      /        \
operator*    operator&
  |             | 
 test           a

, тогда как второе - объявление переменной и инициализация и эквивалентно объявлению int a; , за которым следуют:

   int*     test    =  &a
// ^^        ^^        ^^
//type    variable    expression giving
//          name        initial value

Ни один operator* и operator= даже не используется во второй строке.

Значение токенов * и =& , а также , ) зависит от контекста , в котором они отображаются: внутри выражения, которое они стоят для соответствующих операторов, но в объявлении * обычно появляется как часть типа (что означает «указатель на»), а = используется для отметки начала выражения (копии) инициализации ( , отделяет несколько деклараций , & как «ссылка на» также входит в тип).

    
ответ дан Daniel Jour 17.06.2017 в 10:58
источник
  • Хороший ответ. Это должно быть самое лучшее. Я просто удалил свое объяснение. –  Umashankar Das 17.06.2017 в 11:41
  • Плюс один для использования искусства ASCII. –  NH. 17.06.2017 в 16:26
  • @isanae действительно, моя попытка сделать его легко понятным в одном предложении слишком расплывчата. Я добавил немного подробностей, теперь лучше? –  Daniel Jour 17.06.2017 в 17:30
  • @ DanielJour Выглядит отлично! –  isanae 17.06.2017 в 17:36
  • @ DanielJour большое спасибо! Я не знал, что есть разница между * в объявлении указателя в dereferenceoperator * –  maxE 18.06.2017 в 13:20
3
int a, *test= &a;

эквивалентно:

int a;
int* test = &a;

и отлично действует, когда вы инициализируете test , который имеет тип указателя на int с адресом переменной a , который имеет тип int .     

ответ дан Edgar Rokjān 17.06.2017 в 10:44
источник
  • Rokyan, но почему int a, * test; * test = & a; недействительно? –  maxE 17.06.2017 в 10:55
  • @maxE, потому что в этом случае вы проверяете разрыв, который имеет тип int *, поэтому вы пытаетесь присвоить int * int, который недопустим. –  Edgar Rokjān 17.06.2017 в 11:05
2

Вы смешиваете два использования для *.

В первом примере вы используете его для разыменования указателя. Во втором примере вы используете его для объявления «указателя на int».

Итак, когда вы используете * в объявлении, он должен сказать, что вы объявляете указатель.     

ответ дан haddie 17.06.2017 в 10:48
источник
2

Фактически вы делаете инициализацию в первом случае,

int *test = &a;

Это означает, что вы инициализируете указатель, для которого вы указываете * , чтобы сообщить компилятору, что его указатель.

Но после инициализации выполнение *test ( со звездочкой ) означает, что вы пытаетесь получить доступ к значению по адресу, назначенному для указателя test .
Другими словами, выполнение *test означает, что вы получаете значение a , потому что адрес a хранится в указателе test , который выполняется только с помощью &a .
& is оператор получает адрес любой переменной. И * - это оператор для получения значения по адресу.

Итак инициализация & amp; присваивание по-разному выводятся компилятором, даже если в обоих случаях присутствует звездочка * .

    
ответ дан Game_Of_Threads 17.06.2017 в 10:50
источник
1

Вы просто ударили по двум ужасным языковым пятнам: сжатие деклараций в одну строку и повторное использование символа * для несвязанных целей. В этом случае * используется для объявления указателя (когда он используется как часть сигнатуры типа int a,*test; ) и для обозначения указателя (когда он используется как оператор *test = &a; ). Хорошей практикой было бы объявлять переменные по одному, использовать автоматический вывод типа вместо типа copypasting и использовать выделенный метод addressof :

#include <memory> // for std::addressof

int a{};
auto const p_a{::std::addressof(a)};
    
ответ дан VTT 17.06.2017 в 11:45
источник
  • Это может работать в этом случае, но ваш код фактически изменяет семантику: ваш код использует прямую инициализацию, тогда как исходный код использует инициализацию копирования. Это имеет различия в отношении возможных неявных преобразований. –  Daniel Jour 17.06.2017 в 11:56
  • @ DanielJour Использование прямой инициализации также подразумевает менее строгий поиск конструктора (явные конструкторы также могут использоваться). Мой фрагмент также отличается тем, что он использует инициализацию списка, что может привести к различиям в отношении возможных неявных последовательностей преобразования (int foo = 1.0f; - fine, int foo = {1.0f}; - error). –  VTT 17.06.2017 в 12:15
  • Еще одно отличие здесь заключается в том, что в отличие от неинициализированного int a, вы явно по умолчанию инициализируете его значением по умолчанию, то есть 0. И хотя хорошая практика не оставлять вещи неинициализированными, если мы не можем ее оправдать (потому что там для этого случая) - я бы сказал, что ИМО мы должны явно передать инициализатору базовые типы, а не только неясные {}. –  underscore_d 18.06.2017 в 23:14
  • @underscore_d Цель состоит в том, чтобы использовать {} среднее значение «инициализировано содержимым по умолчанию» во всех ситуациях, независимо от того, был ли инициализирован примитивный тип или вызываемый конструктор по умолчанию класса. –  VTT 18.06.2017 в 23:52
  • Конечно, я понимаю, и полученная последовательность - хорошая цель. Я просто лично считаю, что с базовыми типами, где значение контролируется пользователем, лучше сказать, что мы хотим 0. Я не думаю, что когда-либо писал что-нибудь {}. Но у меня нет ничего против этого, кроме предпочтения! Разумеется, это был бы лучший рефлекс, чем случайное использование неинициализированных переменных. –  underscore_d 19.06.2017 в 00:09
0

Там есть тонкая разница.

Когда вы объявляете int a, * test, вы говорите «объявляете как целое число и объявляете тест как указатель на целое число, причем оба они неинициализированы».

В первом примере вы устанавливаете * test на & amp; right после деклараций. Это означает: «Установите целое число, на которое указывает контрольные точки (адрес памяти), на адрес«. ». Это почти наверняка приведет к сбою, потому что тест не был инициализирован, поэтому он будет либо нулевым указателем, либо тарабарщиной.

В другом примере int a, * test = & amp; a переводит: "объявляет a как неинициализированное целое число и объявляет тест как указатель, инициализированный адресом a." Это верно. Более подробно он переводится на:

int a, *test;
test = &a;
    
ответ дан user1258361 18.06.2017 в 02:49
источник