Если вы похожи на меня, когда я впервые начал использовать 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 ()
Способ думать об этом - «думать как компилятор».
Представьте, что вы пишете компилятор. И вы видите такой код.
// 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.
Я написал сообщение об этом один раз: Разрешение круговых зависимостей в 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;
}
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
К сожалению, во всех предыдущих ответах отсутствуют некоторые детали. Правильное решение немного громоздко, но это единственный способ сделать это правильно.
Вот как вы можете это сделать, точно сохраняя все детали и удобство использования:
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.
Замечания:
class A
имеет объект class B
в качестве члена или наоборот. Читайте FAQ:
Простой пример, представленный в Википедии, работал для меня. (вы можете прочитать полное описание в 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;
}
Вы можете избежать ошибок компиляции, если вы удалите определения метода из файлов заголовков и пусть классы содержат только декларации методов и объявления / определения переменных. Определения методов должны быть помещены в файл .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;
}
Вот решение для шаблонов: Как обрабатывать круговые зависимости с шаблонами
Ключ к решению этой проблемы состоит в том, чтобы объявить оба класса до предоставления определений (реализаций). Невозможно разбить объявление и определение на отдельные файлы, но вы можете структурировать их так, как если бы они были в отдельных файлах.
Я опоздал с ответом на этот вопрос, но на данный момент нет ни одного разумного ответа, несмотря на то, что это популярный вопрос с высокоподдерживаемыми ответами ....
Как проиллюстрировано заголовком <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
и должна завершиться чисто.
Произнести - вместо использования заголовка прямого объявления, как описано выше, - код в a.h
или a.cc
вместо forward-declares class B;
:
a.h
или a.cc
позже включили b.h
: компиляция A завершится с ошибкой после того, как она попадет в противоречивую декларацию / определение B
(т. е. вышеуказанное изменение на B сломало A и любое другой c лижет злоупотребление форвардными декларациями, вместо того, чтобы работать прозрачно). b.h
- возможно, если A просто хранит / передает Bs с помощью указателя и / или ссылки), инструменты построения, основанные на анализе #include
и измененные временные метки файлов, t перестроить A
(и его далее зависимый код) после изменения на B, вызывая ошибки во время соединения или времени выполнения. Если B распределяется как загруженная DLL во время выполнения, код в «A» может не отображать символы с разными причинами во время выполнения, которые могут обрабатываться или не обрабатываться достаточно хорошо, чтобы инициировать упорядоченное завершение работы или приемлемую сокращенную функциональность. Если код А имеет специализированные шаблоны / «признаки» для старого B
, они не вступят в силу.
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
main.cpp
, но приятно, что вы задокументировали, что он должен содержать в вашем комментарии. ура
– Tony Delroy
5 May 2017 в 20:42