Джесс Либерти "С++ за 21 день"
Вверх
До сих пор обсуждались массивы, члены которых размещались в стеке.
Листинг lib13_06.cpp демонстрирует это. Это видоизмененная программа
Массив указателей на объекты
#include <iostream>
class CAT
int GetAge() const
{
private:
int main()
{
for (i = 0; i < 500; i++)
Cat #1: 1
. . .
Cat #494: 987
Анализ:
Класс CAT объявленный в строках 5-17 идентичен классу CAT, объявленному в
(CAT * pCat;) //предыдущая инструкция объявлен указатель на объект
Оператор new возвращает адрес выделенного места под объект в динамической
Затем в поле этого созданного объекта, который находится в области
pCat->SetAge(2*i +1);
В следующей инструкции передадим адрес выделенного места под объект в
Family[i] = pCat;
Теперь один из элементов массива указателей под индексом i указывает на
Далее цикл повторяется. И при каждой итерации цикла в динамической памяти
Вспомним программу с атлетами из книги Франка , когда при каждой итерации
Здесь то же самое новые объекты типа CAT создаются при каждой итерации цикла,
Здесь массив указателей
Вверх
Весь массив можно разместить в области динамической памяти известной так же
CAT*Family=new CAT[500]
Здесь объявлено что Family будет указателем на первый элемент массива из 500
Преимуществом такого способа использования массива является возможность
CAT*Family=new CAT[500];
Здесь в динамической памяти объявлен массив из 500 объектов класса CAT,
CAT*pCat=Family; //pCat указывает на Family[0]
Теперь указатель pCat указывает на первый элемент массива CAT расположенный в
pCat->SetAge[10];//присвоить Family[0] значение 10
Затем следует инструкция
Эта инструкция увеличивает указатель pCat на единицу, но указывает он теперь
Итак теперь указатель увеличивается и указывает уже на следующий объект
Вот как в свете изложенного модифицирована предыдущая программа:
Вверх
class CAT
private:
int main()
{
CAT*Family=new CAT[500];
cout << pCat->GetAge() << endl;
pCat++; //вперед к Family[1]
cout << pCat->GetAge() << endl;
10
Анализ:
Вопрос:Почему здесь нельзя обойтись без промежуточного звена pCat ?
CAT*Family=new CAT[500];
int n=o;
А можно так:
CAT*Family=new CAT[500];
for(int i=0;i<100;i++)
Вверх
Рассмотрим три объявления:
CAT FamilyOne[500]; это массив из 500 объектов класса CAT,
Различие между этими тремя строками весьма существенно и влияет на способ
В этом вся суть проблемы взаимосвязи указателей и массивов.
Вверх
В С++ имя массива является постоянным указателем на первый элемент массива.
CAT Family[50];
Следовательно здесь Family представляет собой указатель на переменную
Вполне допустимо использовать имена массивов как постоянные указатели,
При инкременте и декременте указателя, компилятор делает все вычисления сам.
Создание массива в динамической памяти
// Listing 13.7 - An array on the free store
#include <iostream>
class CAT
private:
CAT :: ~CAT()
{
int main()
for (i = 0; i < 500; i++)
for (i = 0; i < 500; i++)
delete [] Family;
return 0;
Cat #1: 1
Анализ:
//В строке 25 объявлен массив Family содержащий 500 объектов класса CAT.
int main()
for (i = 0; i < 500; i++)
delete [] Family;
return 0;
Результат:
Cat #1: 1
Вверх
Удаление массива Family автоматически возвращает всю выделенную для него
Для демонстрации этого в следующей программе уменьшим количество элементов
// Listing 13.7 - An array on the free store
#include <iostream>
class CAT
private:
CAT :: ~CAT()
{
int main()
for (i = 0; i < 10; i++)
for (i = 0; i < 10; i++)
39: delete [] Family;
return 0;
Cat #1: 1
Анализ:
----------------------------------------------------------------------------------
Вверх
Можно создавать и неинициализированные символьные массивы , но при этом надо
#include <iostream>
int main()
Enter the string:Hello World
Анализ:
Здесь пользователю предлагают ввести строку зарезервировано место под 80
Во первых если пользователь вводит строку длинее 80 символв, то оператор cin
Во вторых если пользователь введет пробел, то cin воспримет его как конец
Для разрешения этих проблем нужно создать специальную функцию cin.get()
-буфер для заполнения
По умолчанию критерием завершения ввода является символ новой строки
Вверх
#include <iostream>
int main()
Результат:
Enter the string:Hello World
Анализ:
В строке 9 вызывается функция cin.get() Буфер, объявленный в строке 7
Вверх
Посмотрите программу, демонстрирующую функцию strcpy()
#include <iostream>
int main()
strcpy(String2, String1);
cout << "String1: " << String1 << endl;
Результат:
Анализ:
Файл заголовка string.h подключен в строке 3 Этот файл содержит прототип
Посмотрите программу где используется функция strncpy() :
#include <iostream>
int main()
strncpy(String2, String1, MaxLength);
std::cout << "String1: " << String1 << std::endl;
String1 = "No man is an island
Анализ:
Вверх
В качестве упражнения создадим собственный специальный класс String.
Подобно всем остальным массивам, символьные массивы статичны.
#include <iostream>
// Rudimentary string class
// overloaded operators
// General accessors
private:
28: String (unsigned short); // private constructor
// default constructor creates string of 0 bytes
// private (helper) constructor, used only by
// Converts a character array to a String
63:// copy constructor
// destructor, frees allocated memory
// operator equals, frees existing memory
//nonconstant offset operator, returns
// constant offset operator for use
// creates a new string by adding current
// changes current string, returns nothing
int 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;
Результат:
S1: initial test
Анализ:
Мы видим в программе объявление класса String.
// constructors
Далее класс String перегружает оператор индекса [], оператор плюс (+),
Как видим оператор индекса перегружен дважды.
char operator[](unsigned short offset) const;
а второй раз как не постоянная функция, возвращающая ссылку на значение
char & operator[](unsigned short offset);
Не постоянная версия используется в операторах типа (строка 161):
SomeString[4]='x';
Это обеспечивает прямой доступ к любому символу строки.Таким образом
cout << "S1[4]:\t" << s1[4] << endl;
Постоянная версия оператора используется в тех случаях, когда необходимо
63:// copy constructor
Обратите внимание что rhs[i] доступен, хотя rhs был объявлен как const String &.
Если возвращаемый объект окажется слишком большим, то придется вернуть не сам объект,
Стандартный конструктор реализован в строках 31-39
Стандартный конструктор создает строку нулевой длины
// default constructor creates string of 0 bytes
String::String()
Общепринято что в классе String длина строки измеряется без учета концевого
Конструктор копий реализован в строках 63-70
63:// copy constructor
Он устанавливает длину новой строки на единицу больше длины исходной
В строках 53-60 реализован конструктор, принимающий исходную строку в стиле С
Конструктор преобразует символьный массив в строку
// Converts a character array to a String
В строке 28 объявлен еще один конструкто, String (unsigned short)
String (unsigned short); // private constructor
Он объявлен как закрытая функция-член. Таково было намерение автора,
Конструктор String (unsigned short); // private constructor
String::String(unsigned short len)
Назад |
Начало урока |
Вверх |
Вперед
Массивы указателей
В этой книге рассматриваются массивы указателей, массивы,
нахождящиеся в динамической памяти, и другие типы структур.
Но более подробная информация по этой теме приведена в моей
предыдущей книгк "C++ Unleashed", опубликованной в издательстве
Sams Publishing.
Но размер стека значительно меньше объема динамической памяти,
поэтому можно объявить объекты массива в динамической памяти,
а в самом массиве хранить лишь указатели на них. Это существенно
уменьшает объем используемой стековой памяти.
lib13_04.cpp .
// Listing 13.6 - An array of pointers to objects
using namespace std;
{
CAT()
{
itsWeight=5;
~CAT()
{} // destructor
int GetWeight() const
{
void SetAge(int age)
{
int itsAge;
int itsWeight;
int i;
CAT * pCat;
for (i = 0; i < 500; i++)
{
pCat->SetAge(2*i +1);
Family[i] = pCat;
{
cout << Family[i]->GetAge() << endl;
return 0;
Результат:
Cat #2: 3
Cat #3: 5
Cat #4: 7
Cat #5: 9
Cat #495: 989
Cat #496: 991
Cat #497: 993
Cat #498: 995
Cat #499: 997
Cat #500: 999
листинге 13.4 А массив Family объявленный в главной программе на сей раз
содержит 500 указателей на объекты класса CAT. (массив указателей на объекты
класса CAT).
CAT * Family[500];
Далее объявляется еще один отдельный указатель pCat на объект класса CAT.
Этот указатель потребуется для использования его внутри цикла.
Далее этот указатель pCat переопределяется на динамическую память,
то есть теперь указатель pCat указывает на место в динамической
памяти где будет содержаться объект типа CAT.
Пока в динамической памяти выделено место для объекта типа CAT.
//класса CAT
pCat = new CAT; //этому указателю присвоен адрес в динамической памяти
памяти, и этот адрес присваивается указателю pCat.
динамической памяти внесем данные при помощи вызова функции SetAge()
с аргументом
динамической памяти, (в эту память уже внесены данные) элементу под
индексом i из массива указателей Family[].
нами созданный объект в динамической памяти.
будет создаваться новый объект типа CAT, а затем адрес этого объекта будет
передаваться следующему элементу из массива указателей Family под индексом i.
появлялся на экране новый атлет. Каждый раз на новом месте, так можно было
заполнить весь экран атлетами (смотри программа .....)
и каждый новый объект занимает новое место, возможно рядом с предыдущим в
динамической памяти. Разумеется так можно заполнить и всю динамическую память.
CAT * Family[500];
создан в стеке, но при инициализации в каждый указатель этого массива вносится
адрес указывающий на динамическую память. По этому адресу в динамической
памяти уже находятся объекты с их данными.
Объявление массивов в области динамической памяти
под именем heap. Для этого при объявлении массива используется оператор new.
Результатом окажется указатель на пространство в динамической памяти,
где и будет содержаться массив. Пример:
объектов класса CAT. Другими словами Family указывает на элемент (или содержит
адрес) Family[0].
арифметических операций над указателями для доступа к каждому элементу
массива Family. Например:
CAT*pCat=Family; //pCat указывает на Family[0]
pCat->SetAge[10];//присвоить Family[0] значение 10
pCat++; //вперед к Family[1]
pCat->SetAge[20];//присвоить Family[1] значение 20
а так же создан указатель pCat, которому присваивается адрес первого элемента
массива или иными словами адрес начала массива.
динамической памяти. Теперь при помощи функции с аргументами SetAge() в
поля этого элемента будут занесены данные:
pCat++; //вперед к Family[1]
уже на следующий элемент(!) массива типа CAT. Таковы свойства и возможности
языка С++.
класса CAT, поэтому при вызове функции SetAge() значение 20 будет присвоено
второму объекту.
#include <iostream>
using namespace std;
{
CAT()
{
~CAT()
{} // destructor
int GetAge() const
{
int GetWeight() const
{
void SetAge(int age)
{
int itsAge;
int itsWeight;
CAT*pCat=Family; //pCat указывает на Family[0]
pCat->SetAge(10);//присвоить Family[0] значение 10
pCat->SetAge(20);//присвоить Family[1] значение 20
cout << Family[0].GetAge() << endl;
cout << Family[1].GetAge() << endl;
return 0;
Результат:
20
10
20
Потому, что к членам массива можно подойти либо через индекс, либо
через этот указатель. Например обратиться к 10-му элементу массива
Family[9].GetAge();
pCat+10->GetAge();
Чтобы перебрать 100 элементов этого массива можно увеличивать индекс
например так:
for(int i=0;i<100;i++)
{
n++;
CAT*pCat=Family; //pCat указывает на Family[0]
{
pCat++;
Указатель на массив и массив указателей
CAT FamilyOne[500];
CAT *FamilyTwo[500];
CAT *FamilyThree=new CAT[500];
CAT * FamilyTwo[500]; массив из 500 указателей на объекты класса CAT, а
CAT * FamilyThree = new CAT[500]; -указатель на массив из 500 объектов
класса CAT.
существования массивов. Но самым удивительным оказывается то, что FamilyThree
является вариантом FamilyOne , а от FamilyTwo отличается принципиально.
В третьем случае FamilyThree представляет собой указатель на массив. То есть
адрес находящийся в указателе FamilyThree , является адресом первого
элемента в этом массиве. Но это аналогично тому, что имеет место для FamilyOne!
Имена массивов и указателей
&Family[0], являющуюся первым элементом массива Family.
и наоборот. Следовательно Family+4 -вполне законный способ доступа
к данным в элементе Family[4].
Адрес, полученный в результате вычисления выражения Family+4 , не на четыре
байта больше исходного, а на четыре объекта.
Следующий листинг демонстрирует объявление и использование массива
в динамической памяти.
{
CAT()
{
itsWeight=5;
~CAT();
int GetAge() const
{
int GetWeight() const
{
void SetAge(int age)
{
int itsAge;
int itsWeight;
{
int i;
{
{
std::cout << Family[i].GetAge() << std::endl;
Результат:
Cat #2: 3
Cat #3: 5
Cat #4: 7
Cat #5: 9
...
Cat #496: 991
Cat #497: 993
Cat #498: 995
Cat #499: 997
Cat #500: 999
// Весь массив создан в динамической памяти с помощью выражения new CAT[500].
// CAT * Family = new CAT[500];
// в следующем цикле этот массив инициализирует свои данные-члены. Далее
// в следующем цикле этот массив выводится на экран
Вопрос:почему здесь не применяется оператор (->) при обращении к функциям
-членам?
Можно применить этот оператор если объявить еще один указатель на тип
CAT и присвоить ему адрес первого элемента массива Family.
{
int i;
CAT *pFam = Family;
for (i = 0; i < 500; i++)
{
pFam->SetAge(2*i +1);
pFam++;
{
std::cout << Family[i].GetAge() << std::endl;
Откомпилируй и сравни результаты обеих программ!
Cat #2: 3
Cat #3: 5
Cat #4: 7
Cat #5: 9
Cat #6: 11
Cat #7: 13
...
Cat #496: 991
Cat #497: 993
Cat #498: 995
Cat #499: 997
Cat #500: 999
Удаление массивов из динамической памяти
память, если оператор delete использован с квадратными скобками[]. Компилятор
достаточно вычисляет размер всех элементов массива и освобождает занимаемую
им область динамической памяти.
в массиве до 10 и раскомментируем строки внутри деструктора чтобы увидеть что
происходит:
По достижению строки 39 массив будет удален и для каждого объекта класса CAT
вызван деструктор.
--------------------------------------------------------------------------------
Когда с помощью оператора new в динамической памяти создается элемент,
то при его удалении непременно следует освободить занимаемую им область
динамической памяти.
Создав в динамической памяти массив с помощью оператора new
впоследствии необходимо освободить эту область памяти с помощью оператора
delete[]. Квадратные скобки сообщают компилятору о том что удаляется массив.
Если квадратные скобки отсутствуют то будет освобожден лишь первый объект
массива. В этом легко убедиться убрав квадратные скобки в строке 39 программы
----------------------------------------------------------------------------------
{
CAT()
{
itsWeight=5;
~CAT();
int GetAge() const
{
int GetWeight() const
{
void SetAge(int age)
{
int itsAge;
int itsWeight;
{
int i;
{
{
std::cout << Family[i].GetAge() << std::endl;
Результат:
Cat #2: 3
Cat #3: 5
Cat #4: 7
Cat #5: 9
Cat #6: 11
Cat #7: 13
Cat #8: 15
Cat #9: 17
Cat #10: 19
Destructor called!
Destructor called!
Destructor called!
Destructor called!
Destructor called!
Destructor called!
Destructor called!
Destructor called!
Destructor called!
Destructor called!
Применяйте для доступа к элементам массива индексы, а для доступа к указателям
массива точечный оператор.
----------------------------------------------------------------------------------
Не записывайте и не читайте данных вне пределов массива.
Не путайте массив указателей с указателем на массив.
----------------------------------------------------------------------------------
Массивы символов
следить чтобы в этот буфер было записано данных не более чем он может
вместить.
Посмотрите программу, которая демонстрирует использование неинициализированного
буфера:
//Listing 13.8 char array buffers
{
std::cout << "Enter the string: ";
std::cin >> buffer;
std::cout << "Here is's the buffer: " << buffer << std::endl;
return 0;
Результат:
Here is's the buffer:Hello
символов. Но здесь возникают две проблемы:
осуществит запись за пределы буфера.
строки и становит запись в буфер.
принимающую три параметра
-максимальное число символов
-символ для завершения ввода
Посмотрите программу:
//Listing 13.9 using cin.get()
using namespace std;
{
cout << "Enter the string: ";
9: cin.get(buffer, 79); // get up to 79 or newline
cout << "Here's the buffer: " << buffer << endl;
return 0;
Here is's the buffer:Hello World
передается в качестве первого аргумента. Второй аргумент-максимальное
число вводимых символов -79 чтобы учесть завершающий символ null.
Третий параметр необязателен, поскольку по умолчанию признаком завершения
является новая строка.
Функции strcpy() strncpy()
//Listing 13.10 Using strcpy()
#include <string.h>
using namespace std;
{
char String2[80];
cout << "String2: " << String2 << endl;
return 0;
Результат:
String1 = "No man is an island
String2 = "No man is an island
функции strcpy() принимающей два символьных массива:результирующий и
исходный.
Если исходный массив окажетсяя больше результирующего, то strcpy() осуществит
запись за пределы результирующего буфера.
Чтобы избежать этого, стандартная библиотека располагает функцией strncpy()
Этот вариант принимает в качестве третьего аргумента и максимальное
число копируемых символов.
strncpy() осуществляет копирование до первого символа null или максимального
числа символов, определенного для результирующего буфера.
//Listing 13.11 Using strncpy()
#include <string.h>
{
char String1[] = "No man is an island";
char String2[MaxLength+1];
std::cout << "String2: " << String2 << std::endl;
return 0;
Результат:
String2 = "No man is an island
Буфер String2 объявлен как массив из MaxLength+1 символов. Дополнительный
элемент предназначен для символа null который и strcpy() и strncpy()
добавляют в конец строки автоматически.
Строковые классы
Этот класс должен преодолеть исходные ограничения символьных массивов
Их размер задается при объявлении и независимо от того, какое
количество элементов массива используется, размер занимаемого
участка памяти остается постоянным, и запись за пределы массива
чревата неприятностями.
//Listing 13.12 Using a String class
#include <string.h>
using namespace std;
class String
{
// constructors
String();
String(const char *const);
String(const String &);
~String();
char & operator[](unsigned short offset);
char operator[](unsigned short offset) const;
String operator+(const String&);
void operator+=(const String&);
String & operator= (const String &);
unsigned short GetLen()const
{
const char * GetString() const
{
char * itsString;
unsigned short itsLen;
String::String()
{
itsString[0] = '\0';
itsLen=0;
// class methods for creating a new string of
// required size. Null filled.
String::String(unsigned short len)
{
for (unsigned short i = 0; i<=len; i++)
itsString[i] = '\0';
itsLen=len;
String::String(const char * const cString)
{
itsString = new char[itsLen+1];
for (unsigned short i = 0; i
itsString[itsLen]='\0';
String::String (const String & rhs)
{
itsString = new char[itsLen+1];
for (unsigned short i = 0; i
itsString[itsLen] = '\0';
String::~String ()
{
itsLen = 0;
// 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;
// reference to character so it can be
// changed!
char & String::operator[](unsigned short offset)
{
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];
// string to rhs
String String::operator+(const String& rhs)
{
String temp(totalLen);
unsigned short i;
for ( i= 0; i
for (unsigned short j = 0; j
temp[totalLen]='\0';
return temp;
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;
{
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;
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?
Строки 11-13 содержат объявления трех конструкторов:
стандартного,
конструктора копий,
конструктора, принимающего строку стиля С, которая завершается символом null.
String();
String(const char *const);
String(const 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.
получив ссылку на символ и вызвав эту функцию, можно изменить его значение.
s1[4]='x';
cout << "S1:\t" << s1.GetString() << endl;
Эти строки из главной программы изменяют значение символа под индексом 4
в символьном массиве s1.
получить доступ к постоянному объекту класса String , например в реализации
конструктора копий (строка 63)
String::String (const String & rhs)
{
itsString = new char[itsLen+1];
for (unsigned short i = 0; i
itsString[itsLen] = '\0';
К этому объекту не возможно получить доступ, используя непостоянные функции-члены.
Следовательно оператор индекса необходимо перегружать как постоянный.
а постоянную ссылку на него. Но поскольку один символ занимает только один байт,
то нет смысла так поступать.
{
itsString[0] = '\0';
itsLen=0;
символа null. Таким образом строка созданная по умолчанию содержит лишь
концевой символ null.
String::String (const String & rhs)
{
itsString = new char[itsLen+1];
for (unsigned short i = 0; i
itsString[itsLen] = '\0';
(дополнительный символ необходим для завершающейго null), а затем копирует
каждый символ исходной строки во вновь созданную и завершает ее символом null.
(с концевым null)Этот конструктор подобен конструктору копий. Длина исходной
строки исчисляется с помощью функции strlen() из стандартной библиотеки String.
String::String(const char * const cString)
{
itsString = new char[itsLen+1];
for (unsigned short i = 0; i
itsString[itsLen]='\0';
чтобы ни один из клиентов этого класс не мог создать строку произвольной
длины. Этот конструктор создает строки для внутреннего пользования,
как например operator += в строке 130. Более подробная информация об операторе
operator += приведена далее в этой главе.
заполняет все элементы строки символом null.
Поэтому условием выхода из цикла for будет i
// class methods for creating a new string of
// required size. Null filled.
{
for (unsigned short i = 0; i<=len; i++)
itsString[i] = '\0';
itsLen=len;
Содержание