Функции strcpy() и strncpy()

Язык C++ унаследовал от С библиотечные функции, выполняющие операции над строками. Среди множества доступных функций есть две, которые осуществляют копирование одной строки в другую. Это функции strcpy() и strncpy(). Функция strcpy() копирует строку целиком в указанный буфер, как показано в листинге 12.10.

Листинг 12.10. Использование функции strcpy()

1: #include <iostream.h>

2: #include <string.h>

3: int main()

4: {

5: char String1[] = "No man is an island";

6: char String2[80];

7:

8: strcpy(String2,String1);

9:

10: cout << "String1: " << String1 << endl;

11: cout << "String2: " << String2 << endl;

12: return 0;

13: }

Результат:

String1: No man is an island

String2: No man is an island

Анализ: Файл заголовка string.h включается в программу в строке 2. Этот файл содержит прототип функции strcpy(). В качестве аргументов функции указываются два массива символов, первый из которых является целевым, а второй — массивом источника данных. Если массив-источник окажется больше целевого массива, то функция strcpy() введетданные за пределы массива.

Чтобы предупредить подобную ошибку, в этой библиотеке функций содержится еще одна функция копирования строк: strncpy(). Эта функция копирует ряд символов, не превышающий длины строки, заданной в целевом массиве. Функция strncpy() также прерывает копирование, если ей повстречается символ разрыва строки. Использование функции strncpy() показано в листинге 12.11.

Листинг 12.11. Использование функции strncpy()

1: #include <iostream.h>

2: #include <string.h>

3: int main()

4: {

5: const int MaxLength = 80;

6: char String1[] = "No man is an island";

7: char String2[MaxLength+1];

8:

9:

10: strncpy(String2,String1,MaxLength);

11:

12: cout << "String1: " << String1 << endl;

13: cout << "String2: " << String2 << endl;

14: return 0;

15: }

Результат:

String1: No man is an island

String2: No man is an island

Анализ: В строке 10 программа вместо функции strcpy() используется функцию strncpy(), третий параметр MaxLength которой задает максимальную длину копируемой строки. Размер массива String2 задан как MaxLength+1. Дополнительный элемент потребовался для концевого нулевого символа строки, который добавляется автоматически обеими функциями — strcpy() и strncpy().

Классы строк

Многие компиляторы C++ содержат библиотеки классов, с помощью которых можно решать различные прикладные задачи. Одним из представителей встроенных классов является класс String.

Язык C++ унаследовал от С концевой нулевой символ окончания строки и библиотеку строковых функций, куда входит функция strcpy(). Но все эти функции нельзя использовать в объектно-ориентированном программировании. Класс String предлагает набор встроенных функций-членов и переменных-членов, а также методов доступа, которые позволяют автоматически решать многие задачи, связанные с обработкой текстовых строк, получая команды от пользователя.

Если в вашем компиляторе нет встроенного класса String, а иногда и в тех случаях, когда он есть, бывает необходимо создать собственный класс работы со строками. Далее в этой главе рассматривается процедура создания и применения класса String и пользовательских классов работы со строками.

Как минимум, класс String должен преодолеть ограничения, свойственные использованию массивов символов. Подобно другим массивам, массивы символов статичны. Вам приходится задавать их размер при объявлении или инициализации. Они всегда занимают все отведенное для них пространство памяти, даже если вы используете только по- ловину элементов массива. Запись данных за пределы массива ведет к катастрофе.

Хорошо написанный класс работы со строковыми данными выделяет столько памяти, сколько необходимо для текущего сеанса работы с программой, и всегда предусматривает возможность добавления новых данных. Если с выделением дополнительной памяти возникнут проблемы, предусмотрены элегантные пути их решения. Первый пример использования класса String показан в листинге 12.12.

Листинг 12.12. Использование класса String

1: // Листинг. 12.12

2:

3: #include <iostream.h>

4: #include <string.h>

5:

6: // Рудиментарный класс string

7: class String

