ПОБИТОВЫЕ ОПЕРАЦИИ. ПЕРЕКЛЮЧЕНИЕ ВВОДА-ВЫВОДА




Министерство образования

Российской Федерации

 

РЯЗАНСКАЯ ГОСУДАРСТВЕННАЯ РАДИОТЕХНИЧЕСКАЯ АКАДЕМИЯ

 

 

ПРОГРАММИРОВАНИЕ НА ЯЗЫКЕ СИ

В СИСТЕМЕ ТУРБО–СИ

 

Методические указания к лабораторным работам № 8–15

 

Гр. 632-6311 выполнить работы 9 и 12

 

 

Рязань 2001 г.


 

Лабораторная работа № 8

 

ОБРАБОТКА ТЕКСТА С ИСПОЛЬЗОВАНИЕМ

ФУНКЦИЙ ВВОДА-ВЫВОДА ОДНОГО СИМВОЛА.

ПОБИТОВЫЕ ОПЕРАЦИИ. ПЕРЕКЛЮЧЕНИЕ ВВОДА-ВЫВОДА

Цель работы

Изучение правил ввода-вывода одного символа getchar (), putchar (), приобретение практических навыков обработки текстов программ, работающих с файлами, в которых используются операции переключения ввода-вывода операционной системы, изучение операций с битами.

 

Методические указания

1. Функция ввода-вывода одного символа

Для ввода одного символа, поступающего с терминала, используется функция getchar. Форма записи этой функции имеет такой вид:

getchar ();

Она не имеет аргументов, получает поступающий символ с терминала и возвращает его значение программе.

Для вывода одного символа на экран дисплея используется функция putchar. Она имеет такой формат записи:

putchar (S);

S - это либо переменная типа char или int, либо константа этих типов.

Описание этих функций содержится в системном файле stdio.h.

Например, фрагмент программы

char ch; ch = getchar (); putchar (ch);

напечатает вводимый символ с клавиатуры на экран дисплея. Данный фрагмент программы, состоящий из двух операторов языка, можно упростить одним оператором putchar(getchar()).

Пример 1. Определить количество вводимых символов. Ввод символов закончить набором символа *.

# include <stdio.h>

# define STOP *

void main ()

{ char ch; int k = 0;

while ((ch = getchar ())! = STOP) { k + +; putchar (ch);} printf("k=%d\n",k);

}

В этой программе цикл while будет выполняться до тех пор, пока не будет введен символ *. Следует отметить, что признак окончания ввода символа выбран не совсем удачно, потому что этот символ может быть неотъемлемой частью текста. Программист может выбрать любой другой признак окончания ввода текста, в том числе и символ "новая строка". В.этом случае нужно только изменить признак STOP на define STOP '\n'.

Однако при обработке текста, состоящего из нескольких строк, необходимо выбирать такой признак окончания ввода, который обычно не используется в тексте. Эта проблема была решена разработчиками вычислительных систем. Имеется специальный признак - конец файла, который обозначается EOF. Описание этого признака содержится в файле stdio.h. Одновременным нажатием клавиш [ctrl + z] вырабатывается признак EOF. Следовательно, с учетом признака EOF программа примера 1 может быть несколько упрощена, для чего нужно убрать директиву define и в операторе while STOP заменить на EOF.

2. Понятие буфера

Буфер – это специальная отводимая область памяти, являющаяся промежуточным хранилищем данных. Ввод данных с помощью функции getchar может быть буферизованным и небуферизованным. При небуферизованном вводе вводимые символы примера 1 будут сразу отражаться на экране оператором putchar. При буферизованном вводе, водимые символы собираются и помещаются в буфер, и программе они передаются либо после нажатия клавиши [Enter], либо когда будет обнаружен признак конца ввода.

Каждый из этих способов имеет свои достоинства и недостатки. Если при буферизованном вводе допущена ошибка при вводе символа, ее можно исправить. Но в диалоговых программах, когда требуется, чтобы каждая команда выполнялась при нажатии какой-либо клавиши, небуферизованный ввод окажется предпочтительным. Следовательно, при проектировании программ целесообразно учитывать эти обстоятельства. В языке Си имеются две функции ввода символов: getchar реализует буферизованный ввод; а функция getch – прямой.

