Zeichenmerkmale sind eine äußerst wichtige Komponente der Streams und String-Bibliotheken, da sie es den Stream- / String-Klassen ermöglichen, die Logik der gespeicherten Zeichen von der Logik der Manipulationen an diesen Zeichen zu trennen .
Zunächst wird die Standardklasse für Zeichenmerkmale char_traits<T>
im C ++ - Standard häufig verwendet. Beispielsweise wird keine Klasse aufgerufen std::string
. Es gibt vielmehr eine Klassenvorlage std::basic_string
, die so aussieht:
template <typename charT, typename traits = char_traits<charT> >
class basic_string;
Dann std::string
ist definiert als
typedef basic_string<char> string;
In ähnlicher Weise sind die Standardströme definiert als
template <typename charT, typename traits = char_traits<charT> >
class basic_istream;
typedef basic_istream<char> istream;
Warum sind diese Klassen so strukturiert, wie sie sind? Warum sollten wir eine seltsame Merkmalsklasse als Vorlagenargument verwenden?
Der Grund dafür ist, dass wir in einigen Fällen eine Zeichenfolge wie diese haben möchten std::string
, jedoch mit etwas anderen Eigenschaften. Ein klassisches Beispiel hierfür ist, wenn Sie Zeichenfolgen so speichern möchten, dass Groß- und Kleinschreibung ignoriert wird. Zum Beispiel möchte ich vielleicht eine Zeichenfolge mit dem Namen CaseInsensitiveString
erstellen, die ich haben kann
CaseInsensitiveString c1 = "HI!", c2 = "hi!";
if (c1 == c2) {
cout << "Strings are equal." << endl;
}
Das heißt, ich kann eine Zeichenfolge haben, bei der zwei Zeichenfolgen, die sich nur in ihrer Groß- / Kleinschreibung unterscheiden, gleich verglichen werden.
Angenommen, die Autoren der Standardbibliothek haben Zeichenfolgen ohne Verwendung von Merkmalen entworfen. Dies würde bedeuten, dass ich in der Standardbibliothek eine immens mächtige String-Klasse hätte, die in meiner Situation völlig nutzlos war. Ich konnte einen Großteil des Codes für diese Zeichenfolgenklasse nicht wiederverwenden, da Vergleiche immer so funktionieren, wie ich sie haben wollte. Durch die Verwendung von Merkmalen ist es jedoch tatsächlich möglich, den Laufwerkscode wiederzuverwenden std::string
, um eine Zeichenfolge ohne Berücksichtigung der Groß- und Kleinschreibung zu erhalten.
Wenn Sie eine Kopie des C ++ - ISO-Standards aufrufen und sich die Definition der Funktionsweise der Vergleichsoperatoren der Zeichenfolge ansehen, werden Sie feststellen, dass sie alle in Bezug auf die compare
Funktion definiert sind. Diese Funktion wird wiederum durch Aufruf definiert
traits::compare(this->data(), str.data(), rlen)
Wo str
ist die Zeichenfolge, mit der Sie vergleichen, und wo rlen
ist die kleinere der beiden Zeichenfolgenlängen? Dies ist eigentlich sehr interessant, da die Definition von compare
direkt die compare
Funktion verwendet, die durch den als Vorlagenparameter angegebenen Merkmalstyp exportiert wird! Wenn wir also eine neue Merkmalsklasse definieren und diese compare
so definieren , dass Zeichen ohne Berücksichtigung der Groß- und Kleinschreibung verglichen werden, können wir eine Zeichenfolgenklasse erstellen, die sich genauso verhält std::string
, die Dinge jedoch ohne Berücksichtigung der Groß- und Kleinschreibung behandelt!
Hier ist ein Beispiel. Wir erben von std::char_traits<char>
, um das Standardverhalten für alle Funktionen zu erhalten, die wir nicht schreiben:
class CaseInsensitiveTraits: public std::char_traits<char> {
public:
static bool lt (char one, char two) {
return std::tolower(one) < std::tolower(two);
}
static bool eq (char one, char two) {
return std::tolower(one) == std::tolower(two);
}
static int compare (const char* one, const char* two, size_t length) {
for (size_t i = 0; i < length; ++i) {
if (lt(one[i], two[i])) return -1;
if (lt(two[i], one[i])) return +1;
}
return 0;
}
};
(Beachten Sie, dass ich auch eq
und lt
hier definiert habe, die Zeichen auf Gleichheit bzw. weniger als vergleichen und dann compare
in Bezug auf diese Funktion definiert werden.)
Jetzt, wo wir diese Merkmalsklasse haben, können wir CaseInsensitiveString
trivial definieren als
typedef std::basic_string<char, CaseInsensitiveTraits> CaseInsensitiveString;
Und voila! Wir haben jetzt eine Zeichenfolge, die alles ohne Berücksichtigung der Groß- und Kleinschreibung behandelt!
Natürlich gibt es neben diesem Grund noch andere Gründe für die Verwendung von Merkmalen. Wenn Sie beispielsweise eine Zeichenfolge definieren möchten, die einen zugrunde liegenden Zeichentyp mit fester Größe verwendet, können Sie sich char_traits
auf diesen Typ spezialisieren und dann Zeichenfolgen aus diesem Typ erstellen. In der Windows-API gibt es beispielsweise einen Typ TCHAR
, der entweder ein schmales oder ein breites Zeichen ist, je nachdem, welche Makros Sie während der Vorverarbeitung festgelegt haben. Sie können dann Zeichenfolgen aus TCHAR
s machen, indem Sie schreiben
typedef basic_string<TCHAR> tstring;
Und jetzt hast du eine Reihe von TCHAR
s.
Beachten Sie in all diesen Beispielen, dass wir gerade eine Merkmalklasse (oder eine bereits vorhandene) als Parameter für einen Vorlagentyp definiert haben, um eine Zeichenfolge für diesen Typ zu erhalten. Der springende Punkt dabei ist, dass der basic_string
Autor nur angeben muss, wie die Merkmale verwendet werden sollen, und wir können sie auf magische Weise dazu bringen, unsere Merkmale anstelle der Standardmerkmale zu verwenden, um Zeichenfolgen zu erhalten, deren Nuancen oder Eigenheiten nicht Teil des Standardzeichenfolgen-Typs sind.
Hoffe das hilft!
EDIT : Wie @phooji wies darauf hin, dieser Begriff der Züge ist einfach nicht von der STL verwendet, noch ist es spezifisch für C ++. Als völlig schamlose Eigenwerbung, schrieb ich vor einiger Zeit eine Implementierung eines ternären Suchbaumes (eine Art von Radix Baum hier beschrieben ) , dass Anwendungen Merkmale zu speichern Zeichenkette jeder Art und mit was auch immer Vergleich des Kunde will , sie zu speichern geben. Es könnte eine interessante Lektüre sein, wenn Sie ein Beispiel dafür sehen möchten, wo dies in der Praxis verwendet wird.
BEARBEITEN : Als Antwort auf Ihre Behauptung, std::string
die nicht verwendet wird traits::length
, stellt sich heraus, dass dies an einigen Stellen der Fall ist. Insbesondere wenn Sie eine Zeichenfolge std::string
aus einem char*
C-Stil erstellen, wird die neue Länge der Zeichenfolge durch Aufrufen traits::length
dieser Zeichenfolge abgeleitet. Es scheint, dass dies traits::length
hauptsächlich verwendet wird, um Zeichenfolgen im C-Stil zu behandeln, die der "kleinste gemeinsame Nenner" von Zeichenfolgen in C ++ sind, während std::string
es verwendet wird, um mit Zeichenfolgen mit beliebigen Inhalten zu arbeiten.