Шаг 64.
Использование строковых функций

    На этом шаге мы приведем примеры использования строковых функций.

Определение длины строк
Копирование строк
Дублирование строк
Сравнение строк
Конкатенация строк


Определение длины строк.

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

   char *с = "Любая старая строка";
   int len;
следующий оператор установит переменную len равной длине строки, адресуемой указателем с:
   len = strlen(с);
Приведем пример использования функции strlen().


    Пример 1.
#include <stdio.h>
#include <string.h>
#include <iostream.h>
#define MAXLEN 256
main()
{
	char string[MAXLEN]; /* Место для 255 символов. */
	cout << "Задайте строку (можно задавать пробелы): ";
	gets(string);
	cout << endl; /* Начать новую строку. */
	cout << "Строка: " << string << endl;
	cout << "Ее длина = " << strlen(string);
}
Текст этой программы можно взять здесь.

    Здесь определяется строковая переменная с именем string для приема ввода от функции gets(). После того как вы введете строку, программа передаст переменную string функции strlen(), которая вычислит длину строки в символах.

    В функцию strlen() можно передавать и другие виды строк. Например, вы можете определить и инициализировать символьный буфер следующим образом:

    char buffer[128] = "Скопировано в буфер";

Затем используйте функцию strlen() для установки целой переменной len, равной числу символов в литеральной строке, скопированной в буфер:

     int len;	/* Определить целую переменную. */
     len = strlen(buffer); /* Вычислить длину строки. */

   

Копирование строк.

    Оператор присваивания для строк не определен. Если с1 и с2 - символьные массивы, вы не сможете скопировать один в другой следующим образом:

    с1 = с2; //???

    Но если с1 и с2 объявить как указатели типа char *, компилятор согласится с этим оператором, но вряд ли вы получите ожидаемый результат. Вместо копирования символов из одной строки в другую оператор с1 = с2 скопирует указатель с2 в указатель с1, перезаписав, таким образом, адрес в с1, потенциально потеряв информацию, адресуемую указателем.

    Чтобы скопировать одну строку в другую, вместо использования оператора присваивания вызовите функцию копирования строк strcpy(). Для двух указателей с1 и с2 типа char * оператор

    strcpy(с1, с2);
копирует символы, адресуемые указателем с2, в память, адресуемую указателем с1, включая завершающие нули. И только на вас лежит ответственность за то, что принимающая строка будет иметь достаточно места для хранения копии.

    Аналогичная функция strncpy() ограничивает количество копируемых символов. Если источник (source) и приемник (destination) являются указателями типа char * или символьными массивами, то оператор

    strcpy(destination, source, 10);

скопирует до 10 символов из строки, адресуемой указателем source, в область памяти, адресуемую указателем destination. Если строка source имеет больше 10 символов, то результат усекается. Если же меньше - неиспользуемые байты результата устанавливаются равными нулю.


    Замечание. Строковые функции, в имени которых содержится дополнительная буква n, объявляют числовой параметр, ограничивающий некоторым образом действие функции. Эти функции безопаснее, но медленнее, чем их аналоги, не содержащие букву n. Программные примеры содержат следующие пары функций: strcpy() и strncpy(), strcat() и strncat(), strcmp() и strncmp().

   

Дублирование строк.

    Использование механизма дублирования строк разберем на конкретном примере: создадим функцию, которая выводила бы приглашение и возвращала строку, введенную с клавиатуры. Желательно, чтобы эта строка запоминалась в куче и она занимала бы ровно столько байтов, сколько требуется.

    Текст примера 2 образет один маленький модуль с единственной функцией GetStringAt(), которая удовлетворяет этим требованиям, используя другую строковую функцию strdup() для того, чтобы сделать копию выводимой строки. Другие примеры этого раздела используют модуль c64_2.cpp.

    Файл c64_2.h - заголовочный: он содержит только объявления для включения в другие модули. Файл c64_2.cpp - это отдельный модуль, содержащий функцию (их может быть несколько), используемую в программе. Ни один из этих файлов не является законченной программой, поэтому не пытайтесь компилировать и запускать их. Ниже мы вспомним, как это можно сделать (см. пример 4 шага 53).


    Пример 2а. Текст заголовочного файла c64_2.h.
#define MAXLEN  256	/* Максимальный размер строки.*/
char *GetStringAt(int,int,int); /* Вычислить длину строки. */
Текст этого модуля можно взять здесь.

    Пример 2б. Текст модуля c64_2.cpp.