3. Операции переключения ввода-вывода и работа с файлами

Операции переключения - это средства операционной системы MS-DOS, а не самого языка Си. С их помощью простую программу на языке Си можно превратить в инструмент для создания, чтения и копирования файлов. Существует два способа написания программ, работающих с файлами. Первый способ заключается в явном использовании специальных функций, которые открывают и закрывают файлы, организуют чтение и запись данных и выполняют множество других операций. Этот способ работы с файлами будет рассмотрен в лабораторной работе № 16. Второй способ состоит в том, чтобы программу, спроектированную первоначально в предположении, что данные вводятся в нее с клавиатуры и выводятся на экран, использовать таким образом, чтобы ввод и вывод осуществлялся из файла в файл. Для реализации этих функций необходимо прежде всего иметь откомпилированную, выполнимую программу.

а) Переключение ввода (создание файла)

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

1.Временно войти в DOS. Для этого нужно, перейти "в меню FILE и выбрать пункт этого меню OS Shell.

2.Ввести команду на выполнение программы \ DD \ priml > TEXT.,

где \ DD \ – имя каталога. Если priml находится в текущем.каталоге, то имя каталога можно опустить. Символ > служит обозначением операции переключения, в результате чего выходные данные программы priml будутнаправляться не на экран дисплея, а в файл ТЕХТ. В файл TEXT будут помещены все вводимые символы, а также число символов в тексте.

Для выхода из DOS и входа в турбо-среду следует набрать команду EXIT.

б) Переключение вывода (ввод данных в программу из файла). Для ввода данных в программу из файла следует выполнять те же действия, что и в пункте "а", за исключением того, что операцию переключения > надо заменить на операцию <. Естественно, для выполнения этой операции должен быть создан текстовый файл. Текстовый файл может быть создан либо с помощью пункта "а", либо с помощью любого текстового редактора, в том числе и редактора турбо-среды языка Си. Например, команда \ DD \ priml< TEXT приводит к тому, что содержимое файла TEXT будет исходными данными для программы priml. Результаты этой программы отражаются на экране дисплея. Используя данную операцию переключения, с помощью простой программы можно распечатать любой текстовый файл.

в) Комбинированное переключение (ввод-вывод). Комбинированное переключение позволяет направлять в программу ^ данные из одного файла, а результат записывать в другой файл, а также позволяет создать копии файлов. Например, команда \ DD \ prim1< ТНХТ > TEXT1 – указывает на то, что входные данные для prim1 содержатся в файле TEXT, а результаты выполнения этой программы запишутся в файл ТЕХТ1. Нельзя использовать в одной команде один и тот же файл и для ввода и для вывода одновременно.

4. Функции классификации символов

В языке имеются специальные функции для работы с символами. Если классифицируемый символ принадлежит к определенному классу, то функция возвращает ненулевое истинное значение и нуль (ложь) в противном случае. Эти функции описаны в файле math.h и приведены в табл. 1.

Имя функции Назначение функции
isalpha (ch) Определяет, является ли проверяемый символ ch буквой
isalnum (ch) Буквой или цифрой
iscntrl (ch) Управляющим символом или символом забоя, т.е. код меньше 32 или равен 127
isdigit (ch) Является цифрой
isgrapg (ch) Печатным, кроме пробела, 33<код<=126
islower (ch) Строчной латинской буквой
isprint (ch) Печатным; код = (от 32 ¸126)
isupper (ch) Прописной латинской буквой

