Шаг 62.
Строки и указатели

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

    В программе доступ к строке осуществляется с помощью указателя на символ. Если описать переменную message как

   char *message;
то в результате выполнения оператора
   message = "и прощай!";
message станет указателем на строку.


    Пример 1. Вывод строк на экран.
#include <iostream.h>
char *message;
char privet[] = "и прощай!";
char *pr = privet;
void main ()
{
      message = "Здравствуй"; 
      cout << " " << message << " " << pr << endl;
      int i = 0;
      while  (*(pr+i)!='\0')
      {
         cout << *(pr+i++) << " ";
      }
   }
Текст этой программы можно взять здесь.

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


    Пример 2.

Рассматривается функция month_name(), которая возвращает указатель на строку, содержащий имя n-го месяца. Это типичная задача для использования внутреннего статического массива (инициализировать можно только массивы внешнего или статистического класса памяти!).

    Функция month_name() содержит локальный массив строк и при обращении к ней возвращает указатель на нужную строку.

    В описании массива указателей на символы name[] инициализатором является просто список строк. Символы i-й строки помещаются в определенное место памяти, а указатель на ее начало хранится в элементе name[i]. Поскольку размер массива name не указан, компилятор сам подсчитывает количество инициализаторов и соответственно устанавливает правильное число.

#include <iostream.h>
#define n 15
void main ()
{
      char *month_name(int);
      /* ------------- */
      for (int i=0; i < n; i++)
         cout << "месяц номер " << i << " - " <<  month_name(i) << endl;
 }
/* ------------------------------------------- */
char *month_name (int k)  /* Название k-го месяца */
{
      static char *name[] = {
                              "неверный месяц","январь",
                              "февраль","март","апрель",
                              "май","июнь","июль","август",
                              "сентябрь","октябрь","ноябрь",
                              "декабрь"
                            };
      return (k<1||k>12)?name[0]:name[k];
}
Текст этой программы можно взять здесь.

    Результат работы программы:

   месяц номер 0 - неверный месяц
   месяц номер 1 - январь
   месяц номер 2 - февраль
   месяц номер 3 - март
   месяц номер 4 - апрель
   месяц номер 5 - май
   месяц номер 6 - июнь
   месяц номер 7 - июль
   месяц номер 8 - август
   месяц номер 9 - сентябрь
   месяц номер 10 - октябрь
   месяц номер 11 - ноябрь
   месяц номер 12 - декабрь
   месяц номер 13 - неверный месяц
   месяц номер 14 - неверный месяц


    Пример 3. Хитросплетение ссылок. Что напечатает следующая программа?
#include <iostream.h>
char *c[]={ "ENTER","NEW","POINT","FIRST" };
char **cp[]={ c+3,c+2,c+1,c };
char ***cpp=cp;
void main ()
{
   cout << **++cpp;
   cout << *--*++cpp+3 << " ";
   cout << *cpp[-2]+3;
   cout << cpp[-1][-1]+1 << endl;
}
Текст этой программы можно взять здесь.

    Результаты работы программы:

   POINTER STEW

    Ответы и комментарии.

char *c[]={
        "ENTER"   c - массив  ссылок на символ. Элементы c иници-
        "NEW"     ализируются так, что они  указывают  на массивы
        "POINT"   символов "ENTER", "NEW", "POINT" и "FIRST".
        "FIRST"
};
char **cp[]={     Описатель  **cp[]  соответствует символу, *cp -
 c+3,c+2,c+1,c    ссылке на  символ и  cp[] - ссылке на ссылку на
};                символ. Элементы cp  инициализируются  так, что
                  они указывают на элементы c.
char ***cpp = cp; Описатель ***cpp дает символ, **cpp - ссылку на
                  символ, *cpp  -  ссылку  на  ссылку  на символ,
                  наконец, cpp - ссылка, указывающая на ссылку на
                  ссылку на символ.

    В рисунках, приведенных ниже, сплошными линиями обозначены указатели, а пунктирными - временные ссылки аргументов для вывода.

    Рисунок 1 показывает взаимосвязь между cpp, cp и c.


Рис.1. Взаимосвязь между cpp, cp и c

*(*(++cpp))         Увеличим  cpp,  а  затем   проследим  цепочку
                    ссылок. (Рис.2)


Рис.2. Цепочка ссылок

(*(--(*(++cpp))))+3 Увеличим  cpp,  по  ссылке  дойдем до  cp[2],
                    уменьшим cp[2],по ссылке дойдем до c[0] и эту
                    ссылку (т.е. c[0]) увеличим на 3. (Рис.3)


Рис.3. Цепочка ссылок

(*(cpp[(-2)]))+3   Индексируя cpp значением -2, получим cp[0], по
                   ссылке дойдем до  c[3]  и  проиндексируем  его
                   значением 3. (Рис.4)


Рис.4. Цепочка ссылок

((cpp[-1])[-1])+1   Индексируя cpp значением  -1, получим  cp[1],
                    снова индексируем с -1 и доходим до c[1], эту
                    последнюю  ссылку  индексируем  значением  1.
                    (Рис.5)


Рис.5. Цепочка ссылок

    Если вы правильно решили последнюю задачу, то знаете все, что можно знать о механизме использования ссылок.

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

    На следующем шаге мы рассмотрим функции работы со строками.


Предыдущий шаг Содержание Следующий шаг