#include<stdio.h>
#include<string.h>
#include<conio.h>
#include "c64_2.h"
/* Замечание: это не полная программа, нужно 
скомпоновать этот модуль с главной программой. */
char *GetStringAt(int x, int y, int size)
{
  static char buffer[MAXLEN]; /* Временный буфер ввода. */
  int i=0; /* Индекс буфера. */
  char c; /* Принимает каждый введенный символ. */

  if ((size > MAXLEN) || (size <= 0))/* Проверить размер. */
	  size = MAXLEN;
  gotoxy(x, y); /* Установка курсора. */
  while (--size > 0) { /* Ввод строки. */
	 c = getchar();
	 if (c == EOF || c == '\n')
		 size = 0;
	 else
		 buffer[i++] = c;
  }
  buffer[i] = '\0'; /* Завершение строки нулем. */
  fflush(stdin); /* Опустить "возврат каретки". */
  return strdup(buffer)	; /* Возвращение копии строки buffer. */
}
Текст этого модуля можно взять здесь.

    Указанный модуль включает свой собственный заголовочный файл, который определяет константу MAXLEN и объявляет прототип функции GetStringAt(). Внутри этой функции расположено объявление статической строки buffer. Использование ключевого слова static создает переменную, которая постоянно хранится в сегменте данных, но доступна только внутри функции GetStringAt().

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

    Оператор if проверяет параметр size. Если его значение находится вне заданного диапазона, то ему присваивается значение MAXLEN.

    После позиционирования курсора с помощью вызова функции gotoxy() в цикле while вызывается функция getchar(), которая ожидает, пока вы введете символ. Программа присваивает этот символ переменной c и проверяет на совпадение с EOF (end of file - конец файла, означающий, что источник ввода закрыт) или с управляющим символом \n ("новая строка", означающая, что вы нажали клавишу Enter). Если одно из этих условий оказалось выполненным, программа устанавливает параметр size равным нулю, завершая тем самым цикл while. В противном случае программа присваивает значение переменной c элементу массива buffer с индексом i, инкрементированным оператором ++ для подготовки к вводу следующего символа.


    Замечание. Выполните цикл while по шагам, используя клавишу F7, наблюдая при этом за значениями переменных buffer, c и i.

    Затем добавляется нулевой символ (литеральное выражение '\0') за последним символом, запомненным в строке buffer. После этого вызывается функция fflush(), с аргументом stdin, который является встроенным символом, представляющим стандартный файл ввода. Вызов функции fflush() очищает "повисший" символ новой строки, который может привести к тому, что функция getchar() (и другие функции ввода) не будет делать паузу для ожидания ввода.

    И, наконец, в строке return strdup(buffer); возвращается результат функции GetStringAt(). Этот оператор передает строку buffer функции strdup(), которая создает копию символов и возвращает адрес дубликата строки.

    Функция strdup() (ее название говорит само за себя) возвращает адрес дубликата строки, адресуемой ее аргументом. Строка создается с помощью вызова функции malloc() и занимает столько памяти, сколько необходимо. Если переменная c имеет тип char *, то оператор

     c = strdup( "Двойная тревога");
выделит ровно 16 байт памяти кучи, скопирует в эту область памяти 15-символьную строку "Двойная тревога" плюс завершающий нуль и возвратит адрес этой области. По окончании работы с этой строкой следует освободить эту область памяти обычным способом:
     free(c);


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


    Пример 3. Иллюстрация использования функции GetStringAt().
#include<stdio.h>
#include<string.h>
#include<conio.h>
#include<alloc.h>
#include "c64_2.h"