Заметим, что перечисленные функции элементарно реализуются программистом. Например, функция isalpha (ch) может быть реализована следующим фрагментом: char ch; ((ch >= 'А' && ch <= 'Z') | | (ch >= 'a' && ch <='z’))? 1:0.

5. Функции преобразования символов

Эти функции описаны в файле с именем ctype.h.

а) функция toupper (ch) - преобразует символ в верхний регистр;

б) функция tolower (ch) - преобразует символ в нижний регистр. Заметим, что коды строчных букв от прописных отличаются на величину 32 в десятичном представлении или на 40 и 20 соответственно в восьмеричном и шестнадцатеричном представлении. Таким образом, чтобы преобразовать заглавную букву в строчную, достаточно прибавить к коду символа величину 32, например ch + == 32.

6. Побитовые операции

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

а) операция поразрядного отрицания ~. Эта унарная операция изменяет 1 на 0 и наоборот. Например, ~5 == 2, потому что ~(101)2 = = (010)2;

б) поразрядное умножение &. Эта операция сравнивает последовательно разряд за разрядом два операнда. Для каждого разряда результат равен 1, если оба операнда равны 1, в противном случае 0. Так 5&2 = = 0, потому что ( 101)2&(010)2== (000)2;

в) поразрядовое сложение |. Эта операция сравнивает последовательно разряд за разрядом два операнда. Для каждого разряда результат равен 1, если любой из соответствующих разрядов операндов равен 1, в противном случае 0. Так 5|2= = 7, потому что (101)2 | (010)2-= (111)2;

г) исключающее или ^ Эта операция сравнивает последовательно разряд за разрядом два операнда. Результат равен 1, если оба операнда различны, в противном случае 0. Так 5 ^ 2 = = 7, потому что (101)2^010)2 ==(111)2.

Описанные операции часто используются для установки некоторых разрядов в нуль или единицу, причем другие разряды остаются неизменными. Так, например, z = z&2 установит все разряды z в 0 кроме первого. Первый – либо в нуль, либо в единицу в зависимости от значения z.

Аналогично оператор z = z|2 установит первый разряд в единицу и оставит все остальные разряды неизменными.

7. Операции сдвига

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

а) Сдвиг влево «. Операция имеет вид:

<операнд> «число разрядов>.

Эта операция сдвигает разряды левого операнда влево на число позиций, указанное правым операндом.

Освобождающиеся позиции заполняются нулями, а разряды, сдвигаемые за левый предел левого операнда, теряются. Так, (101)2«,2==,,= 10100, где каждый разряд сдвигается на две позиции.

б) Сдвиг вправо ». Операция имеет вид:

<операнд> <число позиций сдвига»

Эта операция сдвигает разряды левого операнда вправо на число позиций, указанное правым операндом. Разряды, сдвигаемые за правый предел левого операнда, теряются, а позиции, освобождающиеся слева, заполняются нулями. Так, 5 » 2 == = 1, потому что (101)2 » 2= = (001)2.

Приведем пример на использование побитовых операций.

Пример 2. Вывести на экран коды символов в двоичном представлении.

# include <stdio.h>

/* размер байта в битах*/

# define BYTE 8

void main ()

{ char ch; int i;

printf ("введите символы, ввод закончить <ctrl-z> \n");

while ((ch = getchar ())! = EOF)

putchar ('\n'); /* переход на новую строку */ printf("код символа %с =", ch);

/* цикл для печати одного символа */..,.,, for (i= BYTE; i > 0;i--) printf("%d",ch»i&l);

/* сдвигаются биты на i разрядов */

/* нулевой бит устанавливается либо В1, либо в 0 */

/* остальные биты равны нулю */ }

 

8.Пример выполнения задания

Определить в тексте число символов, число строк и число слов. Слова отделены друг от друга либо пробелами, либо символами новая строка и табуляция. Входные данные для программы находятся в текстовом файле ТЕХТ1. Результаты выполнения программы поместить в файл ТЕХТ2.

Для решения этой задачи, нижеследующую программу оттранслируем в выполняемую программу и запишем в файл под именем variant.

# include <stdio.h>

# define YES 1 /* признак, если символ часть слова */

# define NO 0 /* признак конца слова */

void main ()

