Назад | Начало урока | Вперед
Содержание

Глава 3

Вверх

Строки и характеристики символов
Знакомясь с программой Find.cpp, приведенной ранее в этой главе трудно удержаться от очевидного вопроса: почему сравнение без учета регистра символов не поддерживается в стандартном классе string? Ответ заставляет по-новому взглянуть на истинную природу строковых объектов С++.

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

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

Хотя строки С++ обычно интерпретируются как классы, на самом деле это не совсем так. Тип string представляет собой специализацию более общего шаблона basic_string. Посмотрите как выглядит объявление string в стандартном заголовочном файле С++:

typedef basic_string<char> string;

Чтобы лучше понять природу класса string, стоит взглянуть на шаблон basic_string:

template<class charT, class traits = char_traits<charT>,
class allocator = allocator<charT> > class basic_string

В главе 5 шаблоны будут рассматриваться более подробно. А пока обратите внимание, что тип string создается специализацией шаблона basic_string по типу char. Внутри объявления basic_string<> следующая строка сообщает, что поведение класса, созданного на базе шаблона basic_string, задается классом, созданным на базе шаблона char_traits<>:

class traits = char_traits<charT>

Таким образом на базе шаблона basic_string<>, создаются строковые классы, которые помимо char могут работать с другими типами (например с символами в расширенной кодировке). При этом шаблон char_traits<> определяет порядок сортировки в различных кодировках при помощи функций eq() (равно), ne() (не равно), lt() (меньше). От этих функций зависит работа функций сравнения строк basic_string<>.

Теперь понятно, почему класс string не содержит функций, не учитывающих регистр символов: это не входит в его задачи. Чтобы изменить способ сравнения строк в классе string, следует предоставить другой шаблон char_traits<>, поскольку именно он определяет поведение отдельных функций сравнения символов.

На основе этой инф можно создать новую разновидность класса string игнорирующую регистр символов при сравнениях. Сначала мы должны определить новый шаблон char_traits<>, производный от существующего шаблона, который бы игнорировал регистр символов.

Затем следует переопределить только те функции, которые бы обеспечивали посимвольное сравнение без учета регистра (кроме тех функций лексического сравнения, о которых упоминалось ранее, так же определяется новая реализация функций find() и compare() шаблона char_traits<>).

Наконец мы определяем новую специализацию на базе шаблона basic_string<>, но передаем во втором аргументе новый шаблон char_traits<>:

Вверх


//: C03:ichar_traits.h
// Создание пользовательских классов характеристик символов
#ifndef ICHAR_TRAITS_H
#define ICHAR_TRAITS_H
#include <cassert>
#include <cctype>
#include <cmath>
#include <cstddef>
#include <ostream>
#include <string>

using std::allocator;
using std::basic_string;
using std::char_traits;
using std::ostream;
using std::size_t;
using std::string;
using std::toupper;
using std::tolower;

struct ichar_traits : char_traits<char> {

// Изменяются только функции сравнения символов.
static bool eq(char c1st, char c2nd) {
return toupper(c1st) == toupper(c2nd);
}
static bool ne(char c1st, char c2nd) {
return !eq(c1st, c2nd);
}
static bool lt(char c1st, char c2nd) {
return toupper(c1st) < toupper(c2nd);
}
static int compare(const char* str1, const char* str2, size_t n) {
for(size_t i = 0; i < n; i++) {
if(str1 == 0)
return -1;
else if(str2 == 0)
return 1;
else if(tolower(*str1) < tolower(*str2))
return -1;
else if(tolower(*str1) > tolower(*str2))
return 1;
assert(tolower(*str1) == tolower(*str2));
str1++; str2++; // Compare the other chars
}
return 0;
}

static const char* find(const char* s1, size_t n, char c) {

while(n-- > 0)
if(toupper(*s1) == toupper(c))
return s1;
else
++s1;
return 0;
}
};

typedef basic_string<char, ichar_traits> istring;

inline ostream& operator<<(ostream& os, const istring& s) {

return os << string(s.c_str(), s.length());
} #endif // ICHAR_TRAITS_H ///:~


Мы создали определение типа istring, чтобы наш класс был во всех отношениях аналогичен обычному классу string, кроме одного - все сравнения осуществляются без учета регистра символов. Для удобства так же предоставлена перегруженная версия операторной функции operator<<() для вывода строк.

Пример:


//: C03:ICompare.cpp
#include <cassert>
#include <iostream>
#include "ichar_traits.h"
using namespace std;

int main() {

// Буквы совпадают, отличается только регистр:
istring first = "tHis";
istring second = "ThIS";
cout << first << endl;
cout << second << endl;
assert(first.compare(second) == 0);
assert(first.find('h') == 1);
assert(first.find('I') == 2);
assert(first.find('x') == string::npos);
} ///:~

Результат:

tHis
ThIS

Анализ:

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

Впрочем это сильно упрощенный, "ненастоящий" пример. Чтобы класс istring стал полностью эквивалентным string, необходимо определить другие функции, обеспечивающие его работу.

Посмотрим функцию сравнения строк, без учета регистра символов:


static int compare(const char* str1, const char* str2, size_t n) {

for(size_t i = 0; i < n; i++) {
if(str1 == 0)
return -1;
else if(str2 == 0)
return 1;
else if(tolower(*str1) < tolower(*str2))
return -1;
else if(tolower(*str1) > tolower(*str2))
return 1;
assert(tolower(*str1) == tolower(*str2));
str1++; str2++; // Compare the other chars
}
return 0;
}

Как видим функции передаются три параметра :

указатель на первый символьный массив (строка 1)
указатель на второй символьный массив (строка 2)
переменная n, равная надо полагать длине наибольшего символьного массива

Здесь в этой функции строки рассматриваются как символьные массивы. И сравнение строк будет происходить посимвольно.


Назад | Начало урока | Вверх | Вперед
Содержание

Hosted by uCoz