Указатель на элемент структуры [дубликат]

== сравнивает ссылки на объекты в Java и не является исключением для объектов String.

Для сравнения фактического содержимого объектов (в том числе String) необходимо использовать equals.

Если сравнение двух объектов String с использованием == оказывается true, это связано с тем, что объекты String были интернированы, а виртуальная машина Java имеет несколько ссылки указывают на тот же экземпляр String. Не следует ожидать сравнения одного объекта String, содержащего то же содержимое, что и другой объект String, используя == для оценки как true.

188
задан Recolic Keghart 7 November 2017 в 13:49
поделиться

14 ответов

Это «указатель на член» - следующий код иллюстрирует его использование:

#include <iostream>
using namespace std;

class Car
{
    public:
    int speed;
};

int main()
{
    int Car::*pSpeed = &Car::speed;

    Car c1;
    c1.speed = 1;       // direct access
    cout << "speed is " << c1.speed << endl;
    c1.*pSpeed = 2;     // access via pointer to member
    cout << "speed is " << c1.speed << endl;
    return 0;
}

Что касается , почему вы хотели бы сделать это, то это даст вам другое уровень косвенности, который может решить некоторые сложные проблемы. Но, честно говоря, мне никогда не приходилось использовать их в моем собственном коде.

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

void Apply( SomeClass * c, void (SomeClass::*func)() ) {
    // do hefty pre-call processing
    (c->*func)();  // call user specified function
    // do hefty post-call processing
}

Скобки вокруг c->*func необходимы, поскольку оператор ->* имеет более низкий приоритет, чем оператор вызова функции.

153
ответ дан Oktalist 15 August 2018 в 17:21
поделиться

IBM содержит дополнительную документацию о том, как ее использовать. Вкратце, вы используете указатель как смещение в классе. Вы не можете использовать эти указатели отдельно от класса, на который они ссылаются, поэтому:

  int Car::*pSpeed = &Car::speed;
  Car mycar;
  mycar.*pSpeed = 65;

Кажется немного неясным, но одно из возможных приложений - это если вы пытаетесь написать код для десериализации общих данных во многие разные типы объектов, и ваш код должен обрабатывать типы объектов, о которых он абсолютно ничего не знает (например, ваш код находится в библиотеке, а объекты, в которые вы deserialize были созданы пользователем вашей библиотеки). Указатели-члены предоставляют вам общий, полуразборный способ обращения к смещениям отдельных данных, без необходимости прибегать к беспринципным void * трюкам, как вы могли бы использовать для C-структур.

24
ответ дан AHelps 15 August 2018 в 17:21
поделиться
  • 1
    Не могли бы вы поделиться примером фрагмента кода, где эта конструкция полезна? Благодарю. – Ashwin Nanjappa 22 March 2009 в 10:32
  • 2
    В настоящее время я много делаю это из-за выполнения некоторых работ DCOM и использования управляемых классов ресурсов, которые включают в себя выполнение некоторой работы перед каждым вызовом, а использование членов данных для внутреннего представления для отправки в com, плюс шаблонирование, делает много код котла much меньше – Dan 10 August 2009 в 22:30

Это позволяет единообразно связывать переменные-члены и функции. Ниже приведен пример вашего класса Car. Более частое использование было бы привязкой std::pair::first и ::second при использовании в алгоритмах STL и Boost на карте.

#include <list>
#include <algorithm>
#include <iostream>
#include <iterator>
#include <boost/lambda/lambda.hpp>
#include <boost/lambda/bind.hpp>


class Car {
public:
    Car(int s): speed(s) {}
    void drive() {
        std::cout << "Driving at " << speed << " km/h" << std::endl;
    }
    int speed;
};

