↓ Содержание ↓
↑ Свернуть ↑
| Следующая глава |
В этом тексте будет показана самая примитивная технология разработки прикладных простейших программ.
Так как надобность возникла написать программу для генерации заданий для небольшой контрольной работы по Линейной Алгебре (решение систем уравнений методом Гаусса, Крамера, обратной матрицы), то и технологию буду показывать пошагово, как сам буду ту прогу писать.
(прим. Да, я мог бы ещё поставить в прогу менюху непосредственного вывода на печать, но как показывает опыт, лучше иметь "предварительный" вариант, который можно перерасчитать многажды и после скопипастить нужный кусок в итоговый док. Что выйдет и легче и намного продуктивнее. Ведь генерятся варианты значений коэффициентов рандомно. И там может получаться очень тяжёлая для вычисления система уравнений. Или наоборот — слишком холявная.)
Сразу, главное:
Вся разработка программы должна быть поделена на мелкие, ЭЛЕМЕНТАРНЫЕ шаги, после каждого из которых вы можете откомпилировать программу и проверить работает ли она = работает ли, правильно ли написан тот МАЛЕНЬКИЙ фрагмент, что вы только что написали.
Самый первый шаг — разработка интерфейса
Т.е. Как прога будет выглядеть на экране пользователя.
Вообще — чем проще, тем лучше. А чтобы пользователь не гадал о том, что он таки открыл (случайно) на форме должно отображаться название проги, говорящее хотя бы приблизительно о том, что же она такого выполняет.
Примерно вот так:
Название, как видите, "Рисую матрицы".
Для элемента memo1 включены обе рейки прокрутки. Можно и вертикальной обойтись, но это так — на всякий случай. Типа "не помешает", и если что-то прикручивать придётся по ходу дела не будет голова болеть за то, что что-то элементарное забыл сделать.
Также для memo1 свойство font.size установил на 14. Можно было, конечно, вывести на панель и выбор шрифта, и кегля для него, но посчитал излишним. Всё равно текст копипастой будет переноситься из Мемо в Ворд (или как у меня — в ОпенОфисРайтер). А там возможностей форматирования текста — гораздо больше, чем можно быстро написать в кодах для элементарной подсобной программы. Так что в этом я соблюдаю принцип разумной достаточности.
Правая кнопка типа Битбаттон. На ней включено свойство Kind на Close. Так не нужно писать отдельно обработчик на кнопку — и так будет срабатывать на закрытие проги.
Левая кнопка — из обычных. Надпись на ней показывает что она предназначена для запуска процесса вычисления матриц и рисования их в memo1.
Когда слеплен интерфейс, стоит навесить чего-то в виде кода на исполняющую кнопку. Чтобы можно было посмотреть как оно будет выглядеть и как работать.
Таким образом первый шаг в коде, в файле Unit1.pas будет выглядеть так:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, Buttons, StdCtrls, ExtCtrls;
type
TForm1 = class(TForm)
Memo1: TMemo;
Panel1: TPanel;
Btn2: TButton;
btn1: TBitBtn;
procedure Btn2Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Btn2Click(Sender: TObject);
var
N :Integer;
begin
N:=1;
Memo1.Lines.Add('Вариант '+inttostr(N));
end;
Нажатие кнопки "выполнить" тем самым приведёт к появлению надписи: "Вариант 1". В сущности, завершённая прога. Но не для вычисления а так — что-то показать.
Надо это делать для того, чтобы после "не искать ёжика в тумане" — ошибки сделанные на давно пройденных этапах.
И да: N — вводится как ЛОКАЛЬНАЯ переменная.
Зачем?
Да для того, чтобы после, при очередном нажатии кнопки "Вычислить" исчисление вариантов шло по-новой. С первого. Таким образом можно делать неограниченное кол-во вариантов и контрольных.
Для этого хорошо вводить циклы типа For. Он жёстко задаёт кол-во итераций, и нет опасности зацикливания. Посавим "для тренировки" диапазон значений N для цикла от 1 до 30. Откомпилируем файл.
Получим:
Но тут возникает идея, что стоило бы сделать так ,чтобы пользователь, без перекомпиляции исполняемого файла мог бы сам задавать кол-во этих вариантов. Хорошо! Вводим элемент SpinEdit на панель.
На вкладышах Delphi он находится в Samples.
Чтобы было понятно, для чего сей элемент, рядом ставим элемент Label чтобы отображал надпись "Потребное кол-во вариантов". В самом же SpinEdit свойство Value устанавливаем в 20. Т.е. Кол-во вариантов устанавливается им в 20 штук. Так как можно нащёлкать в нём любое кол-во от нуля до "скокозахочишь" — на откуп пользователю.
При этом стоит исправить слегка обработчик. Ведь ранее N задавался явно до цикла и цикл прокручивался до N. Теперь надо указать ,что цикл прокручивается до значения, указанного в свойстве Value элемента SpinEdit1 (чтобы сразу брал число, что пользователь наклацает).
procedure TForm1.Btn2Click(Sender: TObject);
var
N :Integer;
begin
for N:=1 to SpinEdit1.Value do
begin
Memo1.Lines.Add('Вариант'+inttostr(N));
end;
end;
(прим.: функция inttostr(N) переводит значение N типа Integer в строчный — String)
После правки весьма полезно откомпилировать и посмотреть не закралась ли тут какая-нибудь мелкая ошибка. Заметьте: каждый раз мы доводим до какого-то этапа, когда программа является как-бы законченной. "Как-бы" — это в смысле "можно откомпилировать до исполняемого файла и получить какой-то результат".
Т.е. Если будут "висеть сопли" из недописанного кода, то никакого исполняемого вы не получите, а получите ругань компилятора, что он там нашёл некие "ошибки" и не может сделать исполняемый файл (впрочем, может и скомпилировать, но ответ чаще всего будет несуразный) .
Поэтому, если вы таки что-то наперёд написали, но оно не закончено, то обязательно, перед проверкой-компиляцией заключайте эти куски кода в фигурные скобки {} или на каждую строку ставьте "заглушку" в виде //. Т.е. Переводите эти куски в статус "комментариев" и компилятор их пропустит.
Смысл этого приёма в том, что вы проверяете код кусками. И дальше, в случае появления ошибок, вам не нужно проверять то, что уже работало до.
Кроме этого, если вы не поняли где в вашем блоке идёт ошибка, можно снимать // по отдельным операторам и строкам. Тогда будет видно на чём ваша прога начинает работать неправильно.
Итак, по сути, разработка интерфейса практически завершена.
Мы уже видим как примерно будет выводиться результат. И кусок кода, ответственный за этот вывод работает.
Можно приступать к разработке кода, выводящего на печать сначала матрицы, а после и конкретные текстовые варианты систем уравнений.
— Шаг второй — разработка кода вычисления матриц
Тут стоило бы пояснить почему так — "вычисления матриц".
По сути, программа будет высчитывать и выводить на печать системы уравнений.
Чтобы они были разными, нужно сделать так, чтобы коэффициенты генерировались случайным образом. Но тут возникает проблема: если определитель матрицы коэффициентов равен нулю, то система не имеет решений. Следовательно, надо отсечь от печати именно такие варианты.
Отсюда, пишем код с генерацией конкретных коэффициентов функцией random, и проверяем получившийся набор "на паршивость". Все наборы, определитель которых равен нулю — отбрасываем. Но чтобы соблюсти технологию, вся процедура у нас разбивается на следующие этапы.
— Первый этап — генерация коэффициентов и вывод их на печать
Последнее всё равно понадобится и подправить ту строку вывода достаточно просто если есть уже основа.
Значит, первым делом объявляем двумерный массив: а: array [1..3,1..3] of Integer; так же локально в рамках процедуры, обрабатывающей нажатие кнопки "Вычислить".
Аналогично объявляем массив b: array [1..3] of Integer; В нём будут содержаться числа чему равны каждое из уравнений системы. Да, на данном этапе они не используются, но если есть возможность копипастой продублировать строку и исправить пару символов — почему бы и не?
Вставлять циклы с генератором надо внутрь цикла пишущего номер варианта. Таким образом, для каждого нового варианта у нас будет генерироваться новый набор коэффициентов.
Ясное дело, что надо бы проверить — всё ли хорошо там присваивается. Значит, получающиеся коэффициенты весьма полезно вывести на печать и посмотреть что там в каждом: Memo1.Lines.Add('a11='+inttostr(a[1,1])+' a12='+inttostr(a[1,2])+' a13='+inttostr(a[1,3]));
Обратите внимание на запись ' a12='. После открывающей кавычки вставлены два пробела. Также и для третьего элемента. Это служит для того, чтобы запись предыдущего не сливалась с последующей.
(прим.: функция inttostr служит для перевода целого значения типа Integer в строковый тип).
Итого наш код процедуры стал выглядеть так:
procedure TForm1.Btn2Click(Sender: TObject);
var
N,NN,k,m :Integer;
a: array [1..3,1..3] of Integer;
b: array [1..3] of Integer;
begin
for N:=1 to SpinEdit1.Value do
begin
Memo1.Lines.Add('Вариант '+inttostr(N)); //выводит "вариант N"
for k:=1 to 3 do
for m:=1 to 3 do
begin
a[k,m]:=Random(21); // присваиваем случайные целые числа от 0 до 20 элементам массива
end;
Memo1.Lines.Add('a11='+inttostr(a[1,1])+' a12='+inttostr(a[1,2])+' a13='+inttostr(a[1,3])); //выводим коэффициенты первой строки;
Memo1.Lines.Add('a21='+inttostr(a[2,1])+' a22='+inttostr(a[2,2])+' a23='+inttostr(a[2,3])); //выводим коэффициенты второй строки;
Memo1.Lines.Add('a31='+inttostr(a[3,1])+' a32='+inttostr(a[3,2])+' a33='+inttostr(a[3,3])); //выводим коэффициенты третьей строки;
end;
end;
результат работы программы на этом этапе:
Впрочем, после прикидки сложности вычисления определителей получаемых матриц, уменьшаю число выбора в рандоме до 10 (т. е. выбор будет от 0 до 9). Ибо неча студентов мучить! :-) (прим. В конце концов снизил до 4)
Примечание: вывод коэффициентов на Мемо сейчас — только для контроля результата. Т.е. Всё ли правильно делается и всё ли работает. Вполне естественно, что данный модуль вывода на Мемо впоследствии будет либо отключён скобками {}, либо существенно переработан уже для вывода полноценной системы уравнения.
— Второй этап: вычисление определителя |A|
Тут уже просто: берём уже сгенерированные коэффициенты и загоняем их в стандартную формулу. Попутно загоняя в блок var объявления новых переменных.
procedure TForm1.Btn2Click(Sender: TObject);
var
N,NN,A0,A1,A2,A3,k,m,l :Integer;
a: array [1..3,1..3] of Integer;
b: array [1..3] of Integer;
s: array [1..3,2..3] of String;
C: array [1..3,1..3] of Integer;
begin
Так как мне лень много писать, я в код загоняю формулу подсчёта определителя через миноры. А чоа? Ведь короче!
Таким образом в коде добавляется:
A:= a[1,1]*(a[2,2]*a[3,3]-a[2,3]*a[3,2])-a[2,1]*(a[1,3]*a[3,3]-a[1,3]*a[3,2])+a[3,1]*(a[1,2]*a[2,3]-a[2,2]*a[1,3]);
Memo1.Lines.Add('A='+inttostr(A)); //выводим значение определителя А;
Проверяем как эти строки работают.
Как видите, в Варианте 8 выскочило то, что ожидалось как неприятность, и которую надо отсекать: Определитель с данным набором оказался равен нулю. Т.е. "нет решений у системы".
Прим. авт.: вышеприведённый фрагмент кода с вычислением определителя слегка неверный. Мелкая хохмочка: я много писал на С++ и иногда забываю, что в Паскале нет различия между объявлением переменной в виде "А" и "а"(в С++ это будут РАЗНЫЕ переменные). А в этой проге поймался на "сишных" стереотипах. Пришлось записи к А прибавлять нолик в объявлении типов после рыка компилятора, "redeclared 'a'" поэтому сейчас значение определителя заносится в переменную "А0". То, что в выводе на мемо не менял вид — это уже мелочи. Так запись 'A=' ни что иное как просто строка символов никакого отношения не имеющая в коде к переменной А0 типа Integer).
Вполне естественно, что наборы значений коэффициентов ведущих к появлению А=0 надо отсекать. Значит, при появлении такого значения, надо пересчитывать коэффициенты для данного варианта. Это значит, что здесь нужен небольшой цикл или оператор условия.
Так как связка If — GO TO ныне моветон, используем цикл repeat — until(условие);
Данный оператор выполняется хотя бы один раз, если выполняется условие и многажды, до тех пор, пока не будет выполнено условие. Т.е. Если значение условия False(в нашем случае А0=0) то он выполняется до тех пор, пока не примет значение True (т. е. А0 не ноль).
repeat
for k:=1 to 3 do
for m:=1 to 3 do
begin
l:=Random(2);
if l>0 then
a[k,m]:=Random(10) //присваиваем положительные целые числа от 0 до 9 если l>0
else a[k,m]:=(-1)*Random(10); //присваиваем положительные целые числа от 0 до 9 если l=0
end;
A0:= a[1,1]*(a[2,2]*a[3,3]-a[2,3]*a[3,2])-a[2,1]*(a[1,2]*a[3,3]-a[1,3]*a[3,2])+a[3,1]*(a[1,2]*a[2,3]-a[2,2]*a[1,3]);
until((A0<0)or(A0>0));
Прим: ясное дело, что выразить условие можно ещё двумя способами. Я вставил то, что первым на ум пришло. Бо "красиво смотрится". Так как это — вопрос вкуса. :-)
Как показывает тестовый прогон получившейся проги, А=0 больше не появляется. Чего и добивались.
— Третий этап: вычисление А1,А2,А3 и собственно решения системы уравнений
Для этого во-первых, нужно сгенерить b1,b2,b3.
Уж чего проще, так как код для этого уже есть. Остаётся только скопипастить блоком и поправить где надо.
Чтобы то, что уже сделано и работает не мозолило глаза, закрываем скобками{} превращая временно в "комментарии".
Memo1.Lines.Add('A='+inttostr(A0)); //выводим значение определителя А;
for m:=1 to 3 do
begin
l:=Random(2);
if l>0 then
b[m]:=Random(10) // присваиваем положительные случайные целые числа от 0 до 9 элементам массива b если l>0
else b[m]:=(-1)*Random(10); // присваиваем отрицательные случайные целые числа от 0 до 9 элементам массива b если l=0
end;
Memo1.Lines.Add('b1='+inttostr(b[1])+' b2='+inttostr(b[2])+' b3='+inttostr(b[3]));
{Memo1.Lines.Add('a11='+inttostr(a[1,1])+' a12='+inttostr(a[1,2])+' a13='+inttostr(a[1,3])); //выводим коэффициенты первой строки;
Memo1.Lines.Add('a21='+inttostr(a[2,1])+' a22='+inttostr(a[2,2])+' a23='+inttostr(a[2,3])); //выводим коэффициенты второй строки;
Memo1.Lines.Add('a31='+inttostr(a[3,1])+' a32='+inttostr(a[3,2])+' a33='+inttostr(a[3,3])); //выводим коэффициенты третьей строки;
}
После успеха проверки можно вставить вычисление А1, А2, А3. Вывести их на Мемо(понадобится) и вывести решения системы в виде Х=А1/А и т. д.
↓ Содержание ↓
↑ Свернуть ↑
| Следующая глава |