Справочник от Автор24
Поделись лекцией за скидку на Автор24

Разработка алгоритмов и основами программирования на языке Си.

  • 👀 505 просмотров
  • 📌 439 загрузок
Выбери формат для чтения
Загружаем конспект в формате doc
Это займет всего пару минут! А пока ты можешь прочитать работу в формате Word 👇
Конспект лекции по дисциплине «Разработка алгоритмов и основами программирования на языке Си.» doc
1. Цель и содержание курса Цель курса: овладение знаниями и навыками разработки алгоритмов и основами программирования на языке Си. Любая программа, независимо от алгоритмического языка, на ко­тором она написана, состоит из описания данных и описания дей­ст­вий, выполняемых с этими данными. "Алгоритмы+структуры дан­ных = программы" - это название книги классика современного програм­мирования Н. Вирта является основным принципом разра­ботки про­грамм. Существующие в настоящее время универсальные алгоритми­ческие языки (т.е. языки, содержащие все основные средства совре­менного программирования), такие как Паскаль, Си, Фортран, Бейсик, практически не отличаются по своим возможностям. Различия в пра­вилах построения фраз языка не являются принципиальными, освое­ние этих правил - дело практики, а приверженность программиста к ка­кому-то алгоритмическому языку обычно является следствием мно­голетней привычки. Существенные отличия, имеются, пожалуй, в спо­собах организации многомодульных и многофайловых программ. Вы­бор алгоритмического языка Си для данного пособия объясняется тем, что в настоящее время он является признанным языком систем­ного программирования, т.е. языком для специалистов в области раз­работки программного обеспечения. Чтобы начать писать программы на каком-либо алгоритмиче­ском языке, надо знать: 1. Какова структура простейшей (не использующей нестандартные подпрограммы) программы, в каком порядке в ней разме­щаются ин­струкции языка. 2. Какие типы и структуры данных приняты в языке и как они опреде­ляются (описываются), какие операции над данными различных ти­пов допустимы в языке, как строятся с их помощью выражения и как они вычисляются. Заметим, что на начальном этапе овладения язы­ком необязательно глубоко изучать все допустимые в нем типы и структуры, достаточно знать те, которые необходимы для реше­ния интересующего вас класса задач. 3. Как записывать операторы при­сваивания, с помощью которого осуществляются все преобразова­ния внутренних данных, операторы ввода (передачи данных с внеш­них устройств в память компьютера) и вывода (передачи дан­ных из памяти компьютера на внешние устройства). 4. Как записать алгоритм (порядок, организацию обработки данных). Современные алгоритмические языки, как правило, имеют избы­точный набор управляющих операторов для кодирования алгорит­мов. На первом этапе изучения языка можно познакомиться с тремя-пятью из них, достаточными для программирования основных алго­ритмических структур, с помощью которых можно записать любой алгоритм. Перечисленных сведений достаточно для написания простых программ. Изложению этих сведений в минимальном объеме посвя­щена первая лекция. Дальнейшее изучение программирова­ния связано с подпрограммами (в языке Си формально существуют только функции), организацией многофайловых программ; этот мате­риал из­лагается во второй лекции. Обработке текстовой информации и структурному типу посвящена тре­тья лекция, работе с файлами в Си — четвертая лекция. Следует заметить, что овладение конструкциями алгоритми­ческого языка еще не означает умения программировать. Главную роль в создании программных средств играет умение анализировать задачу и разрабатывать принципиальную схему алгоритма без при­вязки к алгоритмическому языку. Однако обучение начальным навы­кам алгоритмизации не укладывается в формальные рамки, и напи­сать пособие по этой теме также трудно, как самоучитель по чтению и письму. Чтобы помочь начинающим программистам, в пособии, на­ряду с программами на Си, используются блок-схемы, которые, по­жа­луй, являются наиболее общим и наглядным способом описания алго­ритмов. Для читателей, впервые столкнувшихся с программированием, поясним, что программа, написанная на алгоритмическом языке, должна быть переведена в коды компьютера, т. е. на внутренний язык компьютера (машинный язык). Процесс такого перевода назы­вается трансляцией, а специальная программа-переводчик - транс­лято­ром. Существует два способа трансляции: компиляция и интерпре­та­ция. При компиляции программа сначала полностью переводится в коды, а затем выполняется без участия компилятора, т.е. процесс трансляции и выполнения программы четко разделены во времени. При интерпретации последовательно чередуются перевод группы инструкций языка в коды и их выполнение. Алгоритмический язык Си (также как Паскаль и Фортран) требует именно компиляции. Откомпилированная программа (так называемый объектный модуль) далее подвергается редактированию связей1 (процесс под­ключения к программе подпрограмм) и загрузке в оперативную па­мять. В результате такой обработки получается готовая к выполне­нию программа, которая называется исполняемой программой или загру­зочным модулем. В настоящее время трансляторы обычно входят в состав сис­тем программирования, включающих в себя также редактор для на­бора и исправления текстов программ, редактор связей, загрузчик, средства для работы с файлами, программу-отладчик и среду, обес­печиваю­щую удобную работу с этими программами. Мы будем ори­ентиро­ваться на среду Microsoft Visual Studio 2008. Заметим также, что, следуя целям начального обу­чения программированию, в основном будем ориентироваться на подмно­жество языка Си++ , соответствующее стандарту языка Си. Часть. Основы программирования на Си 1.1. Структура простейшей Си-программы Простейшая программа на Си состоит из одного файла и имеет следующую структуру (см. рис.1). Директивы препроцессора (в простейшем случае #include /*ввод/вывод*/ #include /*консольный ввод-вывод*/) void main() { описания операторы } #include #include void main() {int a,b,c;/*описание целых переменных*/ printf("введите a и b\n"); /*приглашение к вводу a и b*/ scanf("%d%d", &a, &b);/*ввод a,b*/ c=a+b;/*вычисление с - суммы */ printf("c=%d\n", c);/*вывод с*/ _getch();/*задержка консольного экрана*/ } а) б) Рис.1 а) общая схема простейшей Си-программы; б) пример программы вычисления суммы двух чисел. В начале программы идут директивы препроцессора, которые более подробно рассмотрены в п. 2.3. В простейшем случае можно огра­ничиться директивами include, которые необходимы для использова­ния библиотек стан­дартных функций Си: include - для функций ввода/вывода, include - для работы с консольным экраном, include - для стандартных математических функций. Обратите внимание, что ди­рективы препроцессора начинаются со значка #. Далее идет заголовок главной (и в простейшем случае единст­венной) функции: void main(). После заголовка в фигурных скобках записывается инструкции программы. Инструкции могут быть либо описаниями, либо операторами. Операторы - это исполняемые инструкции; при компиляции они пере­водятся в одну или несколько машинных команд. Описания - это не­ис­полняемые инструкции языка; они используются компилятором для распределе­ния памяти под данные и определения характера операций, которые могут выполняться с этими данными. Описания характеризуют область значений данных. Они могут стоять в любом месте программы до использования описываемых имен. Хороший стиль про­граммирования предполагает, чтобы по возможности опи­сания были собраны в начале программы и предшествовали опера­торам. В любое место программы можно включить комментарии - по­яс­няющие фразы, окаймленные, как скобками, символами /* и */. Ком­ментарии компилятором не обрабатываются и служат для по­яснения текста программы. В Си++ также допускается использо­вать как комментарий часть строки программы, от сим­вола // до конца строки. 1.2. Данные в Си и операции над ними 1.2.1. Свойства ячейки памяти. Переменные и константы Компьютерные программы, в том числе и написанные на алгоритмическом языке Си, обрабатывают данные, которые хранятся в оперативной памяти компьютера. Каждое элемен­тарное данное имеет некоторый смысл (например, число или символ) и занимает один или несколько байтов памяти2. Эту область памяти часто называют ячейкой памяти. Для того, чтобы ячейки памяти можно было различать и использовать их в программе, каждой ячейке дается имя. Таким образом, имя ячейки памяти – это по существу ее содержимое (т. е. данное, которое в ней хранится). Кроме того, ячейка имеет адрес (адресом ячейки считается адрес (номер) первого из занимаемых ею байтов). При написании программ считают, что ячейка памяти обладает следующими свойствами: 1. Информация в ячейке памяти хранится сколь угодно долго. Это свойство в известной мере является абстрактным, так как при выключении компьютера, например, информация в оперативной памяти не сохраняется. 2. При записи нового содержимого в ячейку предыдущее содержимое не сохраняется. Запись содержимого в ячейку называется присваиванием. Оператор присваивания описан в п.1.2.5. 3. При считывании содержимое ячейки не изменяется. 4. Если в ячейку на протяжении программы не было ничего записано, то ее содержимое считается неопределенным. Отметим, что это свойство не всегда выполняется в алгоритмических языках (например, статические переменные в C++ при объявлении обнуляются). Данные делятся на переменные и константы. Под константой в программировании понимается значение, не изменяющееся в процессе выполнения программы (можно сказать, что ячейка памяти, в которой хранится константа, защищена от изменения). Переменные – данные, которые могут меняться при выполнении программы. В программе, кроме констант обозначенных или именованных (т. е. имеющих имя), могут использоваться явные константы, заданные своим значением. Например, можно использовать константу с именем pi, предварительно задав ей значение 3.1415, а можно использовать в программе явную константу 3.1415. В качестве имен переменных и констант, также как в качестве других имен, в Си используются идентификаторы. Под идентификатором понимается последовательность букв, символов подчеркивания и цифр, начинающаяся с буквы или символа подчеркивания. При формировании имен (а также служебных слов) прописные и соответствующие строчные буквы считаются различными символами. 1.2.2. Типы данных в Си Кроме разделения данных на переменные и константы, существует классификация данных по типу. Описание переменных прежде всего состоит в объявлении их типа. Тип данных характеризует область их значений и форму представления в памяти компьютера. Каждый тип характеризуется набором выполняемых над данными операций. Традиционно в универсальных языках программирования существуют такие стандартные типы, как целый, вещественный, символьный и логический3. Сразу отметим, что логического типа в Си нет. Выражение (в частном случае, переменная) считается истинным, если оно отлично от нуля, в противном случае оно считается ложным. Существование двух числовых типов (целого и вещественного) связано с двумя возможными формами представления чисел в памяти компьютера. Данные целого типа хранятся в форме представления с фиксированной точкой. Для нее характерны абсолютная точность представления чисел и выполнения операций над ними, а также ограниченный диапазон значений чисел. Целый тип используется для данных, которые в принципе не могут иметь дробной части (количество людей, машин, и т.д., номера и счетчики). Тип вещественный соответствует форме представления чисел с плавающей точкой, для которой характерны приближенное представление числа с заданным количеством значащих цифр (знаков мантиссы) и большим диапазоном порядка числа, что обеспечивает возможность представления как очень больших, так и очень малых по абсолютной величине чисел. В силу приближенного представления данных вещественного типа их некорректно сравнивать на равенство. В современных реализациях универсальных языков программирования обычно существует несколько целых и несколько вещественных типов, каждый их которых характеризуется своим размером отводимой под одно значение памяти и, соответственно, своим диапазоном значений чисел, а для вещественных типов - и своей точностью (числом цифр мантиссы). Данные символьного типа принимают значения на всем множестве допустимых для данного компьютера символов. Для хранения одного символьного значения отводится один байт, кодирование символов осуществляется в соответствии с таблицей кодирования. Обратите внимание, что в консольном приложении используется кодовая таблица, отличная от принятых в современных операционных средах. Вследствии этого, в частности, в консольном приложении неправильно выводятся русские буквы. В Си имеется 4 базовых типа: char - символьный тип; int - целый тип, float - вещественный тип одинарной точности, double - вещественный тип двойной точности. Для задания производных типов используются квалификаторы: short (короткий) - используется с типом int, long (длинный) - используется с типами int и double; signed (со знаком), unsigned (без знака) - применимы к любому целому типу. При отсутствии слова unsigned значение считается знаковым, т. е. по умолчанию принято signed. В силу допустимости произвольного соединения квалификаторов и названий базовых типов один тип может иметь несколько обозначений. Сведения о некоторых базовых (встроенных в язык) числовых типах Си представлены в таблицах 1 и 2. Эти таблицы справеливы для большинства реализаций языков Си и Си++ на 32-разрядных компьютерах. Через запятую в клетках первого столбца перечислены описатели-синонимы. Таблица 1. Некоторые базовые целые типы данных Си Тип данных Размер, байты Диапазон значений unsigned char 1 0...255 signed char 1 -128...127 short, short int, signed short, signed short int 2 -32768...32767 unsigned short, unsigned short int 2 0...65535 int, signed, signed int 4 -2147483648...2147483647 unsigned, unsigned int 4 0...4294967295 long, long int, signed long, signed long int 4 -2147483648...2147483647 unsigned long, unsigned long int 4 0...4294967295 Интересно, что в Си тип char в зависимости от контекста может использоваться как символьный или как целый тип. Кроме того, в разных реализациях языка тип char может считаться как знаковым, так и беззнаковым. Таблица 2. Некоторые базовые вещественные типы данных Си Тип данных Размер, байты Диапазон порядка Число цифр мантиссы float 4 -38...+38 6-7 double 8 -308...+308 15-16 Замечание. Для написания программ первой лекции нам потребуются в основном два типа: float и int. 1.2.3. Правила записи констант в Си Мы рассмотрим простейшие правила записи явных констант. Целая десятичная константа - это последовательность десятичных цифр, начинающаяся не с нуля; например, 126789099. Последовательность цифр, начинающаяся с нуля и не содержащая цифр старше 7, воспринимается как целая восьмеричная константа. Последовательность шестнадцатеричных цифр (0, 1, ..., 9, A, B, C, D, E, F), перед которой записаны символы 0x или 0Х, считается целой шестнадцатеричной константой; например, 0ХFF - шестнадцатеричное представление числа 255. Соответствующие отрицательные значения получаются в результате применения унарной операции минус (-) - см. п.1.2.5. Простейшие правила записи вещественных констант состоят в следующем: разделителем между целой и дробной частью числа является точка; можно использовать показатель десятичного порядка Е (большую или малую латинскую букву). Пример вещественных констант: 100.01, 3.44Е3 (значение 3.44*103), 1.0 Е-2 (значение 0.01). Символьная константа - это соответствующий символ клавиатуры, заключенный в апострофы. Примеры: 'a', '5', '?'. Существует несколько способов задания символьных констант, не имеющих графического представления на клавиатуре или экране дисплея. Подробно они будут рассмотрены в п.3.1.1. Сейчас рассмотрим только способ задания некоторых констант в виде комбинации символов, заключенной в апострофы и начинающейся с символа '\' (обратная косая черта - backslash). Например, '\n' - перевод строки, '\t' - горизонтальная табуляция, '\0' - нулевой символ (его байт содержит все нули), '\a' - сигнал-звонок. Уже при написании простейших программ при записи операторов вывода (а иногда и ввода) нам потребуется понятие строковой константы. Это последовательность символов, заключенная в двойные кавычки, Например: "Введите исходные данные". Строковая константа также называется литералом. 1.2.4. Описание переменных и именованных констант в Си. Пока мы рассматриваем переменные и константы, занимающие одну ячейку памяти. Такие переменные и константы называются простыми. Простые переменные описываются с помощью инструкций вида: тип список_имен_переменных; Слово список при описании фрагментов языка обычно означает, что элементы списка перечисляются через запятую. Инструкции в Си разделяются точкой с запятой (обратите внимание на этот знак в конце описания). При описании переменных возможна их инициализация, т. е. задание начальных значений. В список переменных вместо имени переменной может входить конструкция: имя_переменной=начальное_значение; Пример описания: float a, b=1.5, c; int n=10,i=0, j, k=1; /*для a,c,j начальные значения не заданы*/ Именованные константы описываются с помощью инструкций вида: const тип имя_константы=значение константы; Здесь ключевое слово const показывает, что определяемое данное имеет постоянное значение, т. е. доступно только для чтения. Тип при описании констант может отсутствовать, тогда он определяется по значению константы. Описания могут стоять в любом месте программы до использования переменных или констант. Хороший стиль программирования предполагает, чтобы описания были собраны в начале программы. 1.2.5. Выражения в Си Приведем очевидное определение: выражение - это операнды, соединенные знаками операций. В качестве операндов могут использоваться переменные, константы, результаты обращения к функциям4 и выражения, заключенные в круглые скобки. Способы описания переменных и констант мы рассмотрели в предыдущем параграфе; заметим, что пока мы знакомы только с простыми переменными; конечно, в выражении может участвовать и элемент массива (переменная с индексом – см. п. 1.5), и поле структуры или объединения (см. п.3.2). В таблице 3 приведены знаки операций, определенные стандартом языка Си. В первом столбце таблицы указан ранг (приоритет операций). В первую очередь выполняются операции ранга 1, во вторую - ранга 2, и т. д. Операции одного ранга выполняются последовательно, направление выполнения операций (ассоциативность) - справа налево () или слева направо () - указано в третьем столбце. Если один и тот же знак операции встречается в таблице дважды, то первое его появление (с меньшим рангом) соответствует унарной операции, а второе - бинарной. Таблица 3. Основные перации Си. Ранг Обозначение операции Название операции Ассоциативность 1 ( ) [ ] -> . круглые и квадратные скобки косвенный выбор компонента структурированного объекта прямой выбор компонента структурированного объекта (см. п.3.2)  2 !  + - ++ -- & * (тип) sizeof логическое отрицание поразрядное инвертирование внутреннего двоичного кода унарный плюс и минус инкремент (увеличение на 1) декремент (уменьшение на 1) взятие адреса взятие содержимого (см. п.1.5.3) приведение к типу определение размера в байтах  3 * / % арифметическое умножение и деление получение остатка от деления нацело  4 + - арифметические сложение и вычитание  5 << >> поразрядного сдвига  6 < <= > >= отношения (меньше, меньше или равно и т. д.)  7 == != отношения (равно, неравно)  8 & Поразрядная конъюнкция  9 ^ Поразрядное исключающее "или"  10 | Поразрядная дизъюнкция  11 && конъюнкция (логическое "и")  12 || дизъюнкция (логическое "или")  13 ? условная операция  14 = операция= присваивание составное присваивание  15 , операция "запятая"  Рассмотрим особенности некоторых операций. Заметим, что скобки в языке Си в ряде случаев рассматриваются как бинарные операции. Так, например, например, квадратные скобки при указании индексов элемента массива и круглые скобки при обращении к функции. Унарная операция инкремент (увеличение на единицу или автоувеличение) имеет две формы: префиксную (++имя_переменной) и постфиксную (имя_переменной++). В случае префиксной формы увеличение значения операнда происходит до его использования, в случае постфиксной - после. Для лучшего понимания различия этих форм полезно проанализировать 2 фрагмента программы: int i=1,c; 1-й фрагмент 2-й фрагмент с=2*i++;/*”=” – знак присваивания*/ с=2*++i; /*в результате i равно 2, с равно 2*/ /* i равно 2, с равно 4*/ Аналогично существуют префиксная и постфиксная формы декремента. Заметим, что операции инкремент и декремент могут использоваться не только в выражениях, но и как самостоятельный оператор, увеличивающий или уменьшающий переменную на единицу. Операция sizeof вычисляет размер в байтах для типа операнда. Она имеет две формы: sizeof (выражение) и sizeof (тип). Она использует в том случае, когда нужно построить алгоритм, обрабатывающий выражения различных типов. Операция (тип) выражение осуществляет приведение выражения к типу, указанному в скобках. Заметим, что Си допускает использование в выражении операндов различных типов (в отличии, например, от Паскаля, где строго отслеживается соответствие типов операндов и операций). Однако, для того, чтобы воспользоваться такой свободой, надо хорошо знать правила автоматического преобразования типов и иметь некоторый опыт программирования. Начинающим программистам рекомендуется без крайней необходимости не использовать операнды различных типов в пределах одного выражения и использовать для явного преобразования типов операцию (тип). Если говорить о числовых типах, то в Си строго выдерживается правило: если в выражении все операнды одного типа, то и результат имеет тот же тип. Поэтому при делении двух целых операндов получается целый результат. Например, значением выражения 5/2 будет 2 (а не 2.5). Для получения вещественного результата надо использовать вещественные операнды (5.0/2.0). В Си в качестве знака присваивания используется символ "=". Этот символ часто называется простым присваиванием в отличие от составного присваивания. Операция присваивания имеет вид: имя переменной=выражение; Операция предполагает вычисление значения выражения, стоящего справа от знака присваивания, и запись его в ячейку, соответствующую переменной, имя которой стоит слева от этого знака. Результатом операции присваивания считается новое значение переменной. Чаще всего операция присваивания используется как самостоятельный оператор, обеспечивающий запись в ячейку нового значения. Именно как оператор (и только как оператор) присваивание существует в большинстве алгоритмических языков. В Си, однако, присваивание является не только оператором, но и операцией (см. таблицу 3, операции ранга 14). Рассмотрение его как операции делает допустимой в отличии, например, от Паскаля цепочку: a=b=c=d=0, в результате которой все переменные, начиная с самой правой, получают значение 0 (или, конечно, любое другое значение). Заметим, что в Си значение локальной (т.е. описанной в некоторой функции, в том числе и main) переменной считается неопределенным (фактически в ней хранится некоторое "мусорное" значение), если это значение не задано каким-либо способом (присваиванием, вводом, или инициализацией при описании). В Си существуют также статические переменные (см. п.2.4), значения которых при описании обнуляются (если, только, конечно, они не инициализируются другими значениями). 1.3. Простейший ввод и вывод 1.3.1. Общие положения Под вводом понимается процесс передачи данных с внешних устройств в память компьютера, под выводом - передачи данных из памяти компьютера на внешние устройства. Ввод и вывод являются важнейшими операторами, так как с их помощью осуществляется общение пользователя с программой. Без операторов вывода программа вообще не имеет права на существование: если она ничего не сообщает пользователю, то зачем она нужна? Вводу подлежат исходные данные. Это переменные, начальные значения которых меняются от одного выполнения алгоритма к другому. Выводятся, естественно, результаты программы. Соответствующие данные называются выходными данными. Все остальные данные называются промежуточными. Разделение данных на исходные, выходные и промежуточные называется классификацией данных по функциональному признаку. Определение, какие данные будут исходными, а какие выходными, является первым и важнейшим этапом разработки алгоритма и программы. В Си операторы ввода и вывода реализуются с помощью функций, находящихся в библиотеках языка Си, поставляемых в составе конкретной системы программирования. В примере рис. 1 инструкция #include нужна для обеспечения возможности использования одной из библиотек ввода-вывода, подключения так называемых «заголовочных файлов» - см. лекцию 2. Последовательность значений на входном (или выходном) устройстве в Си принято называть потоком. В этом разделе пособия мы рассмотрим только операторы ввода с клавиатуры и вывода на экран. Работа с внешней памятью (файлами на магнитных дисках) будет рассмотрена в третьей части пособия. Из всего множества операторов ввода-вывода языка Си рассмотрим операторы форматного ввода-вывода. Начинающим с нуля, возможно, будет трудно воспринимать этот материал. Тогда стоит воспользоваться более простыми5 операторами ввода-вывода, относящимися, однако, не к языку Си, а к Си++. Чтобы использовать их, надо в начале программы поместить директиву препроцессора #include Эти операторы в простейшем случае имеют вид: cin >> имя_переменной; cout << выражение; здесь cin - стандартный поток ввода (обычно ввод с клавиатуры компьютера), cout - стандартный поток вывода (обычно вывод на экран монитора), >> и << - операции "взять из" и "передать в"; легко видеть, что направление стрелок указывает направление передачи данных. Применяя эти операторы, простейшую программу рис. 1 можно переписать следующим образом: #include void main() {int a,b,c;/*описание трех целых переменных*/ cout << "Введите a и b\n"; /*приглашение к вводу a и b*/ /* \n - переход к следующей строке экрана - см. п. 1.2.3 */ cin >> a>>b; /*ввод a,b*/ c=a+b;/*вычисление с - суммы a,b*/ cout << "c="< b= s= Здесь конструкция <имя переменной> означает значение этой переменной. Заметим, что следующие значения будут выводиться с новой строки, так как форматная строка заканчивается символом '\n'. Такое схематичное изображение выводимой (или вводимой) информации называется формой вывода (ввода). Если a=-2, b=93, s=3.22, то на экране получим: a=-2 b= 93 s= 3.2 Другие примеры оператора printf() представлены на рис.1. 1.3.3. Функция форматного ввода scanf()6 Оператор вызова этой функции имеет вид: scanf(форматная_строка, список_ввода) Список ввода показывает, что выводить. Он содержит перечисленные через запятую адреса вводимых переменных. Почему адреса, а не имена переменных, станет понятно после знакомства с функциями Си (см. лекцию 2). Записать адрес переменной нетрудно, используя операцию & (см. таблицу 3). В список ввода не могут входить выражения или константы, так как ввод предполагает изменение значения. Форматная строка - это строковая константа, которая, так же как при выводе, показывает, в каком виде значения переменных будут выглядеть на экране. Форматная строка при вводе содержит только спецификации формата, включать в нее какой либо пояснительный текст бессмысленно. Спецификации формата при вводе записываются так же при выводе, но ширина поля и точность обычно упускаются. Обратите внимание, что, встретив оператор ввода с клавиатуры, компьютер приостанавливает (задерживает) выполнение программы, которая как бы ждет, когда пользователь введет значения исходных данных. Вводимые значения могут разделяться пробелами (одним или несколькими) или переводом строки (нажатием клавиши Enter), после последнего введенного значения надо обязательно нажать Enter. Оператор ввода с клавиатуры всегда предваряется выводом фразы, приглашающей к вводу. Иначе пользователь может только догадываться, по какой причине программа находится в состоянии ожидания; такая ситуация является необъяснимым остановом и может интерпретироваться как “зависание” компьютера. Пример функции scanf: int i; float a; printf("Введите i и a\n"); /* вывод приглашения к вводу */ scanf("%d%f", &i, &a);... Форма ввода: Введите i и a В фигурные скобки принято заключать альтернативные фрагменты формы ввода или вывода. В данном примере значения i и a можно располагать на одной строке экрана, разделяя их пробелами, а можно на разных строках, разделяя их нажатием клавиши Enter. Заметим также, что при вводе строк символов с помощью функции scanf() действуют более сложные правила. Так, в буфер устройства ввода считываются все символы до нажатия Enter, а во вводимую строковую переменную передаются символы до первого пробела. Такой принцип работы scanf() имеет свои преимущества, но они слишком трудны при начальном освоении языка Си. Поэтому для ввода и вывода строк лучше пользоваться функциями gets() и puts(). Этот вопрос мы отложим до того момента, когда будем рассматривать способы обработки символьной информации в Си (в следующем семестре). 1.4. Разработка программ на Си 1.4.1. Понятие о качестве программы и основные технологические принципы разработки программ Современный уровень развития вычислительной техники и программного обеспечения позволяет на первое место ставить такие характеристики качества программ, как удобство использования, надежность, ясность структуры и текста программы. Технические характеристики программы, такие как объем занимаемой памяти и быстродействие отошли на второй план. Удобство использования программы, т. е. удобство общения с ней, определяется организацией ввода и вывода. Схема общения с программой часто называется интерфейсом (внешними связями) программы. В современных языках программирования существуют специальные средства для эффективного программирования «дружественного» для пользователя интерфейса, удовлетворяющего некоторым стандартам, например, стандартам операционной системы Windows. В настоящем пособии эти средства не рассматриваются, а для организации простейшего диалога между пользователем и программой используются функции ввода-вывода из стандартной библиотеки c заголовочным файлом . Надежность программы означает отсутствие при ее работе остановов, сообщение о причине которых не выводится, т. е. зацикливаний, зависаний и др. Одним из важнейших критериев качества программ является строгое соответствие работы программы постановке задачи. Этот критерий также называется функциональностью программы. Критерии качества программ сформулированы в международном стандарте ISO 9126, а также в стандарте РФ ГОСТ 28195-89. На получение качественных программ направлены положения структурного программирования, которые в основном сводятся к трем моментам: 1. Использование точно обозначенных управляющих структур алгоритмов, имеющих один вход и один выход. Такие структуры называются базовыми (см. п.1.4.4). 2. Разработка алгоритма методом нисходящего проектирования. Этот метод состоит в разбиении алгоритма на части и установлении между ними связей. При установлении связей очень важно, чтобы каждая часть имела один вход и один выход, так что нисходящее проектирование успешно сочетается с использованием базовых структур алгоритмов. Каждая часть в свою очередь разбивается на части, и процесс повторяется. Можно сказать, что нисходящее проектирование алгоритма состоит в иерархической последовательной разработке алгоритма от сложного к простому. 3. Использование системы обозначений, соответствующих содержанию задачи и облегчающих понимание программы. Грамотное проектирование качественных программ состоит из следующих этапов: 1. Анализ задачи и разработка внешней спецификации программы. Во внешнюю спецификацию входит описание входных и выходных данных программы, форм ввода и вывода (см. п.1.3), а также описание методов решения задачи, способов апробации программы, сведения о разработчике программы. 2. Проектирование алгоритма и структур данных. 3. Написание (кодирование) программы на алгоритмическом языке. 4. Отладка программы. Под отладкой подразумевается выявление и исправление ошибок. Ошибки могут обнаруживаться автоматически системой программирования и операционной системой при компиляции, редактировании связей, загрузке и выполнении программы. Могут существовать ошибки в логике программы, которые автоматически не обнаруживаются. Такие ошибки можно выявить дополнительным визуальным анализом программы или с помощью тестов, т. е. наборов значений исходных данных, по которым известен результат. Испытание программы с помощью тестов называется тестированием. Проектирование тестов часто представляет собой самостоятельную задачу. На этапе 1 разрабатываются функциональные тесты, для получения которых используются идеи, отличные от используемых в алгоритме (но они реализуют ту же функцию). При функциональном тестировании алгоритм рассматривается как черный ящик, его внутренняя структура не учитывается. Структурные тесты опираются на структуру программы; например, кроме прогона алгоритма на компьютере, осуществляется вычисление вручную. В идеале структурных тестов должно быть столько, сколько возможных путей выполнения алгоритма. Грамотный и аккуратный пользователь каждый из рассмотренных этапов документирует. 1.4.2. Алгоритм и способы его записи. Алгоритм – это правило получения решения некоторой задачи, выраженное в виде совокупности конечного числа элементарных действий. Слово "алгоритм" произошло от латинского перевода имени выдающегося ученого-энциклопедиста Мухаммеда Аль Хорезми7 (780-850), которые впервые описал правила (т. е. алгоритмы) десятичной арифметики. Строгое определение алгоритма дается в теории алгоритмов. Здесь лишь стоит отметить, что алгоритм должен обладать следующими свойствами: Универсальность (массовость) - это возможность решения с помощью алгоритма класса задач (не одной задачи). Класс задач определяется областью возможных значений исходных данных. Конечность (результативность) - обязательное получение результата за конечное число действий. Определенность означает, что при многократном выполнении алгоритма с одними и теми же исходными данными мы получим одинаковые результаты; другими словами, определенность - это отсутствие случайности. Известны различные способы записи алгоритма. До сих пор широко применяется словесное описание алгоритма "по шагам". Программа в кодах ЭВМ или на любом алгоритмическом языке - другой пример записи алгоритма. Блок-схемы представляют собой еще один способ записи алгоритма, более точный, чем словесное описание, и менее формальный, чем программа на алгоритмическом языке. Блок-схемы не зависят от алгоритмического языка и компьютера, с помощью которых будет выполняться алгоритм, и очень легко воспринимаются. Разработчик программы перед записью ее на алгоритмическом языке обычно обдумывает ее "по шагам". Такое обдумывание очень удобно систематизировать в виде блок-схемы. Написание программы на алгоритмическом языке по готовому алгоритму называется кодированием. 1.4.3. Изображение алгоритмов в виде блок-схем Блок-схема - это конечное число блоков, соединенных стрелками. Блоки соответствуют определенным действиям, а стрелки указывают направление этих действий. Если стрелка идет вниз, то ее можно не рисовать (оставить линию без стрелки). В соответствии с принципом программного функционирования ЭВМ8 необходимо иметь следующие виды блоков. 1. Вычислительный блок. Необходим для изображения любых вычислений (арифметических, логических и др.). Внутри блока записывается действие. Это либо оператор присваивания, либо обобщенное действие (например, "вычисление среднего"). Изображается прямоугольником (см. рис.2, а). Имеет один вход и один выход 2. Блок ввода или вывода. Изображается как параллелограмм (см. рис.2,б). Внутри записывается пояснение, например, "ввод а,b" или "вывод суммы". Имеет один вход и один выход, также как и вычислительный блок. 3. Условный блок. Изображается ромбом (рис.2,в), внутри которого записывается некоторое условие (соотношение или более сложное логическое условие). Имеет один вход и два выхода. Один из выходов (его обозначают "да", или "истина", или "+") соответствует пути алгоритма, где условие выполняется. Другой выход ("нет", "ложь", "‑") соответствует невыполнению условия. 4. Блок начала алгоритма (рис 2, г). Имеет только выход, входа нет. 5. Блок конца алгоритма (рис. 2, д). Имеет только вход. 1.4.4. Базовые структуры алгоритмов и их кодирование на Си. Современная технология программирования предполагает, что алгоритм должен строиться из базовых структур. Таких структур три: следование, развилка, цикл. 1. Следование Эта структура, изображенная на рис. 3, предполагает последовательное выполнение входящих в нее инструкций, которых может две и больше. Последовательно выполняемые операторы в программе на Си записываются друг за другом и разделяются точкой с запятой. 2. Разветвление (развилка) Разветвление, блок-схема которого приведена на рис. 4, применяется в том случае, когда выполнение алгоритма должно развиваться по двум альтернативным ветвям. Ветви обязательно должны соединяться в одной точке, т. е. дальнейшее выполнение алгоритма должно происходить по одному пути; кроме того, ветви алгоритма не должны пересекаться, т. е. не должны иметь общих блоков. Разветвление предполагает проверку некоторого условия. Если на момент проверки условие истинно, то будет выполнен оператор 1, иначе оператор 2. В языке Си ветвление кодируется с помощью условного оператора: if (условие) оператор 1; else оператор 2; В принципе можно было бы записать оператор в одну строку или расположить его по строкам каким-либо другим способом, но практика показывает, что в приведенном виде фрагмент программы легче читается, и такая запись считается хорошим стилем программирования. Возможна ситуация, когда ветвь “Нет ” не содержит операторов (т. е. либо выполняются операторы ветви “Да”, либо сразу переходим к точке соединения ветвей блок-схемы). В этом случае в условном операторе Си слово else и оператор 2 отсутствуют. Если операторы 1 или 2 состоят из нескольких операторов (являются составными), то входящие в них операторы окаймляются фигурными скобками: if (условие) {оператор 1; оператор 2; … оператор N; } else {оператор 1; оператор 2; … оператор M; } Таким образом, фигурные скобки позволяют объединить несколько операторов в один составной. 3. Цикл Циклические структуры (или циклы) служат для организации повторения некоторых операторов. Две базовые циклические структуры приведены на рис. 5. Цикл, кроме стрелок идущих вниз обязательно содержит стрелки, указывающие вверх, – иначе не будет повторения. Следовательно, блок-схема циклического алгоритма обязательно содержит замкнутый путь (петлю). Цикл состоит из тела цикла, т. е. группы подлежащих повторению операторов, и условного оператора, осуществляющего анализ на продолжение цикла. При отсутствии такого анализа возникает зацикливание (бесконечное повторение тела цикла). Зацикливание может также возникнуть из-за неправильного формулирования условия продолжения цикла. В зависимости от порядка выполнения тела цикла и анализа на продолжение цикла различают два вида базовых циклических структур: цикл с предусловием или цикл-пока (сначала анализ на продолжение цикла, а затем тело цикла) и цикл с постусловием или цикл-до (сначала выполнение тела, а затем анализ). На Си циклы кодируются следующим образом: цикл-пока цикл-до while (условие) тело цикла; Do тело цикла; while (условие) Тело цикла должно представлять собой один оператор – простой или составной. Замечания 1. Каждая из трех рассмотренных базовых структур имеет один вход и один выход. Это очень важно, так как любой прямоугольник на рисунках 3 – 5, может быть базовой структурой. Значит, цикл может включать в свой состав базовые структуры: следование, разветвление, цикл. Это утверждение также относится и к двум другим базовым структурам – следование и разветвление. 2. В теории алгоритмов доказано, что для построения любого алгоритма достаточно иметь три базовых структуры: следование, ветвление, цикл. Это положение называется принципом Дейкстры. Причем безразлично, какую циклическую структуру – до или пока – выбрать в качестве базовой. Практика программирования, однако, сложилась так, что равноправно используются обе эти структуры. Кроме того, в программировании широко используется еще одна базовая структура (избыточная), которая является частным случаем цикла-пока и называется параметрическим циклом (см. рис.6). Этот цикл управляется переменной (так называемым параметром, на блок-схеме для него выбрано имя i), которая меняется от начального значения до конечного с заданным шагом. Для кодирования параметрического цикла в Си используется оператор: for(i=нач_знач;i<=кон_знач; i=i+шаг) тело цикла; Как и для предыдущих операторов, тело цикла – один оператор, простой или составной. Заметим, что возможности оператора for в Си не ограничиваются параметрическим циклом, но их рассмотрение выходит за рамки этого пособия. 1.4.4. Примеры разработки программ Пример 1. Программа решения квадратного уравнения ax2 + bx + c=0 Исходные данные: a, b, c - коэффициенты уравнения, вещественные переменные. Выходные данные: х1, х2 - значения двух корней уравнения, если дискриминант неотрицателен, и значения вещественной и мнимой частей комплексно-сопряженных корней, если дискриминант отрицателен; это также вещественные переменные. Промежуточные данные: d - дискриминант уравнения, вещественная переменная. Блок-схема алгоритма представлена на рис.7. Алгоритм должен разделяться на две ветви в зависимости от знака дискриминанта, поэтому он использует базовую структуру ветвление. Ввод исходных данных,вычисление и анализ d соединены последовательно (используется базовая структура следование). По этой блок-схеме написана программа: #include #include #include void main() {float a, b, c, d, x1, x2; printf("введите коэффициенты a,b,c уравнения\n"); scanf("%f%f%f", &a, &b, &c); d=b*b-4*a*c; if (d>=0) {x1=-b/2/a+sqrt(d); x2=-b/2/a-sqrt(d); printf("уравнение имеeт два действительных корня\n x1=%f x2=%f\n", x1,x2); } else {x1=-b/2/a; x2=sqrt(-d)/2/a; printf("уpавнение имеет комплексно-сопpяженные коpни\n"); printf("действ. часть =%f , мнимая часть =%f\n", x1,x2); } _getch(); } Замечания. 1. Заголовочный файл подключается, чтобы можно было использовать функцию _getch(). Эта функция считывает символ с клавиатуры без его отображения на экране. В наших примерах она нужна, чтобы задержать консольный экран до нажатия произвольной клавиши и посмотреть результаты. 2. Русские буквы неправильно выводятся в консольный экран. Их необходимо заменить латинскими. В наших примерах они используются, чтобы содержание поясняющих фраз было лучше понятно русскоязычному читателю. Пример 2. Составить программу решения следующей задачи. На начало первого из рассматриваемых месяцев вклад клиента в банке был равен a руб. В течение каждого следующего месяца со счета снимается b руб.; в конце каждого месяца на остаток вклада начисляется р%. Вывести величину вклада на начало 1, 2,...n месяца. Исходные данные: a, b, p - вещественные переменные, n - целая переменная. Выходные данные: i - номер месяца, v - величина вклада на начало месяца. Блок-схема алгоритма приведена на рис. 8. #include #include void main() /*вычисление величины вклада в банке*/ {float a,b,v,p; int n,i; printf("Введите a, b, p, n\n"); scanf("%f%f%f%d",&a,&b,&p,&n); if (a=b)) {printf(" %2d %e\n",i,v); v=v-b; v=v*(1+p/100); i=i+1; } } _getch(); } 1.5. Массивы и указатели 1.5.1. Понятие массива. Основные правила работы с массивами в Си До сих пор мы рассматривали только простые переменные, т. е. переменные, занимающие одну ячейку памяти. Значение этой переменной представляет собой единое целое, не разделяется на компоненты. Существуют сложные переменные, состоящие из нескольких компонент (и, соответственно, занимающих несколько ячеек памяти). Примером сложной переменной (иногда говорят о переменной сложной структуры или сложного типа) является массив. Массив - это сложная переменная, состоящая из конечного числа упорядоченных компонент, имеющих одно имя, одинаковый тип и расположенных в последовательных ячейках памяти компьютера. В программировании массивы применяются очень часто, так как многие задачи связаны с обработкой информации, представляющей собой конечное множество однотипных данных. Например, массивы оценок в студенческой ведомости, массивы выплат сотрудникам некоторого предприятия, массивы фамилий, имен и отчеств сотрудников, и т. д. Упорядоченность компонент массива означает, что они пронумерованы. "Достать" компоненту можно указав ее номер (индекс) или номера, потому что у одной компоненты может быть несколько номеров; например, оценка в сводной ведомости успеваемости студентов характеризуется двумя номерами - номером студента и номером предмета. В Си принято индексы указывать в квадратных скобках. Количество индексов у элементов массива называется размерностью массива. Массив размерности 1 называется также одномерным, размерности 2 - двумерным, и т. д. Двумерные массивы также называются матрицами. Количество значений какого-либо индекса называется размером массива по данному индексу, а общее количество элементов массива - размером массива или его длиной. Легко видеть, что размер массива равен произведению его размеров по всем индексам. В Си элементы массивов нумеруются, начиная с нуля. Так, одномерный массив а из двадцати элементов имеет элементы а[0], a[2],...,a[19]. Элементы двумерного массива (матрицы) b с размерами 3 и 5 можно представить в виде таблицы из 3 строк и 5 столбцов. Принято считать, что первый индекс является номером строки, а второй - номером столбца таблицы: b[0][0] b[0][1] ... b[0][4] b[1][0] b[1][1] ... b[1][4] b[2][0] b[2][1] ... b[2][4] При описании массивов необходимо указать размеры всех его индексов. Описание массива имеет вид: тип имя [размер1] [размер2]...[размерN]; В квадратных скобках указывается размерi - размер по i-му индексу, обязательно целая положительная константа, иначе компилятор не сможет распределить память под массив. Например, float a[20]; int b[3][5]; Случаи, когда размер первого индекса (и только первого) можно не указывать, будут рассмотрены ниже. Двумерные массивы располагаются в памяти по строкам, многомерные - так, что чаще меняются правые индексы. В программах на Си в качестве индекса элемента массива можно использовать любое целочисленное выражение при условии, что его значение не выходит за объявленные при описании границы. Элемент массива (его также называют переменной с индексами) можно использовать в выражениях точно так же, как простую переменную такого же типа. Ввод и вывод массивов происходит поэлементно, т. е. для их программирования необходимо организовывать циклы. 1.5.2. Примеры программ с массивами. Пример 1. Дан массив а из 20 элементов. Вычислить сумму положительных и количество неположительных элементов массива. Исходные данные. а – заданный массив Выходные данные. s - сумма положительных элементов массива, k – количество неположительных элементов. Промежуточные переменные. i – счетчик элементов массива. Алгоритм состоит из ввода исходных данных, цикла, в котором накапливаются s и k (цикл управляется переменной i, которая изменяется от 0 до 19) и вывода результатов. Перед циклом накапливаемым переменным присваиваются начальные значения. Основной частью тела цикла является ветвление. Блок-схема алгоритма приведена на рис. 9. Далее приведена Си-программа. #include #include void main() {float a[20],s; int k,i; printf("Введите массив из 20 элементов\n"); /* Далее цикл для поэлементного ввода массива*/ for (i=0; i<20; i++) scanf("%f", &a[i]);/*Далее алгоритм по блок-схеме*/ s=0; k=0; for (i=0; i<20; i++) if (a[i]>0) s=s+a[i]; else k=k+1; printf(" s=%f k=%d",s,k ); } 1.5.3. Инициализация массивов При описании элементы массивов можно инициализировать, т. е. задавать им начальные значения. Эти значения записываются в фигурных скобках через запятую и присваиваются элементам массива последовательно в порядке их нумерации. Значений не может быть больше, чем объявлено элементов массива. Если значений меньше, чем элементов в массиве, то оставшиеся элементы считаются неинициализированными9. Список инициализирующих значений можно использовать для определения размера массива по первому индексу. Поясним это на примерах. Одномерные массивы Пусть число элементов массива задано явно. Например, сhar a[10]={'A', 'B', 'C', 'D'}; в результате a[0] имеет значение 'A', a[1] - 'B', a[2] - 'C', a[3] - 'D', остальные элементы массива не инициализированы. Пусть число элементов массива явно не задано: сhar a[ ]={'A', 'B', 'C', 'D'}; С помощью такой инструкции описан и инициализирован массив из четырех элементов. Двумерные массивы Присваивание перечисленных значений происходит по строкам (в соответствии с расположением массивов в памяти компьютера). Пример: int m[2][3]={0,1,2,5,6,7}; Эта инструкция объявляет и присваивает начальные значения матрице . Можно не указывать число строк, тогда оно будет определено по числу начальных значений, т. е. инструкция int m[ ][3]={0,1,2,5,6,7}; приведет к такому же результату. Значения, соответствующие одной строке, могут быть заключены во внутренние фигурные скобки; это может быть полезно, если стороки инициализируются не полностью. Так, инструкция int m[ ][3]={{0},{1,2}}; объявляет матрицу m размером 2*3 и частично инициализирует ее. , незаполненные элементы не инициализированы: неопределены, если m - локальная переменная, и равны нулю, если глобальная. Таким образом, при объявлении массива количество его элементов должно быть задано или явным указанием в квадратных скобках или количеством значений при инициализации. Исключение из этого правила составляют массивы-аргументы функций (см. п.2.2.1). 1.5.4. Указатели в Си10 Указатель - это специальная переменная, которая содержит адрес другой переменной. Работа с указателями и динамической памятью обычно относится к области системного программирования. В Си указатели активно используются даже в простых инструкциях языка. По-видимому, это является следствием того, что Си разрабатывался как язык системного программирования. В п.1.3.3 уже показано использование указателей в функции ввода scanf().В следующем параграфе мы рассмотрим применение указателей для работы с массивами. Этот параграф предваряет его и посвящен правилам работы с указателями в Си. Основные операции для работы с указателями (см., также таблицу 3): * - взятие содержимого по адресу (*i - содержимое переменной с адресом i) & - взятие адреса (&a - адрес переменной а). При описании указателя задается тип значения, на которое он указывает. Описание имеет вид: тип *имя_указателя; Можно интерпретировать смысл этой конструкции так:: объявляется переменная, содержимое которой имеет данный тип. Пример: int *i, j, *pointj; j -переменная целого типа; i, pointj - указатели на значения целого типа. При описании указателей возможна их инициализация. Пример: int v1, *pointv1=&v1, *p=(int*)200; Здесь указателю pointv1 задается начальное значение, равное адресу переменной v1, а указателю р - значение константы 200, приведенное к типу int*, т. е. адрес двухсотой от начала рассматриваемой области памяти ячейки типа int. Как данные любого типа, указатели могут быть переменными или константами. Указателями-константами, например, являются адреса переменных, имена массивов, явные константы (например, (int*)200), а также константа NULL (нулевой или несуществующий адрес). При работе с указателями-константами надо помнить следующее: *2 нельзя брать содержимое от константы без приведения типа; запись *200 является некорректной в отличие от *(int*)200 *3 нельзя брать адрес константы (например, некорректна запись &200), в Си адрес константы считается недоступным; *4 нельзя определять адрес выражения. Размер памяти, отводимой под указатель, зависит от модели памяти. Для "малых" моделей памяти (tiny, small) адресация осуществляется в пределах одного сегмента памяти, и указатель занимает два байта, для "больших" моделей памяти (large, huge) указатели занимают четыре байта. Кроме операции *, к указателям применимы операции сравнения (<, <=, >, >=, ==, !=), присваивания, арифметические операции сложения, вычитания, инкремента и декремента. Справа от операции присваивания должен стоять указатель того же типа, что и слева (от операции присваивания), или указатель NULL. Сравнивать можно указатели одного типа (или указатель произвольного типа с NULL). Нельзя суммировать (вычитать) указатели, можно только прибавлять к ним (вычитать из них) целую величину. При этом результат операции зависит не только от значения операндов, но и от типа, с которым связан указатель. Если объявление указателя р имеет вид тип *р, то в результате оператора р=р+k, где k - некоторое целое значение, р увеличится на k*sizeof (тип). Пример. int *p; long int pp;... p++; /*p увеличилось на 2*/ pp++; /*pp увеличилось на 4*/ 1.5.5. Связь массивов с указателями в Си Одномерные массивы Имя одномерного массива является указателем-константой, равной адресу начала массива, т. е. адресу элемента с индексом 0 (первого элемента). Рассмотрим объявление некоторого одномерного массива, для определенности int a[10]; тогда обозначение &a[0] эквивалентно a, a[0] эквивалентно *a, &a[i] эквивалентно a+i (i=0,1,...9), a[i] эквивалентно *(a+i). Двумерные массивы Имя двумерного массива является указателем-константой на начало (элемент с индексом 0) массива указателей-констант, i-й элемент этого массива - указатель -константа на начало (элемент с индексом 0) i-й строки двумерного массива. Так, например, с массивом int b[5][8] связан массив указателей-констант11 b[0], b[1],...,b[4]; b[ i ] - указатель на начало i-й строки, т. е. на элемент b[ i ][0], i=0,1,...,4; вышесказанное поясняется схемой: b b[0] b[0][0] b[0][1] . . . b[0][7] b[1] b[1][0] b[1][1] . . . b[1][7] b[2] b[2][0] b[2][1] . . . b[2][7] b[3] b[3][0] b[3][1] . . . b[3][7] b[4] b[4][0] b[4][1] . . . b[4][7] Элемент массива b[i][j] можно также обозначить *(b[i]+j) или *(*(b+i)+j); это все равноправные обозначения. &b[i][j], b[i]+j, *(b+i)+j - также равноправные обозначения адреса элемента массива b[i][j]. Для любого из трех обозначений элемента двумерного массива программа в кодах получается практически одинаковой по производительности, хотя при использовании арифметики указателей вместо квадратных скобок несколько более короткой [3]. Хороший стиль программирования предполагает употребление в пределах одной программы одного (из трех) обозначений.
«Разработка алгоритмов и основами программирования на языке Си.» 👇
Готовые курсовые работы и рефераты
Купить от 250 ₽
Решение задач от ИИ за 2 минуты
Решить задачу
Помощь с рефератом от нейросети
Написать ИИ

Тебе могут подойти лекции

Смотреть все 588 лекций
Все самое важное и интересное в Telegram

Все сервисы Справочника в твоем телефоне! Просто напиши Боту, что ты ищешь и он быстро найдет нужную статью, лекцию или пособие для тебя!

Перейти в Telegram Bot