Выпуск 13: Циклы

 

Архив рассылки

Доброго времени суток! :)

Для новых подписчиков я опять повторяю: Сначала прочитайте все предыдущие выпуски, которые находятся в архиве рассылки!

Сначала поговорим насчёт плана создания окна на WinApi. Вам надо запомнить из каких частей состоит код программы. Я представляю всё так.

1. Объявление модулей
2. Переменные и константы
3. Оконная процедура
    3.1 Создание функции
    3.2 Реакция на сообщения
4. Главная программа
    4.1 Определение класса окна
          4.1.01 Стиль
          4.1.02 Большая пиктограмма
          4.1.03 Маленькая пиктограмма
          4.1.04 Курсор
          4.1.05 Экземпляр программы
          4.1.06 Размер структуры класса
          4.1.07 Дополнительная память, используемая Windows по своему усмотрению
          4.1.08 Дополнительная память, используемая Windows по своему усмотрению
          4.1.09 Фон
          4.1.10 Имя меню
          4.1.11 Имя класса
          4.1.12 Указатель на оконную процедуру
    4.2 Регистрация класса
    4.3 Создание окна
          4.3.01 Ссылка на окно
          4.3.02 Имя класса
          4.3.03 Заголовок
          4.3.04 Стиль окна
          4.3.05 Позиция окна от левого края экрана
          4.3.06 Позиция окна от верхнего края экрана
          4.3.07 Ширина окна
          4.3.08 Высота окна
          4.3.09 Родительское окно (если имеется)
          4.3.10 Меню (если имеется)
          4.3.11 Экземпляр программы
          4.3.12 Дополнительный параметр
    4.4 Показ и обновление окна
    4.5 Главный цикл программы
          4.5.01 Получение сообщения
          4.5.02 Перевод сообщения на "понятный компьютеру язык"
          4.5.03 Отправление сообщения в оконную процедуру

    4.6 Выход из программы

Можно всё описать ещё детальнее, но и этого достаточно. Лично мне не нужен третий уровень. Я запоминаю первые два, а их реализация - дело элементарное :) Надо просто запомнить несколько вещей, например каких параметров требует сообщение (этого хватает, чтобы создать оконную процедуру) и ещё несколько функций в главном блоке программы. Тут нужна только практика.

И ещё. Не пытайтесь тупо запомнить все строчки кода. От этого станет только хуже. Вы должны понять, как всё это действует. Разберитесь в коде. "Переспите" с ним :) Тогда всё будет куда легче понять! И в OpenGL программах тоже. Я не думаю, что вы запомните 327 длинных строк! (конечно, не все обязательны, но все важны!)

Вы заметили, в Дельфи коде, там, где надо указать оконную процедуру (в аттрибутах класса окна), я использовал @? Это означает, что дальше идёт указатель. Указатели - это отдельная тема в программировании и преднозначена для чуть более продвинутых :) Вам хватает знать только то, что тут указывается, в какой функции хранится оконная процедура.

With..do

Как это называется? А я и сам не знаю :) Узнаю - скажу :) На самом деле этот оператор используется довольно редко и только в работе с объектами. В программе WinApi я его использовал, когда определял свойства класса окна. Я не знаю, есть ли такой оператор в Си.

Разговоры разговорами... :) Итак, для чего он нужен. Суть этого оператора облегчить вам работу со свойствами объекта, вернее не облегчить, а сократить время на кодирование. Используется, только когда надо у одного объекта изменить кучу свойств. Этот оператор освобождает программиста от записи имени объекта, перед тем, как изменить его свойство. Практика объяснит по лучше :) Вернёмся к визуальному программированию (Дельфи).

Допустим, вам надо во время исполнения программы изменить кучу свойств формы. Как вы это будете делать? Обычным способом, я думаю :)

Delphi:

procedure TForm1.Button1Click(Sender: TObject);
begin
 Form1.Caption:= 'Hello!';
 Form1.Color:= clBlack;
 Form1.Left:= 100;
 Form1.Top:= 100;
end;

Но зачем каждый раз повторять Form1.? Можно сделать куда удобнее и быстрее.

Delphi:

procedure TForm1.Button1Click(Sender: TObject);
begin
 With Form1
  do
   begin
    Caption:= 'Hello!';
    Color:= clBlack;
    Left:= 100;
    Top:= 100;
   end;
end;

