Язык прораммирования С++
p align="left">char s[]={0xa0,0xa1,0xa2,0xa3,0xa4,10,0};Обратите внимание, на подсказка на рис. 1.4. Она сообщает, что действия, выполняемые щелчком по кнопке с восклицательным знаком, продублированы горячими клавишами Ctrl/F5. Аналогичные подсказки закреплены за каждой световой кнопкой, со средой разработки поставляется также обширная справочная документация, в результате процесс разработки и отладки программ осваивается значительно быстрее при практической работе за компьютером, чем при чтении учебника. Поэтому здесь отметим только, что пошаговое выполнение программы без захода в процедуры выполняется по нажатию функциональной клавиши F10, с заходом в процедуры F11, а автоматическое выполнение до строки, в которой установлен курсор, запускается сочетанием клавиш Ctrl/F10. Лекция 3. Знакомство с операторами языка 1. Условный оператор Мы помним, что операторы программы задают последовательность действий, которую надо выполнить с объявленными в ней переменными. Часто она заранее точно неизвестна и определяется уже во время работы по результатам предыдущих вычислений. Пусть, например, необходимо вычислить подоходный налог с заработка, размер которого хранится в переменной Z. Сотрудник имеет право на льготы по налогу, суммарный размер которых хранится в переменной L. Если заработок больше суммы льгот (то есть Z>L), налог берется в размере N=13*(Z-L)/100. Когда сумма разрешенных льгот больше, чем заработок, применение этого выражения даст отрицательный Поэтому они могут участвожения результат. Это означает, что налог не взимается, то есть, при Z<L, значение N=0. Заметим, что в С++ знаки <, меньше и >,больше - это операции, дающие целочисленный результат.. Если условие, заданное операцией сравнения, выполняется, результат операции равен единице, если не выполняется - нулю. То есть, при вычислении выражения Z>L получим единицу, если Z больше, чем L. В С++ нет логического типа данных, поэтому вполне допустимо написать выражение (Z>5)+(Z>L). В зависимости от значений переменных вычисление этого выражения даст значения 0, 1 или 2. Рассмотрим программу, которая вводит значения Z и L с клавиатуры и вычисляет сумму налога условным оператором: #include <stdio.h> int Z,L,N; void main(void) { printf(“\n Введите сумму заработка и размер льгот”); scanf("%d%d ",&Z,&L); if(L<Z) N=13*(Z-L)/100; else N=0; printf(“ Налог равен %d руб.”, N); getchar(); getchar(); } Для сравнения посмотрите, как тот же ввод с клавиатуры выполнить с использованием потока: cin>>Z>>L; Выполнение оператора if(L<Z) N=13*(Z-L)/100; else N=0; заключается в следующем: - вычисляется выражение, записанное в скобках; - если результат не равен нулю, выполняется оператор после скобок, если равен нулю - оператор, записанный после else. Замечания 1. Выше мы использовали запись условного оператора в виде if(<выражение>) <оператор1> else <оператор2>. Можно использовать также краткую форму условного оператора if(<выражение>) <оператор>. Здесь отсутствует ветвь else, поэтому при равенстве выражения нулю не выполняется никаких действий. 2. В языке Паскаль точка с запятой является разделителем между операторами, а в С++ она является неотъемлемой частью оператора. Поэтому в Паскале знак `;' перед else не ставится (слово else само по себе является хорошим разделителем), а в данном примере точка с запятой перед else указана, так как она часть оператора N=13*(Z-L)/100;. 3.В языке С++ строчная и соответствующая ей прописная буквы - это разные символы. Поэтому в рассмотренном примере нельзя объявить переменные int Z, L, N;, а потом записать оператор n=13*(z-l)/100;. 4. Мы помним, что происходит при завершении программы вычисления площади, приведенной в параграфе 1. Завершаясь, главная функция main() возвращает управление тому, кто ее запускал. Если программу запускали из оболочки типа Norton Commander, на экране очень быстро промелькнут выводимые программой числа и мы опять увидим окна Norton Commander, если программа запускается из среды разработки, появится окно редактора с исходным текстом. Чтобы пользователь видел на экране результат, в конце программы вставляется дополнительный оператор ввода символа. При выполнении этого оператора программа не завершается, пока пользователь не нажмет кнопку клавиатуры и на экране видны результаты вычислений. Среди функций стандартного ввода-вывода есть функция getchar();, которая вводит один символ. В предыдущем примере, чтобы приостановить программу она записана дважды. Один оператор не приостановит программу по следующей причине: В начале программы функция scanf("%d%d ",&Z,&L); вводит с клавиатуры два числа Z и N. Ее особенностью является то, что при вводе числа функция отбрасывает все предшествующие первой цифре коды пробела, табуляции, перевода строки и возврата каретки, потом вводит цифры числа, пока не будет нажата клавиша Enter. Код этой клавиши функция scanf() использует как признак конца ввода числа, но оставляет во входном потоке. Это значит, что код Enter функция как бы и не вводила - он поступит следующему оператору ввода данных. Следующим оператором ввода является вызов getchar(). Он получит и введет код Enter, оставшийся не обработанным функцией scanf(), и только второй оператор getchar() будет ожидать ввода человеком следующего символа. Два оператора getchar() не украшают нашу программу, кроме того, как любая функция стандартного ввода, getchar() отображает символ на экране, и после нажатия клавиши, например, буквы А, ждет еще и нажатия клавиши Enter, как признака завершения ввода. Поэтому в дальнейшем мы будем пользоваться функцией консольного ввода символа getch(). Покажем применение консольного ввода на примере применения краткой формы условного оператора для определения наибольшего из трех чисел. Ниже представлена программа, которая вводит с клавиатуры три числа a, b, c, большее из них записывает в переменную max, после чего ожидает нажатия любой клавиши: #include <stdio.h> #include <conio.h> int a,b,c; void main(void) { clrscr(); scanf("%d%d%d",&a,&b,&c); int max=a; if(a<b) max=b; if(c>max) max =c; printf("%d",max); getch (); } В среде Borland C оператор #include <conio.h> вставляет в исходный текст программы прототипы всех функций консольного ввода-вывода. Мы с ними знакомы - это те функции, которые в Паскале реализованы модулем Crt. Надо только записывать их имена строчными буквами: clrscr() - очистить экран, gotoxy(x,y) -переместить курсор и т.д. Только паскалевским функциям проверки нажатия клавиши KeyPressed и ввода символа ReadKey в С++ даны другие имена - здесь они называются kbhit() и getch(). Функция getch() вводит символ без эхо-отображения и не ожидает нажатия Enter. Не совсем понятно, почему ее достаточно вызвать один раз. Кратко (не требуя полного понимания) на это можно пока ответить так. Входной поток стандартного ввода-вывода, это массив байтов, в котором операционная система накапливает введенные символы, не передавая их операторам ввода из входного потока до нажатия Enter. Консольные же функции читают символы не из входного потока. Они опрашивают непосредственно клавиатуру. Поэтому давно нажатая клавиша Enter не оказывает никакого действия на работу функции getch(). Функция getch() ожидает нажатия клавиши, а код Enter так и остается непрочитанным из буферной области оперативной памяти, которую мы называем входным потоком. 2. Оператор цикла в форме for Объявим целочисленную переменную I и рассмотрим оператор цикла на примере вывода на экран десяти чисел, кратных трем: int I; for(I=1;I<=10;I=I+1) printf(“%d”,3*I); Как и в Паскале, оператор состоит из: - заголовка цикла for(I=1;I<=10;I=I+1) - многократно повторяемого тела цикла - оператора printf(“%d”,3*I). Заголовок состоит из служебного слова for и трех взятых в скобки выражений, которые определяют, сколько раз будет повторяться тело цикла, и как будет изменяться параметр цикла I. Первое выражение I=1 выполнится один раз (до первого выполнения тела цикла) и определит начальное значение параметра цикла. Перед каждым выполнением тела цикла вычисляется второе выражение (обычно там помещается проверка какого-то условия) и если условие не выполняется (значение выражения равно нулю), цикл завершается. Если условие выполнено (результат вычисления выражения не равен нулю), выполняется тело цикла. После каждого выполнения тела цикла вычисляется третье выражение, которое обычно изменяет значение параметра цикла. Как видим, в данном случае параметр I будет изменяться от одного до 10, при этом выводимое на экран значение 3*I, будет кратно трем. Результаты экзаменов показывают, что иногда у студентов вызывают затруднения даже такие простые задачи. Поэтому рассмотрим несколько похожих примеров. 1. Те же числа, но в обратном порядке можно вывести оператором for(I=10;I;I=I-1) printf(“%d”,3*I); Оператор интересен тем, что вторым выражением является не проверка условия, а просто переменная I. Когда она станет равной нулю, цикл завершится. 2. Язык С++ предоставляет большую по сравнению с Паскалем свободу при реализации заголовка цикла. В частности, параметр цикла может быть вещественным числом, разрешено пропускать в заголовке цикла любое из выражений (а может, это называется записывать пустое выражение, потому что символы; пишутся обязательно). Следующий фрагмент показывает решение задачи с пропущенным первым выражением: float I=1; for(;I<=10;I=I+1) printf(“%f”,3*I);. 3. Можно пропустить также третье выражение и изменять переменную I в теле цикла: float I=1; for(;I<=10;) {I=I+1; printf(“%d”,3*I); }. Как и в Паскале, циклически повторяется только один записанный после заголовка оператор. Если надо повторять несколько операторов, они объединяются фигурными скобками в блок. В данном случае { I=I+1; Printf (“%d”,3*I); } - это блок операторов. Блок языка С отличается от составного оператора Паскаля тем, что в блоке после открывающей фигурной скобки можно размещать объявления переменных. Такие переменные остаются видимыми только в пределах того блока, где они объявлены (с понятиями области видимости и времени жизни переменных мы знакомились при изучении Паскаля). В отличие от С, язык С++ разрешает объявлять переменные в любом месте, кроме управляющих конструкций операторов. Например, нельзя объявлять переменную в условном операторе (в круглых скобках после if). Но параметр цикла можно объявлять непосредственно в заголовке (хотя заголовок - это управляющая конструкция цикла): for(int i=0; i<10;i=i+1) printf(“%d”,I); При этом переменная i будет доступна не только в этом цикле, но и во всех последующих операторах до конца блока. 4. Если опустить в заголовке все три выражения, результат вычисления второго пустого выражения считается ненулевым и мы получаем бесконечный цикл. В этом случае для решения задачи можно применить оператор принудительного завершения оператора цикла - break: int I=1; for(;;) //Такой заголовок приводит к бесконечному повторению тела цикла. { if (I>10) break; //Выполнение оператора break вызывает завершение цикла // и переход к следующему после цикла оператору. I=I+1; printf(“%d”,3*I); }. Знакомясь с оператором break языка Паскаль, мы отмечали, что его не было в авторской версии языка, его заимствовали из языка Си. 5. При решении данной задачи студенты часто пытаются в теле цикла дополнительно изменять значения I, чтобы они были кратны трем. В теле цикла при этом выводится значение I а не 3*I, например, так: int I; for(I=1;I<=10;I=I+1) { printf(“%d”, I); I=I+3;} Это неправильное решение, так как не учитывается что в каждом проходе к I, будет прибавлять единицу заголовок цикла, и еще три прибавится в теле цикла. Проверьте самостоятельно сколько и каких чисел выведет на экран приведенный выше оператор. Если не хочется применять длинную операцию умножения, цикл следует записать так: for(I=3;I<=30;I=I+3) printf(“%d”, I);. 6. Выведем на экран первые десять элементов последовательности Ѕ,1/4, 1/6, 1/8... Типичная ошибка при решении этой задачи приводит к выводу на экран значения 2-n. Элементы данной последовательности вычисляются оператором float I for(I=1;I<10;I=I+1) printf(“%f”, 1/(2*I)); или, без использования умножения: float I, s=0; for(I=1;I<10;I=I+1) { s=s+2; printf(“%f”, 1/s); }. Чтобы выводить на экран значения 2-n, следует делить на 2 предыдущее вычисленное значение степени: float I, s=1; for(I=1;I<10;I=I+1) { s=s/2; printf(“%f”, s); } Несколько замечаний по форматированию вывода Предыдущий фрагмент выводит вычисленные значения степени одной строкой. Запишем программу вывода значений 2-n полностью и будем выводить показатель и три варианта значения степени, использующие различные спецификации вывода #include <stdio.h> #include <conio.h> int i; float s=1; void main(void) { clrscr(); for(i=1;i<20;i++) { printf("\n -%d cтепень \ равна %06.2f %6.2e %-6.3g ",i-1,s,s,s); s=s/2; } getch(); } При пуске программа выведет на экран следующий текст: -0 cтепень равна 001.00 1.00e+00 1 -1 cтепень равна 000.50 5.00e-01 0.5 -2 cтепень равна 000.25 2.50e-01 0.25 -3 cтепень равна 000.12 1.25e-01 0.125 -4 cтепень равна 000.06 6.25e-02 0.0625 -5 cтепень равна 000.03 3.12e-02 0.0312 -6 cтепень равна 000.02 1.56e-02 0.0156 -7 cтепень равна 000.01 7.81e-03 0.00781 -8 cтепень равна 000.00 3.91e-03 0.00391 -9 cтепень равна 000.00 1.95e-03 0.00195 -10 cтепень равна 000.00 9.77e-04 0.000977 -11 cтепень равна 000.00 4.88e-04 0.000488 12 cтепень равна 000.00 2.44e-04 0.000244 -13 cтепень равна 000.00 1.22e-04 0.000122 -14 cтепень равна 000.00 6.10e-05 6.1e-05 -15 cтепень равна 000.00 3.05e-05 3.05e-05 Здесь каждое вычисленное значение выводится с новой строки. Мы знаем, что для перевода курсора на следующую строку на экран следует вывести символ с кодом 10, а для перемещения в начало строки - символ с кодом 13. Но у этих символов нет изображения, которое можно вставить текстовым редактором в выводимую строку. Если мы напишем printf(“10строка”), то в начале строки будут два символа `1','0'с кодами 49 и 48, а не символ перевода строки с кодом 10. Для включения в строку специальных символов в С++ используют букву, перед которой записан знак \. Например, \n понимается компилятором, как символ с кодом 10, \r - символ с кодом 13, \а - символ с кодом 7 (звонок) и т.д. Чтобы включить в строку знак \, его приходится записать дважды \\. Из-за этого путь к файлу filename.dat, который находится в каталоге ABC, на языках С, С++ приходилось задавать так: C:\\ABC\\filename.dat. Таким образом, знак \n в программе вывода значений степени - это символ перевода строки. Оператор вывода printf("\n -%d cтепень равна %06.2f %6.2e %-6.3g ",i-1,s,s,s); содержит очень много букв, поэтому он записан в две строки. printf("\n -%d cтепень равна %06.2f %6.2e %-6.3g ",i-1,s,s,s); Для перехода в редакторе текста на новую строку мы нажимаем клавишу Enter, у нее тоже есть код, который вставляется в строку после слова «степень». Но этого кода не должно быть в строке, выводимой во время работы программы. Чтобы компилятор выбросил из строки код клавиши Enter, мы вводим \, а потом нажимаем Enter. (То есть, сочетание \<Enter > включает код перевода строки в исходный текст, но подавляет его в компилированной строке). Вычисленная степень s выводится на экран три раза. Все три спецификации %f %g предназначены для вывода вещественных чисел: %f предписывает выводить число без множителя 10n с шестью знаками после десятичной точки, последняя цифра округляется; %e предписывает всегда выводить число в экспоненциальной форме (c множителем 10n); %g сохраняет не больше шести значащих цифр результата (в %f шесть цифр только в дробной части, а здесь всего шесть, включая целую и дробную часть, но значащих цифр). Для очень маленького числа запишутся нули, а потом 6 цифр. Кроме того, в данном формате автоматически из двух вариантов представления числа - экспоненциального и с десятичной точкой, автоматически выбирается тот, который запишется короче. В нашем примере после знака % перед буквой записаны цифры: %6.2f означает, что на число (включая точку) отведено шесть позиций, из них две после точки. Если перед первым числом написать 0, %06.2f, в незаполненные старшие разряды запишутся нули (как в нашем примере); %6.3g означает, что числу отводится не менее шести знакомест, но от числа сохраняется не более трех значащих цифр. На примере распечатки мы не видим, что числу отведено шесть позиций, потому что столбец последний и число выравнивается не по правому, а по левому краю области печати. Указание выравнивать число по левому краю дано знаком минус в спецификации %-6.3g. Лекция 4. Работа с массивами 1. Одномерные массивы Массив, это совокупность однотипных элементов, объединенных общим именем. Например, десять целых чисел, объединенных именем Mas, объявляются следующим образом: int Mas[10];То же, что на Паскале Mas: array [0..9] of integer;. В объявлении записывают тип элементов, имя массива и, в квадратных скобках, количество элементов. Элементами массивов могут быть не только числа. Можно например, объявить массив, состоящий из массивов. Нельзя объявлять массивы, элементами которых являются функции или данные типа void. Массив можно передать функции как параметр, но функция не может возвращать массив в качестве результата своей работы. Объявление массива можно совместить с его инициализацией, перечислив значения элементов в фигурных скобках: int A[5] ={2,-3, 0,0,7}; В операторной части программы при обращении к отдельным элементам указывают имя массива и номер элемента в квадратных скобках. Элементы массива нумеруются начиная с нуля, т.е. оператор printf(“%d”,A[0]); выведет на экран первый элемент, число 2, а оператор printf(“%d”,A[4]); выведет последний элемент, число 7. Элемента A[5] в массиве A нет. При инициализации массивов язык Си позволяет большую свободу, чем Паскаль. В частности, не обязательно перечислять значения всех элементов. В объявлении float M1[10]={4, 2.5, 0.3}; семь последних элементов автоматически заполнятся нулями. Самостоятельно проверьте, можно ли пропускать элементы, если они не последние, например, разделяя отсутствующие элементы запятыми: float M2[10]={4, 2.5,0.3,,0,,,,20.3};. Если при инициализации перечислены все элементы - можно не указывать размер массива: ar[]={2,7,9,3,1};. Заметим, что размер массива можно не указывать также если массив объявлен, как параметр функции или если объявляется ссылка на массив, определенный в другом файле. Пусть, например, наш проект состоит из двух файлов (A.cpp, B.cpp). Если A.cpp мы объявили массив float M1[10], то в B.cpp для работы с этим массивом можно записать строку extern float M1[];, которая указывает, что массив внешний - объявление массива сделано в другом файле. При необходимости можно указать и размер массива extern float M1[10], но инициализацию при ссылке на внешний массив (как и на любую внешнюю переменную) повторять нельзя. Рассмотрим в качестве примера решение следующей задачи: - объявить массив из пяти целых чисел; - заполнить элементы массива данными, вводимыми с клавиатуры; -вывести элементы массива на экран; найти сумму положительных элементов и вывести ее на экран. #include <stdio.h> #include <conio.h> int ar[5]; void main(void) { clrscr(); printf("Enter five numbers");//Это я по словарю привыкаю к английским словам for(int i=0;i<5;i++) scanf("%d",&ar[i]); //Объявленная здесь переменная i видна и дальше. float Sum=ar[0]; for(i=1;i<5;i++) if(ar[i]>0) Sum=Sum+ ar[i]; for(i=0;i<5;i=i+1)printf(“%5d”, ar[I]); printf("The sum is %f",Sum); getch (); } Решим задачу поиска элементов с максимальным значением в массивах, состоящих из целых чисел. Для этого реализуем функцию поиска максимального элемента в виде отдельного файла int MaxArr(int Mas[],int R=10) //Файл Max_Arr.cpp { int Max=Mas[0]; for(int i=1;i<R;i=i+1) if(Mas[i]>Max)Max=Mas[i]; return Max; } Первый параметр функции MaxArr(),- это массив из целых чисел, а второй - количество элементов массива. Размерность массива не указана, поэтому функция может находить максимальный элемент в любом массиве, состоящем из целых чисел. Язык C++ позволяет при описании функции указывать после параметра его значение по умолчанию (параметру R по умолчанию присвоено значение 10). В этом случае при вызове функции можно не указывать один или несколько последних параметров, имеющих заданные по умолчанию значения. Ниже приведена программа, использующая данную функцию для вывода на экран максимальных элементов двух массивов разного размера. #include <stdio.h> #include <conio.h> int MaxArr(int Mas[],int R=10); Это шаблон заголовка. В нем можно опускать имена формальных параметров и писать только их типы, например, int MaxArr(int[],int=10); void main(void) { clrscr(); int M1[5]={-3,5,0,15,6}; int M2[10]={13,25,0,15,-36}; int Max2=MaxArr(M2); Массив M2 состоит из 10 элементов, поэтому можно передавать только первый параметр. printf("\n В первом массиве %d \ Во втором массиве %d", MaxArr(M1,5), Max2); getch(); } Сравните с языком Паскаль - там при описании формального параметра типа массив, требовалось указывать имя параметра и имя предварительно описанного типа передаваемого массива. Из-за этого на Паскале для работы с массивами разных размеров требовалось иметь отдельные функции. Но зато на Си программист может ошибиться и задать больше или меньше элементов, чем есть в массиве. Заметим, что можно указать в заголовке функции размерность массива: int MaxArr(int Mas[10],int R=10); Но даже и в этом случае компилятор не производит контроль соответствия размера указанного в заголовке размеру реально передаваемого массива - предоставляя программисту большие возможности, язык С++ возлагает на него большую ответственность за работу программы. Замечание. Если производится обращение к переменной, объявленной в другом модуле проекта, надо указать, что она внешняя, например, extern int M. Имена функций видны везде, нужно указывать лишь прототип функции. 2. Многомерные массивы Как и в языке Паскаль, в С++и многомерные массивы конструируют, объявляя массив, элементы которого тоже массивы. Так: - одномерный массив int A[10]; - это набор из 10 целых чисел; - двумерный массив int A2[10][3]; - это массив из 10 элементов, а каждый элемент A2[i] массив из трех целых чисел; - int A3[10][3][5]; это массив из 10 элементов, а каждый элемент A3[i] - двумерный массив размером 3?5;. Двумерные массивы используются для работы с матрицами и другими прямоугольными таблицами. Для того, чтобы в программе на языке С++ объявить прямоугольную матрицу t00 t01 t02 t03 T =t10 t11 t13 t12 t20 t21 t22 t23, надо указать, из элементов какого типа (целых или вещественных) она состоит, дать ей имя, указать сколько в ней строк и столбцов. Если показанная выше таблица содержит вещественные числа, ее объявление будем иметь вид: double T[3][4]; где 3 -количество строк, а 4 - столбцов. В языке Паскаль для каждого индекса указывался интервальный тип данных, задающий начальное и конечное значения, в С++ строки и столбцы всегда нумеруются с нуля, поэтому пишется только их количество. Чтобы задать конкретный элемент массива, надо указать номер строки, в которой находится этот элемент, и номер столбца. Так, элемент, который находится во второй строке и третьем столбце надо обозначать T[1][2]. Можно рассуждать и иначе: - выбираем элемент массива, указав его индекс - T[1]; - T[1] это массив из четырех чисел, выбираем элемент массива T[1], указав его индекс - T[1][2]. В Паскале, чтобы увеличить сходство операторов программы с математической записью элементов матриц, разрешалось перечислять индексы массива через запятую. В С++ это недопустимо. Особенно неприятно, что компилятор, встретив обозначение M [1,2] будет считать, что это M[2] и при синтаксическом контроле может не выдать ошибку. Объявление двумерного массива также можно совмещать с его инициализацией: int Mas[3][4]= {{2, 7, 9,4}, {1,3}, {3,3,3,3}} Правила инициализации вытекают из соответствующих правил для одномерных массивов. Двумерный массив - это одномерный массив, элементами которого являются строки матрицы. А при инициализации одномерного массива в фигурных скобках (в примере это внешние скобки) перечисляются через запятую значения элементов массива. Но каждый элемент - это тоже массив. Поэтому его значение - последовательность чисел, взятая в фигурные скобки. Как показано во второй строке, можно перечислять не все элементы строки матрицы - недостающие автоматически заполнятся нулями. В сделанном выше объявлении массива можно не указывать число строк: int Mas[][4]= {{2,7… и т д. В памяти элементы массива располагаются по строкам, сначала элементы первой строки, потом второй и т. д. Для многомерных массивов (у которых больше двух индексов) это правило формулируется так: - при размещении первым записывается в память элемент, у которого все индексы равны нулю; - далее пробегаем последний (правый) индекс от нуля дот максимального значения; - потом увеличиваем на единицу предпоследний индекс и заново изменяем последний от нуля до единицы; - когда предпоследний индекс достигнет максимального значения, увеличиваем третий справа индекс и так далее. Таким образом, в двумерном массиве целых чисел размером M?N элемент с индексами i,j смещен на N*sizeof(int) i+ sizeof(int)*j байтов от начала массива. Учитывая построчное расположение элементов в памяти, в языке разрешено перечислять при инициализации элементы одной строкой. Тот же массив, что показан выше, можно было объявить так: int Mas[][4]= {2, 7, 9,4, 1,3,0,0, 3,3,3,3};, но оставлять незаполненными элементы второй строки уже нельзя. В этом случае тоже можно не указывать первую размерность. Последняя строка может быть не полной - при объявлении int Mas[][4]={1,2,3,4, 1,3} компилятор будет отсчитывать по четыре числа в строке и создаст матрицу 1 2 3 4 1 3 0 0. Вывести элементы двумерного массива на экран можно различными способами. Удобно организовать цикл по строкам и вложить в него цикл по столбцам for(int i=0; i<3;i=i+1) for (j=0;j<4;j=j+1) printf (“%5d”,Mas[i][j]);. Можно также учесть, что в массиве 12 чисел и сделать цикл от нуля до 12, но при этом придется использовать новую арифметическую операцию % (получение остатка от деления целых чисел) for(int i=0; i<12;i=i+1) printf (“%5d”,Mas[i/4][i%4]);. Оба приведенные выше цикла будут печатать элементы массива в одну строку. Если мы хотим выводить массив в виде прямоугольной таблицы, перед каждой следующей строкой (или после каждой строки) массива надо вывести на экран символ перевода курсора \n., как показано ниже: for(int i=0; i<3;i=i+1) { for (j=0;j<4;j=j+1) printf (“%5d”,Mas[i][j]);. printf(“\n”); } Объясните, как изменится работа программы, если в этом фрагменте удалить фигурные скобки. Помню, в школе нас учили решать алгебраические уравнения методом подстановки и методом алгебраического сложения. В ВУЗе в курсе алгебры мы узнаем, что решение систем линейных уравнений школьным методом алгебраического сложения называется методом Гаусса, а также знакомимся с алгоритмами вычисления определителя и обратной матрицы. Они основаны на последовательности шагов, заключающихся в умножении элементов строки на константу и прибавлении к соответствующим элементам другой строки то же матрицы. Составим, для примера, программу решения систем уравнений методом Гаусса и решим систему: 1.7x1 + 10.0x2 - 1.3x3 + 2.1x4 = 3.1 3.1x1 + 1.7x2 - 2.1x3 + 5.4x4 = 2.1 3.3x1 - 7.7x2 + 4.4x3 - 5.1x4 = 1.9 10.0x1 - 20.1x2 + 20.4x3 + 1.7x4 = 1.8 При решении системы все ее числовые коэффициенты можно хранить в массиве, объявленном как double a[4][5]. Столбец свободных членов системы - это элементы a[I][4], где I=0,..., 3. Методом Гаусса основано на последовательности преобразований системы, не изменяющих ее корней. Если прибавить к левой и правой части уравнения одно и то же число, его корни не изменяются. (Когда x1 x2 … xn - это корни уравнения, его левая и правая части равны. Поэтому к одном уравнению можно, не изменяя корней, прибавить другое, умноженное на числовой коэффициент). В методе Гаусса решение системы из n уравнений получается за n шагов. На шаге номер k: уравнение номер k делится на коэффициент a[k][k] - диагональный элемент матрицы становится равным единице; потом (для всех i ? k) к уравнению номер i прибавляется уравнение номер k умноженное на минус a[i][k]. В результате в столбце k все коэффициенты, кроме расположенного на диагонали, станут равными нулю (для ручного счета указанные на данном шаге действия выполняются только для значений i>k, но нам удобнее заменить нулями все коэффициенты столбца, кроме одного). После того, как циклом, изменяющим k от нуля до n-1 выполнится n описанных выше шагов, в каждой строке коэффициенты при всех неизвестных, кроме одного, будут равны нулю - это и есть решение. Проделаем описанные преобразования для заданного уравнения. Разделим все коэффициенты первой строки на a[0][0]=1.7. Первое уравнение приобретет вид: x1 + 5.882x2 - 0.7647x3 + 1.235x4 = 1.824 Теперь, чтобы в уравнении 2 получить нулевой коэффициент при x1, все элементы первого уравнения умножаем на a[1][0]=3.1 и отнимаем от второго, получим 0x1 - 16.54 x2 + 0.2706 x3 + 1.571 x4 = 3.553 и так далее. Программа, реализующая данный алгоритм, оказывается заметно короче его описания: #include<stdio.h> #include<conio.h> double a[4][5]={{1.7, 10.0, -1.3, 2.1, 3.1}, //Записали систему уравнений {1, 1.7, -2.1, 5.4, 2.1}, {3.3, -7.7, 4.4, -5.1, 1.9}, {10.0,-20.1, 20.4, 1.7, 1.8}}; void print (void) //Вывод таблицы коэффициентов оформили в виде функции. { for(int i=0;i<4;i++){for(int j=0;j<5;j++) printf("%8.4lg ",a[i][j]);printf("\n");} printf("\n"); } void main (void) { clrscr(); print(); //Вывели исходную таблицу for(int k=0;k<4;k++) //Цикл по числу уравнений { double Kf=a[k][k]; for(int j=0;j<5;j++) a[k][j]=a[k][j]/Kf; //Получаем единицу при xk for(int i=0;i<4;i++) { //Во всех уравнениях, кроме k-го делаем коэффициент при xk равным нулю. Kf=a[i][k]; if(i!=k) for(j=0;j<5;j++) a[i][j]=a[i][j]-a[k][j]*Kf; } print();//Вывели таблицу со столбцом из нулей. } getch(); } В приведенной программе делитель a[k][k]; предварительно записывается в отдельную переменную: Kf=a[k][k];. Объясните, почему нельзя отказаться от использования промежуточной переменной Kf, записав вместо оператора a[k][j]=a[k][j]/Kf; оператор a[k][j]=a[k][j]/ a[k][k];. Далее показаны результаты вывода на экран. Исходная таблица 1.7 10 -1.3 2.1 3.1 3.1 1.7 -2.1 5.4 2.1 3.3 -7.7 4.4 -5.1 1.9 10 -20.1 20.4 1.7 1.8 Результат обработки при k=0 (сравните с ручным счетом) 1 5.882 -0.7647 1.235 1.824 0 -16.54 0.2706 1.571 -3.553 0 -27.11 6.924 -9.176 -4.118 0 -78.92 28.05 -10.65 -16.44 Результат обработки при k=1 1 0 -0.6684 1.794 0.5596 -0 1 -0.01636 -0.09498 0.2149 0 0 6.48 -11.75 1.708 0 0 26.76 -18.15 0.523 Результат обработки при k=2 1 0 0 0.5818 0.7358 0 1 0 -0.1247 0.2192 0 0 1 -1.814 0.2636 0 0 0 30.37 -6.529 Результат обработки при k=3 1 0 0 0 0.8608 0 1 0 0 0.1924 0 0 1 0 -0.1263 0 0 0 1 -0.215 Проверьте, что подстановка в уравнения значений x0=0.8608, x1=0.1924, x2=-0.1263, x3=-0. 215 дает тождества. Самостоятельно измените функцию печати так, чтобы вместо таблиц на экран выводились уравнения (со знаками действий и обозначениями неизвестных). Заметим, что программа завершится аварийно, если в исходной системе на главной диагонали будет нулевой элемент. Чтобы этого не случилось можно переставить уравнения местами. Пример программы, реализующей модификацию данного алгоритма нечувствительную к нулевым элементам (метод Жордана-Гаусса находится на диске в папке JrdGauss, в тексте учебника мы его разберем при знакомстве с оконным интерфейсом ОС Windows).
Страницы: 1, 2
|