8: {

9: public:

10: // Конструкторы

11: String()

12: Stnng(const char *const),

13: Stnng(const String &),

14: ~Stnng()

15:

16: // Перегруженные операторы

17: char & operator[](unsigned short offset),

18: char operator[](unsigned short offset) const,

19: Stnng operator+(const String&),

20: void operator+=(const String&)

21: Stnng & operator= (const Stnng &),

22:

23: // Основные методы доступа

24: unsigned short GetLen()const { return itsLen, }

25: const char * GetStnng() const { return itsStnng, }

26:

27: private:

28: Stnng (unsigned short), // Закрытый конструктор

29: char * itsStnng,

30: unsigned short itsLen

31: }

32:

33: // Конструктор, заданный no умолчанию, создает строку нулевой длины

34: String String()

35: {

36: itsStnng = new char[1]

37: itsStrmg[0] = '\0'

38: itsLen=0;

39: }

40:

41: // Закрытый (вспомогательный) конструктор

42: // используется только методами класса для создания

43: // строк требуемой длины с нулевым наполнением

4й: String String(unsigned short len)

45: {

46: itsStnng = new char[len+1]

47: for (unsigned short i = 0 i<=len, i++)

48: itsString[i] = \0 ,

49: itsLen=len,

50: }

51:

52: // Преобразование массива символов в строку

53: String String(const char * const cString)

54: {

55: itsLen = strlen(cString);

56: itsString = new char[itsLen+1];

57: for (unsigned short i = 0; i<itsLen: i++)

58: itsString[i] = cString[i];

59: itsString[itsLen]='\0';

60: }

61:

62: // Конструктор-копировщик

63: String::String (const String & rhs)

64: {

65: itsLen=rhs.GetLen();

66: itsString = new char[itsLen+1];

67: for (unsigned short i = 0; i<itsLen;i++)

68: itsString[i] = rhs[i];

69: itsString[itsLen] = '\0';

70: }

71:

72: // Деструктор для освобождения памяти

73: String::~String ()

74: {

75: delete [] itsString;

76: itsLen = 0;

77: }

78:

79: // Оператор присваивания освобождает память

80: // и копирует туда string и size

81: String& String::operator=(const String & rhs)

82: {

83: if (this == &rhs)

84: return *this;

85: delete [] itsString;

86: itsLen=rhs.GetLen();

87: itsString = new char[itsLen+1];

88: for (unsigned short i = 0; i<itsLen;i++)

89: itsString[i] = rhs[i];

90: itsString[itsLen] = '\0';

91: return *this;

92: }

93:

94: //неконстантный оператор индексирования

95: // возвращает ссылку на символ так, что его

96: // можно изменить!

97: char & String::operator[](unsigned short offset)

98: {

99: if (offset > itsLen)

100: return itsString[itsLen-1];

101: else

102: return itsString[offset];

103: }

104:

105: // константный оператор индексирования для использования

106: // с константными объектами (см. конструктор-копировщик!)

107: char String::operator[](unsigned short offset) const

108: {

109: if (offset > itsLen)

110: return itsString[itsLen-1];

111: else

112: return itsString[offset];

113: }

114:

115: // создание новой строки путем добавления

116: // текущей строки к rhs

117: String String::operator+(const String& rhs)

118: {

119: unsigned short totalLen = itsLen + rhs.GetLen();

120: String temp(totalLen);

121: unsigned short i;

122: for ( i= 0; i<itsLen; i++)

123: temp[i] = itsString[i];

124: for (unsigned short j = 0; j<rhs.GetLen(); j++, i++)

125: temp[i] = rhs[j];

126: temp[totalLen]='\0';

127: return temp;

128: }

129:

130: // изменяет текущую строку и возвращает void

131: void String::operator+=(const String& rhs)