{ char ch; /* вводимый символ */

int nc = 0; /* число символов */

int nl = 0; /* число строк */

int nw = 0; /* число слов */

int word = NO /* переменная, которая принимает значение */

/* либо конец, либо часть слова */

while ((ch = qetchar ())! = EOF)

{nc++; /* подсчет символов */

if (ch == ‘\n’) nl++;

if (ch ½ ==’ ‘&& ch! == ‘\n’ && ch! ==’ \t’&& word == NO)

{

word = YES /* начало нового слова */

nw++; /* подсчет слов */

}

if ((ch == ‘ ‘ êêch == ‘ \n ‘êê ch == ‘\t’)&& word == YES)

word = NO; /* достигнут конец слова */;

}

printf (“ число символов = % d \ n”, nc);

printf (“ число строк = % d \ n”, n1);

printf (“ число слов = % d \ n”, nw);

}

Команда переключения variant < TEXT1 > TEXT2 приведет к тому, что в файле TEXT2 будет записано число символов, строк и слов в тексте.

 

Контрольные вопросы

 

1. Каков формат функции ввода-вывода одного символа?

2. Какой целесообразно выбирать признак окончания ввода?

3. Для какой цели используется буфер при вводе?

4. Для какой цели используются операции переключения ввода-вывода?

5. Каким образом реализовать функции классификации символов?

6. Перечислите побитовые операции. Для какой цели они используются?

7. Какие алгебраические операции реализуют операции сдвига?

8. Укажите возможные способы создания текстового файла.

 

Задания для выполнения

 

Для каждого варианта задания выполнить следующее:

а) отлаженную выполняемую программу записать в файл;

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

в) используя операции переключения, результаты работы программы записать в текстовый файл.

Варианты задания

 

1.В строке ввода символов определить длину каждого слова. Слова друг от друга отделены пробелами.

2. Определить коды латинских букв в десятичной форме. Вывод кодов осуществлять по 10 чисел в каждой строке.

3.Определить максимальную длину слова в предложении. Слова отделены друг от друга пробелами.

4.Определить длину каждого слова в предложении. Слова отделены друг от друга пробелами или запятой.

5.Определить максимальную длину слова в предложении. Слова отделены друг от друга пробелами или запятой.

6.Определить коды латинских букв в восьмеричной системе счисления. Вывод кодов осуществлять по 5 чисел в каждой строке.

7.В предложении выделить все символы - цифры.

8.Определить в предложении количество символов 'а' и пробелов по отдельности.

9.В предложении все символы-пробелы заменить на звездочки.

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

11. Кодировка текста. Если в тексте встречается символ цифра, то его код увеличить на 10, иначе код символа увеличить на 2.

12. Раскодировать текст, полученный в варианте 11,

13. Разработать программу, которая печатает символы ch n-раз, где n зависит от кода символа и определяется n = ch%25. Печатаемый текст для каждого символа должен располагаться в центре строки.

14. Выделить все слова в тексте. Слова отделены друг от друга либо пробелом, либо запятой. Каждое слово выводить на новую строку.

15. Определить, есть ли в тексте последовательность символов "она".

16. Определить сколько раз в тексте встречается последовательность символов "мама".

17. Из текста выделить первые символы слов. Слова друг от друга отделены пробелами.

18. Из исходного текста исключить разделители (пробелы, запятые, точки).

19. Дана последовательность цифр-символов (не более 4-х) преобразовать их в целое число и определить, четное оно или нечетное.

20. Дан текст. Из символов-цифр образовать число и определить делится, ли оно на три без остатка.

21. В заданном тексте выделить все цифры-символы и сложить их алгебраически.

22. В тексте выделить первые символы каждого слова. Слова отделены друг от друга пробелами.

23. Напечатать целые числа в двоичном коде.

24. В целых числах определить пятые разряды.

 

Лабораторная работа № 9

 

КЛАССЫПАМЯТИ. МАССИВЫ,

ОПЕРАЦИЯ ИНДЕКСАЦИИ

Цель работы

 

Знакомство с классами памяти объектов и области их действия, изучение правил работы с массивами, операция индексации.

 

Методические указания

1. Классы памяти

 

