Указатели на указатели

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

Рассмотрим следующую программу greet, которая реализует утилиту командной строки в соответсвтие с соглашениями, принятыми в разработке программ для UNIX-подобных ОС:

#include <stdio.h>
#include <stdlib.h>

// Функция main принимает на вход 2 параметра, которые формируются командной 
// оболочкой, из которой вызывается эта программа
// argc - количество аргументов, которые были переданы в программу при
// ее вызове из командной оболочки
// argv - массив строк, каждая из которых это аргумент, который был передан в программу,
// причем argv[0] это имя самой программы
int main(int argc, char **argv){

  // Если передано больше или меньше двух аргументов, завершить
  // исполнение программы и вывести на экран формат аргументов, ожидаемых нашей программой
  if (argc != 2){
      fprintf(stderr, "usage: %s <name>\n", argv[0]);
      exit(1);
    }

  // найдем адрес первой буквы, которая является первым элементом второй строки из argv,  
  // то есть argv[1][0]
  char * first_letter = &argv[1][0];

  // Преобразуем эту букву, адрес которой теперь содержится в указателе first_letter
  // в заглавную, если она является строчной (см. man ascii)
  if((*first_letter >= 'a' && *first_letter <= 'z'))
    *first_letter = *first_letter - 32;

  // Выведем текст c приветствием на экран
  printf("Hello, %s!\n", argv[1]);
  return 0;
}

Эта программа принимает на вход строку текста, предполагая, что это имя человека и выводит на экран приветсвие, адресованное этому человеку. Если переданное имя начинается со строчной буквы, то программа преобразует ее в заглавную прибавляя к ее коду 32. Более подробно о кодах ASCII можно узнать из встроенного руководства, выполнив в командной оболочке команду man ascii.

Согласно принятым конвенциям, функция main в программах для ОС семейства UNIX должна принимать 2 аргумента: argc и argv. Эти аргументы нужны для того, чтобы командная оболочка и операционная система могли передавать в программу любое количество агрументов, включая имя этой самой программы. Проще всего это иллюстрирует следующий пример:

# скомпилируем программу, находясь в директории, содержащей файл greet.c
gcc greet.c -o greet

# далее, вызовем эту программу с заведомо неверным количеством аргументов
./greet mom dad brother sister

В таком случае, заначение аргумента argc при исполнении этой программы будет равно 5, а массив argv будет содержать строки "./greet", "mom", "dad", "brother", "sister".

Благодаря этой особенности мы легко можем проверить корректность входных данных и оповестить пользователя в случае ошибки, напомнив ему правильный формат входных данных:

$ ./greet mom dad brother sister
usage: ./greet <name>

Присмотримся более подробно к аргументу argv. Он объявлен как указатель на указатель на символ (char**), что в данном случае эквивалентно массиву строк.

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

указатель на массив  массив указателей	    строка (массив символов)
   указателей	                      	    
+-------+	       +--------+	    +--------+	 +--------+   +--------+
|       |      	       |        |      	    |  '.'   | 	 |   '/'  |   |  'g'   |
| argv  +------------->|argv[0] +---------->|argv[0] +---+argv[0] +---+argv[0] +-----...
|       |	       |        |    	    |    [0] |	 |    [1] |   |    [2] |	
+-------+	       +---+----+    	    +--------+	 +--------+   +--------+
			   |		    строка (массив символов)
		       +---+----+    	    +--------+	 +--------+   +--------+
		       |        |           |  'm'   | 	 |  'o'   |   |  'm'   |	
		       |argv[1] +---------->|argv[1] +---+argv[1] +---+argv[1] |
		       |        |           |    [0] | 	 |    [1] |   |    [2] |
		       +---+----+    	    +--------+	 +--------+   +--------+
			   |
		       +---+----+
		       |        |    
		       |argv[2] | ...
		       |        |    
		       +---+----+
			   |
		       +---+----+
		       |        |    
		       |argv[3] | ...
		       |        |    
		       +---+----+
			   |
		       +---+----+
		       |        |    
		       |argv[4] | ...
		       |        |
		       +--------+

На этой схеме указатель argv указывает на первый элемент массива указателей, то есть хранит в себе адрес его первого элемента argv[0]. Каждый элемент этого массива указателей в свою очередь тоже является указателем, и указывает на первый элемент строки. Таким образом, можно сделать вывод, что двумерный массив представляет из себя указатель на массив указателей, указывающих на первые элементы одномерных массивов.

Created: 2021-11-08 Пн 19:20

Validate