В чем разница между & C :: c и & (C :: c)?
Проверьте код ниже, и я поставил информацию о выходе в комментарии. Я использовал gcc 4.8.5 и Centos 7.2.
#include <iostream>
#include <cstdio>
class C
{
public:
void foo() {
printf("%p, %p\n", &C::c, &(C::c)); // output value is 0x4, 0x7ffc2e7f52e8
std::cout << &C::c << std::endl; // output value is 1
}
int a;
int c;
};
int main(void)
{
C co;
printf("%p\n", &C::c); // output value is 0x4
std::cout << &C::c << std::endl; // output value is 1
// printf("%p\n", &(C::c)); // compile error, invalid use of non-static data member 'C::c'
co.foo();
return 0;
}
- Согласно C++ оператору Precedence operator оператор
::
имеет более высокий приоритет, чем оператор &
. Я думаю, что &C::c
равен &(C::c)
, но результат говорит об обратном. Почему они разные? -
&(C::c)
вызывает ошибку компиляции в main, но не в функции foo
Почему? - Значение
&C::c
отличается в printf
и std::cout
, почему это так?
Ответы
Ответ 1
C++ различает два вида операндов для оператора &
, l значения в целом и (квалифицированные) идентификаторы в частности. В &C::c
операнд &
является квалифицированным идентификатором (т.е. Просто именем), тогда как в &(C::c)
операнд является общим выражением (потому что (
не может быть частью имени).
Форма квалифицированного идентификатора имеет особый случай: если она ссылается на нестатический член класса (например, ваш C::c
), &
возвращает специальное значение, известное как "указатель на член C". Смотрите здесь для получения дополнительной информации об указателях участников.
В &(C::c)
нет особого случая. C::c
разрешается нормально и дает сбой, потому что нет объекта, для которого нужно получить член c
. По крайней мере, то, что происходит в main
; в методах C
(например, ваш foo
) есть неявный this
объект, поэтому C::c
фактически означает this->c
там.
Что касается того, почему выходные данные отличаются для printf
и cout
: когда вы пытаетесь напечатать указатель на член с помощью <<
, он неявно преобразуется в bool
, возвращая false
если он является нулевым указателем, и true
противном случае. false
печатается как 0
; true
печатается как 1
. Ваш член указатель не является нулевым, поэтому вы получаете 1
. Это отличается от обычных указателей, которые неявно преобразуются в void *
и печатаются как адреса, но указатели-члены не могут быть преобразованы в void *
поэтому единственной применимой перегрузкой operator<<
является та, что для bool
. См. Https://en.cppreference.com/w/cpp/io/basic_ostream/operator_ltlt#Notes.
Обратите внимание, что технически ваши вызовы printf
имеют неопределенное поведение. %p
занимает void *
и вы передаете ему указатели разных типов. При нормальных вызовах функций срабатывает автоматическое преобразование из T *
в void *
, но printf
- это функция переменных аргументов, которая не предоставляет контекста типа своему списку аргументов, поэтому вам нужно ручное преобразование:
printf("%p\n", static_cast<void *>(&(C::c)));
Соответствующей частью стандарта является [expr.unary.op], где говорится:
Результатом унарного оператора &
является указатель на его операнд. Операндом должно быть lvalue или квалифицированный идентификатор. Если операндом является квалифицированный идентификатор, называющий нестатический или вариантный член m
некоторого класса C
с типом T
, результат имеет тип "указатель на член класса C
типа T
" и является предварительным значением, обозначающим C::m
В противном случае, если тип выражения T
, результат имеет тип "указатель на T
" [...]
Ответ 2
В то время как выражение &C::c
приводит к указателю на член c
выражение &(C::c)
дает адрес переменной-члена c
. Различие, которое вы видите в выводе, состоит в том, что std::cout
включает неявное преобразование bool, которое сообщает вам, является ли указатель нулевым или нет.
Поскольку &C::c
на самом деле не равно нулю, неявно преобразуется в bool
со значением true
или 1
Ответ 3
Q1: В синтаксисе &
есть специальное значение, за которым следует квалифицированный идентификатор без скобок. Это значит сформировать указатель на член. Кроме того, нет другого способа сформировать указатель на член. Это покрыто С++ 17 [expr.unary.op]/4:
Указатель на член формируется только тогда, когда используется явное &
а его операндом является квалифицированный идентификатор, не заключенный в скобки. [Примечание: то есть выражение &(qualified-id)
, где квалифицированный идентификатор заключен в скобки, не образует выражение типа "указатель на член". Также не квалифицированный идентификатор [...]
Q3: в обоих случаях, когда вы пишете printf("%p\n", &C::c);
&C::c
является указателем на член. Спецификатор формата %p
предназначен только для void *
поэтому это вызывает неопределенное поведение, и вывод программы не имеет смысла.
Код cout << &C::c;
выводит указатель на член через operator<<(bool val)
, поскольку существует неявное преобразование указателя на член в bool
(с результатом true
во всех случаях), см. [conv.bool]/1.
Для дальнейшего обсуждения того, как напечатать указатель на член, см. Этот ответ.
Q2: код &(C::c)
не формирует указатель на член, как объяснено выше.
Теперь код C::c
находится в грамматической категории id-выражения. (Который является квалифицированным идентификатором и неквалифицированным идентификатором). У id-выражения есть некоторые ограничения по его использованию, [expr.prim.id]/1:
Выражение id, которое обозначает нестатический член данных или нестатическую функцию-член класса, может использоваться только:
- как часть доступа члена класса, в котором выражение объекта ссылается на класс членов или класс, производный от этого класса, или
- сформировать указатель на член (7.6.2.1), или
- если это id-выражение обозначает нестатический элемент данных и появляется в неоцененном операнде.
Когда мы находимся внутри функции C::foo
, применяется первый из этих пунктов. Код такой же, как &c
но с ненужной квалификацией. Это имеет тип int *
. Вы можете вывести это с помощью std::cout << &(C::c);
который будет показывать адрес памяти, адрес this->c
.
Когда мы находимся в main
функции, ни одна из этих трех точек не применяется, и поэтому &(C::c)
имеет правильной формы.
Ответ 4
Во-первых, вы не можете получить доступ к int c
, используя &(C::c)
вне класса. &(C::c)
означает адрес памяти " c
экземпляра C
", в некоторой степени &(this->c)
здесь. Однако ваш c
не является статическим членом класса C
и не существует экземпляра C
Вы также не можете получить доступ к int x = C::c
снаружи.
Итак, вы видите ошибку с:
// printf("%p\n", &(C::c)); // compile error, invalid use of non-static data member 'C::c'
Если у вас есть static int c
, то C::c
вне класса в порядке, потому что здесь не нужен экземпляр.
И пусть бегут
#include <iostream>
#include <cstdio>
class C
{
public:
void foo() {
printf("%p, %p, this=%p\n", &C::c, &(C::c), this);
}
int a;
int c;
};
int main(void)
{
C co;
co.foo();
return 0;
}
Выход:
0x4, 0x7ffee78e47f4, this=0x7ffee78e47f0
// 0x4 + this == 0x7ffee78e47f4
// see reference
А для std::out
: << &C::c
неявно приводится к bool
, так что true
равно 1
вы видели. Вы можете рассматривать &C::c
как an offset of c in C
, это невозможно использовать без экземпляра C
Все это.
Некоторая ссылка: C++: указатель на член данных класса ":: *"
Полное описание: https://en.cppreference.com/w/cpp/language/pointer