Глава 13
Джесс Либерти "С++ за 21 день"
В качестве упражнения создадим собственный специальный класс String/
Этот класс должен преодолеть исходные ограничения символьныйх массивов
Это будет простейший рудиментарный класс, не претендует на большее.
//Listing 13.12 Using a String class
#include <iostream>
#include <string.h>
using namespace std;
// Rudimentary string class
class String
{
public:
// constructors
String();
String(const char *const);
String(const String &);
~String();
// overloaded operators
char & operator[](unsigned short offset);
char operator[](unsigned short offset) const;
String operator+(const String&);
void operator+=(const String&);
String & operator= (const String &);
// General accessors
unsigned short GetLen()const
{
return itsLen; }
const char * GetString() const
{
return itsString; }
private:
28: String (unsigned short); // private constructor
char * itsString;
unsigned short itsLen;
};
//1
// default constructor creates string of 0 bytes
String::String()
{
itsString = new char[1];
itsString[0] = '\0';
itsLen=0;
}
//2
// private (helper) constructor, used only by
// class methods for creating a new string of
// required size. Null filled.
String::String(unsigned short len)
{
itsString = new char[len+1];
for (unsigned short i = 0; i<=len; i++)
itsString[i] = '\0';
itsLen=len;
}
//3
// Converts a character array to a String
String::String(const char * const cString)
{
itsLen = strlen(cString);
itsString = new char[itsLen+1];
for (unsigned short i = 0; i < itsLen; i++)
itsString[i] = cString[i];
itsString[itsLen]='\0';
}
//4
63:// copy constructor
String::String (const String & rhs)
{
itsLen=rhs.GetLen();
itsString = new char[itsLen+1];
for (unsigned short i = 0; i
itsString[i] = rhs[i];
itsString[itsLen] = '\0';
}
//5
// destructor, frees allocated memory
String::~String ()
{
delete [] itsString;
itsLen = 0;
}
//6
// operator equals, frees existing memory
// then copies string and size
String& String::operator=(const String & rhs)
{
if (this == &rhs)
return *this;
delete [] itsString;
itsLen=rhs.GetLen();
itsString = new char[itsLen+1];
for (unsigned short i = 0; i
itsString[i] = rhs[i];
itsString[itsLen] = '\0';
return *this;
}
//7
//nonconstant offset operator, returns
// reference to character so it can be
// changed!
char & String::operator[](unsigned short offset)
{
if (offset > itsLen)
return itsString[itsLen-1];
else
return itsString[offset];
}
//8
// constant offset operator for use
// on const objects (see copy constructor!)
char String::operator[](unsigned short offset) const
{
if (offset > itsLen)
return itsString[itsLen-1];
else
return itsString[offset];
}
//9
// creates a new string by adding current
// string to rhs
String String::operator+(const String& rhs)
{
unsigned short totalLen = itsLen + rhs.GetLen();
String temp(totalLen);
unsigned short i;
for ( i= 0; i
temp[i] = itsString[i];
for (unsigned short j = 0; j
temp[i] = rhs[j];
temp[totalLen]='\0';
return temp;
}
//10
// changes current string, returns nothing
void String::operator+=(const String& rhs)
{
unsigned short rhsLen = rhs.GetLen();
unsigned short totalLen = itsLen + rhsLen;
String temp(totalLen);
unsigned short i;
for (i = 0; i
temp[i] = itsString[i];
for (unsigned short j = 0; j
temp[i] = rhs[i-itsLen];
temp[totalLen]='\0';
*this = temp;
}
// * * * * *
int main()
{
String s1("initial test");
cout << "S1:\t" << s1.GetString() << endl;
char * temp = "Hello World";
s1 = temp;
cout << "S1:\t" << s1.GetString() << endl;
char tempTwo[20];
strcpy(tempTwo, "; nice to be here!");
s1 += tempTwo;
cout << "tempTwo:\t" << tempTwo << endl;
cout << "S1:\t" << s1.GetString() << endl;
cout << "S1[4]:\t" << s1[4] << endl;
s1[4]='x';
cout << "S1:\t" << s1.GetString() << endl;
cout << "S1[999]:\t" << s1[999] << endl;
String s2(" Another string");
String s3;
s3 = s1+s2;
cout << "S3:\t" << s3.GetString() << endl;
String s4;
s4 = "Why does this work?";
cout << "S4:\t" << s4.GetString() << endl;
return 0;
}
* * * * * * * *
Результат:
S1: initial test
S1: Hello World
tempTwo: ; nice to be here!
S1: Hello World; nice to be here!
S1[4]: o
S1: Hellx World; nice to be here!
S1[999]: !
S3: Hellx World; nice to be here! Another string
S4: Why does this work?
Анализ программы:
Мы видим в программе объявление класса String.
Строки 11-13 содержат объявления трех конструкторов:
-стандартного,
-конструктора копий,
-конструктора, принимающего строку стиля С, которая завершается символом null.
// constructors
String();
String(const char *const);
String(const String &);
Далее класс String перегружает оператор индекса [], оператор плюс (+),
и оператор плюс-эквалс (+=) (присвоение с суммой)
// overloaded operators
char & operator[](unsigned short offset);
char operator[](unsigned short offset) const;
String operator+(const String&);
void operator+=(const String&);
String & operator= (const String &);
Как видим оператор индекса [] перегружен дважды.
Один раз как постоянная функция, возвращающая значение типа char,
char operator[](unsigned short offset) const;
а второй раз как не постоянная функция, возвращающая ссылку на значение
типа char.
char & operator[](unsigned short offset);
Не постоянная версия используется в операторах типа (строка 161):
SomeString[4]='x';
Это означает поместить значение типа x в пятый элемент массива SomeString.
Это обеспечивает прямой доступ к любому символу строки. Таким образом
получив ссылку на символ и вызвав эту функцию, можно изменить его значение
как в нижеследующих строках из главной программы:
cout << "S1[4]:\t" << s1[4] << endl;
s1[4]='x'; //изменяем значение пятого элемента массива s1
cout << "S1:\t" << s1.GetString() << endl;
Эти строки из главной программы изменяют значение символа под индексом 4
в символьном массиве s1.
Постоянная версия оператора [] используется в тех случаях, когда необходимо
получить доступ к постоянному объекту класса String , например в реализации
конструктора копий (строка 63)
itsString = new char[itsLen+1];
for (unsigned short i = 0; i < itsLen;i++)
63:// copy constructor
String::String (const String & rhs)
{
itsString[i] = rhs[i];
itsString[itsLen] = '\0';
Обратите внимание что rhs[i] доступен, хотя rhs был объявлен как const String &.
К этому объекту не возможно получить доступ, используя непостоянные функции-члены.
Следовательно оператор индекса необходимо перегружать как постоянный.
В этом конструкторе учитывая то что внутри класса был объявлен как private
указатель на переменную типа char и здесь в конструкторе этот указатель
переопределен на динамическую память
char * itsString;
itsString=new char[itsLen]
Значит строка будет расположениа в динамической памяти, ее размер будет itsLen.
Если возвращаемый объект окажется слишком большим, то придется вернуть не сам объект,
а постоянную ссылку на него. Но поскольку один символ занимает только один байт,
то нет смысла так поступать.
-.-.-.- 1.
Стандартный конструктор реализован в строках 31-39
Стандартный конструктор создает строку нулевой длины
// default constructor creates string of 0 bytes
String::String()
{
itsString[0] = '\0';
itsLen=0;
Общепринято что в классе String длина строки измеряется без учета концевого
символа null. Таким образом строка созданная по умолчанию содержит лишь
концевой символ null.
Строка стандартного конструктора создана тоже в динамической памяти
char * itsString;
itsString=new char[1]
Таким образом если в главной программе мы объявим объект типа String
без параметров(то есть никак не инициализированный) например:
String s1;
то будет по умолчанию , будет вызван этот конструктор и он создаст строку
с одним только завершающим нулевым символом.
-.-.-.- 2.
Конструктор копий реализован в строках 63-70
for (unsigned short i = 0; i < itsLen;i++)
itsString[itsLen] = '\0';
63:// copy constructor
String::String (const String & rhs)
{
itsString = new char[itsLen+1];
itsString[i] = rhs[i];
Здесь параметр rhs -исходная строка
itsString -указатель на вновь создаваемую строку.
Конструктор сначала определяет длину исходной строки используя функцию-член
GetLen()
itsLen=rhs.GetLen();
Затем Конструктор переопределяет указатель на массив в динамической памяти
и устанавливает длину новой строки на единицу больше длины исходной
char * itsString;
itsString=new char[itsLen+1]
(дополнительный символ необходим для завершающего null),
а затем копирует каждый символ исходной строки во вновь созданную и
завершает ее символом null.
for (unsigned short i = 0; i < itsLen;i++)
itsString[i] = rhs[i];
itsString[itsLen] = '\0';
Все! Копия исходной строки создана в динамической памяти!
Напомню что копирующий конструктор вызывается автоматически.
-.-.-.- 3.
В строках 53-60 реализован конструктор, принимающий исходную строку в стиле С
(с концевым null)Этот конструктор подобен конструктору копий. Длина исходной
строки исчисляется с помощью функции strlen() из стандартной библиотеки String.
Конструктор преобразует символьный массив в строку
itsString = new char[itsLen+1];
for (unsigned short i = 0; i
// Converts a character array to a String
String::String(const char * const cString)
{
itsString[itsLen]='\0';
То есть если мы создадим объект типа String с параметром в скобках
например как в данной программе
String s1("initial test");
То будет вызван данный конструктор. Сначала он определит длину данной строки
при помощи функции strlen()
itsLen = strlen(cString);
Затем в динамической памяти создаст место на один элемент больше
нашей введенной строки
char * itsString;
itsString=new char[itsLen+1]
а затем скопирует каждый символ введенной нами строки во вновь созданную и
for (unsigned short i = 0; i < itsLen;i++)
завершает ее символом null.
itsString[i] = rhs[i];
itsString[itsLen] = '\0';
Все! Введенная нами строка находится в динамической памяти и на нее указыает
указатель itsString расположенный в соответствующем поле вновь созданного
нами объекта типа String
-.-.-.- 4.
В строке 28 объявлен еще один конструктор, String (unsigned short)
String (unsigned short); // private constructor
Он объявлен как закрытая функция-член. Таково было намерение автора,
чтобы ни один из клиентов этого класса не мог создать строку произвольной
длины. Этот конструктор создает строки для внутреннего пользования,
как например operator += в строке 130. Более подробная информация об операторе
operator += приведена далее в этой главе.
Конструктор
String (unsigned short); // private constructor
заполняет все элементы строки символом null.
Поэтому условием выхода из цикла for будет i <= len , а не i < len.
String::String(unsigned short len)
for (unsigned short i = 0; i<=len; i++)
// private (helper) constructor, used only by
// class methods for creating a new string of
// required size. Null filled.
{
itsString[i] = '\0';
itsLen=len;
Конструктор переорентирует указатель на массив созданный в динамической памяти
длиной len+1
char * itsString;
itsString=new char[itsLen+1]
Затем заполняет все элементы массива символом null
for (unsigned short i = 0; i <= len; i++)
itsString[i] = '\0';
И присваивает переменной itsLen значение len , чтобы нельзя было выйти за границы
массива
itsLen=len;
Вопрос:где у нас в программе пример использования этого массива?
-.-.-.- 5.
Деструктор, реализованный в строках 73-77, освобождает память, занимаемую
строкой объекта класса String. Удостоверьтесь что при вызове оператора
delete[] не забыты квадратные скобки, иначе вместо всего массива будет
удален только его первый элемент.
// destructor, frees allocated memory
String::~String ()
{
itsLen = 0;
-.-.-.- 6.
Оператор присвоения:
// operator equals, frees existing memory
// then copies string and size
String& String::operator=(const String & rhs)
{
return *this;
delete [] itsString;
itsLen=rhs.GetLen();
itsString = new char[itsLen+1];
for (unsigned short i = 0; i
itsString[itsLen] = '\0';
return *this;
Оператор присвоения сначала проверяет, не равна ли строка справа от оператора
строке слева.
if (this == &rhs)
return *this;
Если это не так, то удалив прежнюю строку, он создает новую
itsLen=rhs.GetLen();
delete [] itsString;
itsString = new char[itsLen+1];
и копирует в нее исходную.
for (unsigned short i = 0; i < itsLen;i++)
itsString[i] = rhs[i];
itsString[itsLen] = '\0';
В качестве результата возвращается ссылка на
новую строку,
return *this;
что позволяет осуществить присвоение типа:
String1=String2=String3;
Вот как например этот перегруженный оператор используется в главной
программе:
На экране мы увидим:
S1: Hello World
char * temp = "Hello World";
s1 = temp;
cout << "S1:\t" << s1.GetString() << endl;
-.-.-.- 7.
Оператор индекса [] перегружен дважды. И в обоих случаях осуществляется
проверка пределов массивов. Если пользователь попытается осуществить доступ
к символу вне пределов массива, то последним возвращаемым символом окажется
последний символ массива, то есть len-1 й элемент
char & String::operator[](unsigned short offset)
// constant offset operator for use
//nonconstant offset operator, returns
// reference to character so it can be
// changed!
{
return itsString[itsLen-1];
else
return itsString[offset];
// on const objects (see copy constructor!)
char String::operator[](unsigned short offset) const
{
return itsString[itsLen-1];
else
return itsString[offset];
Главная идея перегрузки этого оператора состоит в том , чтобы пользователь не
смог обратиться к элементам которые находятся за пределами массива
Вот это место в главной программе:
cout << "S1[999]:\t" << s1[999] << endl;
Попытка обратиться за пределы массива
На экране мы увидим только знак ! последний элемент массива как
и было задумано.
-.-.-.- 8.
В строках 117-127 оператор суммы (+) перегружается в оператор конкатенации.
Было бы очень удобно иметь возможность осуществлять конкатенацию
строк аналогично простому сложению:
// creates a new string by adding current
String String::operator+(const String& rhs)
String3=String1+String2;
// string to rhs
{
String temp(totalLen);
unsigned short i;
for ( i= 0; i
for (unsigned short j = 0; j
temp[totalLen]='\0';
return temp;
Для реализации этой возможности функция заменяющая оператор плюс (+)
вычисляет суммарную длину обеих строк
unsigned short totalLen = itsLen + rhs.GetLen();
и на основании результата создает временную строку temp . Для этого вызывается
закрытый конструктор который получает целое число и создает строку, заполненную
пустыми символами (null).
String temp(totalLen); //вызывается закрытый конструктор
Впоследствии эти пустые символы будут заменены содержимым двух исходных строк.
Сначала во временную строку копируется левая исходная строка (+this),
unsigned short i;
for ( i= 0; i < itsLen; i++)
temp[i] = itsString[i];
а затем -правая (rhs).
for (unsigned short j = 0; j < rhs.GetLen(); j++, i++)
temp[i] = rhs[j];
temp[totalLen]='\0';
Первый цикл for последовательно добавляет в новую строку символы левой строки.
Второй цикл for выполняет ту же операцию с правой строкой. Обратите внимание
что счетчик i не обнуляется и продолжает отсчет символов новой строки
с того места, где закончилась предыдущая строка.
Оператор суммы возвращает временную строку как значение, которое присваивается
строке, расположенной слева от оператора (string1).
Вот строки из главной программы в которых используется этот перегруженный
оператор:
String s2(" Another string");
String s3;
s3 = s1+s2;
cout << "S3:\t" << s3.GetString() << endl;
Результат на экране:
Две строки соединяются в одну
-.-.-.- 9.
Оператор += работает с уже существующими строками, находящимися с левой
стороны оператора
String1 += String2;
Этот оператор работает аналогично оператору суммы , за исключением того ,
что временное значение присваивается текущей строке
// changes current string, returns nothing
+this=temp (строка 142)
void String::operator+=(const String& rhs)
{
unsigned short totalLen = itsLen + rhsLen;
String temp(totalLen);
unsigned short i;
for (i = 0; i
for (unsigned short j = 0; j
temp[totalLen]='\0';
*this = temp;
Вот строки из главной программы которые используют данный перегруженный
char tempTwo[20]; //объявлен массив
оператор
* * * * *
strcpy(tempTwo, "; nice to be here!"); //он инициализирован
s1 += tempTwo; //применен оператор +=
cout << "tempTwo:\t" << tempTwo << endl;
cout << "S1:\t" << s1.GetString() << endl;//строки соединились!
Функция main() проверяет работоспособность созданного нами класса.
char * temp = "Hello World";
char tempTwo[20];
cout << "S1[4]:\t" << s1[4] << endl;
cout << "S1[999]:\t" << s1[999] << endl;
String s2(" Another string");
String s4;
int main()
{
cout << "S1:\t" << s1.GetString() << endl;
s1 = temp;
cout << "S1:\t" << s1.GetString() << endl;
strcpy(tempTwo, "; nice to be here!");
s1 += tempTwo;
cout << "tempTwo:\t" << tempTwo << endl;
cout << "S1:\t" << s1.GetString() << endl;
s1[4]='x';
cout << "S1:\t" << s1.GetString() << endl;
String s3;
s3 = s1+s2;
cout << "S3:\t" << s3.GetString() << endl;
s4 = "Why does this work?";
cout << "S4:\t" << s4.GetString() << endl;
return 0;
В строке 147 создается объект класса String с помощью конструктора,
принимающего строку в стиле С с пустым символом в конце.
Строка 148 выводит на экран ее содержимое используя функцию доступа
GetString().
String s1("initial test");
В строке 150 создается другая строка стиля С. Строка 151 проверяет
cout << "S1:\t" << s1.GetString() << endl;
оператор присвоения, а строка 152 выводит на экран результаты.
char * temp = "Hello World"; //какой здесь использован конструктор?
В строке 154 создается третья строка в стиле С-tempTwo.
151 s1 = temp; //использован оператор присвоения
cout << "S1:\t" << s1.GetString() << endl; // вывод на экран
В строке 155 вызов функции strcpy() заполняет буфер символами:
nice to be here!
Строка 156 вызывает оператор += и добавляет к строке s1 строку tempTwo.
Строка 158 выводит результаты на экран.
char tempTwo[20];
strcpy(tempTwo, "; nice to be here!");
156 s1 += tempTwo;
cout << "tempTwo:\t" << tempTwo << endl;
cout << "S1:\t" << s1.GetString() << endl;
В строке 160 возвращается и выводится на экран пятый символ строки s1.
В строке 161 с помощью оператора индекса [] ему присваивается новое
значение, а в строке 162 на экран выводится результат, демонстрирующий
факт внесения изменений.
cout << "S1[4]:\t" << s1[4] << endl;
s1[4]='x';
cout << "S1:\t" << s1.GetString() << endl;
В строке 164 предпринята попытка доступа к символу за пределами
массива, но возвращен был лишь последний символ массива , как и было
задумано.
cout << "S1[999]:\t" << s1[999] << endl;
В строках 166 , 167 создаются еще два объекта класса String,
а в строке 168 происходит их сложение.
Строка 169 выводит результаты на экран.
String s2(" Another string");
String s3;
s3 = s1+s2;
cout << "S3:\t" << s3.GetString() << endl;
В строке 171 создается новый объект класса String -s4.
В строке 172 вызывается оператор присвоениия, в строке 173
выводится результат на экран.
String s4;
s4 = "Why does this work?";
cout << "S4:\t" << s4.GetString() << endl;
Можно было бы засомневаться :"В строке 21 оператор присвоения определен
так, чтобы принимать постоянную ссылку на объект класса String, а здесь
передается строка в стиле С. Допустимо ли это?"
Хоть компилятор и ожидает объект класса String , но получив символьный
массив, он проверяет можно ли преобразовать его в строку. А в строке 12
как раз объявлен конструктор , который создает объекты класса String из
символьных массивов . Компилятор создает из символьного массива временную
строку и передает ее оператору присвоения. Этот процесс известен под
названием "неявное приведение (implicit casting или promotion).
Если бы не был объявлен соответствующий конструктор, для реализации
подобной функции, такое присвоение привело бы к ошибке компиляции.
return 0;
Задача:
Модифицировать класс String так чтобы каждый конструктор выводил
свое сообщение и посмотреть когда какой конструктор будет вызван при
создании новых объектов.
Использовать этот класс для программы которая из данного массива
выбирает слова с одинаковой длительностью и помещает слова с одинаковой
длительностью в один файл, с другой длительностью в другой файл ,
с иной -в третий файл и т.д.
Использовать готовую функцию сортировки из программы ...
и использовать функции образования на жестком диске файлов.
Выводы:
Этот класс позволяет нам инициализировать символьный массив в динамической
памяти следующими способами:
char * temp = "Hello World";
char tempTwo[20];
String s4;
String s1("initial test");
String s2(" Another string");
cout << "S1:\t" << s1.GetString() << endl;
cout << "S1:\t" << s2.GetString() << endl;
s1 = temp;
cout << "S1:\t" << s1.GetString() << endl;
strcpy(tempTwo, "; nice to be here!");
cout << "tempTwo:\t" << tempTwo << endl;
s4 = "Why does this work?";
cout << "S4:\t" << s4.GetString() << endl;
Этот класс позволяет нам использовать эффективно и удобно следующие
операторы:
(=), для присвоения
(+), (+=) для соединения двух и более строк в одну
[] -оператор индекса для доступа к элементам массива и ограничения
доступа за пределы массива
Оператор присвоения содержимого одного массива другому:
char * temp = "Hello World";
s1 = temp; //оператор присвоения
cout << "S1:\t" << s1.GetString() << endl;
Оператор сложения двух строк и присвоения результата третьей
String s2(" Another string");
Оператор (+=)
String s3;
s3 = s1+s2; //операторы сложения и присвоения
cout << "S3:\t" << s3.GetString() << endl;
char tempTwo[20];
strcpy(tempTwo, "; nice to be here!");
s1 += tempTwo; //оператор (+=)
cout << "tempTwo:\t" << tempTwo << endl;
cout << "S1:\t" << s1.GetString() << endl;
Оператор []
cout << "S1[4]:\t" << s1[4] << endl;
s1[4]='x';
cout << "S1:\t" << s1.GetString() << endl;
Здесь произошло присвоение одному элементу массива
нового значения.
Назад |
Начало урока |
Вверх |
Вперед
Содержание