Язык Си - Уэйт, Прата, Мартин. M. уэит с. Прата д. Мартин
Скачать 4.69 Mb.
|
year, а заставляем переменную month пройти все свои значения. Так выполняется внутренний цикл for, находящийся в первой части программы. Затем мы повторяем процесс для следующего значения year. Это внешний цикл 245 первой части программы. Структура вложенного цикла, подобная описанной, подходит для работы с двумерным массивом. Один цикл управляет одним индексом, а второй цикл - другим. Вторая часть программы имеет такую же структуру, но теперь мы изменяем year во внутреннем цикле, a month во внешнем. Помните, что при однократном прохождении внешнего цикла внутренний цикл выполняется полностью. Таким образом, программа проходит в цикле через все годы, прежде чем изменится месяц, и дает нам общее количество осадков за пять лет для первого месяца, затем общее количество за пять лет для второго месяца и т. д. Инициализация двумерного массива Далее Содержание Для инициализации массива мы взяли пять заключенных в скобки последовательностей чисел, а все эти данные еще раз заключили в скобки. Данные, находящиеся в первых внутренних скобках, присваиваются первой строке массива, данные во второй внутренней последовательности - второй строке и т. д. Правила, которые мы обсуждали раньше, о несоответствии между размером массива и данных применяются здесь для каждой строки. Если первая последовательность в скобках включает десять чисел, то только первым десяти элементам первой строки будут присвоены значения. Последние два элемента в этой строке будут, как обычно, инициализированы нулем по умолчанию. Если чисел больше, чем нужно, то это считается ошибкой; перехода к следующей строке не произойдет. Мы могли бы опустить все внутренние скобки и оставить только две самые внешние. До тех пор пока мы будем давать правильное количество входных данных, результат будет тем же самым. Однако, если данных меньше, чем нужно, массив заполняется последовательно (не обращается внимание на разделение по строкам), пока не кончатся все данные. Затем оставшимся элементам будут присвоены нулевые значения. См. рис. 12.5. РИС. 12.5. Два метода инициализации массива. Все, что мы сказали о двумерных массивах, можно распространить и на трехмерные массивы и т. д. Трехмерный массив описывается следующим образом: int solido[10][20][30]; Вы можете представить его в виде десяти двумерных массивов (каждый 20х30), поставленных 246 друг на друга, или в виде массива из массивов. То есть это массив из 10 элементов, и каждый его элемент также является массивом. Все эти массивы в свою очередь имеют по 20 элементов, каждый из которых состоит из 30 элементов. Преимущество этого второго подхода состоит в том, что можно довольно просто перейти к массивам большей размерности, если окажется, что вы не можете представить наглядно четырехмерный объект! Мы же останемся верны двум измерениям. УКАЗАТЕЛИ И МНОГОМЕРНЫЕ МАССИВЫ Далее Содержание Как создать указатели для многомерных массивов? Чтобы найти ответ на этот вопрос, рассмотрим несколько примеров. Предположим, что у нас есть описания int zippo[4][2]; /* массив типа int из 4 строк и 2 столбцов */ int *pri; /* указатель на целый тип */ Тогда на что pri = zippo; указывает? На первый столбец первой строки: zippo == &zippo[0][0] А на что указывает pri + 1? На zippo[0][l], т.е. на 1-ю строку 2-го столбца? Или на zippo[l][0], элемент, находящийся во второй строке первого столбца? Чтобы ответить на поставленный вопрос, нужно знать, как располагается в памяти двумерный массив. Он размещается, подобно одномерным массивам, занимая последовательные ячейки памяти. Порядок элементов определяется тем, что самый правый индекс массива изменяется первым, т. е. элементы массива располагаются следующим образом: zippo[0][0] zippo[0][1] zippo[1][0] zippo[1][1] zippo[2][0] Сначала запоминается первая строка, за ней вторая, затем третья и т. д. Таким образом в нашем примере: pri == &zippo[0][0] /* 1-я строка, 1 столбец */ pri + 1 == &zippo[0][1] /* 1-я строка, 2 столбец */ pri + 2 == &zippo[1][0] /* 2-я строка, 1 столбец */ pri + 3 == &zippo[1][1] /* 2-я строка, 2 столбец */ Получилось? Хорошо, а на что указывает pri + 5? Правильно, на zippo[2][l]. Мы описали двумерный массив как массив массивов. Если zippo является именем нашего двумерного массива, то каковы имена четырех строк, каждая из которых является массивом из двух элементов? Имя первой строки zippo[0], имя четвертой строки zippo[3]; вы можете заполнить пропущенные имена. Однако имя массива является также указателем на этот массив в том смысле, что оно ссылается на первый его элемент. Значит, zippo[0] == &zippo[0][0] zippo[1] == &zjppo[1][0] zippo[2] == &zippo[2][0] zippo[3] == &zippo[3][0] Это свойство является более, чем новшеством. Оно позволяет использовать функцию, предназначенную для одномерного массива, для работы с двумерным массивом! Вот доказательство (хотя мы надеемся, что теперь вы бы поверили нам и так) использования двумерного массива в нашей программе нахождения среднего значения: /* одномерная функция, двумерный массив */ main( ) 247 { static int junk[3][4] = { {2, 4, 6, 8}, {100, 200, 300, 400}, {10, 40, 60, 90} }; int row; for(row = 0; row < 3; row ++) printf(" Среднее строки %d равно %d.\n", row, mean(junk[row],4)); /* junk [row] - одномерный массив ИЗ четырех элементов */ } /* находит среднее в одномерном массиве */ int mean(array,n) int array[ ], n; { int index; long sum; if(n > 0) { for(index = 0, sum = 0; index < n; index++) sum += (long)array[index]; return((int)(sum/n)); } else { printf(" Нет массива. \n"); return(0); } } Результат работы программы: Cреднее строки 0 равно 5. Cреднее строки 1 равно 250. Cреднее строки 2 равно 50. Функции и многомерные массивы Далее Содержание Предположим, что вы хотите иметь функцию, работающую с двумерным массивом, причем со всем целиком, а не с частями. Как вы запишите определения функции и ее описания? Подойдем к этому более конкретно и скажем, что нам нужна функция, управляющая массивом junk[ ][ ] в нашем последнем примере. Пусть функция main( ) выглядит так: /* junk в main */ main( ) { static int junk[3][4] = { {2, 4, 5, 8}, {100, 200, 300, 400} {10, 40, 60, 90} }; stuff(junk); } Функция stuff( ) использует в качестве аргумента junk, являющийся указателем на весь массив. Как написать заголовок функции, не зная, что делает stuff( )? Попробуем написать: stuff(junk) int junk[ ]; или stuff(junk) int junk[ ][ ]; Нет и нет. Первые два оператора еще будут работать некоторым образом, но они рассматривают junk как одномерный массив, состоящий из 12 элементов. Информация о расчленении массива на 248 строки отсутствует. Вторая попытка ошибочна, потому что хотя оператор и указывает что junk является двумерным массивом, но нигде не говорится, из чего он состоит. Из шести строк и двух столбцов? Из двух строк и шести столбцов? Или из чего-нибудь еще? Компилятору недостаточно этой информации. Ее дают следующие операторы: stuff(junk) int junk[ ][4]; Они сообщают компилятору, что массив следует разбить на строки по четыре столбца. Массивы символьных строк являются особым случаем, так как у них нулевой символ в каждой строке сообщает компилятору о конце строки. Это разрешает описания, подобные следующему: char *list[ ]; Символьные строки представляют одно из наиболее частых применений массивов и указателей; мы вернемся к этой теме в гл. 13. ЧТО ВЫ ДОЛЖНЫ БЫЛИ УЗНАТЬ В ЭТОЙ ГЛАВЕ Далее Содержание Как объявить одномерный массив: long id_no[200]; Как объявить двумерный массив: short chess[8][8]; Какие массивы можно инициализировать: внешние и статические. Как инициализировать массив: static int hats[3]=[10,20,15]; Другой способ инициализации: static int caps[ ]=[3,56,2]; Как получить адрес переменной: использовать операцию &. Как получить значение, ссылаясь на указатель: использовать операцию *. Смысл имени массива: hats == &hats[0]. Соответствие массива и указателя: если ptr = hats; то ptr + 2 == &hat[2]; и *(ptr+2) == hat[2]; Пять операций, которые можно применять для переменных типа указатель: см. текст. Метод указателей для функций, работающих с массивами. ВОПРОСЫ И ОТВЕТЫ Далее Содержание Вопросы 1. Что напечатается в результате работы этой программы? #define PC(X, Y) printf(" %с %c \n", X, Y) char ref[ ] = { D, О, L, Т}; main( ) { char *ptr; int index; for(index =0; ptr = ref; index < 4; index++, ptr++) PC(ref[indcx], *ptr); } 2. Почему в вопросе 1 массив ref описан до оператора main( )? 3. Определите значение *ptr и *(ptr + 2) в каждом случае: 249 а. int *ptr; static int boop[4] = {12, 21, 121, 212}; ptr = bоор; б. float *ptr; static float awk[2][2] = { {1.0, 2.0}, {3.0, 4.0}}; ptr = awk[0]; в. int *ptr; static int jirb[4] = {10023, 7}; ptr = jirb; г. int = *ptr; static int torf[2][2] = {12, 14, 16}; ptr = torf[0]; д. int *ptr; static int fort[2][2] = { { 12}, {14, 16} }; ptr = fort[0]; 4. Предположим, у нас есть описание static int grid[30][100]; а. Выразите адрес grid [22][56] иначе. б. Выразите адрес grid[22][0] двумя способами. в. Выразите адрес grid[0][0] тремя способами. Ответы 1. D D O O L L Т Т 2. По умолчанию такое положение ref относит его к классу памяти типа extern, a массивы этого класса памяти можно инициализировать. 3. а. 12 и 121 б. 1.0 и 3.0 в. 10023 и 0 (автоматическая инициализация нулем) г. 12 и 16 д. 12 и 14 (именно 12 появляется в первой строке из-за скобок). 4. a. &grid[22][56] б. &grid[22][01 и grid[22] в. &grid[ ][ ] и grid[0] и grid УПРАЖНЕНИЕ 1. Модифицируйте нашу метеорологическую программу таким образом, чтобы она выполняла вычисления, используя указатели вместо индексов. (Вы по-прежнему должны объявить и инициализировать массив.) [Содержание] [Вверх] Язык Си Массивы и указатели 250 M. Уэйт, С. Прата, Д. Мартин Язык Си [Содержание] [Вниз] СИМВОЛЬНЫЕ CTРOKИ ИНИЦИАЛИЗАЦИЯ СИМВОЛЬНЫХ СТРОК ВВОД-ВЫВОД СТРОК ИСПОЛЬЗОВАНИЕ ФУНКЦИЙ, РАБОТАЮЩИХ CO CТРОKAMИ АРГУМЕНТЫ КОМАНДНЫХ СТРОК Символьные строки представляют один из наиболее полезных и важных типов данных языка Си. Хотя до сих пор все время применялись символьные строки, мы еще не все знаем о них. Конечно, нам уже известно самое главное: символьная строка является массивом типа char, который заканчивается нуль-символом ('\0'). В этой главе мы больше узнаем о структуре строк, о том, как описывать и инициализировать строки, как их вводить или выводить из программы, как работать со строками. На рис. 13.1 представлена работающая программа, которая иллюстрирует несколько способов создания строк, их чтения и вывода на печать. Мы используем две новые функции: gets( ), которая получаст строку, и puts( ), которая выводит строку. (Вы, вероятно, заметили сходство их имен с функциями getchar( ) и putchar( ).) В остальном программа выглядит достаточно привычно. /* работа со строками */ #include #deline MSG "У вас, наверное, много талантов. Расскажите о некоторых" . /* константа символьной строки */ #define NULL 0 #define LIM 5 #define LINLEN 81 /* максимальная длина строки + 1 */ char ml[ ] = " Только ограничьтесь одной строкой."; /* инициализация внешнего символьного массива */ char *m2 = " Если вы не можете вспомнить что-нибудь, придумайте."; /* инициализация указателя внешнего символьного массива */ main( ) { char name[LlNLEN]; static char talents[LINLEN]; int i; int count = 0; char *m3 = " \n Достаточно обо мне -- Как вас зовут?"; /* инициализация указателя */ static char *mytal[LlM] = ("Быстро складываю числа", "Точно умножаю", "Записываю данные", "Правильно выполняю команды", "Понимаю язык Си"}; /* инициализация массива строк */ printf("Привет! Я Клайд, компьютер. У меня много талантов.\n"); printf("%s \n", "Позвольте рассказать о некоторых из них."); puts(" Каковы они? Ах да, вот их неполный перечень."); for(i = 0; i 251 puts(m3); gets(name); printf(" Хорошо, %s, %s\n" , name, MSG); printf(" %s\n %s\n", m1, m2); gets(talents); puts(" Давайте, посмотрим, получил ли я этот перечень:"); puts(talents); printf(" Спасибо за информацию, %s \n" , name); } PИC. 13.1. Программа, использующая строки. Чтобы помочь вам разобраться в том, что делает эта программа, мы приводим результат ее работы: Привет, я Клайд, компьютер. У меня много талантов. Позвольте рассказать о некоторых из них. Каковы они? Ах да, вот их неполный перечень. Быстро складываю числа. Точно умножаю. Записываю данные. Правильно выполняю команды команды. Понимаю язык Си. Достаточно обо мне - Как вас зовут? Найджел Барнтвит Хорошо, Найджел Барнтвит, у вас, наверное, много талантов. Расскажите о некоторых. Только ограничтесь одной строкой. Если вы не можете вспомнить что-нибудь, придумайте. Фехтование, пение тирольских песен, симуляция, дегустация сыра. Давайте посмотрим, получил ли я этот перечень. Фехтование, пение тирольских песен, симуляция, дегустация сыра. Спасибо за информацию, Найджел Барнтвит. Тщательно исследуем программу. Но вместо того чтобы просматривать строку за строкой, применим более общий подход. Сначала рассмотрим способы определения строк в программе. Затем выясним, что нужно для чтения строки в программе. И наконец, изучим способы вывода строки. ОПРЕДЕЛЕНИЕ СТРОК В ПРОГРАММЕ Далее Содержание Вы, вероятно, заметили, когда читали программу, что есть много способов определения строк. Попытаемся теперь рассмотреть основные: использование строковых констант, массивов типа char, указателей на тип char и массивов, состоящих из символьных строк. В программе должно быть предусмотрено выделение памяти для запоминания строки, и мы еще вернемся к этому вопросу. Строковые константы Далее Содержание Всякий раз, когда компилятор встречается с чем-то, заключенным в двойные кавычки, он определяет это как строковую константу. Символы, заключенные в кавычки, плюс завершающий символ '\0', записываются в последовательные ячейки памяти. Компилятор подсчитывает количество символов, поскольку ему нужно знать размер памяти, необходимой для запоминания строки. Наша программа использует несколько таких строковых констант, чаще всего в качестве аргументов функций printf( ) и puts( ). Заметим также, что мы можем определять строковые константы при помощи директивы #define. Если вы хотите включить в строку символ двойной кавычки, ему должен 252 предшествовать символ обратной дробной черты: рrintf("\"Бегн, Спот, беги!\" - сказал Дик.\n"); В результате работы этого оператора будет напечатана строка: "Беги, Cпот, беги! - "сказал Дик. Cтроковые константы размещаются в статической памяти. Вся фраза в кавычках является указателем на место в памяти, где записана строка. Это аналогично использованию имени массива, служащего указателем на расположение массива. Если это действительно так, то как выглядит оператор, который выводит строку? /* строки в качестве указателей */ main( ) { printf("%s, %u, %c \n", "We", "love", *"figs"); Итак, формат %s выводит строку We. Формат %u выводит целое без знака. Если слово "love" является указателем, то выдается его значение, являющееся адресом первого символа строки. Наконец, *"figs" должно выдать значение, на которое ссылается адрес, т. е. первый символ строки "figs". Произойдет ли это на самом деле? Да, мы получим следующий текст: We, 34, f Ну, вот! Давайте теперь вернемся к строкам, находящимся в символьных массивах. Массивы символьных строк и их инициализация Далее Содержание При определении массива символьных строк необходимо сообщить компилятору требуемый размер памяти. Один из способов сделать это - инициализировать массив при помощи строковой константы. Так как автоматические массивы нельзя инициализировать, необходимо для этого использовать статические или внешние массивы. Например, оператор char m1[ ] = "Только ограничьтесь одной строкой."; инициализировал внешний (по умолчанию) массив m1 для указанной строки. Этот вид инициализации является краткой формой стандартной инициализации массива char m1[ ] = {'Т', 'о', 'л', 'ь', 'к', 'о', ' ', 'о', 'г', 'р', 'а', 'н', 'и', 'ч', 'ь', 'т', 'e', 'с', 'ь', ' ', 'о', 'д', 'н', 'о', 'й', ' ', 'с', 'т', 'р' 'о', 'к', 'о', 'й', ' .', '\0'}; (Обратите внимание на замыкающий нуль-символ. Без него мы имеем массив символов, а не строку.) Для той и другой формы (а мы рекомендуем первую) компилятор подсчитывает символы и таким образом получает размер массива. Как и для других массивов, имя m1 является указателем на первый элемент массива: m1 == &m1[0], *m1 == 'Т', и *(m1 + l) == m1[1] == 'о', Действительно, мы можем использовать указатель для создания строки. Например: char *m3 = " \n Достаточно обо мне - как вас зовут?"; 253 Это почти то же самое, что и static char m3[ ] = "\n Достаточно обо мне - как вас зовут?" ; Оба описания говорят об одном: m3 является указателем строки со словами " Как вас зовут?" . В том и другом случае сама строка определяет размер памяти, необходимой для ее размещения. Однако вид их не идентичен. Массив или указатель Далее Содержание В чем же тогда разница между этими двумя описаниями? Описание с массивом вызывает создание в статической памяти массива из 38 элементов (по одному на каждый символ плюс один на завершающий символ ' |