int main() {

    using namespace std;
    using namespace boost::lambda;

    list<Car> l;
    l.push_back(Car(10));
    l.push_back(Car(140));
    l.push_back(Car(130));
    l.push_back(Car(60));

    // Speeding cars
    list<Car> s;

    // Binding a value to a member variable.
    // Find all cars with speed over 60 km/h.
    remove_copy_if(l.begin(), l.end(),
                   back_inserter(s),
                   bind(&Car::speed, _1) <= 60);

    // Binding a value to a member function.
    // Call a function on each car.
    for_each(s.begin(), s.end(), bind(&Car::drive, _1));

    return 0;
}
18
ответ дан Alex B 15 August 2018 в 17:21
поделиться
  • 1
    Не понял, что вы можете использовать bind с указателями на данные! – Pete 20 April 2017 в 15:11

Я думаю, вы только хотели бы сделать это, если бы данные элемента были довольно большими (например, объект другого довольно здоровенного класса), и у вас есть внешняя подпрограмма, которая работает только с ссылками на объекты этого класса. Вы не хотите копировать объект-член, поэтому это позволяет вам передавать его.

0
ответ дан Andrew Jaffe 15 August 2018 в 17:21
поделиться

Указатели на классы не являются действительными указателями; класс является логической конструкцией и не имеет физического существования в памяти, однако, когда вы строите указатель на член класса, он дает смещение в объект класса члена, где элемент может быть найден; Это дает важный вывод: Поскольку статические члены не связаны с каким-либо объектом, поэтому указатель на элемент НЕ МОЖЕТ указывать на статический член (данные или функции) вообще . Рассмотрим следующее:

class x
{
public:
int val;
x(int i) { val=i;}

int get_val(){return val;}
int d_val(int i){return i+i;}
};
int main()
{
int (x::*data)=&x::val;               //pointer to data member
int (x::*func)(int)=&x::d_val;        //pointer to function member
x ob1(1),ob2(2);
cout<<ob1.*data;
cout<<ob2.*data;
cout<<(ob1.*func)(ob1.*data);
cout<<(ob2.*func)(ob2.*data);
return 0;
}

Источник: полный справочник C ++ - Herbert Schildt 4th Edition

0
ответ дан Arijit Dey 15 August 2018 в 17:21
поделиться

Просто добавьте некоторые варианты использования для @ anon's & amp; @ Ответ Oktalist, вот отличный материал для чтения о функции указателя на член и данные указателя на член. http://www.cs.wustl.edu/~schmidt/PDF/C++-ptmf4.pdf

0
ответ дан Dragonly 15 August 2018 в 17:21
поделиться

Вы можете использовать массив указателей на (однородные) данные элемента, чтобы включить двойной интерфейс с именем-членом (например, x.data) и массивом-индексом (например, x [idx]).

#include <cassert>
#include <cstddef>

struct vector3 {
    float x;
    float y;
    float z;

    float& operator[](std::size_t idx) {
        static float vector3::*component[3] = {
            &vector3::x, &vector3::y, &vector3::z
        };
        return this->*component[idx];
    }
};

int main()
{
    vector3 v = { 0.0f, 1.0f, 2.0f };

    assert(&v[0] == &v.x);
    assert(&v[1] == &v.y);
    assert(&v[2] == &v.z);

    for (std::size_t i = 0; i < 3; ++i) {
        v[i] += 1.0f;
    }

    assert(v.x == 1.0f);
    assert(v.y == 2.0f);
    assert(v.z == 3.0f);

    return 0;
}
8
ответ дан Functastic 15 August 2018 в 17:21
поделиться
  • 1
    Я часто видел, что это реализовано с использованием анонимного объединения, включая поле массива v [3], поскольку это позволяет избежать косвенности, но, тем не менее, разумно и потенциально полезно для несмежных полей. – Dwayne Robinson 21 April 2015 в 04:28
  • 2
    @DwayneRobinson, но использование union для типа-каламбура таким способом не допускается стандартом, поскольку он вызывает множество форм неопределенного поведения ... тогда как этот ответ в порядке. – underscore_d 7 September 2016 в 22:25

Предположим, что у вас есть структура. Внутри этой структуры есть * какое-то имя * две переменные одного и того же типа, но с различным значением

struct foo {
    std::string a;
    std::string b;
};

