С.Б. Липпман, Ж. Лажойе - Язык программирования С++ Вводный курс (1114944), страница 18
Текст из файла (страница 18)
Если тип char занимает 1 байт,int – 4 и double – 8, то прибавление 2 к указателям на char, int и double увеличит ихзначение соответственно на 2, 8 и 16. Как это можно интерпретировать? Если объектыодного типа расположены в памяти друг за другом, то увеличение указателя на 1приведет к тому, что он будет указывать на следующий объект. Поэтому арифметическиедействия с указателями чаще всего применяются при обработке массивов; в любыхдругих случаях они вряд ли оправданы.Вот как выглядит типичный пример использования адресной арифметики при перебореэлементов массива с помощью итератора:int ia[10];89С++ для начинающих90int *iter = &ia[0];int *iter_end = &ia[10];while (iter != iter_end) {do_something_with_value (*iter);++iter;}Упражнение 3.8int ival = 1024, ival2 = 2048;Даны определения переменных:int *pi1 = &ival, *pi2 = &ival2, **pi3 = 0;Что происходит при выполнении нижеследующих операций присваивания? Допущены(a) ival = *pi3;(b) *pi2 = *pi3;(c) ival = pi2;(e) pi1 = *pi3;(f) ival = *pi1;(g) pi1 = ival;ли в данных примерах ошибки?(d) pi2 = *pi1;(h) pi3 = &pi2;Упражнение 3.9Работа с указателями – один из важнейших аспектов С и С++, однако в ней легкоpi = &ival;допустить ошибку.
Например, кодpi = pi + 1024;почти наверняка приведет к тому, что pi будет указывать на случайную область памяти.Что делает этот оператор присваивания и в каком случае он не приведет к ошибке?Упражнение 3.10Данная программа содержит ошибку, связанную с неправильным использованиемint foobar(int *pi) {*pi = 1024;return *pi;}int main() {int *pi2 = 0;int ival = foobar(pi2);return 0;указателей:}В чем состоит ошибка? Как можно ее исправить?С++ для начинающихУпражнение 3.11Ошибки из предыдущих двух упражнений проявляются и приводят к фатальнымпоследствиям из-за отсутствия в С++ проверки правильности значений указателей вовремя работы программы. Как вы думаете, почему такая проверка не была реализована?Можете ли вы предложить некоторые общие рекомендации для того, чтобы работа суказателями была более безопасной?3.4. Строковые типыВ С++ поддерживаются два типа строк – встроенный тип, доставшийся от С, и классstring из стандартной библиотеки С++.
Класс string предоставляет гораздо большевозможностей и поэтому удобней в применении, однако на практике нередки ситуации,когда необходимо пользоваться встроенным типом либо хорошо понимать, как онустроен. (Одним из примеров может являться разбор параметров командной строки,передаваемых в функцию main(). Мы рассмотрим это в главе 7.)3.4.1. Встроенный строковый типКак уже было сказано, встроенный строковый тип перешел к С++ по наследству от С.Строка символов хранится в памяти как массив, и доступ к ней осуществляется припомощи указателя типа char*.
Стандартная библиотека С предоставляет набор функций// возвращает длину строкиint strlen( const char* );// сравнивает две строкиint strcmp( const char*, const char* );// копирует одну строку в другуюдля манипулирования строками. Например:char* strcpy( char*, const char* );Стандартная библиотека С является частью библиотеки С++.
Для ее использования мыдолжны включить заголовочный файл:#include <cstring>Указатель на char, с помощью которого мы обращаемся к строке, указывает насоответствующий строке массив символов. Даже когда мы пишем строковый литерал,напримерconst char *st = "Цена бутылки вина\n";компилятор помещает все символы строки в массив и затем присваивает st адрес первогоэлемента массива. Как можно работать со строкой, используя такой указатель?Обычно для перебора символов строки применяется адресная арифметика.
Посколькустрока всегда заканчивается нулевым символом, можно увеличивать указатель на 1, покаочередным символом не станет нуль. Например:91С++ для начинающихwhile (*st++ ) { ... }st разыменовывается, и получившееся значение проверяется на истинность. Любоеотличное от нуля значение считается истинным, и, следовательно, цикл заканчивается,когда будет достигнут символ с кодом 0. Операция инкремента ++ прибавляет 1 куказателю st и таким образом сдвигает его к следующему символу.Вот как может выглядеть реализация функции, возвращающей длину строки. Отметим,что, поскольку указатель может содержать нулевое значение (ни на что не указывать),int string_length( const char *st ){int cnt = 0;if ( st )while ( *st++ )++cnt;return cnt;перед операцией разыменования его следует проверять:}Строка встроенного типа может считаться пустой в двух случаях: если указатель настроку имеет нулевое значение (тогда у нас вообще нет никакой строки) или указывает намассив, состоящий из одного нулевого символа (то есть на строку, не содержащую ниодного значимого символа).// pc1 не адресует никакого массива символовchar *pc1 = 0;// pc2 адресует нулевой символconst char *pc2 = "";Для начинающего программиста использование строк встроенного типа чреватоошибками из-за слишком низкого уровня реализации и невозможности обойтись безадресной арифметики.
Ниже мы покажем некоторые типичные погрешности,допускаемые новичками. Задача проста: вычислить длину строки. Первая версия неверна.#include <iostream>Исправьте ее.const char *st = "Цена бутылки вина\n";int main() {int len = 0;while ( st++ ) ++len;cout << len << ": " << st;return 0;}В этой версии указатель st не разыменовывается. Следовательно, на равенство 0проверяется не символ, на который указывает st, а сам указатель. Поскольку изначальноэтот указатель имел ненулевое значение (адрес строки), то он никогда не станет равнымнулю, и цикл будет выполняться бесконечно.92С++ для начинающихВо второй версии программы эта погрешность устранена.
Программа успешно#include <iostream>заканчивается, однако полученный результат неправилен. Где мы не правы на этот раз?const char *st = "Цена бутылки вина\n";int main(){int len = 0;while ( *st++ ) ++len;cout << len << ": " << st << endl;return 0;}Ошибка состоит в том, что после завершения цикла указатель st адресует не исходныйсимвольный литерал, а символ, расположенный в памяти после завершающего нуля этоголитерала. В этом месте может находиться что угодно, и выводом программы будетслучайная последовательность символов.st = st – len;Можно попробовать исправить эту ошибку:cout << len << ": " << st;Теперь наша программа выдает что-то осмысленное, но не до конца. Ответ выглядит так:18: ена бутылки винаМы забыли учесть, что заключительный нулевой символ не был включен в подсчитаннуюдлину.
st должен быть смещен на длину строки плюс 1. Вот, наконец, правильныйоператор:st = st – len - 1;а вот и и правильный результат:18: Цена бутылки винаОднако нельзя сказать, что наша программа выглядит элегантно. Операторst = st – len - 1;добавлен для того, чтобы исправить ошибку, допущенную на раннем этапепроектирования программы, – непосредственное увеличение указателя st. Этот операторне вписывается в логику программы, и код теперь трудно понять. Исправления такогорода часто называют заплатками – нечто, призванное заткнуть дыру в существующейпрограмме. Гораздо лучшим решением было бы пересмотреть логику.
Одним из93С++ для начинающихвариантов в нашем случае можетинициализированного значением st:94бытьопределениевторогоуказателя,const char *p = st;Теперь p можно использовать в цикле вычисления длины, оставив значение stнеизменным:while ( *p++ )3.4.2. Класс stringКак мы только что видели, применение встроенного строкового типа чревато ошибками ине очень удобно из-за того, что он реализован на слишком низком уровне. Поэтомудостаточно распространена разработка собственного класса или классов дляпредставления строкового типа – чуть ли не каждая компания, отдел илииндивидуальный проект имели свою собственную реализацию строки.
Да что говорить, впредыдущих двух изданиях этой книги мы делали то же самое! Это порождало проблемысовместимости и переносимости программ. Реализация стандартного класса stringстандартной библиотекой С++ призвана была положить конец этому изобретениювелосипедов.Попробуем специфицировать минимальный набор операций, которыми должен обладатькласс string:•инициализация массивом символов (строкой встроенного типа) или другимобъектом типа string. Встроенный тип не обладает второй возможностью;•копирование одной строки в другую. Для встроенного типа приходитсяиспользовать функцию strcpy();•доступ к отдельным символам строки для чтения и записи. Во встроенноммассиве для этого применяется операция взятия индекса или косвенная адресация;•сравнение двух строк на равенство. Для встроенного типа используетсяфункция strcmp();•конкатенация двух строк, получая результат либо как третью строку, либовместо одной из исходных.
Для встроенного типа применяется функция strcat(),однако чтобы получить результат в новой строке, необходимо последовательнозадействовать функции strcpy() и strcat();•вычисление длины строки. Узнать длину строки встроенного типа можно спомощью функции strlen();•возможность узнать, пуста ли строка. У встроенных строк для этой целиchar str = 0;//...if ( ! str || ! *str )приходится проверять два условия:return;С++ для начинающих95Класс string стандартной библиотеки С++ реализует все перечисленные операции (игораздо больше, как мы увидим в главе 6).
В данном разделе мы научимся пользоватьсяосновными операциями этого класса.Для того чтобы использовать объектысоответствующий заголовочный файл:классаstring,необходимо включить#include <string>Вот пример строки из предыдущего раздела, представленной объектом типа string и#include <string>инициализированной строкой символов:string st( "Цена бутылки вина\n" );Длину строки возвращает функция-член size() (длина не включает завершающийcout <<<<<<<<"Длина "st": " << st.size()" символов, включая символ новой строки\n";нулевой символ).Вторая форма определения строки задает пустую строку:string st2; // пустая строкаКак мы узнаем, пуста ли строка? Конечно, можно сравнить ее длину с 0:if ( ! st.size() )// правильно: пустаяОднако есть и специальный метод empty(), возвращающий true для пустой строки иfalse для непустой:if ( st.empty() )// правильно: пустаяТретья форма конструктора инициализирует объект типа string другим объектом тогоже типа:string st3( st );Строка st3 инициализируется строкой st.
Как мы можем убедиться, что эти строкисовпадают? Воспользуемся оператором сравнения (==):if ( st == st3 )// инициализация сработалаС++ для начинающихКак скопировать одну строку в другую? С помощью обычной операции присваивания:st2 = st3; // копируем st3 в st2Для конкатенации строк используется операция сложения (+) или операция сложения сприсваиванием (+=).