Короче? Думаю, что да (хотя могло быть ещё короче :). Может пример и не очень впечатляет, но представьте себе, что свойств не 4, а 12 (как в нашей WinApi программе), или несколько десятков! Конечно, несколько десятков - это уже слишьком много, но всё зависит от вас :) В таком случае такой способ сократит ваш код и сделает его более наглядным. Как им пользоватся? Пишите with (с), потом указываете объект, за которым следует do. Конечно, если свойств несколько, вам предётся открыть дополнительные командные скобки. Можно оператор использовать и тогда, когда надо указать несколько свойств второго уровня, например:

Delphi:

 With Form1.Font
  do
   begin
    Color:= clRed;
    Size:= 40;
    Style:= [fsBold, fsItalic];
    Name:= 'Amaze';
   end;

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

Надеюсь вы поняли :) Тут то и объяснять нечего :) Если всётаки что то не понятно, пишите мне - объясню.

Циклы

Эта тема уже посвящается не только Дельфи, но и Си программистам :)

Циклы - очень важная тема в программировании. Без них - никуда!!! Вы в этом очень скоро и сами убедитесь. Начну с примера.

Delphi:

{$APPTYPE CONSOLE}

uses
  SysUtils;

var
  i: Integer;

begin
 i:= 1;
 WriteLn(IntToStr(i));
 i:= 2;
 WriteLn(IntToStr(i));
 i:= 3;
 WriteLn(IntToStr(i));
 i:= 4;
 WriteLn(IntToStr(i));
 i:= 5;
 WriteLn(IntToStr(i));
 ReadLn;
end.

C:

#include <stdio.h>
#include <conio.h>

int main()
{
    int i = 1;
    printf("%d\n", i);
    i = 2;
    printf("%d\n", i);
    i = 3;
    printf("%d\n", i);
    i = 4;
    printf("%d\n", i);
    i = 5;
    printf("%d\n", i);
    getch();
}

Что эта программа делает? Тупо выводит на экран "1 2 3 4 5". Можно код ещё укоротить.

C:

int main()
{
    int i = 1;
    printf("%d\n", i++);
    printf("%d\n", i++);
    printf("%d\n", i++);
    printf("%d\n", i++);
    printf("%d\n", i);
    getch();
}

Или так :)...

C:

int main()
{
    int i = 1;
    printf("%d\n", i);
    printf("%d\n", ++i);
    printf("%d\n", ++i);
    printf("%d\n", ++i);
    printf("%d\n", ++i);
    getch();
}

Вот где прелисть языка Си! :) Краткость! :) Не надо забывать, что переменные языка Си можно создать почти в любом месте программы! Чем отличаются i++ и ++i? А тем, что в первом варианте сначала отображается переменная, а потом её значение увеличивается на единицу. В другом варианте наоборот. Например (1 вариант) сначала показывается переменная i (со значением 1), потом увеличивается на единицу, т.е. значение становится 2. Во второй функции отображается переменная уже со значением 2, которое после этого опять увеличивается на единицу. В последней функцие уже отображается пятёрка, а переменную увеличивать больше не надо. Во втором варианте в первой функцие показывается значение переменной (1). Во второй - это значение увеличивается на единицу (уже становится 2) и опять отображается. В последней значение переменной (4) опять увеличивается на единицу (5), и снова отображается на экране. Но совмещать эти вещи не советуется, потому что у программы могут появится глюки! :)

Вернёмся к теме. В чём неудобства этой программы? Есть ли такие вообще? Ведь в Си это получилось даже очень удобно! Но представьте, что вам надо написать программу, которая складывает все числа от 1 до 100 или больше! Кто не знает формулы, тот 100 (или больше) раз будет повторять те же самые действия, т.е.: увеличение значения переменной на единицу и суммирование нового значения с предыдущим. Для простенькой программки предётся писать 200 (или больше) строчек кода! Тут на помощь и приходят циклы.

Циклы нужны для многократного использования группы действий (не важно каких). Циклы также можно вставлять в другие циклы.

Вот так будет выглядить программа, суммирующая числа от 1 до 100.

Delphi:

var
  i, sum: Integer;
begin
 sum:= 0;
 i:= 1;
 sum:= sum+i; // sum = 1
 i:= i+1; // i = 2
 sum:= sum+i; // sum = 1+2 = 3
 i:= i+1 // i = 3
 ...
 sum:= sum+i; // sum = n
end.

C:

int main()
{
    int i = 1, sum = 0;
    sum += i++; // sum = 0+i = 0+1 = 1; i = 1+1 = 2
    sum += i++; // sum = 1+i = 1+2 = 3; i = 2+1 = 3
    sum += i++; // sum = 3+i = 3+3 = 6; i = 3+1 = 4
    ...
    sum += i; // sum = n+i = n+100; i = 100
}

А теперь вам на сравнение :)

Delphi:

var
  i, sum: Integer;
begin
 sum:= 0;
 for i:= 1 to 100
  do
   sum:= sum + i;
end.

C:

int main()
{
    int sum = 0;
    for(int i = 1; i <= 100; i++)
     sum += i;
}

Эта программа делает тоже самое, что и предыдущая, т.е. складывает числа от 1 до 100 :) Ну как? :) Понравилось? :)

Здесь я использовал цикл for, который применяется, когда программист знает число оборотов цикла. Т.е. он вам понадобится, если вы знаете сколько раз должны повторятся нужные действия. Чтобы сложить 100 чисел, мне надо 100 раз повторить пару команд. Как это действует? Пишите for, потом переменной присваиваете начальное значение, за которым следует to и конечное значение. Это в Дельфи. В данном цикле с начала переменной присваивается единица, а конечное значение - сотня :) После каждого выполнения команды внутри цикла, переменная i увеличивается на единицу (автоматически). Значит нам не надо беспокоится за увеличение значения этой переменной. Остаётся только записать главный код, который всё и складывает. В Си запись чуть чуть отличается. Сначала переменной присваивается начальное значение (переменную можно создать прямо в скобках цикла), потом условие и действие по умолчанию. Условие - это не просто конечное значение. В данном случае цикл будет повторятся, пока переменная i будет меньше или равна 100. Действие по умолчанию - повидение этой переменной. В нашей программе эта переменная при каждом обороте цикла увеличивается на единицу. Можно записать и i--, но тогда цикл выполнятся будет хрен знает сколько раз! :) Потому что переменная i будет всегда меньше 100 и не дойдёт до границы (100). Такая программа нам не нужна :) Но можно изменить её так: for(int i = 100; i >= 1; i--). В этом случае программа будет хорошо действовать. Но в Дельфи случае так делать нельзя! to означает, что переменная будет увеличиватся и цикл не будет работать, если конечное значение будет меньше начального. Что делать в таком случае? to можно заменить на downto. Значит такая запись будет делать тоже самое: for i:= 100 DownTo 1 do....

Циклы используются очень часто. Они и вам понадобятся, и именно по этому после пары примеров я вас нагружу задачами! :) Вы их, конечно, можете не решать, но тогда из вас точно не получится хороший программист! И если вы вдруг застряли где то, не прекращайте работу! Лучше отдохните, а потом вернитесь к ней. Можете создать алгоритм решения задачи на листе бумаги. Хороший алгоритм - это не только хорошее начало, но и 80% (если не больше) всей работы! Написать подходящий код может каждый, но создать правильно действующий (а также удобный и наглядный) алгоритм - дело серьёзное и часто тяжёлое, по этому это задача не для каждого. А в OpenGL программировании алгоритм это даже 98% работы! Что делать? Практиковаться! Делать задания! Если возникли проблемы - спрашивать, выяснять что не так! И не в коем случае не оставлять на следущую неделю или типа этого, потому что, я вам гарантирую, вы забудете задачу! А если не забудете - то поленитесь её доделать, потому что предётся начать почти с самого начала! Лень - главный враг программиста. И не только лень! :) Если вы всё хорошо сделали - это не значит, что вы уже хорошие программисты, или лучше других, и вам также не обязательно решать остальные задачи. Это значит только то, что вы просто быстрее нашли решение именно этой задачи. Другие могут вам так легко не поддатся! А те, которые не сразу решили задачу, добиваются тоже много успеха, потому что выясняя проблему они получают новые знания и дополнительную практику. Выводы делать вам. Честно говоря я и сам леньтяй! :) Но если делаю то, что мне нравится, то меня от этой работы не оторвать :) Надеюсь, что вы будете иметь моё второе свойство и противоположное моему первому :)

