K & R: массив указателей символов - PullRequest
5 голосов
/ 08 мая 2009

На стр. 109 из K & R, мы видим:

void writelines(char *lineptr[], int nlines)
{
  while (nlines -- > 0) printf("%s\n", *lineptr++);
}

Я не совсем понимаю, что именно делает * lineptr ++. Насколько я понимаю, printf требует указатель на символ, поэтому мы предоставляем это * lineptr. Затем мы увеличиваем lineptr до следующего указателя на символ в массиве? Разве это не незаконно?

На странице 99 K & R пишет, что «имя массива не является переменной; конструкции типа a = pa [где a - массив, pa - указатель на массив] и a ++ недопустимы».

Ответы [ 4 ]

7 голосов
/ 08 мая 2009

Продолжайте читать! В самом низу р. 99

В качестве формальных параметров в определении функции,

 char s[];

и

 char *s;

эквивалентны; мы предпочитаем последнее, поскольку оно более явно говорит о том, что параметр является указателем.

т.е. Вы никогда не можете передать массив (который не является переменной) в функцию. Если вы объявляете функцию, которая выглядит так, как будто она принимает массив, она действительно принимает указатель (который является переменной). Это имеет смысл. Было бы странным, если бы аргумент функции не был переменной - он может иметь разное значение каждый раз, когда вы вызываете функцию, поэтому он не является константой!

6 голосов
/ 08 мая 2009

lineptr на самом деле не массив; это указатель на указатель. Обратите внимание, что оператор постинкремента ++ имеет более высокий приоритет, чем оператор разыменования *, поэтому в lineptr++ происходит следующее:

  1. lineptr увеличивается, чтобы указывать на следующий элемент в массиве
  2. Результатом lineptr++ является старое значение lineptr, а именно указатель на текущий элемент в массиве
  3. *lineptr++ разыменовывается, так что это значение текущего элемента в массиве

Таким образом, цикл while выполняет итерации по всем элементам массива, на которые указывает lineptr (каждый элемент равен char*), и выводит их на печать.

5 голосов
/ 08 мая 2009

Может быть проще представить *lineptr++ следующим образом:

*(lineptr++)

Там указатель на массив char* s перемещается к следующему элементу массива, то есть с lineptr[0] мы переходим на lineptr[1]. (Приращение указателя происходит после разыменования из-за приращения указателя в постфиксе.)

Итак, в основном происходят следующие вещи:

  1. lineptr[0] (типа char*) извлекается с помощью ссылки.
  2. Указатель увеличивается до следующего элемента массива. (По приращению постфикса по указателю lineptr.)
  3. Теперь указатель указывает на lineptr[1], повторите процесс с шага 1.
4 голосов
/ 08 мая 2009

Выбранный ответ Адама Розенфилда неверен. Ответ Coobird тоже. По этой причине я проголосовал против обоих ответов.

Адам Марковиц интерпретирует *lineptr++ правильно, но он не отвечает на главный вопрос, является ли это законным кодом C99. Только Том Будущий делает; к сожалению, он не объясняет *lineptr++. Я дал им по одному очку.

Итак, для краткости, lineptr является переменной и может управляться как указатель. Таким образом, законно увеличивать указатель.

lineptr - указатель на последовательность указателей на последовательности символов. Другими словами, это указатель на первую строку массива строк. Согласно коду, мы можем предположить, что строки являются нулевыми ('\ 0') символами в конце. nlines - количество строк в массиве.

Тестовое выражение while: nlines-- > 0. nlines-- - это пост-декремент (потому что -- справа от переменной). Таким образом, он выполняется после теста был выполнен и независимо от результата теста, так что в любом случае.

Итак, если значение nlines, заданное в качестве аргумента, было 0, тест выполняется первым и возвращает false; инструкции в цикле не выполняются. Обратите внимание, что поскольку nlines все равно уменьшается, значение nlines после цикла while будет -1.

Если nlines == 1, тест вернет true и nlines будет уменьшено; инструкции в цикле будут выполнены один раз. Обратите внимание, что во время выполнения этих инструкций значение nlines равно 0. Когда тест проводится снова, мы возвращаемся к случаю, когда nlines == 0.

В инструкции printf используется выражение *lineptr++. Это постинкрементный указатель (++ справа от переменной). Это означает, что выражение вычисляется первым, а приращение выполняется после его использования. Таким образом, при первом выполнении printf получает копию первого элемента массива строк, который является указателем на первый символ строки. lineptr увеличивается только после этого. В следующий раз, когда будет выполнено printf, lineptr будет указывать на второй элемент и перейдет к третьему, когда будет напечатана вторая строка. Это имеет смысл, потому что мы, очевидно, хотим напечатать первую строку. Если бы Адам Розенфилд был прав, первая строка была бы пропущена, и в конце мы попытались бы напечатать строку за последней, что, очевидно, является плохой вещью.

Итак, инструкция printf представляет собой краткую форму двух следующих инструкций

printf("%s\n", *lineptr);
++lineptr; // or lineptr++, which is equivalent but not as good. lineptr += 1; is ok too.

Обратите внимание, как правило, когда предварительное и последующее увеличение эквивалентны по своему действию, предварительное увеличение предпочтительнее по соображениям производительности. Компиляторы позаботятся, чтобы переключить его для вас. Ну, большую часть времени. Лучше, если это возможно, самим предоператорам, поэтому они используются всегда. Причина становится более очевидной, когда вы сами реализуете пост- и прекремент в C ++.

...