Каждая переменная, описанная в программе на языке Си, принадлежит к какому-либо классу памяти. Имеется четыре класса памяти и есть четыре ключевых слова, используемые для описания классов памяти: extern (для внешнего), auto (для автоматического), static (для статического) и register (для регистрового). Класс памяти переменной определяет время ее существования в оперативной памяти и область действия. Форма описания переменной с указанием класса памяти имеет такой вид:

[<класс>] <тип> <имя переменной>;

1.1. Автоматические переменные

Если при описании переменной отсутствует класс памяти, то по умолчанию она является автоматической. Область действия такой переменной – функция, в которой она описана. Память для таких переменных отводится динамически во время выполнения при входе в функцию (блок). При завершении работы функции автоматическая переменная исчезает, область памяти может использоваться для других целей.

1.2. Внешние переменные

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

 

1.3. Статические переменные

 

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

Внешние статические переменные определяются вне функции, также как и внешние (extern), однако в определении должно быть ключевое слово static. Разница между внешней переменной и внешней статической переменной заключается в области их действия. Обычная внешняя переменная может использоваться функциям в любом файле, в то время как внешняя статическая переменная может использоваться только в том файле, в котором она описана.

1.4. Регистровые переменные

Регистровые (register) переменные хранятся в регистрах центрального процессора, где доступ к ним и работа с ними выполняются гораздо быстрее, чем в памяти. В этом случае регистровые переменные рассматриваются как автоматические. Использование регистровых переменных является скорее просьбой, чем обычным делом. Компилятор сравнивает эту просьбу с количеством доступных регистров. Если свободных регистров нет, то в этом случае переменная становится простой автоматической переменной.

 

2. МАССИВЫ

 

Массив – это упорядоченное множество однотипных элементов, составляющих объект, и обозначенное именем. Массивы используются для представления в программе векторов, матриц, символьных строк. Основными характеристиками массива являются: имя, размерность, размер и тип его элементов. В данной лабораторной работе рассматриваются только численные массивы.

2.1. Одномерные массивы

Если в программе используется массив, то он должен быть описан. Форма описания массива имеет вид:

[<класс памяти] <тип> <имя> [<размер>];

Например, следующий фрагмент программы

static int Z [10];

float x [15], y [20]

Описывает статический массив целых чисел с именем Z из 10 элементов и два массива вещественных чисел с именами x и y размерами 15 и 20 соответственно. Элементы массива в памяти ЭВМ располагаются последовательно друг за другом и проиндексированы целыми положительными числами, называемыми индексами массива.

Индексирование массива в языке Си начинается от нуля, поэтому элементы массива Z из рассмотренного примера будут располагаться в память ЭВМ следующим образом: Z [0], Z [1],… Z [9]. Обращения к отдельной компоненте массива осуществляется указанием имени массива, за которым в квадратных скобках следует выражение, определяющее значение индекса. Например, запись Z [1] = 20; приводит к присвоению второму элементу массива Z значение 20.

 

2.2. Инициализация массивов

 

Инициализация – это процесс присваивания начальных значений элемента массива в процессе его описания. Для инициализации массива в языке Си используется список инициализации, представляющий собой список констант, заключенных в фигурные скобки.

Например int x [4] = {1, 2, 3, 4};

static float Z [3] = {1.2, 3, 5.4};

В этом случае первому элементу массива соответствует первая константа из списка инициализации, второму – вторая и т.д. Если количества элементов в списке инициализации будет меньше размера массива, недостающие элементы примут значение нуль. В случае излишне большего списка компилятор выдает ошибку.

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

Пример:

static float S [] = {1.5, 3.5, 4};

Компилятор определит размер массива S равным трем. В программе размер массива можно определить выражением sizeof<имя массива> / s izeof(<тип>). Если внешние и статические массивы не инициализировать, то элементы массива принимают нулевое значение, автоматические и регистровые принимают произвольные значения. Ввод–вывод элементов массива выполняется поэлементно.

Рассмотрим примеры работы с одномерными массивами.

Пример 1.

В массиве из 50 элементов отыскать максимальный элемент.

/* программа */