Думаю, вы суть поняли. Сейчас придумаю ещё один примерчик. Допустим, вам надо на экран вывести сто разных значений. Не просто разных, а случайных чисел! Для вывода случайного числа в Дельфи существует функция random(). Её аргументом долно быть целое число - интервал. Т.е. написав random(6); будут генерироватся случайные числа от 0 до 5. Чтобы было от 1 до 6 (как и планировалось) надо просто после генерации прибавить единицу: random(6)+1;. В Си тоже существует похожая функция. Чтобы ею воспользоваться, надо подключить модуль stdlib.h. Называется она rand() и не имеет никаких параметров. Если её просто так вызвать, она сгенерирует кучу разных чисел, которые возмёт из воздуха :) Чтобы определить интервал, надо в конце добавить %n, где n - интервал. Т.е. такая функция может выглядить так: rand()%6+1.

Приступим к программированию.

В Дельфи сначала надо создать переменную - счётчик. Она должна быть целого типа. Потом надо создать сам цикл и вывести на экран полученное значение. Интервалом будет номер оборота.

Delphi:

var
  i: Integer;
begin
 for i:= 1 to 100
  do
   WriteLn(random(i)+1);
 ReadLn;
end.

Можете запустить программу и проверить значения. В Си тоже самое, только переменную я создам в скобках цикла.

C:

int main()
{
    for(int i = 1; i <= 100; i++)
     printf("%d\n", rand()%i+1);
    getch();
}

В зависимосьти от языка полученные значения немного отличаются. Но если запустите эти программы несколько раз, вы заметите, что полученые значения всё время такие же! Эту проблему можно решить :) В Дельфи в начале кода (сразу после открытия командных скобок) надо добавить Randomize;. И всё :) В Си, конечно, по другому. Там существует функция srand(). Она действует чуть чуть по другому, чем randomize. У неё есть один параметр - положительное число, от которого зависит случайность получаемых значений. Но, если там всегда будет стоять одно число, результат опять при каждом вызове функции будет таким же. Что делать? Очевидно, надо менять число в скобках. Очень хороший способ генерации - как аргумент использовать время! Вы каждый раз будете включать программу в разное время, и соответственно число в функции будет другое. Чтобы получить время, надо просто вставить функцию time(NULL). Не забудьте, что в качестве аргумента - NULL! Но, чтобы вставить время, надо подключить модуль time.h. Вот как всё теперь выглядит:

Delphi:

var
  i: Integer;
begin
 Randomize;
 for i:= 1 to 100
  do
   WriteLn(random(i)+1);
 ReadLn;
end.

C:

int main()
{
    srand(time(NULL));
    for(int i = 1; i <= 100; i++)
     printf("%d\n", rand()%i+1);
    getch();
}

Надеюсь, тут всё понятно :) Теперь об операторе % в Си. На самом деле он служит для другой цели :)

В Дельфи попробуйте число 25 разделить на десять. Только таким способом:

Delphi:

var
  i, x: Real;
begin
 i:= 25;
 x:= 25/10;
 WriteLn(FloatToStr(x));
 ReadLn;
end.

Получилось? Должно! :) А теперь тоже самое, только с целыми числами.

Delphi:

var
  i, x: Integer;
begin
 i:= 25;
 x:= 25/10;
 WriteLn(IntToStr(x));
 ReadLn;
end.

Работает? А знаете, почему нет? Всё очень просто :) Ведь после деления получается дробное число, а в целой переменной нельзя хранить дробного числа! Тут есть другие способы деления, т.е. можно отдельно получить целое число (вместо / надо использовать div), а также остаток от деления (надо использовать mod). В Си точно также, только вместо div там стоит тот же самый слэш (/), а вместо mod - %. Теперь вы знаете в чём тут дело и можете свободно использовать это в своих программах.

Задание #11: напишите программу, которая на экран выводит 100 случайных чисел. Также напишите функции (к программе), которые находят самое большое значение, самое маленькое значение, в каком месте они находятся (при котором обороте цикла). Сколько там нечетных чисел? И ещё укажите какой процент всех чисел занимают нечетные. Интервал случайных чисел должен быть от 119 до 539.

Не ожидали такого задания? :) Это вам шанс проверить свои знания. В будущем таких задачь будет ещё больше! Эта задача не такая уж и сложная, просто очень длинная :) Ну, ничего. Это вам только на пользу. У не вздумайте отмазаться от её решения! :)

На сегодня хватит. В следущем выпуске мы продолжим с циклами, а также вы получите ещё порцию задачь! :) Не забывайте практиковатся по созданию окна на WinApi. Это вы должны делать до того, пока я скажу СТОП. Только теперь это делайте раз в каждый второй день.

 

2005-11-04

Spider3D http://spider3d.narod.ru

Евгений Нарышкин Spider3D@yandex.ru

Архив Рассылки

Хостинг от uCoz