Хорошо, теперь предположим, что у вас есть куча foo s в контейнере:

// key: some sort of name, value: a foo instance
std::map<std::string, foo> container;

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

Вы могли бы сделайте что-то вроде этого:

void readDataFromText(std::istream & input, std::map<std::string, foo> & container, std::string foo::*storage) {
    std::string line, name, value;

    // while lines are successfully retrieved
    while (std::getline(input, line)) {
        std::stringstream linestr(line);
        if ( line.empty() ) {
            continue;
        }

        // retrieve name and value
        linestr >> name >> value;

        // store value into correct storage, whichever one is correct
        container[name].*storage = value;
    }
}

std::map<std::string, foo> readValues() {
    std::map<std::string, foo> foos;

    std::ifstream a("input-a");
    readDataFromText(a, foos, &foo::a);
    std::ifstream b("input-b");
    readDataFromText(b, foos, &foo::b);
    return foos;
}

. В этот момент при вызове readValues() будет возвращен контейнер с унисоном «input-a» и «input-b»; все клавиши будут присутствовать, а foos имеют либо a, либо b или оба.

0
ответ дан inetknght 15 August 2018 в 17:21
поделиться

Еще одно приложение - это интрузивные списки. Тип элемента может рассказать о том, каковы его следующие / предыдущие указатели. Таким образом, список не использует жестко заданные имена, но все равно может использовать существующие указатели:

// say this is some existing structure. And we want to use
// a list. We can tell it that the next pointer
// is apple::next.
struct apple {
    int data;
    apple * next;
};

// simple example of a minimal intrusive list. Could specify the
// member pointer as template argument too, if we wanted:
// template<typename E, E *E::*next_ptr>
template<typename E>
struct List {
    List(E *E::*next_ptr):head(0), next_ptr(next_ptr) { }

    void add(E &e) {
        // access its next pointer by the member pointer
        e.*next_ptr = head;
        head = &e;
    }

    E * head;
    E *E::*next_ptr;
};

int main() {
    List<apple> lst(&apple::next);

    apple a;
    lst.add(a);
}
51
ответ дан Johannes Schaub - litb 15 August 2018 в 17:21
поделиться
  • 1
    Если это действительно связанный список, не хотите ли вы что-то вроде этого: void add (E * e) {e- & gt; * next_ptr = head; head = e; } ?? – eeeeaaii 25 August 2011 в 17:56
  • 2
    @eee Я рекомендую вам прочитать о контрольных параметрах. То, что я сделал, в основном эквивалентно тому, что вы сделали. – Johannes Schaub - litb 25 August 2011 в 19:55
  • 3
    +1 для вашего примера кода, но я не видел необходимости использовать указатель-член, какой-либо другой пример? – Alcott 14 August 2012 в 09:32
  • 4
    @Alcott: вы можете применить его к другим структурам с похожими списками, где следующий указатель не назван next. – icktoofay 19 May 2013 в 00:13

Это самый простой пример, который я могу себе представить, который передает редкие случаи, когда эта особенность уместна:

#include <iostream>

class bowl {
public:
    int apples;
    int oranges;
};

int count_fruit(bowl * begin, bowl * end, int bowl::*fruit)
{
    int count = 0;
    for (bowl * iterator = begin; iterator != end; ++ iterator)
        count += iterator->*fruit;
    return count;
}

int main()
{
    bowl bowls[2] = {
        { 1, 2 },
        { 3, 5 }
    };
    std::cout << "I have " << count_fruit(bowls, bowls + 2, & bowl::apples) << " apples\n";
    std::cout << "I have " << count_fruit(bowls, bowls + 2, & bowl::oranges) << " oranges\n";
    return 0;
}

Здесь следует отметить указатель, переданный в count_fruit. Это избавит вас от необходимости писать отдельные функции count_apples и count_oranges.