# include <stdio:h>

# define RAS 50

void main ()

{float max, a[RAS];

int i;

puts («вводите элементы массива \n»;

/* Цикл ввода элементов */

for (i = 0; i < RAS; scanf (“%f”, & a[i]), i ++);

/* Цикл определения max */

for (i = 1, max = a [0]; i <RAS; i ++)

if (a[i] > max) max = a[i];

printf (“max = % 5.2f \n”, max);

}

Пример 2. Сортировка элементов массива:

# include <stdio.h>

void main ()

{static int a [ ] = {50, 20, 70,… 2};

int i, j, k, n, t;

/* определение размера массива */

n = sizeof A / sizeof (int);

/* сортировка методом выбора */

for (i = 0; i < n – 1; i ++)

{for (J = i + 1, k = i; j <n; j++)

if(a[j]<a[k] k = j;

t = a[k]; a[k] = a[i]; a[i] = k;

}

/* вывод массива по 5 элементов в строке */

for (i = 0); i < n; i++)

printf (“a[%d] = % d % C ”, i, a[i], (i = = 5)? ‘\n’:’ ‘);

}

 

2.3. Многомерные массивы

 

Если в описании массива размерность равна N,то массив называется многомерным. В языке не накладывается ограничений на число измерений массива, однако на практике, как правило, используются двухмерные массивы. Приведем примеры описания многомерных массивов

static int x [5][4]; /*матрица размером 5´4*/

float Z [2][3][5]; /*трехмерный массив */

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

x [ a ][0], x [0][1],…, x [0][3], x [1][0], x [1][1],…, x [4][3].

Для обращения к элементам многомерного массива используется та же конструкция, как и к одномерным массивам, за исключением того, что в каждой квадратной скобке указывается значение индекса по каждому измерению. Например, x [2][1] = 5 присваивает элементу, расположенному на пересечении третьей строки и второго столбца, значение 5.

Если массив – матрица, то при изменении второго индекса на единицу перемещаемся вдоль строки, а при изменении первого индекса на единицу – вдоль столбца. Для инициализации двухмерных массивов список инициализации может содержать разделительные фигурные скобки, например,

static int Z [3][2] = {{1, 2}, {4, 5}, {8, 9}}.

Данные, находящиеся в первых внутренних скобках присваиваются первой строке массива, данные во вторых внутренних скобках – второй и т. д. Если в какой–то последовательности число элементов будет меньше длины строки, то недостающим элементам присваивается ноль, если больше, то выдается сообщение об ошибке. Внутренние скобки можно опускать, в этом случае список инициализации рассматривается как линейная последовательность элементов в соответствии с их расположением в памяти, т. е. первые n элементов присваиваются первой строке массива, вторые n элементов – второй и т. д. Например

static int Z [3][2] = {1, 2, 3, 4}; /* Недостающие элементы инициализируются по умолчанию значением ноль */

 

3. Пример выполнения задания

 

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

Организуем данные по количеству осадков в виде матрицы Z [10][12] так, что каждая строка матрицы будет содержать осадки по месяцам соответствующего года. Ниже приведен текст программы с пояснениями.

# include <stdio.h>

# define GOD 10

# define MES 12

void main ()

{

int i, J;

float Summes, Sumqod; /* количество осадков за год и месяц*/

float Z[GOD][MES];

/* ввод элементов матрицы Z */

for (i = 0; i <GOD; i++)

for (J = 0; J<MES; J++)

{printf («вводите элемент Z[%d, %d] =? \n”, i, J);

scanf (“% f”, & Z[i][J];

}

/* печать заголовка количества осадков по годам */

printf (“год количество осадков \n”);

/* циклы определения количества осадков за каждый год */

for (i = 0; i <GOD; i++)

for (J = 0, Sumqod = 0; J < MES; J++)

Sumqod+ = Z[i][J];

printf (“%5 d % 10.1f \n”, 1990+i, Sumqod);

}

/* печать заголовка количества осадков по месяцам */

printf (“ янв. фев. мар. апр. май. июн. июл. авг.”);

printf (“сен. окт. ноя. дек. \n”);

/* Циклы определения среднего количества осадков по месяцам*/

for (i = 0; i < MES; i++);

{

for (J = 0, Summes = 0; J < GOD; J++)

SUMMES+ = Z[J][i];

printf (“%4.1f”, SUMMES/GOD);

}

printf (“\n”);

}

Контрольные вопросы

 

1. Какова область действия автоматических переменных?

2. Каким образом описываются внешние переменные и какова область их действия?

3. Чем отличаются внешние переменные от статических?

4. Какова особенность использования регистровых переменных?

5. Как располагаются в оперативной памяти элементы массива?

6. Перечислить все возможные способы инициализации массивов.

7. Каким образом осуществляется ввод–вывод элементов массива?

 

Варианты заданий

 

Для варианта заданий, имеющих 2 номера, в скобках содержится условие для второго номера.

1–2. Дана матрица C размером 3´4, составить программу для подсчета количества четных (нечетных) элементов в каждой строке матрицы с их запоминанием.

3–4. Дана матрица B размером 5´4. Составить программу формирования вектора, элементы которого равны сумме элементов строк (столбцов) матрицы B.

5–6. Дана матрица A размером 7´7. Составить программу нахождения суммы элементов, лежащих выше (ниже) главной диагонали.

7–8. Дана матрица A размером 7´7. Составить программу нахождения максимального элемента среди элементов, лежащих выше (ниже) побочной диагонали.

9–10. Дана матрица C размером 5´6. Составить программу, осуществляющую перестановку элементов в каждой строке (столбце) так, чтобы первый элемент поменялся с последним, второй – с предпоследним и т. д.

11–12. Расстояние между K –й и L –й строками матрицы определяется как . Указать номер строки, максимально удаленной от первой (последней) строки заданной матрицы.

13–14. Среди столбцов заданной матрицы C найти столбец с минимальным (максимальным) произведением элементов.

15. Составить программу вычисления матрицы C = A × B, где A – матрица размером 4´5; B – матрица размером 5´3.

16. Составить программу вычисления вектора C = A × B, где A – матрица размером 5´6; B – вектор из шести элементов.

17. Для заданной матрицы B размером 5´5 найти такие K, для которых K – строка матрицы совпадает с K –м столбцом.

18. Вычислить значения полинома , используя схему Горнера; представленную формулой

.

Значение N принять равным 10, x и элементы массива a выбрать произвольно.

19–20. Составить программу записи элементов прямоугольной матрицы A в одномерный массив в порядке следования столбцов (строк). Найти наименьший (наибольший) элемент наилучшего массива.

21–22. Составить программу формирования вектора из количества ненулевых элементов каждой строки (столбца) произвольного двухмерного массива.

23–24. Дана матрица A размером 5´5. Составить программу для вычисления

25–26. Дана матрица A размером 5´5. Переставить местами первую (последнюю) строки со строкой максимального элемента главной диагонали.

27–28. Дана матрица B размером 6´6. Составить программу для перестановки первого (последнего) столбца со столбцом максимального элемента побочной диагонали.

Лабораторная работа № 10

 

МАССИВЫ. СВЯЗЬ МАССИВОВ И УКАЗАТЕЛЕЙ

Цель работы

Изучение правил работы с массивами, с использованием указателей.

 

Методические указания

 

1. Определение указателя

 

Значением переменной типа указатель служит адрес некоторой величины, т. е. указатель – это адрес памяти. Общая форма описания указателя имеет следующий вид: <тип> *<имя указателя>. Например;

int* p; / * указатель на переменные типа int */

char* Z; / *указатель на символьную переменную*/

float* x; /* указатель на переменные с плавающей точкой */

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

Наиболее важные операции с указателями – это операция задания адреса указателю и операция обращения по адресу * (косвенная ссылка).

Например;

int x = 2, z = 10;

int *p;

p = & x; /*& x – это адрес размещения переменной x */.

p; / * это величина, помещенная по адресу & x, т. е. * p = 2 */.

p = & Z; /* принимает значение адреса размещения Z */.

* p / * величина, помещенная по адресу & Z, т. е. * p = 10 */.

Таким образом указатель характеризуется тремя свойствами:

1. Под указатель отводится память, размер которой можно определить с помощью функции sizeof <указатель>, адрес размещения указателя можно определить через операцию &.

2. Значением указателя является адрес памяти, т. е. адрес той переменной, на которую ссылается указатель.

3.Указатель определяет значения по адресу, которая имеет переменная указатель (операция * – косвенная ссылка).

 

1.1. Инициализация указателей

 

При работе с указателями в программе могут возникнуть серьезные ошибки, вызванные неинициализированным указателем. Подобная ошибка одна из наиболее частых при работе с указателями. Например, описание int * x приводит к тому, что компилятор резервирует память, необходимую для размещения указателя. В этой ячейке памяти содержится некоторый адрес памяти. Этот адрес может совпадать с адресом операционной системы или транслятора. Если программа в этот момент вводит по этому адресу какое–то значение, например * x = 10, то это может привести к тому, что будет затерта часть операционной системы. К сожалению, компилятор языка Си не может обнаружить такую ошибку. Поэтому при использовании указателей в программе они обязательно должны быть инициализированы.

Для инициализации указателей существует несколько способов:

1. Присвоить указателю адрес переменной, используя операцию взятия адреса «&».

2. Присвоить указателю значение другого указателя, к этому моменту уже правильно инициализированного.

3. Использовать функции распределения памяти такие как malloc и calloc. Описание этих функций содержатся в файле < alloc.h >. Этот файл должен быть включен с помощью директивы # include.

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

Например, float * x = (float *) malloc (sizeof (float)); приводит к тому, что из свободной памяти выделяется 4 байта, преобразуется к типу float, и указатель x принимает адрес выделенной памяти. Если в памяти нет свободного места, то в качестве результата функция malloc возвращает адрес ноль. Поэтому вышеуказанный пример можно переписать таким образом

float *x;

if ((x = (float*) malloc (sizeof (float))! = 0)

{*/* фрагмент программы */}

 

2. Массивы и указатели

 

Имя массива является константой–указателем, т. е. имя массива определяет адрес размещения первого элемента массива. Например объявление int x [5] приводит к тождеству x = = & x [0]. Оба обозначения являются константами, поскольку адреса размещения массива изменить нельзя. Однако, их можно присвоить переменной типа указатель и изменять значение переменной. Приведем текст программы с различными вариантами операций с указателями и массивами.

void main ()

{

float *q; static float Z[2] = {4, 3.5}; int i;

q = Z; /* либо q = & Z[0] */

/* присваивание указателю адрес первого элемента массива Z*/

for (i = 0; i < 2; i++)

printf («указатель + % d: адрес = % u: значение: % f\n”, i, q, * q++);

/* аналогично ниже */

/* printf («указатель + % d: адрес = % u: значение: % g\n”,i, & Z [i]) */

/* printf (“указатель + % d: адрес = % u: значение: % f \n”, q + i, q [i]) */

/* printf (“указатель + % d: адрес = % u: значение: % f \n”, q + i, *(q+i) */

/* printf (“указатель + % d: адрес = % u: значение: % f \n”, i, Z+i, *(Z+i)*/

}

При выполнении этой программы с любым одним оператором вывода на экране дисплея получим:

указатель + 0: адрес 56026: значение 4

указатель + 1: адрес 56030: значение 3.5

Адрес 56026 – это начальный адрес массива Z.

Вторая строка–результат прибавления единицы к адресу. Прибавление единицы к указателю означает что, компилятор языка добавляет единицу памяти (для float – эта величина 4), а для массивов осуществляется переход к следующему элементу массива. Поэтому величина 56026 + 1 дает адрес равный 56030.

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

p = Z; Z [ i ] = = *(Z + i) = = p [ i ] = = * (p + i).

Таким образом, при работе с массивами допустимы два вида операций, а именно: операция индексации и опера



Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2017-04-20 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: