На этом шаге мы рассмотрим пример, иллюстрирующий работу со строками.
В первом примере строится имя временного файла по аргументам командной строки. Например, рассмотрим следующую команду:
pr359_1.exe prog.dat mydir hello. oops.tmp end.dat
Если запустить программу с этими аргументами, то результат будет выглядеть так:
Рис.1. Результат работы приложения
В большинстве случаев временный файл снабжается расширением .tmp, а при передаче имени временного файла в аргументе вместо него используется расширение .ххх.
Приведем текст приложения.
//--------------------------------------------------------------------------- #include <vcl.h> #include <iostream> #include <string> #include <conio.h> //необходимо для getch() #pragma hdrstop //--------------------------------------------------------------------------- #pragma argsused using namespace std; std::string ToRus(const std::string &in) { char *buff = new char [in.length()+1]; CharToOem(in.c_str(),buff); std::string out(buff); delete [] buff; return out; } int main (int argc, char* argv[]) { string filename, basename, extname, tmpname; const string suffix("tmp"); // Для каждого аргумента командной строки // (который является обычной C-строкой) for (int i=1; i<argc; ++i) { // Аргумент интерпретируется как имя файла filename = argv[i]; // Поиск точки в имени файла string::size_type idx = filename.find('.'); if (idx == string::npos) { // Имя файла не содержит точек tmpname = filename + '.' + suffix; } else { // Разделение имени файла на базовое имя и расширение // - базовое имя содержит все символы перед точкой // - расширение содержит все символы после точки basename = filename.substr(0, idx); extname = filename.substr(idx+1); if (extname.empty()) { // Содержит точку, но без расширения: // присоединить символы tmp tmpname = filename; tmpname += suffix; } else if (extname == suffix) { // Расширение tmp заменяется на xxx tmpname = filename; tmpname.replace (idx+1, extname.size(), "xxx"); } else { // Замена любого расширения на tmp tmpname = filename; tmpname.replace (idx+1, string::npos, suffix); } } // Вывод имен исходного и временного файлов cout << filename << " => " << tmpname << endl; } getch(); return 0; } //---------------------------------------------------------------------------
Прокомментируем приведенную программу.
Сначала показанная ниже директива включает заголовочный файл стандартных строковых классов C++:
#include <string>
Как обычно, эти классы объявляются в пространстве имен std. Следующее объявление создает четыре строковые переменные:
string filename, basename, extname, tmpname;
Аргументы не передаются, поэтому инициализация выполняется конструктором по умолчанию класса string. Конструктор по умолчанию инициализирует переменные пустыми строками.
Следующее объявление создает константную строку suffix со стандартным расширением для временных файлов:
const string suffix("tmp");
Переменная инициализируется обычной С-строкой и получает значение tmp. С-строки могут комбинироваться с объектами класса string практически всегда, когда могут использоваться два объекта string. В частности, все вхождения переменной suffix в программе можно заменить С-строками "tmp".
При каждой итерации цикла for показанная ниже команда присваивает новое значение строковой переменной filename:
filename = argv[1];
В данном случае новое значение определяется обычной С-строкой, однако оно также может определяться другим объектом класса string или отдельным символом (char).
Следующая команда ищет в строке filename первое вхождение символа . (точка):
string::size_type idx = filename.find('.');
Функция find() входит в группу функций, предназначенных для поиска в строках. Другие функции этой группы позволяют выполнить поиск в обратном направлении, поиск подстрок, ограничить поиск определенной частью строки или найти вхождение одного из нескольких возможных символов. Все поисковые функции возвращают не итератор, а целочисленный индекс позиции первого совпадения. Стандартный интерфейс строк не соответствует интерфейсу шаблонов STL, хотя итераторы могут работать и со строками. Возвращаемое значение всех функций относится к типу string::size_type - целочисленному беззнаковому типу, определяемому в строковом классе. Как обычно, индекс первого символа равен 0, а индекс последнего символа - numberofCharacters-1. Помните, что индекс numberOfCharacters не является допустимым индексом. В отличие от C-строк объекты класса string не завершаются специальным символом "\0".
Признак неудачи при поиске возвращается в виде специального значения npos, которое также определяется строковым классом. Таким образом, следующая строка проверяет, была ли найдена точка в имени файла:
if (idx == string::npos)
Тип и значение npos часто становятся причиной ошибок при работе со строками. Будьте внимательны и всегда используйте следующую конструкцию при проверке возвращаемого значения функции поиска (а не int или unsigned int):
sitring::size_type
В противном случае сравнение с string::npos может не сработать.
Если поиск точки оказывается неудачным, значит, имя файла не содержит засширения. В этом случае имя временного файла строится из исходного имени райла, точки и заранее определенного расширения для временных файлов:
tmpname = filename + '.' + suffix;
Конкатенация двух строк производится при помощи обычного оператора +. В конкатенации также могут участвовать С-строки и одиночные символы.
При обнаружении точки используется секция else. В этом случае имя файла по индексу найденного символа разбивается на две части: базовое имя и расширение. Разбиение производится функцией substr():
basename = filename.substr(0, idx); extname = filename.substr(idx+1);
В первом аргументе функции substr() передается начальный индекс. Необязательный второй аргумент определяет количество символов (а не конечный индекс!). Если второй аргумент отсутствует, в возвращаемую подстроку включаются все оставшиеся символы строки.
Всюду, где в аргументах передаются индекс и длина, выполняются представленные ниже два правила.
Следовательно, при отсутствии точки в следующем выражении произойдет исключение:
filename.substr(filename.find('.'));
С другой стороны, показанное ниже выражение будет выполнено без исключений:
filename.substr(0, filename.find ('.''));
Если точка не найдена, возвращается все имя файла.
Но даже при наличии точки расширение, возвращаемое при вызове substr(), может оказаться пустым, если за точкой нет ни одного символа. Данная ситуация проверяется условием:
if (extname.empty())
Если условие истинно, то имя временного файла состоит из обычного имени файла с заранее определенным расширением:
tmpname = filename; tmpname += suffix;
Расширение присоединяется оператором +=.
Также нельзя исключать, что имя файла уже содержит расширение для временных файлов. Чтобы выявить эту ситуацию, мы сравниваем две строки оператором == следующим образом:
if (extname == suffix)
Если это условие истинно, то обычное расширение временных файлов заменяется расширением ххх:
tmpname = filename; tmpname.replace (idx+1, extname.size(), "xxx");
Выражение extname.size() возвращает количество символов в строке extname. Вместо size() также можно воспользоваться эквивалентной функцией length(). Таким образом, обе функции size() и length() возвращают количество символов.
Помните, что функция size() не имеет отношения к объему памяти, выделяемой для хранения строки.
После проверки всех специальных условий происходит основная обработка данных. Расширение файла заменяется расширением .tmp, обычно используемым для временных файлов:
tmpname = filename; tmpname.replace (idx+1. string::npos, suffix);
Конструкция string::npos в данном случае обозначает "все остальные символы". Иначе говоря, все символы после точки заменяются строкой suffix. Замена работает и в том случае, если имя файла содержит точку без дальнейших символов - "пустое расширение" заменяется строкой suffix.
Команда вывода имен исходного и временного файлов показывает, что для отображения строк могут применяться стандартные потоковые операторы вывода:
cout << filename << " => " << tmpname << endl;
На следующем шаге мы рассмотрим еще один пример работы со строками.