/* Замечание: эта программа должна	
быть скомпонована с файлом c64_2.obj,
созданным при компиляции c64_2.cpp. */
#define X_ENTRY 12 /* Координата ввода Х. */
#define Y_ENTRY 12 /* Координата ввода Y. */
#define PROMPT "Строка: " /* Приглашение на ввод. */
void main()
{
  char *s; /* Указатель на результат функции GetStringAt(). */
  clrscr();
  gotoxy(X_ENTRY - strlen(PROMPT), Y_ENTRY);
  printf("Строка: ");
  s = GetStringAt(X_ENTRY,Y_ENTRY,MAXLEN);
  gotoxy(1, 24);
  if (s) {
    puts("Вы ввели строку: ");
    puts(s);
    printf("Ее длина = %d символов\n",strlen(s));
    free(s);
         } 
  else
    puts("Ошибка при копировании строки!");
}
Текст этой программы можно взять здесь.

    Пример 3 демонстрирует применение функции GetStringAt(), объявленной в файле c64_2.h и описанной в файле c64_2.cpp. Чтобы создать законченную программу, вы должны скомпилировать c64_3.cpp и скомпоновать с модулем c64_2. Произведем эти операции из командной строки, получив в результате DOS-приложение.

     bcc32 -c c64_2.cpp
     bcc32 c64_3.cpp c64_2.obj

    Первая команда компилирует модуль c64_2.cpp, создавая объектный файл с именем c64_2.obj который содержит скомпилированный код для функции GetStringAt(). Вторая команда компилирует модуль c64_3.cpp, создавая объектный файл с именем c64_3.obj, а также компонует эти два объектных файла и создает окончательный исполняемый файл c64_3.exe, который можное запустить в DOS.

    В строке

     s = GetStringAt(X_ENTRY,Y_ENTRY,MAXLEN);

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

    В строке

     if (s) {
программа проверяет результат функции GetStringAt(). Если она возвращает нуль, это значит, что функция strdup() не смогла создать копию ввода, возможно, из-за недостатка памяти в куче. Обратите внимание также на освобождение памяти после того, как она оказывается больше не нужной (конструкция free(s)).

Сравнение строк.

    Используя функцию GetStringAt() из предыдущего раздела, можно написать программу, которая предлагает ввести пароль. Чтобы определить правильность введенного пароля, воспользуемся функцией strcmp(), которая сравнивает две строки.

    Чтобы программа успешно завершилась, аккуратно введите текст Borland C++, используя прописные и строчные буквы. Если вы введете пароль неправильно, программа отобразит сообщение об ошибке и потребует начать сначала. Как и другие подобные программы с парольным входом, программа примера 4 очищает экран, чтобы никто не смог подсмотреть пароль.


    Пример 4. Иллюстрация использования функции strcmp().
#include<stdio.h>
#include<string.h>
#include<conio.h>
#include<alloc.h>
#include<stdlib.h>
#include "c64_2.h"

#define FALSE 0
#define TRUE 1
#define PASSWORD "Borland C++"
#define PROMPT "Пароль: "

/* Замечание: эта программа должна	
быть скомпонована с файлом c64_2.obj,
созданным при компиляции c64_2.cpp. */
void main()
{
  char *s; /* Указатель на результат функции GetStringAt(). */
  int done = FALSE;

  clrscr();
  while (!done)
  {
    printf(PROMPT);
    s = GetStringAt(strlen(PROMPT)+1,wherey(),MAXLEN);
    clrscr();
    if (s) 
      {
         done = (strcmp(s, PASSWORD)==0);
         if (!done)
             puts("Вы неправильно ввели пароль. Повторите ввод.");
         free(s);
       } 
  else
      { puts("Не хватает памяти!");exit(1); }
  }
}
Текст этой программы можно взять здесь.

    В строке

     done = (strcmp(s, PASSWORD)==0);

показано, как сравнивать две строки, в данном случае для того, чтобы определить правильность введенного пароля. Здесь переменная done получает значение "истина" (что соответствует любому ненулевому значению), если строка, адресуемая указателем s, и PASSWORD (макроопределение, которое расширяется в литеральную строку) равны. Если i - переменная типа int и если a и b - указатели на char или символьные массивы, то оператор

     i = strcmp(a, b);

установит i равной -1 или другому отрицательному числу, если строка, адресуемая указателем a, в алфавитном порядке меньше строки, адресуемой указателем b. Если строки в точности совпадают, функция возвратит нуль. Она вернет +1 или другое положительное число, если строка a в алфавитном порядке больше строки b.

    Функция strcmp() чувствительна к регистру букв - она считает строчные буквы больше их прописных эквивалентов (так как буквы нижнего регистра имеют большие значения кода ASCII, чем буквы верхнего регистра). Для сравнения двух строк без учета регистра вызовите функцию stricmp(). Буква i символизирует приказ ignore case (игнорировать регистр). Эта функция действует аналогично функции strcmp(), но перед сравнением преобразует все буквы в прописные. Строка Apple алфавитно окажется меньше строки apple, если сравнение выполнялось с помощью функции strcmp(). Если же для сравнения использовать функцию stricmp(), то эти строки будут считаться идентичными.

    Чтобы сравнить только часть двух строк, используйте функцию strncmp(). Например, оператор

     i = strncmp(s1, s2, 2);

установит целую переменную i равной нулю только в том случае, если первые два символа строк, адресуемых указателями s1 и s2, в точности совпадают. Для безрегистрового сравнения вызывайте функцию strnicmp().

Конкатенация строк.

    Конкатенация двух строк означает их сцепление, при этом создается новая, более длинная строка. При объявлении строки

    char original[128] = "Проверка";
оператор
     strcat(original, " один, два, три!");
превратит значение первоначальной строки original в "Проверка один, два, три!"

    При вызове функции strcat() убедитесь, что первый аргумент типа char * инициализирован и имеет достаточно места, чтобы запомнить результат. Если c1 адресует строку, которая уже заполнена, а c2 адресует ненулевую строку, оператор strcat(c1, c2); перезапишет конец строки, вызвав серьезную ошибку.

    Функция strcat() возвращает адрес результирующей строки (совпадающий с ее первым параметром) и может использоваться как каскад нескольких вызовов функций:

     strcat(strcat(c1,c2),c3)
Этот оператор добавит строку, адресуемую c2, и строку, адресуемую c3, к строке, адресуемой c1, что эквивалентно двум отдельным операторам:
     strcat(c1,c2);
     strcat(c1,c3);

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


    Пример 5. Иллюстрация использования функции strcat().
#include <iostream.h>
#include <string.h>
void main()
{ //Резервирование места для ввода трех строк.
  char *fam = new char[128];
  char *im = new char[128];
  char *otch = new char[128];
  //Ввод данных.
  cout << "Задайте" << endl;
  cout << "\tфамилию: ";
  cin >> fam;
  cout << "\tимя: ";
  cin >> im;
  cout << "\tотчество: ";
  cin >> otch;
  //Резервирование места для результата.
  //Нужно учесть два пробела и результирующий
  //нулевой символ.
  char *rez=new char[strlen(fam)+strlen(im)+strlen(otch)+3];
  //"Сборка" результата.  
  strcat(strcat(strcpy(rez,fam)," "),im);
  strcat(strcat(rez," "),otch);
  //Возврат памяти в кучу.
  delete [] fam;
  delete [] im;
  delete [] otch;
  //Вывод результата.
  cout << "\nРезультат: " << rez;
  delete [] rez;
}
Текст этой программы можно взять здесь.

    Приведенная программа демонстрируют важный принцип конкатенации строк: всегда инициализируйте первый строковый аргумент. В данном случае символьный массив rez инициализируется вызовом функции strcpy(), которая вставляет fam в rez. После этого программа добавляет пробелы и две другие строки - im и otch. Никогда не вызывайте функцию strcat() с неинициализированным первым аргументом.

    Если вы не уверены в том, что в строке достаточно места для присоединяемых подстрок, вызовите функцию strncat(), которая аналогична функции strcat(), но требует числового аргумента, определяющего число копируемых символов. Для строк s1 и s2, которые могут быть либо указателями типа char *, либо символьными массивами, оператор

     strncat(s1, s2, 4);

присоединяет и максимум четыре символа из s2 в конец строки s1. Результат обязательно завершается нулевым символом.

    Существует один способ использования функции strncat(), гарантирующий безопасную конкатенацию. Он состоит в передаче функции strncat() размера свободной памяти строки-приемника в качестве третьего аргумента. Рассмотрим следующие объявления:

     #define MAXLEN 128 
     char s1[MAXLEN] = "Кот";
     char s2[] = "в шляпе";

Вы можете присоединить s2 к s1, формируя строку "Кот в шляпе", с помощью функции strcat():

     strcat(s1, s2);

    Если вы не уверены, что в s1 достаточно места, чтобы запомнить результат, используйте альтернативный оператор:

     strncat(s1, s2, (MAXLEN-1)-strlen(s1));
Этот способ гарантирует, что s1 не переполнится, даже если s2 нужно будет урезать до подходящего размера. Этот оператор прекрасно работает, если s1 - нулевая строка.

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


Определение длины строк Копирование строк Дублирование строк Сравнение строк Конкатенация строк

Предыдущий шаг Содержание Следующий шаг