59
ответ дан John McFarlane 15 August 2018 в 17:21
поделиться
  • 1
    Разве это не должно быть &bowls.apples и &bowls.oranges? &bowl::apples и &bowl::oranges ничего не указывают. – Dan Nissenbaum 30 March 2014 в 10:20
  • 2
    &bowl::apples и &bowl::oranges не указывают на члены объекта ; они указывают на члены класса . Они должны быть объединены с указателем на фактический объект, прежде чем они указывают на что-то. Эта комбинация достигается с помощью оператора ->*. – John McFarlane 30 March 2014 в 21:56
  • 3
    Теперь ясно; Я не видел прямо. Благодарю. – Dan Nissenbaum 30 March 2014 в 22:41

Вы можете позже получить доступ к этому члену в любом экземпляре:

int main()
{    
  int Car::*pSpeed = &Car::speed;    
  Car myCar;
  Car yourCar;

  int mySpeed = myCar.*pSpeed;
  int yourSpeed = yourCar.*pSpeed;

  assert(mySpeed > yourSpeed); // ;-)

  return 0;
}

Обратите внимание, что вам нужен экземпляр для его вызова, поэтому он не работает как делегат.

Обычно использование интерфейса (т. Е. Чистого базового класса на C ++) является лучшим выбором дизайна.

33
ответ дан peterchen 15 August 2018 в 17:21
поделиться
  • 1
    Но, конечно, это просто плохая практика? должен делать что-то вроде youcar.setspeed (mycar.getpspeed) – thecoshman 6 October 2010 в 22:08
  • 2
    @ thecoshman: все зависит от того, скрывают ли данные элементы за методами set / get не инкапсуляция, а просто попытка доярки на абстракции интерфейса. Во многих сценариях "денормализация" для публичных членов - разумный выбор. Но эта дискуссия, вероятно, превышает пределы функциональности комментариев. – peterchen 12 October 2010 в 16:21
  • 3
    +1 для указания, если я правильно понимаю, что это указатель на член любого экземпляра, а не указатель на конкретное значение одного экземпляра, который является частью, которую я полностью отсутствовал. – johnbakers 21 May 2013 в 10:49
  • 4
    @Fellowshee Вы правильно понимаете :) (подчеркнули, что в ответе). – peterchen 22 May 2013 в 17:58

Вот пример, в котором может быть полезен указатель на элементы данных:

#include <iostream>
#include <list>
#include <string>

template <typename Container, typename T, typename DataPtr>
typename Container::value_type searchByDataMember (const Container& container, const T& t, DataPtr ptr) {
    for (const typename Container::value_type& x : container) {
        if (x->*ptr == t)
            return x;
    }
    return typename Container::value_type{};
}

struct Object {
    int ID, value;
    std::string name;
    Object (int i, int v, const std::string& n) : ID(i), value(v), name(n) {}
};

std::list<Object*> objects { new Object(5,6,"Sam"), new Object(11,7,"Mark"), new Object(9,12,"Rob"),
    new Object(2,11,"Tom"), new Object(15,16,"John") };

int main() {
    const Object* object = searchByDataMember (objects, 11, &Object::value);
    std::cout << object->name << '\n';  // Tom
}
0
ответ дан prestokeys 15 August 2018 в 17:21
поделиться

Вот реальный пример, над которым я сейчас работаю, от систем обработки / управления сигналами:

Предположим, что у вас есть структура, которая представляет данные, которые вы собираете:

struct Sample {
    time_t time;
    double value1;
    double value2;
    double value3;
};

Теперь предположим, что вы введете их в вектор:

std::vector<Sample> samples;
... fill the vector ...

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

double Mean(std::vector<Sample>::const_iterator begin, 
    std::vector<Sample>::const_iterator end,
    double Sample::* var)
{
    float mean = 0;
    int samples = 0;
    for(; begin != end; begin++) {
        const Sample& s = *begin;
        mean += s.*var;
        samples++;
    }
    mean /= samples;
    return mean;
}

...
double mean = Mean(samples.begin(), samples.end(), &Sample::value2);

Примечание Редактировано 2016/08/05 для более сжатого подхода к шаблону-функции

