Почему он говорит & ldquo; необъявленный идентификатор & rdquo ;? [Дубликат]

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

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

Вариант 1: Сравнение строк Java с методом equals Большая часть (возможно, в 95% случаев). Я сравниваю строки с методом equals класса Java String следующим образом:

if (string1.equals(string2))

Этот метод String равен методу для двух строк Java, и если они содержат точно такую ​​же строку символов, они считаются равными.

Взглянув на быстрый пример сравнения строк с методом equals, если был выполнен следующий тест, две строки не будут считаться равными, поскольку символы не являются точно такими же (случай символов различен):

String string1 = "foo";
String string2 = "FOO";

if (string1.equals(string2))
{
    // this line will not print because the
    // java string equals method returns false:
    System.out.println("The two strings are the same.")
}

Но, когда две строки содержат одну и ту же строку символов, метод equals вернет true, как в этот пример:

String string1 = "foo";
String string2 = "foo";

// test for equality with the java string equals method
if (string1.equals(string2))
{
    // this line WILL print
    System.out.println("The two strings are the same.")
}

Вариант 2: Сравнение строк с методом equalsIgnoreCase

В некоторых строковых сравнительных тестах вы захотите игнорируйте, являются ли строки строчными или строчными. Если вы хотите проверить свои строки на равенство в этом случае нечувствительным образом, используйте метод equalsIgnoreCase класса String, например:

String string1 = "foo";
String string2 = "FOO";

 // java string compare while ignoring case
 if (string1.equalsIgnoreCase(string2))
 {
     // this line WILL print
     System.out.println("Ignoring case, the two strings are the same.")
 }

Вариант 3: сравнение строк Java с методом compareTo

Существует также третий, менее распространенный способ сравнения строк Java, и это с методом сравнения String класса. Если две строки точно совпадают, метод compareTo вернет значение 0 (ноль). Ниже приведен краткий пример того, как выглядит этот метод сравнения строк:

String string1 = "foo bar";
String string2 = "foo bar";

// java string compare example
if (string1.compareTo(string2) == 0)
{
    // this line WILL print
    System.out.println("The two strings are the same.")
}

Пока я пишу об этой концепции равенства в Java, важно отметить, что язык Java включает в себя метод equals в базовый класс Java Object. Всякий раз, когда вы создаете свои собственные объекты, и вы хотите предоставить средства для проверки того, являются ли два экземпляра вашего объекта «равными», вы должны переопределить (и реализовать) этот метод equals в своем классе (точно так же, как язык Java предоставляет это равенство / сравнение в методе String равно).

Вы можете посмотреть на это ==, .equals (), compareTo () и compare ()

276
задан StoryTeller 12 October 2017 в 14:20
поделиться

9 ответов

Способ думать об этом - «думать как компилятор».

Представьте, что вы пишете компилятор. И вы видите такой код.

// file: A.h
class A {
  B _b;
};

// file: B.h
class B {
  A _a;
};

// file main.cc
#include "A.h"
#include "B.h"
int main(...) {
  A a;
}

Когда вы компилируете файл .cc (помните, что .cc, а не .h - это единица компиляции), вам нужно выделить пространство для объекта A. Итак, хорошо, сколько пространства тогда? Достаточно хранить B! Каков размер B? Достаточно хранить A!

Очевидно, что круговая ссылка, которую вы должны сломать.

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

// file: A.h
class A {
  // both these are fine, so are various const versions of the same.
  B& _b_ref;
  B* _b_ptr;
};

Теперь все лучше. В некотором роде. main() все еще говорит:

// file: main.cc
#include "A.h"  // <-- Houston, we have a problem

#include, для всех экстентов и целей (если вы выберете препроцессор) просто копирует файл в .cc. Так что, .cc выглядит так:

// file: partially_pre_processed_main.cc
class A {
  B& _b_ref;
  B* _b_ptr;
};
#include "B.h"
int main (...) {
  A a;
}

Вы можете понять, почему компилятор не может с этим справиться - он понятия не имеет, что B - он даже не видел символ раньше .

Итак, давайте расскажем компилятору о B. Это известно как декларация вперед и обсуждается далее в этом ответе .

// main.cc
class B;
#include "A.h"
#include "B.h"
int main (...) {
  A a;
}

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

Причина, по которой это исправление плохо, заключается в следующем: f17] должны объявить B, прежде чем они смогут его использовать, и получите ужасную ошибку #include. Итак, давайте переместим декларацию в себя.

// file: A.h
class B;
class A {
  B* _b; // or any of the other variants.
};

И в Bh на данный момент вы можете просто #include "A.h" прямо.

// file: B.h
#include "A.h"
class B {
  // note that this is cool because the compiler knows by this time
  // how much space A will need.
  A _a; 
}

HTH.

222
ответ дан Community 16 August 2018 в 04:43
поделиться

Я написал сообщение об этом один раз: Разрешение круговых зависимостей в c ++

Основной метод состоит в том, чтобы отделить классы, используя интерфейсы. Итак, в вашем случае:

//Printer.h
class Printer {
public:
    virtual Print() = 0;
}

//A.h
#include "Printer.h"
class A: public Printer
{
    int _val;
    Printer *_b;
public:

    A(int val)
        :_val(val)
    {
    }

    void SetB(Printer *b)
    {
        _b = b;
        _b->Print();
    }

    void Print()
    {
        cout<<"Type:A val="<<_val<<endl;
    }
};

//B.h
#include "Printer.h"
class B: public Printer
{
    double _val;
    Printer* _a;
public:

    B(double val)
        :_val(val)
    {
    }

    void SetA(Printer *a)
    {
        _a = a;
        _a->Print();
    }

    void Print()
    {
        cout<<"Type:B val="<<_val<<endl;
    }
};

//main.cpp
#include <iostream>
#include "A.h"
#include "B.h"

int main(int argc, char* argv[])
{
    A a(10);
    B b(3.14);
    a.Print();
    a.SetB(&b);
    b.Print();
    b.SetA(&a);
    return 0;
}
7
ответ дан Eduard Wirch 16 August 2018 в 04:43
поделиться
  • 1
    Обратите внимание, что использование интерфейсов и virtual влияет на производительность во время выполнения. – cemper93 22 June 2016 в 19:10

Однажды я решил эту проблему, перемещая все inlines после определения класса и помещая #include для других классов непосредственно перед inlines в файле заголовка , Таким образом, убедитесь, что все определения + встроенные строки заданы до того, как строки синтаксического анализа проанализированы.

Выполнение этого действия позволяет по-прежнему иметь связку строк в обоих (или нескольких) файлах заголовков. .

Как это

// File: A.h
#ifndef __A_H__
#define __A_H__
class B;
class A
{
    int _val;
    B *_b;
public:
    A(int val);
    void SetB(B *b);
    void Print();
};

// Including class B for inline usage here 
#include "B.h"

inline A::A(int val) : _val(val)
{
}

inline void A::SetB(B *b)
{
    _b = b;
    _b->Print();
}

inline void A::Print()
{
    cout<<"Type:A val="<<_val<<endl;
}

#endif /* __A_H__ */

... и делает то же самое в B.h

11
ответ дан epatel 16 August 2018 в 04:43
поделиться
  • 1
    Зачем? Я думаю, что это элегантное решение сложной проблемы ... когда нужно встроить строки. Если вам не нужны встроенные строки, не нужно писать код, как это было написано с самого начала ... – epatel 10 March 2009 в 21:01
  • 2
    Что произойдет, если пользователь сначала включит B.h? – Mr Fooz 18 March 2014 в 18:00
  • 3
    Обратите внимание, что ваш защитник заголовка использует зарезервированный идентификатор, все с двойным смежным подчеркиванием зарезервировано. – Lars Viklund 10 August 2015 в 15:09

К сожалению, во всех предыдущих ответах отсутствуют некоторые детали. Правильное решение немного громоздко, но это единственный способ сделать это правильно.

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

  • решение в точности соответствует такие же, как изначально предназначенные
  • встроенные функции, все еще встроенные
  • пользователи A и B могут включать Ah и Bh в любом порядке

Создайте два файла: A_def.h, B_def.h. Они будут содержать только определения A и B:

// A_def.h
#ifndef A_DEF_H
#define A_DEF_H

class B;
class A
{
    int _val;
    B *_b;

public:
    A(int val);
    void SetB(B *b);
    void Print();
};
#endif

// B_def.h
#ifndef B_DEF_H
#define B_DEF_H

class A;
class B
{
    double _val;
    A* _a;

public:
    B(double val);
    void SetA(A *a);
    void Print();
};
#endif

И тогда, Ah и Bh будут содержать это:

// A.h
#ifndef A_H
#define A_H

#include "A_def.h"
#include "B_def.h"

inline A::A(int val) :_val(val)
{
}

inline void A::SetB(B *b)
{
    _b = b;
    _b->Print();
}

inline void A::Print()
{
    cout<<"Type:A val="<<_val<<endl;
}

#endif

// B.h
#ifndef B_H
#define B_H

#include "A_def.h"
#include "B_def.h"

inline B::B(double val) :_val(val)
{
}

inline void B::SetA(A *a)
{
    _a = a;
    _a->Print();
}

inline void B::Print()
{
    cout<<"Type:B val="<<_val<<endl;
}

#endif

Обратите внимание, что A_def .h и B_def.h являются «частными» заголовками, пользователи A и B не должны их использовать. Общий заголовок - A.h и B.h.

0
ответ дан geza 16 August 2018 в 04:43
поделиться

Замечания:

  • Это не сработает, если class A имеет объект class B в качестве члена или наоборот.
  • Переслать декларацию - это путь.
  • Применяется порядок декларирования (именно поэтому вы выходите из определений). Если оба класса называют функции другого, вы должны перенести определения.

Читайте FAQ:

17
ответ дан Ken Y-N 16 August 2018 в 04:43
поделиться
  • 1
    ссылки, которые вы предоставили, больше не работают, знаете ли вы, что новые упоминают? – Ramya Rao 22 February 2017 в 23:11

