Размерность векторов N и их координаты вводятся с консоли. Функция scal() вызывается из главной программы трижды с разными фактическими параметрами. Вычисляется угол и результат выводится на экран. Обратите внимание на способ передачи параметров в функцию scal() из основной программы. По правилам языка C при передаче простой переменной в функцию передается ее значение, а при передаче массива - адрес начала размещения этого массива в памяти. Изменяя локальную переменную, являющуюся формальным параметром в вызываемой функции, мы не затрагиваем соответствующую ей простую переменную - фактический параметр в вызывающей функции. Иная ситуация при работе с массивами. И вызывающая , и вызываемая функции ссылаются на один и тот же участок памяти и, следовательно, изменение значений элементов массива - формального параметра в вызываемой функции приводят к точно таким же изменениям фактического параметра в вызывающей функции.
В качестве фактических параметров при вызове функции scal() из главной функции используются имена массивов x и y. (Имена массивов являются указателями на начало размещения массивов в памяти, а значениями этих указателей служат адреса.) В определении самой функции scal() этим именам соответствуют формальные параметры - указатели float*a и float*b того же типа, что и элементы массивов x и y. С помощью этих указателей, используя адресную арифметику, функция scal() получает доступ к соответствующим элементам массивов x и y. Для иллюстрации в строке 2 доступ осуществляется и с помощью индексирования, и с помощью операции разыменования.
Для создания программы, которая будет перемножать матрицу на вектор-столбец используем ту же функцию scal() перемножения векторов. Операцию умножения матрицы на вектор определим обычным правилом: i-тая строка матрицы скалярно умножается на вектор-столбец и даёт i-тый элемент результирующего вектора. При таком определении, очевидно, число столбцов матрицы должно совпадать с числом элементов вектор-столбца.
Пример 3.
/*Program 10.3*/
#include
#include
#include
float scal(float*,float*,int);
void main()
{
static float a[100][100], b[100], c[100];
int i,j,n,m; char* str;
system(“cls”); //Выполняет команду DOS очистки экрана.
printf(“\nВведите размеры матрицы :”);
printf(\n число столбцов N “); gets(str); n = atoi(str);
printf(“\nчисло строк M “); gets(str); m = atoi(str);
При вызове функции scal() в неё передаются в качестве параметров i-тая строка матрицы A и вектор b. a[i] является указателем на нулевой элемент i-той строки двумерного массива a, а имя одномерного массива b являтся указателем на начало его расположения в памяти. В определении функции scal() им соответствуют формальные параметры - указатели float *a, float*b. С помощью индексирования в функции scal() становятся доступными элементы i-той строки массива a и массива b. Главная программа содержит некоторые отличия от описанной в предыдущем примере.
Во-первых массивы a,b,c описаны с классом static. Связано это с особенностями использования памяти компилятором TС++. По умолчанию в C все данные имеют класс auto и размещаются в стеке. Предусмотрена возможность размещать данные в других сегментах. Для этого они могут быть описаны с классом static, и(или) программы должны компилироваться для большой (large) модели памяти. Для двумерных массивов N на N типа float это ограничивает их размер N = sqrt(64000/sizeof(float)) = 128. Если потребуется работать с массивами большего размера,нужно установить модель Huge при компиляции программы и использовать модификатор huge при объявлении массива. (На ТС этот режим не реализован.)
Второе отличие - объявление прототипа функции scal(). Синтаксис объявления прототипа похож на синтаксис объявления функции, но вместо параметров функции могут стоять только их типы и в конце объявления прототипа должна стоять точка с запятой. Появление прототипов связано с особенностями работы компиляторов языка С. К моменту, когда компилятор, последовательно обрабатывающий строки исходного текста, доходит до вызова функции, он уже должен “знать” какое количество памяти нужно зарезервировать в стеке для передачи параметров и возвращаемого функцией значения. В связи с этим, функция либо должна быть определена до вызова, как это сделано в примере 2, либо должен быть описан её прототип как в примере 3. В прототипе обязательно должен указываться тип возвращаемого значения и типы формальных параметров. При вызове функции формальные параметры заменяются фактическими, при этом язык С предусматривает автоматическое преобразование типов в тех случаях, когда тип фактических параметров не совпадает с типом формальных.
Вообще говоря, если вызывающая и вызываемая функции размещаются в одном файле, использование прототипа в языке С в тех случаях, когда вызов функции размещён до её определения, не является обязательным. (В отличие от С++). Например, если в примере 3 и главная функция, вычисляющая угол, и функция scal, перемножающая вектора, будут находится в одном файле, то строку с описанием прототипа можно закоментировать - результат не изменится. Если же вызываемая функция размещается в другом файле использование прототипа обязательно. Это относится и к стандартным библиотечным функциям, прототипы которых описаны в заголовочных файлах. (Они имеют расширение .h и находятся в подкаталоге INCLUDE).
В следующем примере приводится программа перемножения матрицы на матрицу. Матрицы перемножаются по следующему правилу. i-тая строка первой матрицы скалярно умножается на j-тый столбец второй и получается элемент стоящий на пересечении i-той строки и j-того столбца результирующей матрицы. Иначе, перемножая первую матрицу на j-тый столбец второй, получим j-тый столбец результирующей матрицы. Так как программа перемножения матрицы на вектор у нас уже есть используем её, оформив в виде отдельной функции.
Пример 4.
/*Program 10.4*/
/*Функция перемножения двух квадратных матриц A и B размером N на N с вызовом функции mult() перемножения матрицы на вектор. Матрицы A и B формируются в главной функции. В функцию mult() передается указатель на массив указателей на строки матрицы A - ptl_A (point to line of A) и указатель на столбцы матрицы B (на строки транспониованной матрицы B) - ptl_b. Возвращаемое значение - указатель ptl_c на столбцы результирующей матрицы C.*/
#include
#include
#include
#include
void mult(float**,float*,float*,int); // Пототипы функций mult() и
float scal(float*,float*,int); // scal().
void main()
{
static float a[50][50],b[50][50],c[50][50];
float *ptl_A[50], *ptl_b, ptl_c[50], btmp;
int i,j,n; char* str;
system(“cls”); // Функция выполняет команду DOS очистки экрана.
printf(“\nВведите N “); gets(str); n=atoi(str);
// Настройка массива указателей ptl_A на строки массива A.
for(i=0;i
// Формирование матриц A и B.
for(i=0;i
for(j=0;j
// Подготовка (транспонирование) матрицы B к перемножению.
for(i=0;i
for(j=0;j
// Перемножение матрицы на матрицу с вызовом функции умножения // матрицы на вектор.
for(i=0;i
ptl_b = (float*)&b[i]; //Настройка указателя на i-тую строку
//матрицыB
mult(ptl_A, ptl_b, ptl_c, n); // Вызов функции mult.
for(j=0;j
c[j][i] = ptl_c[j]; }} // матрицы.
// Восстановление исходной матрицы B.
for(i=0;i
for(j=0;j
// Вывод результирующей матрицы на экран.
printf(“\n\n Результирующая матрица”);
for(i=0;i
for(j=0;j
printf(“%8.3f”,c[i][j]);
}
}
// Функция перемножения матрицы на вектор.
void mult(float**a,float*b,float*c,int n)
{
float *ptl_A; int i;
for(i=0;i
ptl_A = (float*)&a[i][0];
c[i] = scal(ptl_A,b,n); }
}
Функция scal() описана выше.
Для передачи массива а переменного размера n в качестве параметра в функцию mult() перемножения матрицы на вектор используется промежуточный одномерный массив указателей ptl_A, в котором сохраняются адреса строк масива a. Эта процедура требует некоторых пояснений. В функцию mult() перемножения матрицы на вектор требуется передавать двумерный массив - матрицу A и одномерный массив - очередной вектор-столбец матрицы B. С передачей одномерного массива проблем не возникает. Учитывая, что в памяти машины матрицы хранятся по строкам, транспонируем матрицу B и тогда указатель на j-тую строку транспонированной матрицы будет одновременно адресовать j-тый столбец исходной матрицы. Этот указатель и передается в функцию mult() в качестве второго параметра.
С передачей двумерных массивов переменных размеров возникают некоторые сложности связанные с тем, что компилятор не может распознать по имени массива его размерность и размеры. Чтобы обойти эту трудность можно, например, подменить двумерный массив одномерным и иммитировать доступ внутри функции к двумерному массиву. В нашей программе использован другой способ. Заводится вспомогательный массив указателей *point_A[100], который настраивается на строки исходной матрицы A, т.е. элементам массива *point_A присваиваются адреса, с которых начинаются строки массива a. И именно этот одномерный массив указателей передается в функцию mult() в качестве первого фактического параметра. Так как имя любого массива есть указатель на его нулевой элемент, то в функцию mult() передается указатель на массив указателей.
Задания.
1. Изучите приведенные в примерах 1-4 программы. Скопируйте эти программы к себе в каталог. Убедитесь в правильности их работы.
2*. Напишите пограмму перемножающую по обычному правилу матрицу размера N на N, на вектор-столбец размера N. Матрицу и вектор сформируйте в программе, и выведите их вместе с результирующим вектором на экран.
3*. По аналогии с программойиз примера 4 напишите программу перемножения прямоугольных матриц A размера N на M и B размера M на N, оформив транспонирование матрицы B в виде отдельной функции.
4**. Напишите программу непосредственно перемножающую две матрицы без обращения к функциям перемножения матрицы на вектор и вектора на вектор. Оформите ее в виде отдельной функции.
5**. Напишите программу, вычисляющую коммутатор двух квадратных матриц A и B размера N на N, т.е. матрицу равную разности произведения A на B и B на A. Оформите для этого программу перемножения матрицы на матрицу в виде отдельной функции.
Лабораторная работа 12
1. Ввод и вывод символов. Копирование файла.
Стандартная библиотека включает функции для чтения и записи по одному символу за один раз: getchar(); putchar(). Функция getchar() извлекает следующий вводимый символ каждый раз, когда к ней обращаются, и возвращает этот символ в качестве своего значения. Это значит, что после c = getchar() переменная 'c' содержит следующий символ из входных данных. Символы обычно вводятся с клавиатуры. Функция putchar(c) является дополнением к getchar() : в результате обращения putchar (c) содержимое переменной 'c' выдается на некоторый выходной носитель, обычно на монитор.
Как и функция printf , функции getchar и putchar не содержат ничего экстраординарного.Они не входят в состав языка "C", но к ним всегда можно обратиться, если подключить библиотеку. Имея в своем распоряжении только функции getchar и putchar Вы можете, не зная ничего более об операциях ввода–вывода, написать удивительное количество полезных программ. Простейшим примером может служить программа посимвольного копирования вводного файла в выводной. Общая схема имеет вид:
ввести символ
while (символ не является признаком конца файла)
вывести только что прочитанный символ
ввести новый символ
Пpимеp 1. Программа, написанная на языке "C", выглядит следующим образом:
main() /* copy input to output; 1st version */
{ int c;
c = getchar();
while (c != EOF)
{ putchar (c);
c = getchar(); }
} Основная проблема заключается в том, чтобы зафиксировать конец файла ввода. Обычно, когда функция getchar наталкивается на конец файла ввода, она возвращает значение, не являющееся действительным символом; таким образом, программа может установить, что файл ввода исчерпан. Единственное осложнение, являющееся значительным неудобством, заключается в существовании двух общеупотребительных соглашений о том, какое значение фактически является признаком конца файла. Мы отсрочим решение этого вопроса, использовав cимволическое имя EOF для этого значения, каким бы оно ни было. На практике EOF будет либо –1, либо 0, так что для правильной работы перед программой должно стоять собственно либо
#define EOF -1
либо #define EOF 0
Использовав символическую константу EOF для представления значения, возвращаемого функцией getchar при выходе на конец файла, мы обеспечили, что только одна величина в программе зависит от конкретного численного значения. Мы также описали переменную 'c' как int , а не char, с тем чтобы она могла хранить значение, возвращаемое getchar (эта величина действительно int, так как она должна быть в состоянии в дополнение ко всем возможным символам представлять и EOF).
Программистом, имеющим опыт работы на "C", программа копирования была бы написана более сжато. В языке "C" любое присваивание, такое как c = getchar() может быть использовано в выражении; его значение – просто значение, присваиваемое левой части. Если присваивание символа переменной 'c' поместить внутрь проверочной части оператора while, то программа копирования файла запишется в виде:
К ф. м н. А. О. Беляков. Список публикаций Замечания к статье Л. Д. Акуленко и С. В. Нестерова "Устойчивость равновесия маятника переменной длины". Пмм. 2009. Т. 73. Вып. С....
Лабораторная работа «Одномерные массивы» Цели: формирование практических умений и навыков составления блок-схем и записи на языке программирования Паскаль алгоритмов заполнения,...