Выделение памяти под строки
В следующем фрагменте программы мы динамически выделяем память под строку переменной длины и копируем туда исходную строку
// стандартная функция strlen подсчитывает
// количество символов в строкеint length = strlen(src_str);// выделить память и добавить один байт// для завершающего нулевого байтаchar* buffer = new char[length + 1];strcpy(buffer, src_str); // копирование строкиОперация new возвращает адрес выделенной памяти. Однако нет никаких гарантий, что new обязательно завершится успешно. Объем оперативной памяти ограничен, и может случиться так, что найти еще один участок свободной памяти будет невозможно. В таком случае new возвращает нулевой указатель (адрес 0). Результат new необходимо проверять:
char* newstr;
size_t length = 4;newstr = new (std::nothrow) char[length];if (newstr == NULL) { // проверить результат // обработка ошибок}// память выделена успешноРекомендации по использованию указателей и динамического распределения памятиУказатели и динамическое распределение памяти – очень мощные средства языка. С их помощью можно разрабатывать гибкие и весьма эффективные программы. В частности, одна из областей применения Си++ – системное программирование – практически не могла бы существовать без возможности работы с указателями. Однако возможности, которые получает программист при работе с указателями, накладывают на него и большую ответственность. Наибольшее количество ошибок в программу вносится именно при работе с указателями. Как правило, эти ошибки являются наиболее трудными для обнаружения и исправления.
Приведем несколько примеров.
Использование неверного адреса в операции delete. Результат такой операции непредсказуем. Вполне возможно, что сама операция пройдет успешно, однако внутренняя структура памяти будет испорчена, что приведет либо к ошибке в следующей операции new, либо к порче какой-нибудь информации.
Пропущенное освобождение памяти, т.е. программа многократно выделяет память под данные, но "забывает" ее освобождать. Такие ошибки называют утечками памяти. Во-первых, программа использует ненужную ей память, тем самым понижая производительность. Кроме того, вполне возможно, что в 99 случаях из 100 программа будет успешно выполнена. Однако если потеря памятиокажется слишком большой, программе не хватит памяти под какие-нибудь данные и, соответственно, произойдет сбой.
Запись по неверному адресу. Скорее всего, будут испорчены какие-либо данные. Как проявится такая ошибка – неверным результатом, сбоем программы или иным образом – предсказать трудно
Примеры ошибок можно приводить бесконечно. Общие их черты, обуславливающие сложность обнаружения, это, во-первых, непредсказуемость результата и, во-вторых, проявление не в момент совершения ошибки, а позже, быть может, в том месте программы, которое само по себе не содержит ошибки (неверная операция delete – сбой в последующей операции new, запись по неверномуадресу – использование испорченных данных в другой части программы и т.п.).
Отнюдь не призывая отказаться от применения указателей (впрочем, в Си++ это практически невозможно), мы хотим подчеркнуть, что их использование требует внимания и дисциплины. Несколько общих рекомендаций.
1. Используйте указатели и динамическое распределение памяти только там, где это действительно необходимо. Проверьте, можно ли выделить память статически или использоватьавтоматическую переменную.
2. Старайтесь локализовать распределение памяти. Если какой-либо метод выделяет память (в особенности под временные данные), он же и должен ее освободить.
3. Там, где это возможно, вместо указателей используйте ссылки.
4. Проверяйте программы с помощью специальных средств контроля памяти (Purify компании Rational, Bounce Checker компании Nu-Mega и т.д.)
Ссылки
Ссылка – это еще одно имя переменной. Если имеется какая-либо переменная, например
Complex x;то можно определить ссылку на переменную x как
Complex& y = x;
и тогда x и y обозначают одну и ту же величину. Если выполнены операторы
x.real = 1;
x.imaginary = 2;то y.real равно 1 и y.imaginary равно 2. Фактически, ссылка – это адрес переменной (поэтому при определении ссылки используется символ & -- знак операции взятия адреса ), и в этом смысле она сходна с указателем, однако у ссылок есть свои особенности.
Во-первых, определяя переменную типа ссылки, ее необходимо инициализировать, указав, на какую переменную она ссылается. Нельзя определить ссылку
int& xref;
можно только
int& xref = x;
Во-вторых, нельзя переопределить ссылку, т.е. изменить на какой объект она ссылается. Если после определения ссылки xref мы выполним присваивание
xref = y;
то выполнится присваивание значения переменной y той переменной, на которую ссылается xref. Ссылка xref по-прежнему будет ссылаться на x. В результате выполнения следующего фрагмента программы:
int x = 10;
int y = 20;int& xref = x;xref = y;x += 2;cout << "x = " << x << endl;cout << "y = " << y << endl;cout << "xref = " << xref << endl;будет выведено:
x = 22
y = 20xref = 22В-третьих, синтаксически обращение к ссылке аналогично обращению к переменной. Если для обращения к атрибуту объекта, на который ссылается указатель, применяется операция ->, то для подобной же операции со ссылкой применяется точка " .".
Complex a;
Complex* aptr = &a;Complex& aref = a;aptr->real = 1;aref.imaginary = 2;Как и указатель, ссылка сама по себе не имеет значения. Ссылка должна на что-то ссылаться, тогда как указатель должен на что-то указывать.