Назад | Начало урока | Вперед
Содержание

Глава 8

Вверх

Арифметические операции над указателями

Один указатель можно вычитать из другого. Если например два указателя ссылаются на разные элементы массива, то вычитание одного указателя из другого позволяет получить количество элементов массива, находящихся между ними. Наиболее эффективно эта методика используется при обработке массивов. Пример:

Разделение символьной строки по словам. Вы вводите символьную строку, а программа выводит каждое слово с отдельной новой строки, таким образом разделяет введенную вами строку по словам. Но если ввести строку кириллицей то будут казусы. Чтобы не было казусов с кирилицей в этой программе, надо включать соответствующие заголовочные файлы для кирилицы.

Listing8_11 Разделение символьной строки по словам


#include <iostream>
#include <ctype.h>
#include <string.h>

bool GetWord(char* theString, char* word, int& wordOffset);

// driver program
int main() {

const int bufferSize = 255;

char buffer[bufferSize+1]; // hold the entire string
char word[bufferSize+1]; // hold the word

int wordOffset = 0; // start at the beginning

std::cout << "Enter a string: ";
std::cin.getline(buffer,bufferSize);

while (GetWord(buffer,word,wordOffset)) {

std::cout << "Got this word: " << word << std::endl;
}

return 0;

}

// function to parse words from a string.

bool GetWord(char* theString, char* word, int& wordOffset) {

if (!theString[wordOffset]) // end of string?
return false;

char *p1, *p2;
p1 = p2 = theString+wordOffset; // point to the next word

// eat leading spaces
for (int i = 0; i<(int)strlen(p1) && !isalnum(p1[0]); i++)
p1++;

// see if you have a word
if (!isalnum(p1[0]))
return false;

// p1 now points to start of next word
// point p2 there as well
p2 = p1;

// march p2 to end of word
while (isalnum(p2[0]))
p2++;

// p2 is now at end of word
// p1 is at beginning of word
// length of word is the difference
int len = int (p2 - p1);

// copy the word into the buffer
strncpy (word,p1,len);

// null terminate it
word[len]='\0';

// now find the beginning of the next word
for (int j = int(p2-theString); j<(int)strlen(theString)
&& !isalnum(p2[0]); j++) {


p2++;
}

wordOffset = int(p2-theString);

return true;

}

1. Сначала объявляется и инициализируется константа

const int bufferSize = 255;

Эта константа будет определять длину символьного массива.

2. Затем объявляется два символьных массива , их длина определяется вышеуказанной константой. Массивы расположены в стеке.

char buffer[bufferSize+1]; // hold the entire string char word[bufferSize+1]; // hold the word

Здесь первый массив будет буфером для вводимой пользователем строки. А второй массив будет буфером для отдельных слов уже во время работы программы.

3. Затем объявляется еще одна переменная инициализируемая нулем.

int wordOffset = 0; // start at the beginning

4. Далее предлагается ввести строку.

std::cout << "Enter a string: ";
std::cin.getline(buffer,bufferSize);

И введенная строка при помощи библиотечной функции cin.getline() помещается в массив buffer[]

5. Далее массив buffer в котором содержится введенная строка передается функции GetWord() в качестве аргумента, наряду с буфером для первого слова(неинициализированный массив word) и целочисленной переменной WordOffset, инициализированной ранее нулевым значением.

while (GetWord(buffer,word,wordOffset)) {


std::cout << "Got this word: " << word << std::endl;
}

Помним что первый и второй параметр этой функции были объявлены как указатели на переменную типа char !.

Суть цикла таков:как только функция GetWord() возвратит 1 или true сразу выполнится оператор внутри фигурных скобок. Если функция возвратит false то сразу выход из цикла.

Слова выводятся на экран по мере возвращения их функцией GetWord() до тех пор пока она не вернет значение false.

6. Рассмотрим работу функции GetWord().

bool GetWord(char* theString, char* word, int& wordOffset)

Обратите внимание здесь снова используется способность С++ интерпретировать значение 0 как false. В противном случае строку пришлось бы переписать так

if (theString[wordOffset]==0) // end of string? return false;

сравни:

if (!theString[wordOffset]) // end of string?
return false;

Этим условием программа проверяет, не является ли рассматриваемый символ символом окончания строки?Если истинно то функция вернет false в противном случае -продолжается выполнение функции.

6. Посмотрим работу функции GetWord()

Объявляются два указателя на переменную символьного типа . В следующей строке оба указателя устанавливаются в начало следующего слова., заданного значением переменной wordOffset.

char *p1, *p2;
p1 = p2 = theString+wordOffset; // point to the next word

Вначале значение переменной wordoffset равно нулю, следовательно они указывают на начало строки. Вместо theString мы передали как аргумент указатель на массив buffer. Мы передали имя массива без индекса , а Имя массива без индекса указывает на первый элемент массива.(в данном случае это первый символ строки , это может быть даже пробел.) Итак оба указателя p1 и p2 мы инициализировали адресом начала строки.

С помощью следующего цикла указатель p1 перемещается на первый символ являющийся буквой или цифрой.(убираются все пробелы в начале строки, если таковые имеются)Если такой символ не найден, функция возвращает false.(если вообще не найдено ни одного символа кроме пробела, то- false)

//убрать ведущие пробелы
// eat leading spaces

for (int i = 0; i<(int)strlen(p1) && !isalnum(p1[0]); i++)
p1++;

Суть этого цикла в том чтобы прибавлять на 1 указатель p1 до тех пор пока не встретится первый элемент (символ) массива.p1[0]-это первый элемент массива и пока i не превысит длину массива.

Выражение (int)strlen(p1) обозначает количество символов во введенном нами символьном массиве. Эта величина постоянная. Я имею в виду что при выполнении цикла, p1 в скобках в результате инкремента изменяется но количество элементов в символьном массиве, то есть значение вышеуказанного выражения не изменяется и является первым постоянным условием этого цикла. Второе выражение как второе условие цикла !isalnum(p1[0]) означает "пока пробел" или "пока указатель p1 указывает на пробел".

Но этот цикл не будет выполнен ни разу если указатель сразу оказался на первом слове введенного нами предложения. И программа сразу пойдет выполняться дальше.

Следующие две строки обозначают что если вообще не встретится символ, а длина строки уже исчерпана, то возвратить как результат работы функции нуль или false.

//является ли это словом?
// see if you have a word

if (!isalnum(p1[0])) return false;

Таким образом указатель p1 соответствует началу очередного (или первого) слова , а следующая строка присваивает указателю p2 то же значение. p2 теперь указывает на начало следующего(или первого) слова так же как и p1

// p1 now points to start of next word
// point p2 there as well
p2 = p1;

В следующих строках осуществляется осуществляется поиск в строке первого символа, не являющегося ни цифрой ни буквой(являющегося пробелом). Указатель p2 перемещается на этот символ. Теперь p1 и p2 указывают на начало и конец слова соответственно. Напомню что выражение isalnum(p2[0]) обозначает "не пробел" или другими словами "буква или цифра" //переместить p2 в конец слова

// march p2 to end of word
while (isalnum(p2[0]))
p2++;

Теперь p1 и p2 указывают на начало и конец слова соответственно.

Вычтем из значения указателя p2 значение p1 и преобразуем результат к целочисленному типу. Результатом выполнения такой операции будет длина очередного (или первого) слова.

//p2 теперь в конце слова, p1 теперь в начале слова //их разница составляет длину слова

// p2 is now at end of word
// p1 is at beginning of word
// length of word is the difference

int len = int (p2 - p1);

Затем на основании данных о начале и длине полученное слово копируется в буфер. //копировать слово в буфер

// copy the word into the buffer
strncpy (word,p1,len);

p1 здесь указывает на первый символ слова, len-количество символов в слове.

Теперь в конец слова надо поместить нулевой символ

// null terminate it
word[len]='\0';

Далее указатель p2 перемещается в начало следующего слова, а переменной wordoffset присваивается значение смещения начала очередного слова относительно начала строки. Возвращая значение true функция сообщает что слово найдено.

//найти начало следующего слова

// now find the beginning of the next word

for (int j = int(p2-theString); j<(int)strlen(theString)
&& !isalnum(p2[0]); j++) {


p2++;
}

Этот цикл будет выполняться пока не будет найден первый символ следующего слова. Теперь p2 указывает на первый символ следующего слова.

Выражение int(p2-theString) обозначает число символов от начала массива до начала следующего взятого для рассмотрения слова.

Вычислим смещение этого слова относительно начала строки.

wordOffset = int(p2-theString);

wordOffset -смещение слова относительно начала строки.
return true;

Это классический пример кода который лучше всего изучать поместив в отладчик и запустив пошаговое выполнение.

Вверх

Резюме
Указатели являются мощнейшим средством косвенного доступа к данным. Каждая переменная имеет адрес, получить который можно с помощью оператора взятия адреса(&). Для хранения адреса используются указатели.

Для объявления указателя достаточно установить тип объекта, адрес которого он будет содержать, а затем ввести оператор косвенного доступа (*) и имя указателя. После объявления указатель следует инициализировать. Если адрес объекта неизвестен, указатель инициализируется значением 0.

Для доступа к значению, записанному по адресу указателя, используется оператор косвенного доступа(*). Указатель можно объявить постоянным. В этом случае не допускается присвоение ему нового адреса. Указатель, хранящий адрес постоянного объекта, не может использоваться для изменения этого объекта.

Чтобы создавать новый объект в динамической памяти, используется ключевое слово new. Полученный адрес присваивается указателю. Чтобы освободить выделенную память, применяется ключевое слово delete. Оператор delete освобождает память, но не уничтожает указатель. Поэтому после освобождения памяти указатель необходимо переприсвоить, по крайней мере назначить ему нулевое значение.

Вверх

Вопросы и ответы

1. Почему указатели так важны?

Сегодня рассматривалось использование указателей для доступа к объектам, размещенным в динамической памяти, а также для передачи аргументов в функции по ссылке. В уроке 14 будет рассмотрено использование указателей в полиморфизме классов.

2. Чем удобно размещение объектов в динамической памяти?

Объекты находящиеся в динамической памяти , не уничтожаются после выхода из той области, в которой они были объявлены. Кроме того появляется возможность уже в процессе выполнения программы решать, какое количество объектов требуется объявить. Более подробно эта тема в следующем уроке.

3. Зачем объявлять объект постоянным, ведь это ограничивает его возможности?

Каждый программист желает привлечь компилятор для поиска ошибок. Одной из серьезнейших ошибок, крайне трудных для обнаружения является функция изменяющая объект нетрадиционным способом, которые не очевидны ни для вызывающей функции, ни для самого программиста. Объявление объекта постоянным, предотвращает такие действия.


Назад | Начало урока | Вверх | Вперед
Содержание

Hosted by uCoz