И, конечно, вы можете использовать шаблон это вычислить среднее значение для любого форварда-итератора и любого типа значения, который поддерживает добавление с самим собой и деление на size_t:

template<typename Titer, typename S>
S mean(Titer begin, const Titer& end, S std::iterator_traits<Titer>::value_type::* var) {
    using T = typename std::iterator_traits<Titer>::value_type;
    S sum = 0;
    size_t samples = 0;
    for( ; begin != end ; ++begin ) {
        const T& s = *begin;
        sum += s.*var;
        samples++;
    }
    return sum / samples;
}

struct Sample {
    double x;
}

std::vector<Sample> samples { {1.0}, {2.0}, {3.0} };
double m = mean(samples.begin(), samples.end(), &Sample::x);

EDIT - приведенный выше код имеет последствия для производительности

Вы следует отметить, как я вскоре обнаружил, что приведенный выше код имеет некоторые серьезные последствия для производительности. Резюме состоит в том, что если вы вычисляете итоговую статистику по временному ряду или вычисляете FFT и т. Д., То вы должны хранить значения для каждой переменной в памяти. В противном случае итерация по ряду приведет к пропуску кеша для каждого полученного значения.

Рассмотрим производительность этого кода:

struct Sample {
  float w, x, y, z;
};

std::vector<Sample> series = ...;

float sum = 0;
int samples = 0;
for(auto it = series.begin(); it != series.end(); it++) {
  sum += *it.x;
  samples++;
}
float mean = sum / samples;

На многих архитектурах один экземпляр Sample будет заполнять строку кэша. Таким образом, на каждой итерации цикла один образец будет извлечен из памяти в кеш. 4 байта из строки кэша будут использоваться, а остальные будут удалены, а следующая итерация приведет к еще одному пропуску кэша, доступу к памяти и т. Д.

Гораздо лучше сделать это:

struct Samples {
  std::vector<float> w, x, y, z;
};

Samples series = ...;

float sum = 0;
float samples = 0;
for(auto it = series.x.begin(); it != series.x.end(); it++) {
  sum += *it;
  samples++;
}
float mean = sum / samples;

Теперь, когда первое значение x загружается из памяти, следующие три будут также загружены в кеш (предположительно подходящее выравнивание), то есть вам не нужны значения, загруженные для следующих трех итераций.

Вышеупомянутый алгоритм может быть несколько улучшен за счет использования SIMD-инструкций, например, для архитектур SSE2. Тем не менее, они работают намного лучше, если значения все смежны в памяти, и вы можете использовать одну инструкцию для загрузки четырех образцов вместе (подробнее в последующих версиях SSE).

YMMV - проектируйте свои структуры данных в соответствии с вашими алгоритм.

28
ответ дан Tom 15 August 2018 в 17:21
поделиться
  • 1
    Это отлично. Я собираюсь реализовать что-то очень похожее, и теперь мне не нужно выяснять странный синтаксис! Благодаря! – Nicu Stiurca 26 March 2013 в 04:47

Один из способов, который я использовал, - это если у меня есть две реализации того, как делать что-то в классе, и я хочу выбрать его во время выполнения без необходимости постоянно проходить через оператор if, т. е.

class Algorithm
{
public:
    Algorithm() : m_impFn( &Algorithm::implementationA ) {}
    void frequentlyCalled()
    {
        // Avoid if ( using A ) else if ( using B ) type of thing
        (this->*m_impFn)();
    }
private:
    void implementationA() { /*...*/ }
    void implementationB() { /*...*/ }

    typedef void ( Algorithm::*IMP_FN ) ();
    IMP_FN m_impFn;
};

Очевидно, что это практически полезно, если вы чувствуете, что код забивается настолько, что оператор if замедляет работу, например. где-то в глубине кишки какого-то интенсивного алгоритма. Я все еще думаю, что это более элегантно, чем утверждение if даже в ситуациях, когда у него нет практического использования, но это только мое одобрение.

2
ответ дан Troubadour 15 August 2018 в 17:21
поделиться