132: {

133: unsigned short rhsLen = rhs.GetLen();

134: unsigned short totalLen = itsLen + rhsLen;

135: String temp(totalLen);

136: unsigned short i;

137: for (i = 0; i<itsLen; i++)

138: temp[i] = itsString[i];

139: for (unsigned short j = 0; j<rhs.GetLen(); j++, i++)

140: temp[i] = rhs[i-itsLen];

141: temp[totalLen]='\0';

142: *this = temp;

143: }

144:

145: int main()

146: {

147: String s1("initial test");

148: cout << "S1:\t" << s1.GetString() << endl;

149:

150: char * temp = "Hello World";

151: s1 = temp;

152: cout << "S1:\t" << s1.GetString() << endl;

153:

154: char tempTwo[20];

155: strcpy(tempTwo,"; nice to be here!");

156: s1 += tempTwo;

157: cout << "tempTwo:\t" << tempTwo << endl;

158: cout << "S1:\t" << s1.GetString() << endl;

159:

160: cout << "S1[4] :\t" << s1[4] << endl;

161: s1[4]='o';

162: cout << "S1:\t" << s1.GetString() << endl;

163:

164: cout << "S1[999] :\t" << s1[999] << endl;

165:

166: String s2(" Another string");

167: String s3;

168: s3 = s1+s2;

169: cout << "S3:\t" << s3.GetString() << endl:

170:

171: String s4;

172: s4 = "Why does this work?";

173: cout << "S4:\t" << s4.GetString() << endl;

174: return 0;

175: }

Результат:

S1: initial test

S1: Hello world

tempTwo: ; nice to be here!

S1: Hello world; nice to be here!

S1[4]: o

S1: Hello World; nice to be here!

S1[999]: !

S3: Hello World; nice to be here! Another string

S4: Why does this work?

Анализ: В строках 7—31 объявляется простой класс String. В строках 11—13 объявляются конструктор по умолчанию, конструктор-копировщик и конструктор для приема существующей строки с концевым нулевым символом (стиль языка С).

В классе String перегружаются операторы индексирования ([]), суммирования (+) и присваивания с суммой (+=). Оператор индексирования перегружается дважды. Один раз как константная функция, возвращающая значение типа char, а другой — как неконстантная функция, возвращающая указатель на char.

Неконстантная версия оператора используется в выражениях вроде строки 161: SomeString[4]=V;

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

Константная версия оператора используется в тех случаях, когда необходимо получить доступ к константному объекту класса String, например при выполнении конструктора-копировщика в строке 63. Обратите внимание, что в этом случае открывается доступ к rhs[i], хотя rhs был объявлен как const String &. К этому объекту невозможно получить доступ, используя неконстантные функции-члены. Поэтому оператор индексирования необходимо перегрузить как константный.

Если возвращаемый объект окажется слишком большим, возможно, вам потребуется установить возврат не значения, а константной ссылки на объект. Но поскольку в нашем случае один символ занимает всего один байт, в этом нет необходимости.

Конструктор, заданный по умолчанию, выполняется в строках 33—39. Он создает строку нулевой длины. Общепринято, что в классе String длина строки измеряется без учета концевого нулевого символа. Таким образом, строка, созданная по умолчанию, содержит только концевой нулевой символ.

Конструктор-копировщик выполняется в строках 63—70. Он задает длину новой строки равной длине существующей строки плюс одна ячейка для концевого нулевого символа. Затем конструктор-копировщик копирует существующую строку в новую и добавляет в конце нулевой символ окончания строки.

В строках 53—60 выполняется конструктор, принимающий строку с концевым нулевым символом. Этот конструктор подобен конструктору-копировщику. Длина существующей строки определяется с помощью стандартной функции strlen() из библиотеки String.

В строке 28 объявляется еще один конструктор, String(unsigned short), как закрытая функция-член. Он был добавлен для того, чтобы не допустить создания в классе String строк произвольной длины каким-нибудь другим пользовательским классом. Этот конструктор позволяет создавать строки только внутри класса String в соответствии со сделанными установками, как, например, в строке 131 с помощью operator+=. Более подробно этот вопрос рассматривается ниже, при объявлении operator+=.

Конструктор String(unsigned short) заполняет все элементы своего массива символов значениями NULL. Поэтому в цикле for выполняется проверка i<=len, а не i<len.

Деструктор, выполняемый в строках 73—77, удаляет строку текста, поддерживаемую классом String. Обратите внимание, что за оператором delete следуют квадратные скобки. Если опустить их, то из памяти компьютера будут удалены не все объекты класса, а только первый из них.

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

String1 = String2 = String3;

Оператор индексирования перегружается дважды. В обоих случаях проверяются границы массива. Если пользователь попытается возвратить значение из ячейки памяти, находяшейся за пределами массива, будет возвращен последний символ массива (len-1).

В строках 117-128 оператор суммирования (+) выполняется как оператор конкатенации. Поэтому допускается создание новой строки из двух строк, как в следующем выражении:

String3 = String1 + String2;

Оператор (+) вычисляет длину новой строки и сохраняет ее во временной строке temp. Эта процедура вовлекает закрытый конструктор, который принимает целочисленный параметр и создает строку, заполненную значениями NULL. Нулевые значения затем замещаются символами двух строк. Первой копируется строка левого операнда (*this), после чего — строка правого операнда (rhs).

Первый цикл for последовательно добавляет в новую строку символы левой строки'. Второй цикл for выполняет ту же операцию с правой строкой. Обратите внимание, что счетчик i продолжает отсчет символов новой строки после того, как счетчик j начинает отсчет символов строки rhs.

Оператор суммирования возвращает временную строку temp как значение, которое присваивается строке слева от оператора присваивания (string1). Оператор += манипулирует с уже существующими строками, как в случае string1 += string2. В этом примере оператор += действует так же, как оператор суммирования, но значение временной строки temp присваивается не новой, а текущей строке (*this = temp), как в строке 142.

Функция main() (строки 145—175) выполняется для проверки результатов работы данного класса. В строке 147 создается объект String с помощью конструктора, задающего строки в стиле языка С с концевым нулевым символом. Строка 148 выводит содержимое этого объекта с помощью функции доступа GetString(). В строке 150 создается еще одна строка текста в стиле языка С. В строке 151 тестируется перегруженный оператор присваивания, а строка 152 выводит результат.

В строке 154 создается третья строка с концевым нулевым символом — tempTwo. В строке 155 с помощью функции strcpy() происходит заполнение буфера строкой символов nice to be here!. В строке 156 с помощью перегруженного оператора += осуществляется конкатенация строки tempTwo к существующей строке s1. Результат выводится на экран в строке 158.

В строке 160 возвращается и выводится на экран пятый символ строки — s1. Затем в строке 161 этот символ замещается другим с помощью неконстантного оператора индексирования ([]). Результат выводится строкой 162, чтобы показать, что символ строки действительно изменился.

В строке 164 делается попытка получить доступ к символу за пределами массива. Возвращается и выводится на печать последний символ строки, как и было предусмотрено при перегрузке оператора индексирования.

В строках 166 и 167 создаются два дополнительных объекта String, и в строке 168 используется перегруженный оператор суммирования. Результат выводится строкой 169.

В строке 171 создается еще один объект класса String — s4. В строке 172 используется оператор присваивания, а строка 173 выводит результат. Оператор присваивания перегружен таким образом, чтобы использовать константную ссылку класса String, объявленную в строке 21, но в данном случае в функцию передается строка с концевым нулевым символом. Разве это допустимо?

Хотя компилятор, ожидая получить объект String, вместо этого получает массив символов, он автоматически проверяет возможность преобразования полученного значения в ожидаемую строку. В строке 12 объявляется конструктор, который создает объект String из массива символов. Компилятор создает временный объект String из полученного массива символов и передает его в функцию оператора присваивания. Такой процесс называется неявным преобразованием. Если бы в программе не был объявлен соответствующий конструктор, преобразующий массивы символов, то для этой строки компилятор показал бы сообщение об ошибке.

Наши рекомендации