Простой пример, представленный в Википедии, работал для меня. (вы можете прочитать полное описание в http://en.wikipedia.org/wiki/Circular_dependency#Example_of_circular_dependencies_in_C.2B.2B )

Файл '' 'a.h' ':

#ifndef A_H
#define A_H

class B;    //forward declaration

class A {
public:
    B* b;
};
#endif //A_H

Файл' '' b.h '' ':

#ifndef B_H
#define B_H

class A;    //forward declaration

class B {
public:
    A* a;
};
#endif //B_H

Файл' '' main.cpp '' ':

#include "a.h"
#include "b.h"

int main() {
    A a;
    B b;
    a.b = &b;
    b.a = &a;
}
2
ответ дан madx 16 August 2018 в 04:43
поделиться

Вы можете избежать ошибок компиляции, если вы удалите определения метода из файлов заголовков и пусть классы содержат только декларации методов и объявления / определения переменных. Определения методов должны быть помещены в файл .cpp (как говорится в рекомендациях по лучшей практике).

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

//A.h
#ifndef A_H
#define A_H
class B;
class A
{
    int _val;
    B* _b;
public:

    A(int val);
    void SetB(B *b);
    void Print();
};
#endif

//B.h
#ifndef B_H
#define B_H
class A;
class B
{
    double _val;
    A* _a;
public:

    B(double val);
    void SetA(A *a);
    void Print();
};
#endif

//A.cpp
#include "A.h"
#include "B.h"

#include <iostream>

using namespace std;

A::A(int val)
:_val(val)
{
}

void A::SetB(B *b)
{
    _b = b;
    cout<<"Inside SetB()"<<endl;
    _b->Print();
}

void A::Print()
{
    cout<<"Type:A val="<<_val<<endl;
}

//B.cpp
#include "B.h"
#include "A.h"
#include <iostream>

using namespace std;

B::B(double val)
:_val(val)
{
}

void B::SetA(A *a)
{
    _a = a;
    cout<<"Inside SetA()"<<endl;
    _a->Print();
}

void B::Print()
{
    cout<<"Type:B val="<<_val<<endl;
}

//main.cpp
#include "A.h"
#include "B.h"

int main(int argc, char* argv[])
{
    A a(10);
    B b(3.14);
    a.Print();
    a.SetB(&b);
    b.Print();
    b.SetA(&a);
    return 0;
}
86
ответ дан Sandeep Datta 16 August 2018 в 04:43
поделиться
  • 1
    Это лучший ответ – rank1 17 April 2013 в 16:26
  • 2
    Благодарю. Это легко разрешило проблему. Я просто перемещал циркуляр в файлы .cpp. – Lenar Hoyt 4 October 2014 в 19:16
  • 3
    да, так вы можете решать круговые зависимости – Guru 16 October 2014 в 05:41
  • 4
    Что делать, если у вас есть метод шаблона? Тогда вы не можете переместить его в файл CPP, если вы не создадите шаблоны вручную. – Malcolm 1 September 2016 в 12:55

Вот решение для шаблонов: Как обрабатывать круговые зависимости с шаблонами

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

3
ответ дан Tatyana 16 August 2018 в 04:43
поделиться

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

Лучшая практика: вперед Заголовки заголовков

Как проиллюстрировано заголовком <iosfwd> стандартной библиотеки, правильный способ предоставления форвардных объявлений для других должен иметь заголовок forward declare . Например:

a.fwd.h:

#pragma once
class A;

ah:

#pragma once
#include "a.fwd.h"
#include "b.fwd.h"

class A
{
  public:
    void f(B*);
};

b.fwd.h:

#pragma once
class B;

bh:

#pragma once
#include "b.fwd.h"
#include "a.fwd.h"

class B
{
  public:
    void f(A*);
};

Составители библиотек A и B должны отвечать за сохранение заголовков своих прямых деклараций в соответствии с их заголовками и файлами реализации, поэтому - для Например, если сопровождающий «B» входит и перезаписывает код, который должен быть ...

b.fwd.h:

template <typename T> class Basic_B;
typedef Basic_B<char> B;

bh:

template <typename T>
class Basic_B
{
    ...class definition...
};
typedef Basic_B<char> B;

... тогда перекомпиляция кода для «А» будет вызвана изменениями к включенному b.fwd.h и должна завершиться чисто.


Плохая, но обычная практика: вперед объявить материал в других libs

Произнести - вместо использования заголовка прямого объявления, как описано выше, - код в a.h или a.cc вместо forward-declares class B;:

  • , если a.h или a.cc позже включили b.h: компиляция A завершится с ошибкой после того, как она попадет в противоречивую декларацию / определение B (т. е. вышеуказанное изменение на B сломало A и любое другой c лижет злоупотребление форвардными декларациями, вместо того, чтобы работать прозрачно).
  • в противном случае (если A в конечном итоге не включал b.h - возможно, если A просто хранит / передает Bs с помощью указателя и / или ссылки), инструменты построения, основанные на анализе #include и измененные временные метки файлов, t перестроить A (и его далее зависимый код) после изменения на B, вызывая ошибки во время соединения или времени выполнения. Если B распределяется как загруженная DLL во время выполнения, код в «A» может не отображать символы с разными причинами во время выполнения, которые могут обрабатываться или не обрабатываться достаточно хорошо, чтобы инициировать упорядоченное завершение работы или приемлемую сокращенную функциональность.

Если код А имеет специализированные шаблоны / «признаки» для старого B, они не вступят в силу.

11
ответ дан Tony Delroy 16 August 2018 в 04:43
поделиться
  • 1
    Это действительно чистый способ обработки форвардных объявлений. Единственный "недостаток" будет в дополнительных файлах. Я предполагаю, что вы всегда включаете a.fwd.h в a.h, чтобы убедиться, что они остаются в синхронизации. Код примера отсутствует, если используются эти классы. a.h и b.h оба должны быть включены, поскольку они не будут работать изолированно: `` `//main.cpp #include & quot; a.h & quot; #include & quot; b.h & quot; int main () {...} `` `Или один из них должен быть полностью включен в другой, как в начальном вопросе. Где b.h включает a.h и main.cpp включает b.h – Farway 5 May 2017 в 16:37
  • 2
    @Farway Право на все счета. Я не стал показывать main.cpp, но приятно, что вы задокументировали, что он должен содержать в вашем комментарии. ура – Tony Delroy 5 May 2017 в 20:42
  • 3
    Один из лучших ответов с хорошим подробным объяснением того, почему с делами и не связано с плюсами и минусами ... – Francis Cugler 16 January 2018 в 06:06
Другие вопросы по тегам:

Похожие вопросы: