- •Указатели
- •Что представляют собой указатели
- •Операторы, используемые с указателями
- •О важности базового типа указателя
- •Присваивание значений с помощью указателей
- •Указатели и массивы
- •Индексирование указателя
- •Указатели и строковые литералы
- •Все познается в сравнении
- •Массивы указателей
- •Соглашение о нулевых указателях
- •Многоуровневая непрямая адресация
- •Проблемы, связанные с использованием указателей
- •Неинициализированные указатели
- •Некорректное сравнение указателей
- •Не забывайте об установке указателей
Указатели и массивы
В C++ указатели и массивы тесно связаны между собой, причем настолько, что зачастую понятия "указатель" и "массив" взаимозаменяемы. В этом разделе мы попробуем проследить эту связь. Для начала рассмотрим следующий фрагмент программы.
char str[80];
char *p1;
p1 = str;
Здесь str представляет собой имя массива, содержащего 80 символов, a p1 — указатель на тип char. Особый интерес представляет третья строка, при выполнении которой переменной p1 присваивается адрес первого элемента массива str. (Другими словами, после этого присваивания p1 будет указывать на элемент str[0].) Дело в том, что в C++ использование имени массива без индекса генерирует указатель на первый элемент этого массива. Таким образом, при выполнении присваивания p1 = str адрес stг[0] присваивается указателю p1. Это и есть ключевой момент, который необходимо четко понимать: неиндексированное имя массива, использованное в выражении, означает указатель на начало этого массива.
Имя массива без индекса образует указатель на начало этого массива.
Поскольку после рассмотренного выше присваивания p1 будет указывать на начало массива str, указатель p1 можно использовать для доступа к элементам этого массива. Например, если нужно получить доступ к пятому элементу массива str, используйте одно из следующих выражений:
str[4]
или
*(p1+4)
В обоих случаях будет выполнено обращение к пятому элементу. Помните, что индексирование массива начинается с нуля, поэтому при индексе, равном четырем, обеспечивается доступ к пятому элементу. Точно такой же эффект производит суммирование значения исходного указателя (p1) с числом 4, поскольку p1 указывает на первый элемент массива str.
Необходимость использования круглых скобок, в которые заключено выражение p1+4, обусловлена тем, что оператор "*" имеет более высокий приоритет, чем оператор "+". Без этих круглых скобок выражение бы свелось к получению значения, адресуемого указателем p1, т.е. значения первого элемента массива, которое затем было бы увеличено на 4.
Важно! Убедитесь лишний раз в правильности использования круглых скобок в выражении с указателями. В противном случае ошибку будет трудно отыскать, поскольку внешне программа может выглядеть вполне корректной. Если у вас есть сомнения в необходимости их использования, примите решение в их пользу — вреда от этого не будет.
В действительности в C++ предусмотрено два способа доступа к элементам массивов: с помощью индексирования массивов и арифметики указателей. Дело в том, что арифметические операции над указателями иногда выполняются быстрее, чем индексирование массивов, особенно при доступе к элементам, расположение которых отличается строгой упорядоченностью. Поскольку быстродействие часто является определяющим фактором при выборе тех или иных решений в программировании, то использование указателей для доступа к элементам массива— характерная особенность многих С++-программ. Кроме того, иногда указатели позволяют написать более компактный код по сравнению с использованием индексирования массивов.
Чтобы лучше понять различие между использованием индексирования массивов и арифметических операций над указателями, рассмотрим две версии одной и той же программы. В этой программе из строки текста выделяются слова, разделенные пробелами. Например, из строки "Привет дружище" программа должна выделить слова "Привет" и "дружище". Программисты обычно называют такие разграниченные символьные последовательности лексемами (token). При выполнении программы входная строка посимвольно копируется в другой массив (с именем token) до тех пор, пока не встретится пробел. После этого выделенная лексема выводится на экран, и процесс продолжается до тех пор, пока не будет достигнут конец строки. Например, если в качестве входной строки использовать строку Это лишь простой тест., программа отобразит следующее.
Это
лишь
простой
тест.
Вот как выглядит версия программы разбиения строки на слова с использованием арифметики указателей.
// Программа разбиения строки на слова:
// версия с использованием указателей.
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
char str[80];
char token[80];
char *p, *q;
cout << "Введите предложение: ";
gets(str);
p = str;
// Считываем лексему из строки.
while(*р) {
q = token; // Устанавливаем q для указания на начало массива token.
/* Считываем символы до тех пор, пока не встретится либо пробел, либо нулевой символ (признак завершения строки). */
while(*p != ' ' && *р) {
*q = *р;
q++; р++;
}
if(*p) р++; // Перемещаемся за пробел.
*q = '\0'; // Завершаем лексему нулевым символом.
cout << token << '\n';
}
return 0;
}
А вот как выглядит версия той же программы с использованием индексирования массивов.
// Программа разбиения строки на слова:
// версия с использованием индексирования массивов.
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
char str[80];
char token[80];
int i, j;
cout << "Введите предложение: ";
gets(str);
// Считываем лексему из строки.
for(i=0; ; i++) {
/* Считываем символы до тех пор пока не встретится либо пробел, либо нулевой символ (признак завершения строки). */
for(j=0; str[i]!=' ' && str[i]; j++, i++)
token[j] = str[i];
token[j] = '\0'; // Завершаем лексему нулевым символом.
cout << token << '\n';
if(!str[i]) break;
}
return 0;
}
У этих программ может быть различное быстродействие, что обусловлено особенностями генерирования кода С++-компиляторами. Как правило, при использовании индексирования массивов генерируется более длинный код (с большим количеством машинных команд), чем при выполнении арифметических действий над указателями. Поэтому неудивительно, что в профессионально написанном С++-коде чаще встречаются версии, ориентированные на обработку указателей. Но если вы— начинающий программист, смело используйте индексирование массивов, пока не научитесь свободно обращаться с указателями.
