Читать интересную книгу Программирование. Принципы и практика использования C++ Исправленное издание - Бьёрн Страуструп

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 183 184 185 186 187 188 189 190 191 ... 337
делаем

  }

  else {

    // используем р

    *p = 7;

  }

}

Основными средствами, позволяющими избежать ошибок, связанных с нулевыми указателями, являются ссылки (см. раздел 17.9.1) и исключения (см. разделы 5.6 и 19.5).

 

 Инициализируйте указатели.

int* p;

*p = 9; // Ой!

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

 

 Не обращайтесь к несуществующим элементам массива.

int a[10];

int* p = &a[10];

*p = 11;     // Ой!

a[10] = 12;  // Ой!

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

 

 Не обращайтесь к памяти с помощью удаленного указателя.

int* p = new int(7);

// ...

delete p;

// ...

*p = 13;  // Ой!

Инструкция delete p или код, размещенный после нее, может неосторожно обратиться к значению *p или использовать его косвенно. Все эти ситуации совершенно недопустимы. Наиболее эффективной защитой против этого является запрет на использование “голых” операторов new, требующих выполнения “голых” операторов delete: выполняйте операторы new и delete в конструкторах и деструкторах или используйте контейнеры, такие как Vector_ref (раздел Д.4).

 

 Не возвращайте указатель на локальную переменную.

int* f()

{

  int x = 7;

  // .. .

  return &x;

}

// ...

int* p = f();

// ...

*p = 15;   // Ой!

Возврат из функции f() или код, размещенный после него, может неосторожно обратиться к значению *p или использовать его косвенно. Причина заключается в том, что локальные переменные, объявленные в функции, размещаются в стеке перед вызовом функции и удаляются из него при выходе. В частности, если локальной переменной является объект класса, то вызывается его деструктор (см. раздел 17.5.1). Компиляторы не способны распознать большинство проблем, связанных с возвращением указателей на локальные переменные, но некоторые из них они все же выявляют.

Рассмотрим эквивалентный пример.

vector& ff()

{

  vector x(7);

  // ...

  return x;

}   // здесь вектор х был уничтожен

// ...

vector& p = ff();

// ...

p[4] = 15;   // Ой!

Только некоторые компиляторы распознают такую разновидность проблемы, связанной с возвращением указателя на локальную переменную. Обычно программисты недооценивают эти проблемы. Однако многие опытные программисты терпели неудачи, сталкиваясь с бесчисленными вариациями и комбинациями проблем, порожденных использованием простых массивов и указателей. Решение очевидно — не замусоривайте свою программу указателями, массивами, операторами new и delete. Если же вы поступаете так, то просто быть осторожным в реальной жизни недостаточно. Полагайтесь на векторы, концепцию RAII (“Resource Acquisition Is Initialization” — “Получение ресурса — это инициализация”; см. раздел 19.5), а также на другие систематические подходы к управлению памятью и другими ресурсами.

18.6. Примеры: палиндром

Довольно технических примеров! Попробуем решить маленькую головоломку. Палиндром (palindrome) — это слово, которое одинаково читается как слева направо так и справа налево. Например, слова anna, petep и malayalam являются палиндромами, а слова ida и homesick — нет. Есть два основных способа определить, является ли слово палиндромом.

• Создать копию букв, расположенных в противоположном порядке, и сравнить ее с оригиналом.

• Проверить, совпадает ли первая буква с последней, вторая — с предпоследней, и так далее до середины.

Мы выбираем второй подход. Существует много способов выразить эту идею в коде. Они зависят от представления слова и от способа отслеживания букв в слове. Мы напишем небольшую программу, которая будет по-разному проверять, является ли слово палиндромом. Это просто позволит нам выяснить, как разные особенности языка программирования влияют на внешний вид и работу программы. 

18.6.1. Палиндромы, созданные с помощью класса string

Прежде всего напишем вариант программы, используя стандартный класс string, в котором индексы сравниваемых букв задаются переменной типа int.

bool is_palindrome(const string& s)

{

  int first = 0;           // индекс первой буквы

  int last = s.length()–1; // индекс последней буквы

  while (first < last) {   // мы еще не достигли середины слова

    if (s[first]!=s[last]) return false;

    ++first;  // вперед

    ––last;   // назад

  }

  return true;

}

Мы возвращаем значение true, если достигли середины слова, не обнаружив разницы между буквами. Предлагаем вам просмотреть этот код и самим убедиться, что он работает правильно, когда в строке вообще нет букв, когда строка состоит только из одной буквы, когда в строке содержится четное количество букв и когда в строке содержится нечетное количество букв. Разумеется, мы не должны полагаться только на логику, стараясь убедиться, что программа работает правильно. Попробуем выполнить функцию is_palindrome().

int main()

{

  string s;

  while (cin>>s) {

    cout << s << " is";

    if (!is_palindrome(s)) cout << " not";

    cout << " a palindromen";

  }

}

По существу, причина, по которой мы используем класс string, заключается в том, что объекты класса string хорошо работают со словами. Они достаточно просто считывают слова, разделенные пробелами, и знают свой размер. Если бы мы хотели применить функцию is_palindrome() к строкам, содержащим пробелы, то просто считывали бы их с помощью функции getline() (см. раздел 11.5). Это можно было бы продемонстрировать на примере строк ah ha и as df fd sa.

18.6.2. Палиндромы, созданные с помощью массива

А если бы у нас не было класса string (или vector) и нам пришлось бы хранить символы в массиве? Посмотрим.

bool is_palindrome(const char s[], int n)

 // указатель s ссылается на первый символ массива из

1 ... 183 184 185 186 187 188 189 190 191 ... 337
На этом сайте Вы можете читать книги онлайн бесплатно русская версия Программирование. Принципы и практика использования C++ Исправленное издание - Бьёрн Страуструп.
Книги, аналогичгные Программирование. Принципы и практика использования C++ Исправленное издание - Бьёрн Страуструп

Оставить комментарий