Шаг цикла
До сих пор переменная цикла менялась с шагом 1. Но оказывается шаг можно задавать любой и тогда оператор For удобно использовать вместо Do – программы получаются короче.
Пусть нужно распечатать числа 600, 605, 610, 615 . . . 900. Вот программа:
Dim a As Integer
For a = 600 To 900 Step 5
Debug.WriteLine(a)
Next a
Здесь Step 5 означает «Шаг 5».
Пусть нужно распечатать числа 3.00, 3.02, 3.04 . . . 5.00. Вот программа:
Dim a As Decimal
For a = 3 To 5 Step 0.02
Debug.WriteLine(Format(a, "0.00"))
Next a
Как видите, шаг может быть дробным. Тип переменной цикла я выбрал Decimal, если бы я выбрал Double, то при многократном прибавлении шага значение a стало бы чуть-чуть неточным.
Пусть нужно распечатать числа 5.00, 4.98, 4.96 . . . 3.00. Вот программа:
Dim a As Decimal
For a = 5 To 3 Step -0.02
Debug.WriteLine(Format(a, "0.00"))
Next a
Как видите, шаг может быть отрицательным.
Сравниваем For и Do. Таким образом, оператор For удобнее оператора Do в двух случаях: когда цикл нужно выполнить определенное количество раз и когда нам известны начальное и конечное значения переменной цикла и шаг. Однако за компактность и удобство For платит бедностью возможностей. Do гораздо более гибок, чем For.
Синтаксис и работа оператора For
Синтаксис оператора For:
For переменная цикла = выражение1 To выражение2 [ Step выражение3
]
операторы
операторы
……………..
Next [ переменная цикла ]
Переменная цикла должна иметь числовой тип. Обычно назначают Integer. Допустимы также нечисловые типы, для которых имеют смысл операции сложения и сравнения. В качестве примера приведу перечисления (см. 13.3).
После Next вы можете писать, а можете и не писать переменную цикла, но в программах, где много пар For и Next, я рекомендую это делать для легкости чтения программы, чтобы вам удобней было разобраться, для какого именно For написан данный конкретный Next.
Вместо чисел в операторе For можно писать переменные и выражения. Пример записи:
For I = s To 2*s+1 Step k*10
Если шаг не указан, он считается равным 1, то есть переменная на каждой итерации увеличивается на 1. Если же мы хотим уменьшать ее на 1, нам придется явно указать Step -1.
Работа оператора For
при положительном (или нулевом) шаге:
Прежде всего вычисляется выражение1, и переменной цикла (пусть это будет i) присваивается его значение. Затем вычисляется выражение2 и сравнивается с i. Если i> выражения2, то оператор For завершает свою работу, так ничего и не сделав. В противном случае выполняются операторы, стоящие между строками For и Next. После их выполнения значение i увеличивается на значение выражения3 (или при его отсутствии на 1) и снова сравнивается с выражением2. Если i > выражения2, то оператор For завершает свою работу, иначе снова выполняются операторы, снова i увеличивается и т.д.
при отрицательном шаге:
Прежде всего вычисляется выражение1, и переменной цикла (пусть это будет i) присваивается его значение. Затем вычисляется выражение2 и сравнивается с i. Если i < выражения2, то оператор For завершает свою работу, так ничего и не сделав. В противном случае выполняются операторы, стоящие между строками For и Next. После их выполнения значение i уменьшается на значение модуля выражения3 и снова сравнивается с выражением2. Если i < выражения2, то оператор For завершает свою работу, иначе снова выполняются операторы, снова i уменьшается и т.д.
Примечание: В VB 2003 в отличие от VB вы можете объявлять переменную цикла не заранее (оператором Dim), а непосредственно в заголовке цикла:
For переменная цикла As тип = выражение1 To …….
В этом случае областью видимости переменной цикла является блок оператора For (см. 11.3.4).
Оператор Exit For
Оператор Exit For – еще один способ выхода из цикла For. Применяется он совершенно аналогично оператору Exit Do, описанному в 8.3.8. .
Задание 34.
Напечатать с помощью оператора For:
Прямой счет: -5 -4 -3 -2 -1 0 1 2 3 4 5 Обратный счет: 5 4 3 2 1 0 -1 -2 -3 -4 -5 Конец счета
Мыльные пузыри» и другие шалости
Пришло время пожинать плоды ученья. В этом разделе вы получите несколько заданий на рисование красочных и пестрых картинок. Все они основаны на использовании циклов и случайных величин.
Разноцветное звездное небо. Рассмотрим пример. Вспомните задачу о звездном небе, которую мы решали в 7.3.2. Тогда за одно нажатие на кнопку рисовалась одна звезда. Оператор, рисующий одну белую звезду размером в 3 пикселя, выглядел так:
Граф.DrawEllipse(Pens.White, 500 * Rnd(), 400 * Rnd(), 3, 3)
Поставим задачу за одно нажатие на кнопку нарисовать «тыщу» звезд. Тем, кто знает операторы цикла, сделать это очень просто:
Dim i As Integer
For i = 1 To 1000
Граф.DrawEllipse(Pens.White, 500 * Rnd(), 400 * Rnd(), 3, 3)
Next
Впечатляет. Но мне не нравится, что звезда внутри «пустая», так как сделана из окружности, а не из круга. Заменим оператор рисования:
Граф.FillEllipse(Brushes.White, 500 * Rnd(), 400 * Rnd(), 3, 3)
Добьемся теперь, чтобы звездное небо рисовалось во всю форму:
Граф.FillEllipse(Brushes.White, Me.Width * Rnd(), Me.Height * Rnd(), 3, 3)
Картина звездного неба получится абсолютно реальной и изумительно красочной, если звезды будут разноцветные и разных размеров. Займемся сначала размером звезды. Пусть он будет случайным, причем самые крупные звезды имеют размер = 5:
Dim i, Размер_звезды As Integer
For i = 1 To 1000
Размер_звезды = 5 * Rnd()
Граф.FillEllipse(Brushes.White, Me.Width * Rnd(), Me.Height * Rnd(), Размер_звезды, Размер_звезды)
Next
Как по-вашему: почему я не написал вместо
Размер_звезды = 5 * Rnd()
Граф.FillEllipse(Brushes.White, Me.Width * Rnd(), Me.Height * Rnd(), Размер_звезды, Размер_звезды)
попроще:
Граф.FillEllipse(Brushes.White, Me.Width * Rnd(), Me.Height * Rnd(), 5 * Rnd(), 5 * Rnd())
Если не догадались, попробуйте и сразу увидите, почему. Звезды потеряют форму круга и станут разнокалиберными эллипсами.
Мы пока не готовы сделать цвет звезды случайным, поэтому поступим просто: выберем несколько любимых звездных цветов (скажем, белый, желтый, голубой и красный) и напишем в цикле соответствующее количество операторов рисования:
For i = 1 To 200
Размер_звезды = 5 * Rnd()
Граф.FillEllipse(Brushes.White, Me.Width * Rnd(), Me.Height * Rnd(), Размер_звезды, Размер_звезды)
Граф.FillEllipse(Brushes.Yellow, Me.Width * Rnd(), Me.Height * Rnd(), Размер_звезды, Размер_звезды)
Граф.FillEllipse(Brushes.LightBlue, Me.Width * Rnd(), Me.Height * Rnd(), Размер_звезды, Размер_звезды)
Граф.FillEllipse(Brushes.LightPink, Me.Width * Rnd(), Me.Height * Rnd(), Размер_звезды, Размер_звезды)
Next
Напоминаю, что сколько бы раз вы ни запускали программу с указанным фрагментом, картина созвездий на экране будет абсолютно одинакова. Если вам нужно, чтобы от запуска к запуску набор значений случайной величины менялся (а значит и созвездия), употребите разик до использования функции Rnd функцию Randomize.
Как замедлить работу компьютера. Пустой цикл. Если вы эстет (а я эстет), то вам захочется, чтобы звезды на небе зажигались помедленнее. Для этого достаточно, чтобы после рисования очередной звезды перед рисованием новой возникала небольшая пауза. Вообще-то, для этих целей используют таймер. Но поскольку вы с ним еще не знакомы, вставьте для замедления внутрь цикла оператор, который, ничего не изменяя на экране, будет выполняться достаточно долго. Обычно для этого используют «пустой цикл»:
For j = 1 To 1000000 : Next
Пока компьютер будет бестолку считать до миллиона, пройдет некая значительная доля секунды:
Dim i, j, Размер_звезды As Integer
For i = 1 To 20
Размер_звезды = 5 * Rnd()
Граф.FillEllipse(Brushes.White, Me.Width * Rnd(), Me.Height * Rnd(), Размер_звезды, Размер_звезды)
Граф.FillEllipse(Brushes.Yellow, Me.Width * Rnd(), Me.Height * Rnd(), Размер_звезды, Размер_звезды)
Граф.FillEllipse(Brushes.LightBlue, Me.Width * Rnd(), Me.Height * Rnd(), Размер_звезды, Размер_звезды)
Граф.FillEllipse(Brushes.LightPink, Me.Width * Rnd(), Me.Height * Rnd(), Размер_звезды, Размер_звезды)
For j = 1 To 10000000
: Next j 'Пустой цикл
Next i
Здесь маленькая пауза возникает после рисования каждой четверки звезд.
Задание 35.
“Дождь в луже”. Поместите на форму PictureBox. Покрасьте его в цвет воды. Сделайте ему рамочку. Все это в режиме проектирования. Это наша лужа или, скорее, бассейн. Нарисуйте на нем в случайных местах штук 20 эллипсов, сжатых по вертикали раза в два. Результат – на Рис. 8.2.
Рис. 8.2
Задание 36.
«Мыльные пузыри». Разноцветные окружности случайных радиусов на темном фоне.
Задание 37.
«Сноп света в глаза». То есть пучок лучей, выходящих из одной точки. Реализуется множеством случайных разноцветных отрезков прямых, причем одна точка всех отрезков не случайна, а находится в центре формы. Хорошо смотрится на черном фоне.
Задание 38.
«Стог сена». Множество случайных разноцветных отрезков прямых преимущественно желтоватых оттенков, причем одна точка любого отрезка находится в случайной точке левой трети стога, другая – в случайной точке правой. Размер стога – 600 на 600.
Оператор цикла For
Я говорил, что имеются две разновидности оператора For. Здесь я рассмотрю только одну. Вторая будет разобрана в 16.2.2.
Вспомните программу печати чисел 3 5 7 9:
f = 3
Do
Write(f & " ")
f = f + 2
Loop While f <= 9
Здесь было выполнено 4 итерации цикла Do или, как еще говорят, цикл выполнился 4 раза, или, как говорят уже не очень правильно, было выполнено 4 цикла. Но обычно, когда мы пишем операторы Do, нам совсем не интересно знать, сколько итераций будет сделано.
Тем не менее, существует множество задач, для решения которых цикл нужно выполнить именно определенное количество раз. В этом случае вместо оператора Do удобнее использовать оператор цикла For.
Используем в рисовании переменные величины
Я уже потихоньку использовал в рисовании переменные величины, но без особых пояснений, и, главное, в недостаточной степени. Переменные должны почти полностью вытеснить числа из текста программ, это неизбежно.
Если вы нарисовали человечка и паровоз, то наверное согласитесь, что для этого вам пришлось основательно потрудиться, хотя сам рисунок получился не слишком сложным, в нем всего-то порядка двух десятков элементов.
Как заставить VB короткой программой рисовать множество элементов, сплетая их в красивые узоры? Ответ: применять циклы, используя в обращениях к графическим методам вместо чисел переменные величины и арифметические выражения. Поучимся.
Задача: Нарисовать горизонтальный ряд окружностей диаметром 20 на расстоянии 100 от верхнего края формы и с такими горизонтальными координатами 50, 80, 110, 140, ¼ , 290.
Как видим, центры соседних окружностей отстоят друг от друга на 30. Вот примитивный фрагмент, решающий эту задачу:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim Граф As Graphics = Me.CreateGraphics
Граф.DrawEllipse(Pens.Black, 50, 100, 20, 20)
Граф.DrawEllipse(Pens.Black, 80, 100, 20, 20)
Граф.DrawEllipse(Pens.Black, 110, 100, 20, 20)
Граф.DrawEllipse(Pens.Black, 140, 100, 20, 20)
Граф.DrawEllipse(Pens.Black, 170, 100, 20, 20)
Граф.DrawEllipse(Pens.Black, 200, 100, 20, 20)
Граф.DrawEllipse(Pens.Black, 230, 100, 20, 20)
Граф.DrawEllipse(Pens.Black, 260, 100, 20, 20)
Граф.DrawEllipse(Pens.Black, 290, 100, 20, 20)
End Sub
При вводе этой программы вас будет раздражать необходимость вводить много раз почти одно и то же. Воспользуйтесь копированием, которое объяснено в Приложении 2.
Мы видим, что здесь VB 9 раз выполняет один и тот же метод DrawEllipse, причем при каждом следующем обращении второй параметр метода вырастает на 30.
Придумаем для этого параметра переменную величину, например, х. Немного изменим программу:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Dim Граф As Graphics = Me.CreateGraphics
Dim x As Integer = 50
Граф.DrawEllipse(Pens.Black, x, 100, 20, 20) : x = x + 30
Граф.DrawEllipse(Pens.Black, x, 100, 20, 20) : x = x + 30
Граф.DrawEllipse(Pens.Black, x, 100, 20, 20) : x = x + 30
Граф.DrawEllipse(Pens.Black, x, 100, 20, 20) : x = x + 30
Граф.DrawEllipse(Pens.Black, x, 100, 20, 20) : x = x + 30
Граф.DrawEllipse(Pens.Black, x, 100, 20, 20) : x = x + 30
Граф.DrawEllipse(Pens.Black, x, 100, 20, 20) : x = x + 30
Граф.DrawEllipse(Pens.Black, x, 100, 20, 20) : x = x + 30
Граф.DrawEllipse(Pens.Black, x, 100, 20, 20) : x = x + 30
End Sub
В последней строке оператор x = x + 30 я написал только для красоты, от него в ней нет никакой пользы, хотя и вреда тоже особого нет.
Эта программа рисует абсолютно то же самое, что и предыдущая, но она проще нее, так как не пришлось самим вычислять координаты.
Что мы видим? Мы видим, что программа состоит из 9 совершенно одинаковых фрагментов. Это прямое приглашение применить цикл:
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
Dim Граф As Graphics = Me.CreateGraphics
Dim x As Integer= 50
Do Until x > 290
Граф.DrawEllipse(Pens.Black, x, 100, 20, 20) : x = x + 30
Loop
End Sub
Эта программа также рисует абсолютно то же самое, что и две предыдущие, но она существенно короче. Здесь я объявил переменную x, как целую. Иначе при многократном прибавлении 30 могло бы оказаться, что результат равен не 290, а, скажем, 290.0000067 (Такой же случай я рассматривал в 8.2). А это значит, что последняя окружность не была бы нарисована. Можно было бы перестраховаться по-другому: вместо Do Until x > 290 написать Do Until x > 291.
Кое-чему мы научились. Когда у вас возникнут проблемы с использованием переменных величин в графических (и не только графических) задачах, постарайтесь пройти по ступенькам, по которым только-что прошел я: от чисел – к одинаковым фрагментам, а от них – к циклу.
Задание 39.
«Труба». А. Попробуйте уменьшить расстояние между центрами окружностей, не изменяя их радиуса, нарисовав их плотнее, чтобы они пересекались, еще плотнее, пока они не образуют «трубу».
Б. Удлините трубу налево и направо до краев формы.
В. Увеличьте толщину трубы.
Ряд окружностей наискосок. Заставим окружности вести себя посложнее. Например, расположим их не по горизонтали, а примерно по диагонали формы в направлении от левого верхнего угла вправо вниз.
Начнем с первой ступеньки:
Граф.DrawEllipse(Pens.Black, 0, 0, 20, 20)
Граф.DrawEllipse(Pens.Black, 30, 20, 20, 20)
Граф.DrawEllipse(Pens.Black, 60, 40, 20, 20)
Граф.DrawEllipse(Pens.Black, 90, 60, 20, 20)
На второй ступеньке организуем две переменные: горизонтальную координату x и вертикальную координату у – и заставим их изменяться:
x = 0
y = 0
Граф.DrawEllipse(Pens.Black, x, y, 20, 20) : x = x + 30 : y = y + 20
Граф.DrawEllipse(Pens.Black, x, y, 20, 20) : x = x + 30 : y = y + 20
И наконец мы на последней ступеньке:
Dim x, y As Integer
x = 0
y = 0
Do Until x > 290
Граф.DrawEllipse(Pens.Black, x, y, 20, 20)
x = x + 30
y = y + 20
Loop
Задания. Теперь вы достаточно подготовлены для того, чтобы выполнить следующие задания:
Задание 40.
«Две очереди трассирующими ночью». Мы «перечеркнули» форму окружностями наискосок из левого верхнего угла приблизительно в правый нижний. Пусть теперь это будут очень маленькие кружочки на черной форме. Получилась «очередь трассирующими». Вслед за этим перечеркните форму очередью также и из левого нижнего угла, чтобы на экране получились две пересекающиеся очереди.
Задание 41.
Нарисуйте «трубу» наискосок. Получилось? Теперь попробуйте вместе с координатами x и у менять также и диаметр окружности. Если вы будете понемножку его увеличивать, начиная с 0, то получите «трубопровод», уходящий в бесконечность.
Задание 42.
Начертите ряд квадратов. Получилось? Теперь сделайте «квадратный трубопровод».
Задание 43.
А. Разлинуйте экран в линейку.
Б. А теперь в клетку.
В. А теперь в косую линейку.
Во дворце 40 залов. Компьютер
Во дворце 40 залов. Компьютер запрашивает длину, ширину и высоту каждого зала. Вычислить площадь пола и объем каждого зала.
Сначала напишем фрагмент для одного зала:
Dlina = InputBox("Введите длину") 'Начало фрагмента
Shirina = InputBox("Введите ширину")
Visota = InputBox("Введите высоту")
S = Dlina * Shirina 'Площадь пола
V = S * Visota 'Объем
Debug.WriteLine("Площадь пола = " & S & " Объем зала = " & V) 'Конец фрагмента
Для решения задачи этот фрагмент нужно выполнить 40 раз, для чего вполне естественно вложить его внутрь оператора For:
Dim Dlina, Shirina, Visota, S, V As Double
Dim i As Integer
For i = 1 To 40
Dlina = InputBox("Введите длину") 'Начало фрагмента
Shirina = InputBox("Введите ширину")
Visota = InputBox("Введите высоту")
S = Dlina * Shirina 'Площадь пола
V = S * Visota 'Объем
Debug.WriteLine("Площадь пола = " & S & " Объем зала = " & V) 'Конец фрагмента
Next
Полужирным шрифтом я выделил новые по сравнению с предыдущим фрагментом строки.
Чтобы программа подходила для любого числа залов, нужно вместо строки
For i = 1 To 40
написать две строки:
N = InputBox("Сколько залов во дворце?")
For i = 1 To N
Задание 44.
Построить диаграмму численности населения городов (см. Рис. 10.1).
Рис. 10.1
Подробнее: Компьютер запрашивает количество городов на диаграмме. Затем спрашивает название города и число его жителей, после чего строит первый столбец диаграммы с надписью. Затем запрашивает данные о втором городе, строит второй столбец и так далее. Неплохо бы под столбцом указывать число жителей.
Роль ошибок в программе
Пусть во дворце три зала размерами 20*15*4, 30*20*5 и 10*5*3. В этом случае, выполняя программу предыдущего подраздела, мы вводим N=3 и оператор For выполняет цикл три раза.
Мы знаем, что по ошибочной программе компьютер выдает ошибочные результаты. Попробуйте угадать результаты, если в программе мы вместо V=S*visota напишем V=S+visota. Ответ:
Площадь пола = 300 Объем зала = 304
Площадь пола = 600 Объем зала = 605
Площадь пола = 50 Объем зала = 53
Если же вы случайно вместо For i=1 To N напишете For i=2 To N и не заметите этого, то результаты будут такими:
Площадь пола = 300 Объем зала = 1200
Площадь пола = 600 Объем зала = 3000
На этом программа закончит работу и не спросит размеров третьего зала. Вам не кажется странным, что она посчитала 1 и 2 залы, а не 2 и 3? Если кажется, то учтите, что вы ничего не знаете об ошибке в программе, а компьютер не говорит вам, размеры какого по счету зала нужно вводить.
Задание 45.
Определите без компьютера, что он напечатает, если
А. Строку For i=1 To N поместить на три строки ниже, а именно – перед строкой S=Dlina*Shirina
Б. Поменять местами строки Debug.WriteLine и Next
Если задания не получаются, введите программы в компьютер и используйте пошаговый режим.
Вычисления в цикле
Вычисления в цикле встречаются очень часто и поэтому, несмотря на то, что никакой особой техники тут не требуется, я рассмотрю пример.
Счетчики
Счетчик - это переменная величина, в которой вы что-нибудь подсчитываете. Для чего нужны счетчики? Ну хотя бы для того, чтобы подсчитать количество жизней главного персонажа в компьютерной игре.
Задача 1: В компьютер с клавиатуры вводятся числа. Компьютер после ввода каждого числа должен печатать, сколько среди них уже введено положительных.
Фрагмент, решающий задачу:
Dim a, c As Integer
c = 0 'Обнуляем счетчик
Do
a = InputBox("Введите очередное число")
If a > 0 Then c = c + 1
Debug.WriteLine("Введено положительных чисел - " & c)
Loop
Пояснения: Нам уже приходилось сталкиваться со счетчиком. Это был счетчик циклов. Тогда мы просто придумали некую переменную, которую и назвали счетчиком циклов. Здесь мы тоже придумали переменную c. Она у нас выполняет роль счетчика положительных чисел. Сердце счетчика – оператор c=c+1. Именно он в нужный момент увеличивает счетчик на 1. Но и без части If a>0 Then тоже никак нельзя. Если бы ее не было, то c подсчитывал бы все числа без разбору, то есть был бы обыкновенным счетчиком циклов. В нашем же фрагменте увеличение с на 1 выполняется не всегда, а лишь при положительном а.
Обязательно покрутите программу в пошаговом режиме.
В сложных программах не забывайте обнулять счетчик перед входом в цикл, а не то он начнет считать вам не с нуля, а бог знает с чего. А это нехорошо. Как бы вам понравилось, если бы таксист в начале поездки не обнулил счетчик?
Задача 2: В предыдущем фрагменте значения счетчика печатаются при каждом выполнении цикла. Изменим задачу: В компьютер вводится ровно 200 чисел. Компьютер должен подсчитать и в конце один раз напечатать, сколько среди них положительных.
Программа:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Dim a, c, i As Integer
For i = 1 To 200
a = InputBox("Введите очередное число")
If a > 0 Then c = c + 1
Next i
Debug.WriteLine("Из них положительных - " & c)
End Sub
Пояснения: Путь рассуждений здесь тот же, что и в первой задаче. В результате применения оператора For тело цикла выполняется ровно 200 раз, благодаря чему счетчик с накапливает нужное значение. Оператор Debug.WriteLine выполняется только один раз и печатает последнее накопленное значение, потому что в ячейке с в момент печати будет находиться именно оно..
Задание 46.
«Ошибки». Что будет, если
А. Вместо c=0 написать c=10
Б. Вместо c=c+1
написать c=c+2
В. Строки Next и Debug.WriteLine поменять местами
Г. Строки c=0 и For поменять местами
Д. Строки For и InputBox поменять местами
Задача 3: А в следующей программе мы используем уже два счетчика. Изменим задачу: В компьютер вводится ровно 200 чисел. Компьютер должен подсчитать и один раз напечатать, сколько среди них положительных чисел и сколько нулей.
Программа:
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
Dim a, i, c_полож, c_нулей As Integer
c_полож = 0 'Обнуляем счетчик положительных чисел
c_нулей = 0 'Обнуляем счетчик нулей
For i = 1 To 200
a = InputBox("Введите очередное число")
If a > 0 Then c_полож += 1
If a = 0 Then c_нулей += 1
Next i
Debug.WriteLine("Из них положительных - " & c_полож & " нулей - " & c_нулей)
End Sub
Задача 4: Подсчитывать можно не только числа, но и строки и данные любых других типов. Как, например, узнать, насколько Лев Толстой любил слово «добро»? Для этого можно подсчитать, сколько раз встречается это слово в его произведениях. Решим похожую задачу:
В компьютер один за другим вводятся произвольные символы. Ввод заканчивается символом "/". Подсчитать, какой процент от общего числа введенных символов составляют символ «W» и символ «:» по отдельности.
Здесь мы организуем три счетчика одновременно: сW – для подсчета букв W, сDv – для подсчета двоеточий, а также i – счетчик общего числа введенных символов, кроме «/».
Программа:
Private Sub Button4_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button4.Click
Dim i, cW, cDv As Integer
Dim simvol As String
i = 0 : cW = 0 : cDv = 0 'Обнуляем все три счетчика
Do
simvol = InputBox("Введи символ")
If simvol <> "/" Then i = i + 1 'Если это не /, то "посчитай" его
Select Case simvol
Case "W" 'Если это W, то
cW = cW + 1 'увеличь счетчик символов W
Case ":" 'Если это : , то
cDv = cDv + 1 'увеличь счетчик символов :
Case "/" 'Если это /, то
Exit Do 'завершай работу цикла
End Select
Loop
Debug.WriteLine("Символов W было " & Format(cW / i, "P") & " Двоеточий было " & Format(cDv / i, "P"))
End Sub
Печатаются результаты в таком виде:
Символов W было 48,39% Двоеточий было 3,23%
Здесь cW/i дает долю символов W в общем числе символов, а формат "P" преобразовывает эту долю в проценты.
Задание 47.
В компьютер вводится N чисел. Подсчитать из них по-отдельности количество положительных, отрицательных и тех, что превышают число 10.
Задание 48.
В компьютер вводятся пары целых чисел. Подсчитать, сколько среди них пар, дающих в сумме число 12. Подсчет закончить вводом пары нулей.
Задание 49.
«Считаем звезды». Возьмите решенную нами задачу о звездном небе и подсчитайте заодно, сколько звезд из 100 попадет в левую часть неба (конкретнее, сколько звезд отстоят от левого края формы не более, чем на половину ее ширины). Пусть компьютер тут же ответит и на вопрос: где больше звезд – в левом верхнем квадрате неба размером 100 на 100 или в левом нижнем квадрате того же размера (см. Рис. 10.2)? Указание: Вам придется использовать переменные величины – координаты звезд.
Рис. 10.2
Сумматоры
Сумматор – это переменная величина, в которой вы подсчитываете сумму чего-либо. Для чего нужны сумматоры? Ну хотя бы для того, чтобы подсчитать общее количество золота, которое вы нашли в нескольких кладах в компьютерной игре.
Если вы поняли идею счетчика, то понять идею сумматора будет нетрудно. Посмотрим, как работает следующий фрагмент:
s = 0 'Обнуляем сумматор. Это не менее важно, чем обнулить счетчик
Do
a = InputBox("Введите очередное число")
s = s + a 'Увеличиваем сумматор
Debug.WriteLine("Сумма= " & s)
Loop
В ячейке s накапливается сумма вводимых чисел a, поэтому назовем эту ячейку сумматором. Отличие сумматора от счетчика в том, что счетчик увеличивается на 1 оператором c=c+1, а сумматор – на суммируемое число оператором s=s+a.
Задача: В компьютер вводится N чисел. Вычислить и один раз напечатать их сумму.
Программа:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Dim a, s, i, N As Integer
N = InputBox("Сколько чисел будем складывать?")
s = 0
For i = 1 To N
a = InputBox("Введите очередное число")
s = s + a
Next i
Debug.WriteLine("Сумма равна " & s)
End Sub
Задание 50.
«Ошибки». Пусть N=2, a =5 и 3. Тогда по вышеприведенной программе VB напечатает 8. Определите без компьютера, что он напечатает, если:
А. Вместо s=0 написать s=10.
Б. Вместо s=s+a написать s=s+a+1.
В. Строки Next и Debug.WriteLine поменять местами.
Г. Строки s=0 и For поменять местами.
Д. Строки For и a=InputBox поменять местами.
Е. Строки s=s+a и Next поменять местами.
Ж. Вместо For i=1 To N написать For i=2 To N.
Задание 51.
Во дворце 40 залов. Известны длина и ширина каждого зала. Вычислить площадь пола всего дворца.
Задание 52.
Вычислить средний балл учеников вашего класса по физике. Указание: Средний балл находится делением суммы баллов на число оценок.
Задание 53.
Вычислить произведение N произвольных чисел. Подсказка: Несмотря на то, что произведение – не сумма, эта программа будет отличаться от программы суммирования всего лишь одним числом и одним значком, а структура обеих программ совершенно одинакова.
Для тренировки определите без компьютера,
Для тренировки определите без компьютера, что напечатает следующий фрагмент:
a = 9
For i = 1 To 6
If i * i = a Then
For k = 5 To 6
Debug.Write(k)
If 3 > 2 Then a = 16
Next k
Else
Debug.WriteLine(2004)
End If
Next i
Здесь внутрь For i вложен If i*i=a , внутрь которого вложен For k, внутрь которого в свою очередь вложен If 3>2. Обратите внимание на различную величину отступов от левого края листа.
Ответ:
2004
2004
56562004
2004
Вложенные циклы – «Таблица умножения»
Вложенные циклы или «цикл внутри цикла» – весьма распространенная конструкция при программировании.
Поставим себе задачу – вычислить и напечатать таблицу умножения (см. Рис. 10.3).
Рис. 10.3
Сразу же рисовать прямоугольники и писать на форме нам будет сложно, поэтому пока будем печатать таблицу в окне Output при помощи оператора Debug.Write.
Начнем с малого – пусть нужно напечатать
1*1=1
Вот фрагмент программы:
Фрагмент 1
a = 1
b = 1
proizv = a * b
Debug.Write (a & "*" & b & "=" & proizv)
Здесь в операторе Debug.WriteLine 5 элементов:
* сомножитель a
* символ знака умножения "*"
* сомножитель b
* символ "="
* произведение proizv
Я намеренно ввел во фрагмент максимально возможное число переменных, так как предвижу, что фрагмент станет сердцем более сложной программы для вычисления всей таблицы, где сомножители меняются.
Печатаем строку. Попробуем заставить компьютер напечатать первый ряд таблицы:
1*1=1 1*2=2 1*3=3 1*4=4 1*5=5 1*6=6 1*7=7 1*8=8 1*9=9 1*10=10
Замечаем, что здесь нам нужно решить 10 элементарных задач на вычисление произведения, первую из которых решает фрагмент 1. Все они очень похожи и различаются лишь значением второго сомножителя. Таким образом, для решения каждой из 10 задач подошел бы наш фрагмент 1, если бы в нем в операторе b=1 вместо единицы стояло нужное число. В данном случае идеально подходит оператор For:
Фрагмент 2
a = 1
For b = 1 To 10
proizv = a * b
Debug.Write(a & "*" & b & "=" & proizv & " ")
Next
Обратите внимание, что для пробела между столбцами будущей таблицы в оператор Debug.Write добавился 6-й элемент – " ".
Прокрутите программу в пошаговом режиме.
6*1=6 6*2=12 6*3=18 6*4=24 6*5=30 6*6=36 6*7=42 6*8=48 6*9=54 6*10=60
7*1=7 7*2=14 7*3=21 7*4=28 7*5=35 7*6=42 7*7=49 7*8=56 7*9=63 7*10=70
8*1=8 8*2=16 8*3=24 8*4=32 8*5=40 8*6=48 8*7=56 8*8=64 8*9=72 8*10=80
9*1=9 9*2=18 9*3=27 9*4=36 9*5=45 9*6=54 9*7=63 9*8=72 9*9=81 9*10=90
10*1=10 10*2=20 10*3=30 10*4=40 10*5=50 10*6=60 10*7=70 10*8=80 10*9=90 10*10=100
Как видите, печатает фрагмент 4 плохо. Аккуратных столбцов не получается. А нам и не очень хотелось. Задачу создания красивой таблицы умножения я вынес в Задание 72.
В целом программа иллюстрирует идею вложенных циклов, когда один, внутренний, цикл вложен внутрь другого, внешнего. У нас тело внешнего цикла выполняется 10 раз, а тело внутреннего – 100 раз, так как на каждое выполнение внешнего цикла он выполняется 10 раз.
Нельзя. Бессмысленно и нельзя переменные внешнего и внутреннего цикла называть одним именем. Так, следующий фрагмент
Dim i, a As Integer
For i = 1 To 10
For i = 1 To 10
a = 0
Next
Next
не пройдет. Переменная i во внутреннем цикле будет подчеркнута. Вообще, не рекомендуется внутри цикла как-то менять ее переменную.
Задание 54.
А. Распечатать все возможные сочетания из двух цифр – первая цифра может быть любой от 3 до 8, вторая - любой от 0 до 7. Например, 36, 44, 80.
Б. Распечатать все возможные сочетания из четырех цифр, каждая из которых может принимать значения 1,2,3. Например, 2123, 3312, 1111. Получилось? А теперь подсчитайте количество таких сочетаний.
В. Подсчитать из них количество неубывающих сочетаний, то есть таких, где каждая следующая цифра не меньше предыдущей – 1123, 1223, 2222 и т.п., но не 3322. Распечатать все такие сочетания.
Вложенные циклы – «Небоскреб»
Мы видим, что ввод в программу переменных величин вместо чисел делает программу более гибкой и способной к развитию. Попробуем в следующей программе использовать переменные пошире.
Задача. Нарисовать небоскреб (см. Рис. 10.4) с заданным числом этажей и подъездов (условимся: один столбец окон – один подъезд).
Рис. 10.4
Начнем с малого – пусть нужно нарисовать одно единственное окно:
Фрагмент 1
Ширина = 5 'Ширина окна
Высота = 10 'Высота окна
y = 30 'Вертикальная координата окна
x = 20 'Горизонтальная координата окна
Граф.DrawRectangle(Pens.Black, x, y, Ширина, Высота)
Пусть этот фрагмент рисует левое верхнее окно небоскреба.
Рисуем этаж. Попробуем заставить компьютер нарисовать верхний этаж. Кстати, мы с вами ведь уже рисовали «квадратную трубу» в 8.5? Это то же самое.
Замечаем, что здесь нам нужно решить несколько элементарных задач на рисование окна, первую из которых решает фрагмент 1. Все задачи очень похожи и различаются лишь значением координаты x. Таким образом, для решения каждой из этих задач подошла бы последняя строчка фрагмента 1, если бы в ней x равнялся не 20, а нужному числу. В данном случае идеально подходит оператор For:
Фрагмент 2
Зазор_х = 4 'Зазор между окнами на этаже
Ширина = 5 : Высота = 10
П = InputBox("Введите количество подъездов")
y = 30
x = 20
For j = 1 To П
Граф.DrawRectangle(Pens.Black, x, y, Ширина, Высота)
x = x + Ширина + Зазор_х 'Вычисляем горизонтальную координату следующего окна
Next
Рисуем весь небоскреб. Следующая ступень усложнения – последняя – нарисовать все этажи небоскреба. Для этого 5 нижних строчек фрагмента 2 должны быть выполнены заданное число раз, каждый раз – с новым значением y. Чтобы этого достичь, «обнимем» эти строчки оператором For:
Программа
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
Зазор_х = 4
Зазор_у = 6 'Зазор между окнами соседних этажей
Ширина = 5 : Высота = 10
Э = InputBox("Введите количество этажей")
П = InputBox("Введите количество подъездов")
y = 30
For i = 1 To Э
x = 20
For j = 1 To П
Граф.DrawRectangle(Pens.Black, x, y, Ширина, Высота)
x = x + Ширина + Зазор_х
Next j
y = y + Высота + Зазор_у 'Вычисляем вертикальную координату следующего окна
Next i
Граф.DrawRectangle(Pens.Black, 8, 15, x, y) 'Рисуем контур небоскреба
End Sub
Задание 55.
«Ковер» (Рис. 10.5).
Рис. 10.5
Ковер сделан из пересекающихся окружностей. Если центры соседних окружностей отстоят друг от друга на одинаковое расстояние как по горизонтали, так и по вертикали, и если удачно подобраны размеры, то ковер у вас получится красивым и с аккуратными краями. Дополнение А: Если ковер получился, сделайте, чтобы у него был вырезан правый верхний угол. Дополнение Б: Если и это получилось, сделайте, чтобы у него был вырезан вдобавок и квадрат посередине.
Задание 56.
«Шахматная доска». Нарисуйте шахматную доску (Рис. 10.6).
Рис. 10.6
Указание: Здесь основные трудности возникнут при раскраске клеток в шахматном порядке. У Волчёнкова (См. Список литературы) я встретил следующую идею относительно того, как закрашивать клетки: Те клетки, у которых сумма номеров строки и столбца четная, закрашивать одним цветом, остальные – другим.
Задание 57.
«Таблица умножения». Нарисовать на форме таблицу умножения, как на Рис. 10.3.
Вложенные операторы
Реальная процедура на VB может представлять собой сложную мозаику из циклических и разветвляющихся частей, вложенных друг в друга. Мы уже видели в 7.5.1 как в оператор ветвления был вложен оператор ветвления, а в 10.2.1. – как в оператор цикла был вложен оператор ветвления. В свою очередь в них могут быть вложены другие операторы цикла или ветвления, и так до бесконечности. Вам нужно привыкнуть разбираться в этой матрешке.
Поиск максимума и минимума
Ищем максимальное число.
Задача программисту: Найти максимальное из вводимых в компьютер чисел.
Задача рыбаку: Принести домой самую большую из выловленных рыб.
Решение рыбака: Рыбак приготовил для самой большой рыбы пустое ведро с водой. Первую пойманную рыбу рыбак не глядя бросает в это ведро. Каждую следующую рыбу он сравнивает с той, что в ведре. Если она больше, то он бросает ее в ведро, а ту, что была там раньше, выпускает в реку.
Решение программиста: Программист приготовил для самого большого числа ячейку и придумал ей название, скажем, max. Первое число программист не глядя вводит в эту ячейку. Каждое следующее число (назовем его chislo) он сравнивает с max. Если оно больше, то он присваивает переменной max значение этого числа.
Напишем программу для определения максимального из 10 вводимых чисел:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim max, chislo, i As Integer
max = InputBox("Введите число") 'первую рыбу - в ведро
For i = 2 To 10 'ловим остальных рыб:
chislo = InputBox("Введите число") 'поймали очередную рыбу
If chislo > max Then max = chislo 'и если она больше той, что в ведре, бросаем ее в ведро
Next i
Debug.WriteLine(max) 'несем самую большую рыбу домой
End Sub
В этой программе переменная max исполняла роль «памяти». Она сохраняла в себе нужное значение и передавала его из цикла в цикл, благодаря чему каждый следующий цикл «знал», с чем сравнивать очередное число. Эта идея – использовать переменные для запоминания нужной информации на всем процессе выполнения программы – весьма продуктивна и широко используется программистами.
Примечание: Не путайте изобретенную нами переменную max со стандартной функцией Math.Max, которая нужна немножко для другого.
Задание 58.
В нашей программе функция InputBox("Введите число") встречается два раза. Не всем нравится такая избыточность. А. Попробуйте избавится от одной из них, усложнив If. Б. Можно избавиться и по-другому: заранее присвоив переменной max очень маленькое число, такое, что все числа заведомо больше него.
Ищем порядковый номер максимального числа. Дополним нашу программу, чтобы она искала также порядковый номер максимального числа из N заданных чисел, для чего организуем переменную Номер_макс_числа:
Private Sub Button4_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button4.Click
Dim max, chislo, i, N, Номер_макс_числа As Integer
N = InputBox("Сколько чисел?")
max = InputBox("Введите число")
Номер_макс_числа = 1
For i = 2 To N
chislo = InputBox("Введите число")
If chislo > max Then
max = chislo
Номер_макс_числа = i
End If
Next i
Debug.WriteLine(max)
Debug.WriteLine(Номер_макс_числа)
End Sub
Задание 59.
Найти из N чисел минимальное. Каким по порядку было введено минимальное число?
Задание 60.
У вас есть результаты забега на 100 метров (в секундах). Правда ли, что результат самого быстрого бегуна отличается от результата самого медленного больше, чем на 0,4 сек.?
Задание 61.
На небе 10 звезд. Напечатайте координаты самой правой звезды.
Понятие о процедурах пользователя
Рассмотрим бессмысленную программу:
Public Class Form1
Inherits System.Windows.Forms.Form
Windows Form Designer generated code
Dim a, b, d, f As Integer
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
d = 100
a = 2
b = 30
Debug.WriteLine(a + b)
Debug.WriteLine(a * b + 1)
Debug.WriteLine(a ^ b - 20)
d = 9 - b
f = 0
a = 2
b = 30
Debug.WriteLine(a + b)
Debug.WriteLine(a * b + 1)
Debug.WriteLine(a ^ b - 20)
d = a + 1
a = 2
b = 30
Debug.WriteLine(a + b)
Debug.WriteLine(a * b + 1)
Debug.WriteLine(a ^ b - 20)
f = f - a
End Sub
End Class
Предположим, эта программа раздражает нас не своей бессмысленностью, а своей длиной. Как сократить ее?
Замечаем, что программа содержит 3 одинаковых фрагмента (в тексте я их выделил полужирным курсивом):
a = 2
b = 30
Debug.WriteLine(a + b)
Debug.WriteLine(a * b + 1)
Debug.WriteLine(a ^ b - 20)
В этом случае программисты всего мира сокращают программу так. Они придумывают повторяющемуся фрагменту произвольное имя, например,
Печать_разных_чисел
Затем они вписывают в окно кода этот фрагмент отдельно, снабдив его заголовком
Sub Печать_разных_чисел()
и конечной строкой
End Sub
Получается вот что:
Sub Печать_разных_чисел()
a = 2
b = 30
Debug.WriteLine(a + b)
Debug.WriteLine(a * b + 1)
Debug.WriteLine(a ^ b - 20)
End Sub
Они называют все это процедурой пользователя, после чего имеют право во всем окне кода вместо полного текста фрагмента писать только его имя Печать_разных_чисел. Посмотрите на получившуюся программу целиком:
Public Class Form1
Inherits System.Windows.Forms.Form
Windows Form Designer generated code
Dim a, b, d, f As Integer
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
d = 100
Печать_разных_чисел()
d = 9 - b
f = 0
Печать_разных_чисел()
d = a + 1
Печать_разных_чисел()
f = f - a
End Sub
Sub Печать_разных_чисел()
a = 2
b = 30
Debug.WriteLine(a + b)
Debug.WriteLine(a * b + 1)
Debug.WriteLine(a ^ b - 20)
End Sub
End Class
Получившаяся программа выполняет абсолютно то же самое, что и исходная.
Как видите, программа получилась короче, а если бы в исходной программе фрагмент встретился не 3 раза, а больше (как часто и бывает), то укорочение было бы еще заметнее.
Все операторы, из которых состоит процедура, без заголовка и конечной строки, будем называть телом процедуры. А вместе с ними – объявлением процедуры.
Слово Печать_разных_чисел используется внутри процедуры Sub Button1_Click, как настоящий оператор, и выполняется, как настоящий оператор. Суть его выполнения в том, что когда VB в процессе выполнения программы натыкается на оператор Печать_разных_чисел, он ищет в программе объявление процедуры с именем Печать_разных_чисел и начинает выполнять тело этой процедуры. Этот процесс называется вызовом процедуры или обращением к процедуре. Говорят также, что управление передается процедуре. После выполнения тела процедуры VB возвращается к выполнению программы. Говорят, что управление возвращается к программе.
Вы обязательно должны проверить то, что я только что сказал, и собственными глазами убедиться в правдивости моих слов. Для этого обязательно выполните эту программу в пошаговом режиме! Это самое необходимое из того, что вы должны сделать. При этом обязательно же обратите внимание вот на что:
Порядок исполнения операторов показывает желтая полоса. Она перескакивает с оператора Печать_разных_чисел() на строку Sub Печать_разных_чисел(), после чего пробегает по всем 5 операторам тела процедуры. Дойдя до конечной строки End Sub, она возвращается на тот же оператор Печать_разных_чисел(), с которого и «прыгала», после чего переходит на оператор, следующий сразу же за этим самым оператором (в первый раз в нашем случае это оператор d=9-b). Вы просто обязаны усвоить этот порядок на всю жизнь.
Два вида процедур. Вот у вас в окне кода две процедуры. Одна из них – Button1_Click и вы знаете, когда она выполняется – когда нажимается кнопка Button1. Она принадлежит к числу привычных нам процедур обработки событий. А когда выполняется другая – Печать_разных_чисел? Только тогда (и если), когда (и если) в других процедурах компьютер наткнется на оператор Печать_разных_чисел(). А если этот оператор мы забыли там написать, то процедура Печать_разных_чисел не выполнится никогда.
Вы усвоили, что когда завершает свою работу процедура пользователя, VB возвращается в процедуру, которая ее вызвала. А куда он возвращается, когда завершает свою работу процедура обработки событий? А никуда. Просто ждет ваших дальнейших действий, вызывающих события, например, нажатий на кнопку.
Пример процедуры пользователя
Я пояснил применение процедуры пользователя на бессмысленном примере. А теперь – пример программы, имеющей реальный смысл. Если вы хорошо поймете его по тексту, то проверять на компьютере не обязательно.
Мы с вами уже написали процедуры, которые рисуют человечка, паровозик, звездное небо, ковер и т.п. Пусть теперь мы хотим создать проект – альбом рисунков, создаваемых нашими процедурами. В нем столько кнопок, сколько рисунков. По нажатии на каждую кнопку что-нибудь рисуется. Пусть для краткости в нашем проекте при нажатии на первую кнопку рисуется пушка (кружочек-колесо и палочка-ствол), а при нажатии на вторую – Буратино (кружочек-голова и палочка-нос). Добавим еще одну кнопку – для стирания. Назовем ее «Стираем». Вообще-то, для стирания достаточно процедуры из одного оператора:
Private Sub Стираем_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles Стираем.Click
Граф.Clear(Color.White)
End Sub
но нам бы хотелось, чтобы это было не просто стирание, а что-нибудь более экстравагантное, причем с музыкальным сопровождением. Посмотрите, что у нас получилось:
1 вариант программы:
'Процедура стирания:
Private Sub Стираем_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles Стираем.Click
Beep() 'Короткий звуковой сигнал
'Постепенно стираем рисунок, не спеша заполняя форму белыми эллипсами:
For i = 1 To 10000
Граф.FillEllipse(Brushes.White, Me.Width * Rnd(), Me.Height * Rnd(), 10, 50)
Next
Граф.Clear(Color.White) 'Окончательное стирание, так как эллипсов не хватает
Плеер.FileName = "D:\WINNT\Media\tada.wav" 'Играем мелодию
End Sub
'Рисуем пушку:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Граф.DrawEllipse(Pens.Black, 100, 100, 200, 200) 'Колесо
Граф.DrawLine(Pens.Black, 20, 300, 350, 20) 'Ствол
End Sub
'Рисуем Буратино:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Граф.DrawEllipse(Pens.Black, 100, 100, 200, 200) 'Голова
Граф.DrawLine(Pens.Black, 300, 200, 400, 200) 'Нос
End Sub
2 вариант программы. Все хорошо. Мы обошлись без процедур пользователя. Но теперь мы хотим улучшить проект. Пусть нам лень каждый раз перед нажатием на кнопку рисунка нажимать на кнопку стирания. Давайте сэкономим одно нажатие и сделаем так, чтобы при нажатии на кнопку любого рисунка сначала происходило стирание, а уж потом рисование. Для этого достаточно было бы операторы процедуры стирания дописать наверх каждой процедуры рисования:
'Рисуем пушку:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim i As Integer
Beep()
For i = 1 To 10000
Граф.FillEllipse(Brushes.White, Me.Width * Rnd(), Me.Height * Rnd(), 10, 50)
Next
Граф.Clear(Color.White)
Плеер.FileName = "D:\WINNT\Media\tada.wav"
Граф.DrawEllipse(Pens.Black, 100, 100, 200, 200) 'Колесо
Граф.DrawLine(Pens.Black, 20, 300, 350, 20) 'Ствол
End Sub
'Рисуем Буратино:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Dim i As Integer
Beep()
For i = 1 To 10000
Граф.FillEllipse(Brushes.White, Me.Width * Rnd(), Me.Height * Rnd(), 10, 50)
Next
Граф.Clear(Color.White)
Плеер.FileName = "D:\WINNT\Media\tada.wav"
Граф.DrawEllipse(Pens.Black, 100, 100, 200, 200) 'Голова
Граф.DrawLine(Pens.Black, 300, 200, 400, 200) 'Нос
End Sub
3 вариант программы. Результат достигнут. Но за счет удлинения программы. Как бы убить двух зайцев: и программу не удлинять и лишних кнопок не нажимать? Очень просто – так же, как мы это делали в бессмысленной программе, применив процедуру пользователя:
'Процедура пользователя для стирания рисунка:
Sub Стирание_старого_рисунка()
Dim i As Integer
Beep()
For i = 1 To 10000
Граф.FillEllipse(Brushes.White, Me.Width * Rnd(), Me.Height * Rnd(), 10, 50)
Next
Граф.Clear(Color.White)
Плеер.FileName = "D:\WINNT\Media\tada.wav"
End Sub
'Рисуем пушку:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Стирание_ старого_рисунка()
Граф.DrawEllipse(Pens.Black, 100, 100, 200, 200) 'Колесо
Граф.DrawLine(Pens.Black, 20, 300, 350, 20) 'Ствол
End Sub
'Рисуем Буратино:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Стирание_ старого_рисунка()
Граф.DrawEllipse(Pens.Black, 100, 100, 200, 200) 'Голова
Граф.DrawLine(Pens.Black, 300, 200, 400, 200) 'Нос
End Sub
Как видите, здесь в каждой из двух процедур рисования мы заменили длинный фрагмент стирания оператором Стирание_старого_рисунка(), который обращается к одноименной процедуре пользователя.
Процедуры пользователя улучшают читабельность. Итак, процедуры пользователя укорачивают программы. Но не только это. Они еще делают их более «читабельными», то есть более понятными при чтении, когда нужно быстро схватить общий смысл процедуры. Вообразите, что эта программа чужая и вы ее читаете впервые. Сравните в этом аспекте текст процедуры рисования пушки из 2 и 3 вариантов программы. Очевидно, что вы быстрее разберетесь, что делает эта процедура, именно в 3 варианте. Но этого не произойдет, если вы неудачно назовете процедуру пользователя. Если вы вместо имени Стирание_старого_рисунка употребите имя Крутая_процедурка, то не о какой понятности и читабельности не может быть и речи.
Call. Есть еще один способ обратиться к процедуре. Вместо оператора
Стирание_старого_рисунка()
можно написать оператор
Call Стирание_старого_рисунка()
Смысл этих двух операторов совершенно одинаков. Вторым способом часто пользовались раньше. С английского слово «Call» переводится «Вызов».
Понятие о процедурах с параметрами
Вернемся к началу. Изменим слегка нашу бессмысленную программу из 11.1.1:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
d = 100
a = 2
b = 30
Debug.WriteLine(a + b)
Debug.WriteLine(a * b + 1)
Debug.WriteLine(a ^ b - 20)
d = 9 - b
f = 0
a = 2
b = 8
Debug.WriteLine(a + b)
Debug.WriteLine(a * b + 999)
Debug.WriteLine(a ^ b - 20)
d = a + 1
a = 2
b = -4
Debug.WriteLine(a + b)
Debug.WriteLine(a * b + 73)
Debug.WriteLine(a ^ b - 20)
f = f - a
End Sub
Мы замечаем здесь те же 3 фрагмента, но они уже чуть-чуть неодинаковые (неодинаковые места я выделил полужирным курсивом). Наша задача прежняя – укоротить программу. Спрашивается, как сделать это в усложнившихся условиях?
Посмотрим повнимательнее, в чем изменения. Мы видим, что они коснулись двух мест фрагментов: в операторе b= число каждый раз разное и в операторе Debug.WriteLine(a*b+ тоже каждый раз разное число.
Действуем следующим образом. Придумываем одно имя для чисел в одном месте и другое имя для чисел в другом месте. Пусть мы придумали имена Параметр1 и Параметр2. Теперь создадим процедуру пользователя примерно так же, как мы делали это в 11.1.1, но с некоторыми изменениями.
Прежде всего, в теле процедуры вместо непослушных чисел записываем имена переменных Параметр1 и Параметр2. Но раз есть переменные, их нужно объявлять. А объявляются они специальным образом в заголовке процедуры. Окончательно получается вот что:
Sub Печать_разных_чисел (ByVal Параметр1 As Integer, ByVal Параметр2 As Integer)
a = 2
b = Параметр1
Debug.WriteLine(a + b)
Debug.WriteLine(a * b + Параметр2)
Debug.WriteLine(a ^ b - 20)
End Sub
Здесь полужирным шрифтом я выделил новые для вас конструкции. Вместо Dim мы пишем ByVal. На смысле слова ByVal я остановлюсь позже.
Теперь о том, как вызывать эту процедуру. Вызывается она просто:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
d = 100
Печать_разных_чисел(30, 1)
d = 9 - b
f = 0
Печать_разных_чисел(8, 999)
d = a + 1
Печать_разных_чисел(-4, 73)
f = f - a
End Sub
Вы видите, что в скобках оператора вызова указываются два числа. Это как раз те числа, которые нужно подставить во фрагмент на место переменных Параметр1 и Параметр2. Вот и все отличие.
Мы достигли поставленной цели и укоротили программу путем некоторого усложнения процедуры пользователя и ее вызова.
Переменные, объявленные в скобках заголовка процедуры, называются параметрами процедуры.
В качестве значений параметров в обращениях к процедурам можно писать не только константы, но и переменные, и выражения. Например, вместо
Печать_разных_чисел(30, 1)
можно было написать
Dim m = 30
Печать_разных_чисел (m, m - 29)
Вообще, вы наверное уже привыкли, что в VB там, где можно писать числа, чаще всего можно писать и переменные, и выражения. Это же относится и к другим литералам, например, строкам.
Параметры разных типов. Параметры могут иметь не только числовой, но и строковый и многие другие типы. Пример:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Печатаем_3_раза("Кто там? - Это почтальон Печкин!")
Печатаем_3_раза("Дядя Федор")
End Sub
Private Sub Печатаем_3_раза(ByVal Что_нибудь As String)
Debug.WriteLine(Что_нибудь)
Debug.WriteLine(Что_нибудь)
Debug.WriteLine(Что_нибудь)
End Sub
Здесь вы видите процедуру пользователя Печатаем_3_раза и ее параметр – строковую переменную с именем Что_нибудь. При нажатии на кнопку программа начинает работать и печатает следующий текст:
Кто там? - Это почтальон Печкин!
Кто там? - Это почтальон Печкин!
Кто там? - Это почтальон Печкин!
Дядя Федор
Дядя Федор
Дядя Федор
Пример процедуры с параметрами
После бессмысленного примера процедур с параметрами хотелось бы рассмотреть реальный пример. Вернемся ко 2 варианту нашей программы из 11.1.2 об альбоме рисунков и стирании старого рисунка. Изменим его слегка, чтобы перед каждым рисунком стирание шло по-своему. Пусть у каждой картинки будет свое время стирания и своя музыка.
'Рисуем пушку:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim i As Integer
Beep()
For i = 1 To 10000
Граф.FillEllipse(Brushes.White, Me.Width * Rnd(), Me.Height * Rnd(), 10, 50)
Next
Граф.Clear(Color.White)
Плеер.FileName = "D:\WINNT\Media\tada.wav"
Граф.DrawEllipse(Pens.Black, 100, 100, 200, 200) 'Колесо
Граф.DrawLine(Pens.Black, 20, 300, 350, 20) 'Ствол
End Sub
'Рисуем Буратино:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Dim i As Integer
Beep()
For i = 1 To 2000
Граф.FillEllipse(Brushes.White, Me.Width * Rnd(), Me.Height * Rnd(), 10, 50)
Next
Граф.Clear(Color.White)
Плеер.FileName = "D:\WINNT\Media\chimes.wav"
Граф.DrawEllipse(Pens.Black, 100, 100, 200, 200) 'Голова
Граф.DrawLine(Pens.Black, 300, 200, 400, 200) 'Нос
End Sub
Мы замечаем здесь тот же фрагмент о стирании, но он уже в обеих процедурах чуть-чуть неодинаковый. Наша задача прежняя – укоротить программу. Спрашивается, как сделать это в усложнившихся условиях? Точно так же, как мы поступили в 11.1.3 с бессмысленной программой.
Посмотрим повнимательнее, в чем неодинаковость. Мы видим, что она имеет место в двух местах фрагмента: в операторе For i=1 To (число каждый раз разное) и в операторе Плеер.FileName = (указываются разные звуковые файлы).
Придумываем имя для числа – Число_эллипсов. Придумываем имя для строки с именем файла – Звуковой_файл.
В теле процедуры вместо изменчивых числа и строки записываем имена переменных Число_эллипсов и Звуковой_файл. Затем объявляем их в заголовке процедуры, причем Звуковой_файл объявляем, конечно, как String. В скобках операторов вызова указываем число и строку. Это как раз те число и строка, которые нужно подставить во фрагмент на место переменных Число_эллипсов и Звуковой_файл.
Получается вот что:
'Рисуем пушку:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Стирание_старого_рисунка(10000, "D:\WINNT\Media\tada.wav")
Граф.DrawEllipse(Pens.Black, 100, 100, 200, 200) 'Колесо
Граф.DrawLine(Pens.Black, 20, 300, 350, 20) 'Ствол
End Sub
'Рисуем Буратино:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Стирание_старого_рисунка(2000, "D:\WINNT\Media\chimes.wav")
Граф.DrawEllipse(Pens.Black, 100, 100, 200, 200) 'Голова
Граф.DrawLine(Pens.Black, 300, 200, 400, 200) 'Нос
End Sub
Sub Стирание_старого_рисунка(ByVal Число_эллипсов As Integer, ByVal Звуковой_файл As String)
Dim i As Integer
Beep()
For i = 1 To Число_эллипсов
Граф.FillEllipse(Brushes.White, Me.Width * Rnd(), Me.Height * Rnd(), 10, 50)
Next
Граф.Clear(Color.White)
Плеер.FileName = Звуковой_файл
End Sub
Мы видим, что параметры могут иметь разные типы.
Теперь мы понимаем, зачем в конце заголовков процедур и в других операторах ставится пара скобок (). Это для параметров, буде они объявятся.
Вызов процедур из процедуры пользователя
Вызываемая процедура сама в процессе своей работы может вызвать какую-нибудь другую процедуру. Та – тоже. И так далее. Потренируемся. Определим без компьютера, что напечатает программа:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Write(1)
A()
Write(2)
B()
Write(3)
End Sub
Sub C()
Write(7)
End Sub
Sub B()
Write(5)
C()
Write(6)
End Sub
Sub A()
Write(4)
End Sub
Мы видим здесь 4 процедуры с именами Button1_Click, C, B, A. Работать программа начинает по щелчку по кнопке Button1 с выполнения процедуры Button1_Click. В процессе своей работы эта процедура вызовет по очереди процедуры A и B. Порядок записи объявлений процедур в окне кода безразличен и не влияет на порядок их вызова. Поглядите на тело процедуры B. В процессе своей работы эта процедура вызовет процедуру C. Процедура пользователя вызвала процедуру пользователя. Это не только допустимо, но и приветствуется. Помните только, что когда вызываемая процедура C свое отработала, компьютер возвращается туда, откуда она была вызвана, то есть внутрь процедуры B, после чего выполняется следующий оператор, в нашем случае Write(6).
Вряд ли вам с непривычки удастся без компьютера угадать правильный ответ. Тогда непременно программу – в компьютер и – пошаговый режим. Желтая полоска будет скакать по программе туда-сюда. Перед каждым нажатием на F11 вы обязаны предсказать, куда она прыгнет! Не сможете – нет смысла читать книгу дальше.
Ответ:
1425763
Вы спросите: зачем все это нужно? Это не просто нужно, без этого при программировании не обойтись.
Операторы Stop, End и Exit Sub
До сих пор мы писали процедуры, которые выполняли свою работу до конца и заканчивали ее, как положено, то есть только на операторе End Sub, не раньше. Существуют ли операторы, которые подобно операторам выхода из цикла Exit Do и Exit For заставляют компьютер покинуть процедуру, не доходя до ее конца? Такие операторы существуют.
End. Оператор End заставляет VB завершить работу не только процедуры, а всего проекта. Пример: программа
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Write(1)
Write(2)
End
Write(3)
End Sub
напечатает
12
после чего End завершает режим работы и проект переходит в режим проектирования.
Потренируйтесь. Определите без компьютера, что напечатает программа:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Write(2) : A() : Write(3) : End : Write(4)
End Sub
Sub A()
Write(6) : End : Write(7)
End Sub
Ответ:
26
Помните, что при выполнении оператора End и при завершении работы проекта кнопкой Stop Debugging на панели инструментов событие формы Closed не наступает.
Stop. Ненавистник пошагового режима мог бы мечтать о таком способе отладки: «Хорошо бы существовал специальный оператор паузы, чтобы наткнувшись на него, компьютер приостанавливал выполнение программы, а мы могли бы спокойно посмотреть на результаты и подумать». Такой оператор есть, это Stop.
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Write(1)
Write(2)
Stop
Write(3)
End Sub
Здесь программа напечатает 12 и сделает паузу. После щелчка по Start она допечатает 3 и закончит выполнение процедуры.
Механика работы оператора Stop проста: наткнувшись на него, компьютер переходит в режим прерывания. Фактически оператор Stop равносилен установке точки прерывания. Как только Stop выполнился, вы можете делать все, что можно делать в режиме прерывания, например узнавать значения переменных. Чтобы продолжить выполнение программы, вы щелкаете по кнопке Start. Тогда он продолжает работу с того места, где остановился. А если хотите, можете вместо Start нажать F11 и тем самым продолжить выполнение программы в пошаговом режиме.
Exit Sub. Оператор Exit Sub не такой решительный, как End. Он не выбрасывает VB из режима работы, а просто заставляет компьютер выйти из процедуры, в которой он выполнился. Если он выполнился в вызываемой процедуре, то VB возвращается, как положено, в процедуру, ее вызвавшую, на положенное место. Если он выполнился в процедуре обработки события, то VB просто завершает работу этой процедуры.
Пример:
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
Write(2) : B() : Write(3) : Exit Sub : Write(4)
End Sub
Sub B()
Write(6) : Exit Sub : Write(7)
End Sub
Эта программа напечатает
263
Задание 62.
Вот вам программа с процедурами. Вам нужно, не запуская ее, записать на бумажке весь разговор, который ведут герои «Трех мушкетеров».
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
WriteLine("Я, король Франции, спрашиваю вас - кто вы такие? Вот ты - кто такой?")
ATOS()
WriteLine("А ты, толстяк, кто такой?")
PORTOS()
WriteLine("А ты что отмалчиваешься, усатый?")
DARTANIAN()
WriteLine("Анна! Иди-ка сюда!!!")
Exit Sub
WriteLine("Аудиенция закончена, прощайте!")
End Sub
Sub ATOS()
WriteLine("Я - Атос")
End Sub
Sub ARAMIS()
WriteLine("Это так же верно, как то, что я - Арамис!")
End Sub
Sub PORTOS()
WriteLine("А я Портос! Я правильно говорю, Арамис?")
ARAMIS()
WriteLine("Он не врет, ваше величество! Я Портос, а он Арамис.")
End Sub
Sub DARTANIAN()
WriteLine("А я все думаю, ваше величество - куда девались подвески королевы?")
Exit Sub
WriteLine("Интересно, что ответит король?")
PORTOS()
End Sub
Сверьте с ответом. Если не сходится, запустите программу в пошаговом режиме.
Задание 63.
Среди графических методов VB нет методов «крестик» и «треугольник». Вы можете возместить этот недостаток, написав две соответствующие процедуры с тремя параметрами: координата_х, координата_у, размер. Если не получается, то прочтите следующий раздел и тогда получится обязательно.
Процедуры
До сих пор мы с вами имели дело только с процедурами, задающими реакцию компьютера на те или иные события (нажатие на кнопку, загрузка формы и т.д.). Если вспомнить пример программы из 1.1, то это процедуры типа "Что делать, если …". Обязательно перечитайте 1.1, если вы забыли, что это такое, так как настала пора познакомить вас с другими процедурами – процедурами типа "Как…". Будем называть их процедурами пользователя.
Задание на проект
Задание на проект. Нарисовать парк под луной (см. Рис. 11.1):
Рис. 11.1
Мы создадим программу, которая широко использует процедуры пользователя с параметрами. Я разберу с вами эту задачу не столько для того, чтобы у нас получился правильный рисунок, сколько для того, чтобы вы привыкли в правильном порядке работать с большими задачами. Поэтому не нужно стараться сделать все по-своему, чтобы только побыстрее выполнить задание. Старайтесь уловить порядок работы. Ведь это будет наш первый реальный проект с взаимодействующими процедурами.
Пытаемся угадать, где тут будут процедуры пользователя. На рисунке мы видим несколько деревьев. Каждое из них придется рисовать. Если бы мы решили действовать по старинке, то в программе у нас присутствовало бы несколько десятков почти одинаковых фрагментов, рисующих деревья в разных местах. Кошмар! Мы, конечно, будем вместо этого создавать процедуру пользователя Дерево. Так, одну процедуру угадали. Аналогично угадываем процедуру Фонарь. С остальным неясно: звездное небо, месяц … Ладно, угадывание остальных процедур отложим. Займемся деревом.
Проповедь для язычников. Начинающим программистам не хочется писать процедуры пользователя, как не хочется им писать и длинные имена. «Ну и лопухи же эти так-называемые профессиональные программисты, что осложняют себе жизнь этой морокой!» – думают они – «Наши программы отлично работают и безо всего этого». Верно, работают. Потому что программы коротенькие. Когда они станут длинными, то будут напоминать винегрет, и тогда все такие подрастающие программисты дружно зарыдают: "Мамочка, мы ни в чем не можем разобраться! Почему ты нас в детстве не научила слушаться взрослых?!"
Процедуры должны быть короткими. Запомните еще одно хорошее правило: Размеры любой процедуры не должны превышать одного экрана монитора. Если превышают, то даже если в ней нет повторяющихся фрагментов, все равно разбейте ее по смыслу на два-три фрагмента и каждый сделайте процедурой. Ваша программа будет гораздо легче читаться.
От чисел – к переменным
Дерево. Создайте новый проект. Создайте в нем единственную кнопку Рисуем. При нажатии на эту кнопку будет рисоваться весь пейзаж целиком. Значит в процедуре обработки нажатия на эту кнопку будет много обращений к процедурам пользователя, рисующим те или иные элементы пейзажа. Одна из них – Дерево. Создайте ее и попробуйте в ней нарисовать дерево. Пока безо всяких переменных. Вот что получилось у меня:
Public Class Form1
Inherits System.Windows.Forms.Form
Windows Form Designer generated code
Dim Гр As Graphics
Private Sub Рисуем_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Рисуем.Click
Гр = Me.CreateGraphics
Дерево()
'Здесь будет много вызовов других процедур:
' . . . . . . . . .
End Sub
Sub Дерево()
Гр.FillEllipse(Brushes.Green, 200, 100, 20, 60) 'Зеленая крона
Гр.DrawEllipse(Pens.Black, 200, 100, 20, 60) 'Черный контур для зеленой кроны
Гр.DrawLine(Pens.Black, 200 + 10, 100 + 60, 200 + 10, 100 + 60 + 15) 'Ствол
End Sub
End Class
Дерево получилось примерно такое, как на рисунке. Смысл чисел такой. 200 и 100 определяют местоположение дерева. Я их взял наобум. 20 и 60 – размер кроны. В строке процедуры, рисующей ствол, я написал много плюсов. Это чтобы было понятней, откуда взялись числа. Ведь, чтобы ствол находился на нужном месте, их величина должна зависеть от положения и размеров кроны. Так вот. 10 – это половинка от 20, иначе бы ствол был смещен влево или вправо. 15 – длина ствола.
Внимательно разберитесь в смысле чисел, иначе дальнейшее изложение будет непонятным.
Числа – плохо, переменные – хорошо. Забудем пока о грядущих параметрах, подумаем о вреде чисел.
Предположим, вам захотелось нарисовать дерево поправее. Для этого вам нужно в процедуре в четырех местах поменять число 200 на 250. Вот то-то и неудобно, что в четырех, а не в одном. Слишком много труда. В нашей программе это, конечно, пустяк, а вот в больших и сложных программах одна и та же величина может встречаться сотни раз, и чтобы ее изменить, придется вносить сотни исправлений.
Посмотрим, как нам помогут переменные величины. Придумаем переменную величину с именем x и везде заменим 200 на x. Вот как выглядит та же процедура, но с использованием переменной величины:
Sub Дерево()
Dim x As Single = 200
Гр.FillEllipse(Brushes.Green, x, 100, 20, 60)
Гр.DrawEllipse(Pens.Black, x, 100, 20, 60)
Гр.DrawLine(Pens.Black, x + 10, 100 + 60, x + 10, 100 + 60 + 15)
End Sub
Теперь для того, чтобы изменить горизонтальное положение дерева, достаточно заменить число 200 только в одном месте.
Избавляемся ото всех чисел. То, что мы сделали, настолько мне нравится, что я хочу заменить все числа переменными. Вот что у меня получилось:
Sub Дерево()
Dim x As Single = 200
Dim y As Single = 100
Dim Ширина_кроны As Single = 20
Dim Высота_кроны As Single = 60
Dim Длина_ствола As Single = 15
Гр.FillEllipse(Brushes.Green, x, y, Ширина_кроны, Высота_кроны)
Гр.DrawEllipse(Pens.Black, x, y, Ширина_кроны, Высота_кроны)
Гр.DrawLine(Pens.Black, x + Ширина_кроны / 2, y + Высота_кроны, _
x + Ширина_кроны / 2, y + Высота_кроны + Длина_ствола)
End Sub
Как видите, для этого хватило 5 переменных. Обратите внимание, что вместо 10 я написал Ширина_кроны/2. Можно было бы для 10 придумать шестую переменную, но слишком увеличивать число переменных тоже ни к чему. А главное – тогда, если бы мы захотели изменить ширину кроны, нам бы пришлось менять значение и шестой переменной тоже, иначе бы ствол был смещен влево или вправо. А это лишний труд.
Вы спросите: а зачем было придумывать переменную для числа 15? Ведь оно встречается только один раз. А на перспективу! Ведь в будущем вы захотите сделать дерево более красивым и сложным. Процедура усложнится и, глядишь, длина ствола будет фигурировать в нескольких местах.
Вторая причина, по которой мы используем переменные вместо всех чисел, состоит в том, что с ними программа, становясь длиннее, тем не менее становится понятнее, так как имена переменным мы придумываем, исходя из их смысла.
И третья причина – из переменных легко получаются параметры. Об этом мы сейчас и поговорим.
Для переменных я выбрал тип Single. Почему – я объяснил в 6.2.8. .
От переменных – к параметрам
Мы видим, что на рисунке деревья находятся в разных местах и имеют разный размер. Значит мы должны создать параметры, управляющие местоположением и размером дерева. Начнем с местоположения.
Параметры местоположения дерева. Очевидно, положением дерева на форме управляют только две переменные: x – по горизонтали и y – по вертикали. Превратим их в параметры. Превратить переменную в параметр – это значит просто переписать ее объявление из тела процедуры в заголовок процедуры:
Private Sub Рисуем_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Рисуем.Click
Гр = Me.CreateGraphics
Дерево(50, 20)
Дерево(150, 40)
End Sub
Sub Дерево(ByVal x As Single, ByVal y As Single)
Dim Ширина_кроны As Single = 20
Dim Высота_кроны As Single = 60
Dim Длина_ствола As Single = 15
Гр.FillEllipse(Brushes.Green, x, y, Ширина_кроны, Высота_кроны)
Гр.DrawEllipse(Pens.Black, x, y, Ширина_кроны, Высота_кроны)
Гр.DrawLine(Pens.Black, x + Ширина_кроны / 2, y + Высота_кроны, _
x + Ширина_кроны / 2, y + Высота_кроны + Длина_ствола)
End Sub
Теперь, щелкнув по кнопке, вы увидите два дерева в разных местах.
Уменьшаем число переменных. Размерами дерева в процедуре управляют три переменные: Ширина_кроны, Высота_кроны и Длина_ствола. Очень гибко, но многовато переменных. Хорошо бы вместо трех иметь одну переменную, определяющую размер дерева.
Чтобы это сделать, придумаем математическую зависимость между тремя переменными. Посмотрим на их значения: 20, 60 и 15. Мы видим, что ширина кроны меньше высоты в 3 раза, а длина ствола – в 4 раза. Зададим новую переменную Размер. Пусть ее величина будет равна высоте кроны. Посмотрите, как упростится теперь наша процедура:
Sub Дерево(ByVal x As Single, ByVal y As Single)
Dim Размер As Single = 60
Гр.FillEllipse(Brushes.Green, x, y, Размер / 3, Размер)
Гр.DrawEllipse(Pens.Black, x, y, Размер / 3, Размер)
Гр.DrawLine(Pens.Black, x + Размер / 6, y + Размер, x + Размер / 6, y + Размер + Размер / 4)
End Sub
Благодаря запрограммированной нами связи между шириной и высотой кроны и длиной ствола, при изменении значения переменной Размер пропорции между частями дерева не изменятся.
Параметр размера дерева. А теперь превратим переменную Размер в параметр:
Private Sub Рисуем_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Рисуем.Click
Гр = Me.CreateGraphics
Дерево(50, 20, 200)
Дерево(150, 50, 100)
Дерево(200, 70, 40)
End Sub
Sub Дерево(ByVal x As Single, ByVal y As Single, ByVal Размер
As Single)
Гр.FillEllipse(Brushes.Green, x, y, Размер / 3, Размер)
Гр.DrawEllipse(Pens.Black, x, y, Размер / 3, Размер)
Гр.DrawLine(Pens.Black, x + Размер / 6, y + Размер, x + Размер / 6, y + 5 * Размер / 4)
End Sub
Теперь, щелкнув по кнопке, вы увидите три дерева в разных местах и разных размеров.
Процедура «Фонарь». Процедуру Фонарь в качестве упражнения сделайте сами. Параметры пусть будут такие же, как и у дерева. Вот что получилось у меня:
Sub Фонарь(ByVal x As Single, ByVal y As Single, ByVal Размер As Single)
Гр.FillRectangle(Brushes.Gold, x, y, Размер / 30, Размер) 'Столб
Гр.DrawRectangle(Pens.Black, x, y, Размер / 30, Размер) 'Черный контур для столба
Гр.FillPie(Brushes.DarkOrange, x + Размер / 30, y, Размер / 5, Размер / 10, 180, 180) 'Плафон
Гр.DrawPie(Pens.Black, x + Размер / 30, y, Размер / 5, Размер / 10, 180, 180) 'Черный контур для плафона
End Sub
Вам не кажется, что, несмотря на наши старания, чисел в процедуре осталось многовато? В общем-то, да. В принципе, всех их надо было превратить в переменные. Но я не захотел затемнять изложение излишними подробностями.
А теперь забегите немного вперед и напишите в теле процедуры Рисуем_Click пять операторов для рисования трех одиночных деревьев и двух одиночных фонарей над прудом примерно в тех местах и тех размеров, что вы видите на рисунке. Получилось? – Прекрасно! А теперь уберите из проекта этих пятерых выскочек, они нарушают стройный и размеренный шаг нашей бронированной армии. Но не уничтожайте их, переместите куда-нибудь или закомментируйте: они вам еще пригодятся.
Делим задачу на части
То, что мы сейчас делали – это учились писать процедуры с параметрами. Но мы еще не приступали к программированию нашего проекта как целого. Процедура Рисуем_Click у нас практически пуста. И вот теперь мы приступаем к планомерной осаде этой крепости.
Когда Наполеон встречал превосходящего по силам врага, он бил его армию по частям. Когда программисту нужно решить сложную задачу, он делит ее на части и программирует каждую часть по-отдельности.
Легко сказать – делит. А как ее разделить, если все в задаче перепутано и взаимосвязано. Здесь, конечно, помогают программистский опыт и мастерство. А если их пока еще нет? Тогда можно руководствоваться соображениями здравого смысла и общей «понятности» разбиения на части. В нашем случае разбиение напрашивается само собой: посмотри, из каких крупных кусков сделан пейзаж – на то и дели! Причем нужно примириться с тем, что мы можем пока и не представлять, как каждый из этих кусков запрограммировать.
Перечислим, что мы видим на рисунке:
Звездное небо
Месяц
Земля
Пруд
Три одиночных дерева
Два одиночных фонаря
Ряд деревьев (на горизонте)
Ряд фонарей (на горизонте)
Аллея (состоящая из двух рядов деревьев и одного ряда фонарей)
Так разделил бы любой прохожий с улицы, ничего не ведающий о программировании. Удивитесь ли вы, если я скажу, что мнение этого прохожего я посчитаю истиной в последней инстанции и именно такие процедуры пользователя обязуюсь создать? Вот как примерно будет выглядеть в окончательном виде главная процедура рисования:
Private Sub Рисуем_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Рисуем.Click
Гр = Me.CreateGraphics
Звездное_небо()
Месяц()
Земля()
Пруд()
Три_одиночных_дерева()
Два_одиночных_фонаря()
Ряд_деревьев()
Ряд_фонарей()
Аллея()
End Sub
Почему я послушался прохожего? Потому что он делил по принципу: как проще и очевидней. А это великий принцип. Будем и мы ему следовать. Потому что:
Понятная программа – правильная программа!
Не изящная, не замысловатая, не сверхкороткая, а именно понятная! Заставляйте компьютер думать так, как привычно человеку, а не наоборот. Потому что наоборот гены не позволят.
Пока я у всех этих процедур не указал параметров. Но если вдруг выяснится, что параметры нужны, мы их, конечно, создадим.
Почему я выбрал именно такой порядок рисования? Можно было бы выбрать и любой другой, только надо помнить, что фигуры, нарисованные, раньше, загораживаются фигурами, нарисованными позже, поэтому одиночные деревья, например, нельзя рисовать раньше пруда.
Программируем части по-отдельности
Ну что ж. Начинаем программировать отдельные части. В каком порядке? Все равно. Но логичнее в том порядке, в каком они появляются на рисунке, а значит в порядке сверху-вниз в теле главной процедуры рисования. Следовательно, начинаем со звездного неба.
Вы спросите, а как же наши процедуры Дерево и Фонарь, над которыми мы так долго трудились? Неужели они не понадобятся? Понадобятся в свое время, причем в полном объеме.
Звездное небо. Прежде чем рисовать звездное небо, надо задать, на каком уровне по вертикали кончается небо и начинается земля. Если мы зададим этот уровень числом, то наверняка это число встретится также при рисовании на горизонте деревьев и фонарей, а может быть и в других процедурах. Так что надо задать это число переменной величиной, а чтобы этой переменной могли пользоваться все процедуры, объявим ее не в теле процедуры, а вне процедур.
Поскольку звездное небо вы уже раньше рисовали, то дальнейшие пояснения не требуются. Вот какое у меня получилось на этом этапе окно кода:
Public Class Form1
Inherits System.Windows.Forms.Form
Windows Form Designer generated code
Dim Гр As Graphics
Dim Уровень_горизонта As Single = 200
Private Sub Рисуем_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Рисуем.Click
Гр = Me.CreateGraphics
Звездное_небо()
End Sub
Sub Звездное_небо()
Dim i, Размер_звезды As Single
Гр.FillRectangle(Brushes.Black, 0, 0, Me.Width, Уровень_горизонта) 'Черный прямоугольник неба
For i = 1 To 100 'Рисуем сто звезд
Размер_звезды = 5 * Rnd()
Гр.FillEllipse(Brushes.White, _
Me.Width * Rnd(), Уровень_горизонта * Rnd(), Размер_звезды, Размер_звезды)
Гр.FillEllipse(Brushes.Yellow, _
Me.Width * Rnd(), Уровень_горизонта * Rnd(), Размер_звезды, Размер_звезды)
Гр.FillEllipse(Brushes.LightBlue, _
Me.Width * Rnd(), Уровень_горизонта * Rnd(), Размер_звезды, Размер_звезды)
Гр.FillEllipse(Brushes.LightPink, _
Me.Width * Rnd(), Уровень_горизонта * Rnd(), Размер_звезды, Размер_звезды)
Next i
End Sub
End Class
Небо – это просто большой черный прямоугольник во всю ширину формы выше уровня горизонта. Мы не видим его краев, так как они совпадают с краями формы или выходят за них После нажатия на кнопку рисования мы увидим черный прямоугольник неба, заполненный разноцветными звездами.
Серп молодого месяца или «В час по чайной ложке»
Если вы видели солнечное или лунное затмения, то знаете, что картинка солнца или луны в этих случаях тоже напоминает месяц, не правда ли? Во всяком случае, у нас принцип получения изображения месяца будет такой же, как при затмении: рисуем желтый круг, а поверх него с небольшим смещением – черный круг. Получается серпик толщиной в это смещение. Программируем:
Процедура Месяц небольшая, но и ее так сразу не напишешь. Начинаем с того, что записываем в окно кода ее заготовку:
Sub Месяц()
End Sub
Затем увеличиваем на один оператор главную процедуру рисования:
Private Sub Рисуем_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Рисуем.Click
Гр = Me.CreateGraphics
Звездное_небо()
Месяц()
End Sub
Запускаем проект. Ничего нового вдобавок к звездному небу, конечно, не рисуется, но мы хотя бы убеждаемся, что VB ни на что не жалуется, значит ошибок нет.
Теперь записываем в процедуру Месяц оператор, рисующий желтый круг. Вряд ли вы уже «на ты» с переменными, поэтому пишите его, не стесняясь – в числах. Запускайте проект и проверяйте, хорошо ли рисуется круг. Если все в порядке, записывайте в процедуру Месяц оператор, рисующий черный круг. Запускайте – проверяйте. Если все в порядке, переходите к замене чисел на переменные. Запускайте – проверяйте. Идея такая: выполнив маленький кусочек работы, обязательно нужно запустить проект и проверить, как он работает. Нужно это для того, чтобы обнаружить ошибку как можно раньше. Представьте ситуацию: ваш проект работал хорошо, потом вы дописали в него 10 строк, запустили – он стал работать плохо. Ясно, что виновата одна из 10 строк, но какая? Попробуйте найти ошибку в 10 строках! А если там не одна ошибка? А теперь другая ситуация: строк не 10, а 1. Ясно, что искать ошибку в 10 раз легче. Вот так: «Тише едешь …».
Вот какая у меня в конце концов получилась процедура:
Sub Месяц()
Dim x As Single = 100
Dim y As Single = 30
Dim Размер_месяца As Single = 50
Dim Толщина_серпа As Single = 10
Гр.FillEllipse(Brushes.Yellow, x, y, Размер_месяца, Размер_месяца) 'Желтый круг
Гр.FillEllipse(Brushes.Black, x - Толщина_серпа, y, Размер_месяца, Размер_месяца) 'Черный круг
End Sub
Земля, пруд, три дерева и два фонаря
Пора переходить к процедуре Земля. Мы будем работать над ней и над другими процедурами точно так же, как и над процедурой Месяц. То есть, сначала запишем в окно кода ее заготовку, затем увеличим на один оператор главную процедуру рисования и затем постепенно будем создавать процедуру Земля, постоянно проверяя результат. Таким же манером мы будем действовать и в дальнейшем, пока проект не будет готов.
Земля. Это большой зеленый прямоугольник ниже уровня горизонта:
Sub Земля()
Гр.FillRectangle(Brushes.LightGreen, 0, Уровень_горизонта, Me.Width, Me.Height)
End Sub
Пруд. Это эллипс черного цвета с белой окантовкой:
Sub Пруд()
Dim x As Single = 400
Dim y As Single = 300
Dim Размер As Single = 200
Гр.FillEllipse(Brushes.Black, x, y, Размер, Размер / 3) 'Вода
Гр.DrawEllipse(Pens.White, x, y, Размер, Размер / 3) 'Берег
End Sub
Обратите внимание, как легко превратить эту процедуру в процедуру с параметрами. Тогда легко будет разместить в парке несколько прудов. В будущем вы сможете делать отражение звезд в пруду.
Три одиночных дерева и два одиночных фонаря. Вот когда понадобились наши Дерево и Фонарь:
Sub Три_одиночных_дерева()
Дерево(420, 240, 50)
Дерево(600, 260, 70)
Дерево(430, 260, 100)
End Sub
Sub Два_одиночных_фонаря()
Фонарь(480, 230, 60)
Фонарь(540, 260, 140)
End Sub
Ряд деревьев
Ряд деревьев – 1 версия. Нарисовать ряд деревьев с помощью процедуры Дерево – все равно, что нарисовать ряд окружностей с помощью процедуры DrawEllipse, что мы делали в 8.5. Вот как выглядит процедура, рисующая подходящий ряд из пары десятков деревьев на горизонте:
Sub Ряд_деревьев()
Dim x As Single = 400
Dim y As Single = Уровень_горизонта - 30
Dim i As Integer
For i = 1 To 20
Дерево(x, y, 30)
x = x + 15
Next
End Sub
Числа 400, 30, 30, 15 я подобрал так, чтобы картинка была более-менее похожа на заданную.
Кстати, вы видите, что в нашей программе мы обращаемся из процедуры пользователя Ряд_деревьев к процедуре пользователя Дерево.
Ряд деревьев – 2 версия. На нашем рисунке присутствуют еще два ряда деревьев. Они чем-то похожи, а чем-то непохожи на наш ряд. Нам нужно решить: будем ли мы, когда дело дойдет до их рисования, скромно создавать для них новые процедуры или же прямо сейчас смело сконструируем единую процедуру с такими хитрыми параметрами, чтобы она могла нарисовать любой из трех рядов деревьев на нашем рисунке. Нет ни малейшего сомнения, что мы выбираем второе.
Эта процедура получится небольшим усложнением 1 версии. Попробуем ее переписать так, чтобы получился левый из двух рядов в аллее. Рисовать нужно начинать с самого дальнего дерева, иначе дальние загородят ближних. Если в первой версии у нас от дерева к дереву менялась только горизонтальная координата x, то здесь у нас должны меняться и вертикальная координата y (увеличиваться), и Размер (увеличиваться). Причем, если в 1 версии x рос, то здесь он должен уменьшаться:.
Sub Ряд_деревьев_2_версия()
Dim x As Single = 220
Dim y As Single = Уровень_горизонта - 30
Dim Размер As Single = 30
Dim i As Integer
For i = 1 To 20
Дерево(x, y, Размер)
x = x - 22
y = y + 20
Размер = Размер + 10
Next
End Sub
Временно допишите в процедуру рисования вызов этой процедуры и проверьте, как она работает. Есть ли у вас уверенность, что меняя только числа в процедуре, вы сможете рисовать любые нужные на рисунке ряды деревьев? У меня есть. Действительно, ряд деревьев на горизонте получается при помощи этой процедуры изменением четырех чисел в следующих строках:
Dim x As Single = 400
x = x + 15
y = y + 0
Размер = Размер + 0
А раз так, то пора числа и переменные превращать в параметры.
Ряд деревьев – окончательная версия. Посмотрим, от каких переменных и чисел зависит вид ряда деревьев. Начнем просматривать нашу процедуру сверху вниз:
Dim x As Single = 220
Dim y As Single = Уровень_горизонта - 30
Dim Размер As Single = 30
Эти три строки определяют положение и размер самого первого дерева ряда. Конечно же, все три переменные должны стать параметрами.
For i = 1 To 20
Эта строка задает число деревьев в ряду. Конечно, мы захотим иметь возможность рисовать ряды с разным числом деревьев. Число 20 превращаем в параметр.
x = x - 22
y = y + 20
Размер = Размер + 10
Три числа: -22, 20, 10 определяют направление ряда деревьев, расстояние между деревьями и увеличение размеров от дерева к дереву. Без них не обойтись. Еще три параметра.
Итого 7 параметров. Все они нужны, ничего не попишешь. Превращаем нашу процедуру в процедуру с параметрами:
Sub Ряд_деревьев(ByVal x As Single, ByVal y As Single, ByVal Размер As Single, ByVal Число_деревьев _
As Integer, ByVal Шаг_по_гориз As Single, ByVal Шаг_по_вертик As Single, ByVal Увеличение As Single)
Dim i As Integer
For i = 1 To Число_деревьев
Дерево(x, y, Размер)
x = x + Шаг_по_гориз
y = y + Шаг_по_вертик
Размер = Размер + Увеличение
Next
End Sub
Вы спросите: почему в строках
x = x + Шаг_по_гориз
y = y + Шаг_по_вертик
стоят плюсы, ведь часто там должны быть и минусы? Отвечаю: Плюсы трогать не будем, а нужного результата будем добиваться отрицательным значением шага. Например, вот как будет выглядеть вызов процедуры для рисования левого из двух рядов в аллее:
Ряд_деревьев (220, Уровень_горизонта - 30, 30, 20, -22, 20, 10)
А вот как – для ряда деревьев на горизонте:
Ряд_деревьев (400, Уровень_горизонта - 30, 30, 20, 15, 0, 0)
У процедуры есть недостаток: расстояния между соседними деревьями вдали и вблизи одинаковы, что не соответствует законам перспективы. Вы можете исправить это положение, сделав шаги зависящими от размера.
Ряд фонарей и аллея
Ряд фонарей. Эта процедура строится совершенно аналогично процедуре Ряд_деревьев. Создайте ее самостоятельно. Вот что получилось у меня:
Sub Ряд_фонарей(ByVal x As Single, ByVal y As Single, ByVal Размер As Single, ByVal Число_фонарей _
As Integer, ByVal Шаг_по_гориз As Single, ByVal Шаг_по_вертик As Single, ByVal Увеличение As Single)
Dim i As Integer
For i = 1 To Число_фонарей
Фонарь(x, y, Размер)
x = x + Шаг_по_гориз
y = y + Шаг_по_вертик
Размер = Размер + Увеличение
Next
End Sub
Процедуры Ряд_деревьев и Ряд_фонарей до смешного одинаковы. Это значит, что если как следует подумать, процедуру Ряд_фонарей не надо было выдумывать, а надо было просто скопировать Ряд_деревьев и немного изменить.
Одинаковость наводит еще на одну мысль: а что, если сделать единую процедуру для рисования ряда деревьев или фонарей? Что именно рисовать, решают еще один строковый параметр и оператор If внутри процедуры. Но это упражнение я оставляю для энтузиастов.
Теперь добавьте в главную процедуру рисования вызов процедуры рисования ряда фонарей на горизонте:
Ряд_фонарей (20, Уровень_горизонта - 30, 70, 6, 25, 0, 0) 'на горизонте
Кстати, ваше окно кода сильно выросло и доставляет вам неудобство тем, что для поиска нужной процедуры вам приходится его долго прокручивать вверх-вниз. Хотите избавиться от лишней прокрутки – перечтите 6.1.3 и щелкните по крохотным минусикам у заголовков ненужных пока процедур. Их тела скроются из вида, освободив дефицитное место. Чтобы их вернуть, щелкайте по плюсикам.
Аллея. Мы прекрасно понимаем, что вместо создания и вызова процедуры Аллея можно бы просто написать два вызова процедуры Ряд_деревьев и один вызов процедуры Ряд_фонарей. И мороки было бы меньше. Все это верно. Но в этом случае мы ослушались бы прохожего, а этого нельзя делать, потому что мы нарушили бы великий принцип простоты и наглядности. К тому же, кто знает, может быть в будущем наш парк украсится несколькими аллеями? А?
Пояснять процедуру Аллея излишне. Вот она:
Sub Аллея()
Ряд_деревьев(220, Уровень_горизонта - 30, 30, 20, -22, 20, 10) 'левый
Ряд_деревьев(240, Уровень_горизонта - 30, 30, 20, 12, 20, 10) 'правый
Ряд_фонарей(220, Уровень_горизонта, 70, 20, -36, 64, 30)
End Sub
Обратите внимание, что значение первого параметра во всех трех обращениях почти одинаково. Это наводит на мысль превратить его в параметр процедуры Аллея. Вот так:
Sub Аллея(ByVal x As Single)
Ряд_деревьев(x, Уровень_горизонта - 30, 30, 20, -22, 20, 10) 'левый
Ряд_деревьев(x + 20, Уровень_горизонта - 30, 30, 20, 12, 20, 10) 'правый
Ряд_фонарей(x, Уровень_горизонта, 70, 20, -36, 64, 30)
End Sub
А вот окончательный вид главной процедуры рисования парка:
Private Sub Рисуем_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Рисуем.Click
Гр = Me.CreateGraphics
Звездное_небо()
Месяц()
Земля()
Пруд()
Три_одиночных_дерева()
Два_одиночных_фонаря()
Ряд_деревьев(400, Уровень_горизонта - 30, 30, 20, 15, 0, 0) 'на горизонте
Ряд_фонарей(20, Уровень_горизонта - 30, 70, 6, 25, 0, 0) 'на горизонте
Аллея(220)
End Sub
Проект завершен.
Меняя параметр процедуры Аллея, мы сдвигаем всю аллею целиком влево-вправо. Ощущение могущества! Чем хорошо программирование: минимальные усилия, а результат меняется колоссально. Если бы мы рисовали вручную, сколько времени нам понадобилось бы, чтобы перерисовать всю аллею?
Попробуйте как следует понизить уровень горизонта. Вся земля сдвинулась, а пруд и окружающие его деревья и фонари остались на месте. В результате чего очутились на небе. Н-да-а … Это потому, что при их рисовании мы уровень горизонта не учитывали.
Два способа программирования
Основываясь на опыте составления этой программы и на опыте мирового программирования, можно сказать, что существуют два способа программирования:
Способ «сверху вниз», когда сначала вся задача дробится на части и каждая часть программируется по-отдельности. Если среди частей попадаются слишком крупные, они тоже дробятся на все более мелкие части до тех пор, пока не станет ясно, как любую из этих мелких частей запрограммировать. В нашем примере мы в основном так и поступали.
Можно программировать и по способу «снизу вверх», когда программист сначала составляет список самых мелких частей, из которых состоит задача, программирует их, а потом собирает из них более крупные части. Когда мы в нашем примере начали работу с программирования процедур Дерево и Фонарь, мы пользовались именно этим способом.
Из сказанного ясно, что при программировании можно пользоваться комбинацией этих способов. Что бы вы ни программировали в будущем, начинать вы будете с выбора одного из этих способов или их комбинации в нужной пропорции.
Задание 64.
Попробуйте придумать какую-нибудь картинку и запрограммировать ее. Надо только, чтобы на ней присутствовали одинаковые предметы разной величины. Пусть это будет атака космических кораблей. Или длинный стол, накрытый на 24 персоны (вид сверху-сбоку в перспективе). Или какой-нибудь орнамент. Или батальон на марше.
Задание это важно выполнить. Потому что у вас нет пока опыта самостоятельного создания больших проектов с процедурами пользователя и вы не скоро его еще наберетесь.
Проект «Парк под луной»
Теперь вы достаточно знаете о процедурах пользователя, чтобы они стали для вас удобными кирпичиками для постройки программ. Уже сейчас вы готовы составлять с их помощью более качественные программы, чем раньше. Однако, в моих примерах я писал процедуры пользователя не сразу. Сначала я для большей понятности писал почти одинаковые фрагменты, а уж от них шел к процедурам пользователя. В реальном программировании никто не пишет одинаковых фрагментов перед тем, как написать процедуру пользователя. Ее пишут сразу. И именно этому мы сейчас будем учиться.
Создание, инициализация и уничтожение переменных
Перед тем, как начать работать, переменная величина автоматически, независимо от желания программиста, инициализируется, то есть ей присваивается некоторое начальное значение. Поясним на примере.
Создайте проект для изучения переменных и в нем такую бессмысленную процедуру:.
Private Sub Button4_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button4.Click
Dim b As Integer
b = 2
Dim a As Integer = 5
a = b
Dim c As Integer
c = b
Dim d As String = "Привет"
End Sub
Запустите проект в пошаговом режиме. Имейте в виду, что пока вы еще не нажали на кнопку Button4 (то есть пока процедура не выполняется), переменные в процедуре не имеют никаких значений. Но как только желтая полоса встала на заголовок процедуры в знак того, что процедура начала выполняться, немедленно произошла инициализация: VB нашел все операторы Dim внутри процедуры и всем числовым переменным согласно указанным в них типам (но не значениям, приведенным после знака равенства!) присвоил значение 0, а строковым – Nothing, что означает «Ничто». Проверьте.
А теперь потихоньку переводите желтую полосу от оператора к оператору, следя за значениями переменных и обращая внимание вот на что: Поскольку операторы Dim компьютер выполнил в самом начале, теперь он через них просто перепрыгивает, однако, если в операторе Dim есть присваивание, то оно, конечно, выполняется.
Оператор Dim может быть записан в любом месте процедуры, однако не ниже появления «своей» переменной в теле процедуры. Например, ошибкой было бы поменять местами первые две строки тела нашей процедуры. Из-за этого программисты обычно собирают операторы Dim в верхней части процедуры.
Когда желтая полоса уйдет со строки End Sub в знак того, что процедура закончила выполняться, переменные потеряют свои значения.
Закон такой:
Переменная, объявленная внутри процедуры, не существует в те периоды времени, когда эта процедура не выполняется. Она создается в тот момент, когда VB начинает выполнять эту процедуру. На уровне «железа» это выражается в том, что в этот момент под переменную отводится ячейка памяти. Тут же переменная инициализируется. Пока процедура работает, переменная живет и работает тоже. Когда VB выходит из этой процедуры, ячейка уничтожается вместе с переменной и ее значением.
Если переменная встречается в теле процедуры, но не объявлена в операторе Dim, то она просто не создается и VB выдает ошибку.
Этот закон касается только переменных, объявленных внутри процедуры. Но есть и другие переменные, о которых чуть позже.
Области видимости переменных
Шапка-невидимка. Введите в окно кода такой код:
Public Class Form1
Inherits System.Windows.Forms.Form
Windows Form Designer generated code
Dim a As Integer = 5
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim b As Integer = 4
a = b
End Sub
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
a = b
End Sub
End Class
VB подчеркнул во второй из двух процедур переменную b. Ошибка. При наведении мышки на ошибку VB выдал подсказку: Name 'b' is not declared, что означает «Имя b не объявлено». Как не объявлено?! Ведь в первой процедуре мы b объявили! В чем дело? Дело в так называемых областях видимости. Закон такой:
Переменная, объявленная внутри процедуры, не видна снаружи, в том числе и из других процедур.
Происходит вот что: Когда первая процедура выполняется, ее переменная b живет и здравствует, но второй процедуре от этого никакой выгоды нет, так как она сама в это время спит летаргическим сном и ждет своей очереди. Когда же мы ее будим щелчком по кнопке Button2, то оказывается, что первая процедура уже заснула, а значит и ее переменная b уничтожена. Уничтожена – не уничтожена! Какая разница, если это все равно чужая переменная, то есть принадлежащая другой процедуре?!
Умный VB предвидит эту ситуацию и подчеркивает переменную b во второй процедуре, намекая, что неплохо бы ее там объявить. Ну что ж, объявим, добавив оператор во вторую процедуру:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Dim b As Integer = 1
a = b
End Sub
Теперь все в порядке и все работает.
Стеклянные стены. Вопрос: а почему VB не жаловался на переменную a? Ведь мы ее не объявляли ни в одной процедуре. Ответ: а потому что мы ее объявили вне процедур. Закон такой:
Переменная, объявленная вне процедур, видна изо всех процедур окна кода.
Создается и инициализируется такая переменная раньше тех, что объявлены в процедурах, а уничтожается позже.
Тезки. Еще один вопрос, «философский»: правда ли, что переменная b из первой процедуры и переменная b из второй процедуры – это одна и та же переменная, время от времени рождающаяся и умирающая, или же это «тезки» – две разные переменные, только с одним именем? Ответ однозначный: верно второе. В доказательство рассмотрим ситуацию, когда обе процедуры работают одновременно:
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
Dim b As Integer
b = 11
Процедура()
Debug.WriteLine(b)
b = 4
End Sub
Sub Процедура()
Dim b As Integer
b = 8
Debug.WriteLine(b)
End Sub
После щелчка по кнопке Button3 компьютер напечатает вот что:
8
11
Запустите проект в пошаговом режиме. После выполнения оператора b=11 компьютер прыгает на процедуру пользователя, создает ее переменную b и начинает процедуру выполнять. Обратите внимание, что выполнение первой процедуры при этом не завершено, а значит и переменная b этой процедуры не уничтожена. К сожалению, пошаговый режим не показывает нам разницу значений этих переменных, однако оператор Debug.WriteLine(b) делает это безошибочно. Первой выполняется печать в процедуре пользователя и, естественно, печатается 8. Затем компьютер возвращается в первую процедуру, прямо к выполнению ее оператора Debug.WriteLine(b). Если бы переменная b была одна на обе процедуры, то конечно была бы напечатана еще раз 8. Однако у каждой из процедур своя переменная, а чужая вообще не видна, поэтому оператор Debug.WriteLine(b) спокойно печатает свою переменную b, которая равна 11.
Итак:
Переменные с одинаковыми именами, объявленные в разных процедурах, являются разными переменными. Не путайте.
Терминология. Подводим итоги:
Мы обнаружили две области видимости переменных: конкретная процедура или все окно кода.
Если переменные объявлены внутри процедуры, то они и использованы могут быть только внутри нее. Их зона видимости – эта конкретная процедура. Называют такие переменные локальными переменными.
Параметры процедуры тоже являются локальными переменными в этой процедуре, так как они объявлены именно в ее заголовке.
Если переменные объявлены вне процедур, то они могут быть использованы во всем окне кода внутри любой процедуры. Называют такие переменные модульными переменными или переменными уровня модуля. О причине такой терминологии поговорим попозже (21.9).
Тезки из разных областей. Теперь вопрос относительно модульной переменной: что будет, если нам захотелось назвать одинаковыми именами локальную переменную и переменную уровня модуля? Проверим:
Public Class Form1
Inherits System.Windows.Forms.Form
Windows Form Designer generated code
Dim a As Integer = 5
Private Sub Button5_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button5.Click
Dim a As Integer = 44
Debug.WriteLine(a)
End Sub
End Class
Здесь компьютер напечатает
44
Спрашивается: что это было – одна и та же переменная или разные? А если разные, то почему предпочли локальную, ведь из процедуры видны обе? Или нет? Ответом будет закон «Своя рубашка ближе к телу»: Если к вам в гости пришли Петя и Коля, а у вас в доме уже живет член семьи Коля, то вы пришедшего Колю не пускаете на порог. А с Петей все нормально. В переводе на язык программистов:
Локальная и модульная переменные с одинаковыми именами являются разными переменными, причем модульная переменная не видна из процедуры, где объявлена локальная переменная с тем же именем.
Говорят: локальная переменная затеняет в своей процедуре модульную с тем же именем.
Тщательно пережевывайте пищу. Если вы поняли все, что я только что объяснял, то без компьютера определите, что напечатает программа:
Public Class Form1
Inherits System.Windows.Forms.Form
Windows Form Designer generated code
Dim x As Integer 'x - модульная переменная
Dim z As Integer 'z - модульная переменная
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
x = 1 : z = 2 'x и z - модульные переменные
B()
Debug.WriteLine(x & " " & z) 'x и z - модульные переменные
End Sub
Private Sub B()
Dim x As Integer 'x - локальная переменная
Dim y As Integer 'y - локальная переменная
x = 20 : y = 30 : z = 40
End Sub
End Class
Ответ приведен в конце подраздела.
Если с ответом не сходится, то значит вы поняли не все, и вот вам пояснение:
Оператор Debug.WriteLine(x & " " & z) находится снаружи процедуры В, и поэтому локальная переменная х=20, объявленная внутри В, из него не видна. Зато прекрасно видна модульная переменная х=1, которую он и печатает. Переменная же z не объявлена внутри В, поэтому она является модульной переменной, и оператор z=40 с полным правом меняет ее значение с 2 на 40.
А теперь приведу длинное пояснение «ближе к железу»:
Переменная х, объявленная снаружи процедуры, это совсем другая переменная, чем х, объявленная в процедуре, и помещаются эти переменные в разных местах памяти. Поэтому и не могут друг друга испортить. Вы можете вообразить, что это переменные с разными именами xмод и xлок.
Переменная, объявленная снаружи процедуры, видна из любой процедуры окна кода и каждая процедура может ее испортить. Однако, когда процедура натыкается на переменную x, объявленную внутри самой этой процедуры, то она, благодаря описанному выше механизму, работает только с ней и не трогает переменную x, объявленную снаружи.
Вот порядок выполнения программы:
В оперативной памяти VB отводит ячейки под Х мод и Z мод.
Процедура Button1_Click начинает выполняться с присвоения значений Х мод = 1 и Z мод = 2. Почему мод, а не лок? Потому что переменные с именами X и Z в процедуре Button1_Click не объявлены, а значит волей-неволей процедуре приходится пользоваться модульными переменными.
Вызывается процедура В. При этом в оперативной памяти отводится место под Х лок и У лок.
Присваиваются значения Х лок = 20, У лок = 30 и Z мод = 40.
Программа выходит из процедуры В. При этом исчезают переменные Х лок (20) и У лок (30). А Z мод (40) остается.
Компьютер печатает Х мод = 1 и Z мод = 40.
Таким образом программа напечатает 1 и 40.
Зачем нужны разные области видимости
Зачем такие ограничения и неудобства? Какой во всем этом смысл? Поговорим об этом.
Создание разных областей видимости для разных переменных является способом повышения надежности больших программ и понижения вероятности запутаться при их написании. Программы, создаваемые сегодня профессиональными программистами, очень велики – десятки и сотни тысяч строк. Таково, например, большинство игровых программ. Естественно, один человек не может достаточно быстро создать такую программу, поэтому пишется она обычно группой программистов. Для этого программа делится на части, и каждый программист пишет свою часть. И все равно, в каждой части присутствуют десятки и сотни процедур с десятками и сотнями переменных, в которых человеку легко запутаться. Ну а уж когда все эти части соединяют вместе, начинается кавардак!
Рассмотрим бессмысленную «сложную» программу, не использующую локальных переменных, и покажем, как она из-за этого уязвима. Пусть в ней присутствуют сотни процедур. Рассмотрим две из них:
Dim x As Integer
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
x = 2 * 2
B()
Debug.WriteLine(x)
End Sub
Private Sub B()
x = 5 * 5
Debug.WriteLine(x)
End Sub
Автор первой процедуры хочет, чтобы человечество узнало, сколько будет дважды два, для чего и написал первую и третью строчку своей процедуры. Между ними вклинилась вторая строчка – вызов процедуры B. Для чего она нужна, автор и сам не знает, но начальство потребовало, чтобы она была вставлена именно в это место процедуры в интересах проекта. Ну и ладно, жалко что ли?
Автор второй процедуры сообщает человечеству результат умножения 5 на 5.
И все было бы хорошо, и все были бы довольны, если бы авторов не угораздило назвать переменные одинаковыми именами. Начальство ожидало, что сначала компьютер напечатает 25, а потом 4, но на самом деле, как легко видеть, будет напечатано
25
25
Для защиты от таких ошибок директор проекта, не использующего локальных переменных, должен внимательно следить, чтобы разные процедуры не использовали переменных с одинаковыми именами. Но для больших программ этот контроль очень трудоемок и неудобен. Для того, чтобы избежать его, в современных языках программирования и разработан механизм локальных переменных. Если программист знает, что его данные нигде, кроме как в родной процедуре, не нужны, он объявляет соответствующие переменные любыми именами внутри
процедуры, ничуть не заботясь, что переменные с такими же именами встречаются в других местах программы. И все нормально работает:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim x As Integer
x = 2 * 2
B()
Debug.WriteLine(x)
End Sub
Private Sub B()
Dim x As Integer
x = 5 * 5
Debug.WriteLine(x)
End Sub
Данная программа к удовольствию директора напечатает
25
4
Таким образом, вы видите, что смысл механизма локальных переменных – в увеличении надежности программирования. Я настоятельно советую:
Те переменные, которые наверняка не понадобятся вне процедуры, делайте локальными.
Область видимости – блок
Блоком называется последовательность всех выполняющихся друг за другом операторов. Если вы помните, эти операторы легко узнать, так как у них одинаковый отступ от левого края окна. Блоки могут входить внутрь других блоков. Рассмотрим бессмысленную программу:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Dim a, b, c As Integer
b = 6
For a = 5 To 6
b = 8
c = 2 * 2
Next
a = 1
If a > 0 Then
a = 7
c = 5 * 5
a = 8
End If
a = 3
End Sub
Все тело процедуры есть блок из 5 операторов:
b = 6
For . . .
a = 1
If . . .
a = 3
Внутрь этого блока входят два блока. Один есть содержимое оператора For:
b = 8
c = 2 * 2
Другой – содержимое оператора If:
a = 7
c = 5 * 5
a = 8
Если бы внутрь этих двух блоков входили сложные операторы, то операторы, из которых состоят эти сложные операторы, тоже составляли бы блоки, входящие в упомянутые. Матрешка.
Оказывается, целесообразно иметь области видимости меньшие, чем процедура. Это блоки.
Зачем они могут понадобиться? Ну, например, мы видим, что в нашем примере переменная c в каждом блоке делает «важные» дела, причем в каждом блоке свои. Мы опасаемся, как бы при дальнейшей доработке программы значения переменной c из одного блока при нашей невнимательности не повредили бы значения переменной c из другого блока. Что делать для спокойствия? Уберем объявление c из начала процедуры и объявим переменную c внутри каждого блока:
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
Dim a, b As Integer
b = 6
For a = 5 To 6
Dim c As Integer
b = 8
c = 2 * 2
Next
a = 1
If a > 0 Then
Dim c As Integer
a = 7
c = 5 * 5
a = 8
End If
a = 3
End Sub
Теперь мы имеем в процедуре две совершенно независимые переменные, имеющие одинаковое имя c. Каждая из них создается и инициализируется в момент, когда компьютер заходит в ее блок. Она живет и работает, пока работает блок. Когда же компьютер выходит из блока, переменная уничтожается вместе со своим значением.
Только не вздумайте объявлять переменную, имеющую то же имя, в начале процедуры, то есть в ее главном блоке. VB не допускает, чтобы одноименные переменные объявлялись в охватываемом и охватывающем блоках.
У констант тоже существует область видимости блок.
Статические переменные
Исчезновение значения локальной переменной при выходе из процедуры – это хорошо, но не всегда удобно. Пусть мы хотим подсчитывать количество нажатий на кнопку Button1. Приведенная ниже процедура почти подходит, но она не будет накапливать счетчик, так как при каждом вызове процедуры счетчик обнуляется в результате инициализации:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim Счетчик As Integer
Счетчик = Счетчик + 1
TextBox1.Text = Счетчик
End Sub
Делать счетчик модульной переменной не хочется, никому он кроме нас не нужен. Чтобы счетчик не обнулялся, объявим его статической переменной:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Static Счетчик As Integer
Счетчик = Счетчик + 1
TextBox1.Text = Счетчик
End Sub
Статическая переменная объявляется не словом Dim, а словом Static. Это обычная локальная переменная, но с одним отличием: ее значение не уничтожается после выхода из процедуры и доживает до следующего ее вызова.
Теперь все в порядке. Счетчик накапливается. А для других процедур переменная невидима.
Пока об областях видимости нам достаточно. Более подробную информацию вы найдете в 21.9.
Области видимости переменных
Материал следующих трех разделов сложен для восприятия и для новичка по большей части непонятно, зачем он вообще нужен. Ничего впечатляющего мы здесь не создадим. Зато следующие за ними три главы – настоящий сказочный сон. Для тех же, кто малодушно перепрыгнет через эти три раздела, он может превратиться в кошмар непонимания.
Да, для уверенного плавания по океану VB этот материал необходим! Не усвоивший его дальше будет тонуть в неожиданных местах. Итак, наберите побольше кислорода и терпения. Идем вглубь.
Передача параметров по ссылке и по значению
Рассмотрим задачу: Известны стороны двух прямоугольников. Нужно вычислить площадь и периметр каждого прямоугольника, а затем напечатать периметр того, чья площадь больше.
Внимательно разберитесь в приведенной ниже программе. На ее примере мы пройдем путь от процедур к функциям.
Поскольку прямоугольника два, мы уже предвидим, что будет выгодно создать процедуру, вычисляющую площадь и периметр прямоугольника. Попробуем сделать это по старинке:
Dim A1, B1, S1, P1 As Integer 'Две стороны, площадь и периметр 1 прямоугольника
Dim A2, B2, S2, P2 As Integer 'Две стороны, площадь и периметр 2 прямоугольника
Dim Площадь As Integer 'площадь, вычисленная процедурой
Dim Периметр As Integer 'периметр, вычисленный процедурой
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
'Работаем с 1 прямоугольником:
A1 = 10 : B1 = 50
Прямоугольник(A1, B1)
P1 = Периметр : S1 = Площадь
'Работаем со 2 прямоугольником:
A2 = 20 : B2 = 30
Прямоугольник(A2, B2)
P2 = Периметр : S2 = Площадь
'Анализируем:
If S1 > S2 Then Debug.WriteLine(P1) Else Debug.WriteLine(P2)
End Sub
Sub Прямоугольник(ByVal Сторона1 As Integer, ByVal Сторона2 As Integer)
Площадь = Сторона1 * Сторона2
Периметр = 2 * Сторона1 + 2 * Сторона2
End Sub
В учебных целях я здесь написал
A1 = 10 : B1 = 50
Прямоугольник(A1, B1)
хотя короче было бы написать
Прямоугольник(10, 50)
В нашей программе нас явно раздражает необходимость писать строки:
Dim Площадь As Integer 'площадь, вычисленная процедурой
Dim Периметр As Integer 'периметр, вычисленный процедурой
P1 = Периметр : S1 = Площадь
P2 = Периметр : S2 = Площадь
К тому же пришлось объявлять слишком много модульных переменных, что снижает безопасность проекта. Нельзя ли как-то укоротить и улучшить программу? Можно. Для этого необходимо добавить в заголовок процедуры один параметр для площади и другой – для периметра. Вот более короткий вариант программы:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim A1, B1, S1, P1 As Integer 'Две стороны, площадь и периметр 1 прямоугольника
Dim A2, B2, S2, P2 As Integer 'Две стороны, площадь и периметр 2 прямоугольника
A1 = 10 : B1 = 50
Прямоугольник(A1, B1, S1, P1)
A2 = 20 : B2 = 30
Прямоугольник(A2, B2, S2, P2)
If S1 > S2 Then Debug.WriteLine(P1) Else Debug.WriteLine(P2)
End Sub
Sub Прямоугольник(ByVal Сторона1 As Integer, ByVal Сторона2 As Integer, _
ByRef Площадь As Integer, ByRef Периметр As Integer)
Площадь = Сторона1 * Сторона2
Периметр = 2 * Сторона1 + 2 * Сторона2
End Sub
Пояснения. До этого момента мы без пояснений писали в заголовке процедуры перед каждым параметром слово ByVal (по значению). Оно рекомендуется для исходных данных, по которым процедура вычисляет результаты. Ну а вот если процедура эти результаты должна «сообщить внешнему миру», чтобы они использовались где-то вне процедуры (что мы и видим в нашем случае), тогда эти результаты нужно включить в число параметров процедуры и предварить словом ByRef (по ссылке).
Вам нужно очень точно понять, что происходит в компьютере при вызове и работе такой процедуры. Разберем момент выполнения вызывающего оператора
Прямоугольник(A1, B1, S1, P1)
Лучше всего это делать в пошаговом режиме. К этому моменту в ячейках A1 и B1 уже находятся числа 10 и 50. А в ячейках S1 и P1 пока пусто, точнее – нули.
Но вот желтая полоса прыгает на заголовок процедуры Прямоугольник, управление передалось этой процедуре. В этот славный момент создаются все ее локальные переменные: параметры Сторона1, Сторона2, Площадь, Периметр. И VB требует, чтобы они тут же получили свои значения от вызывающего оператора Прямоугольник (A1, B1, S1, P1). Причем в том порядке, в каком они приведены в скобках. Поэтому Сторона1 получает свое значение от переменной A1, а значит становится равной 10. Сторона2 получает 50 от B1. А Площадь и Периметр получают по нулю от S1 и P1. Кстати, вы можете сказать, что этим двум и не надо было получать никаких нулей, все равно процедура их правильно бы вычислила. Верно, но такая ненужная здесь механика может пригодиться в других программах.
Смысл. А сейчас вам нужно понять разницу между тем, как процедура работает с параметрами по значению и по ссылке. Представьте себе, что весь проект – это здание школы, процедуры – это различные классы в школе. В классах развешено по нескольку классных досок. Каждая доска – это ячейка под локальную переменную этой процедуры или под ее параметр. Работу каждой процедуры осуществляет учитель с мелом и тряпкой, который сидит в классе.
Теперь будьте внимательны. Что происходит при вызове и работе процедуры Прямоугольник? В классе, отведенном под процедуру Button1_Click, в этот момент на досках A1, B1, S1, P1 написаны числа: соответственно 10, 50, 0, 0. Для определенности назовем комнатой класс, отведенный под процедуру Прямоугольник. В момент вызова мы видим, что в комнатке на доски, помеченные значком ByVal, автоматически переписываются числа с соответствующих досок класса. То есть, на доску Сторона1 переписывается число 10, а на доску Сторона2 – 50. Но вот странная вещь: вместо досок, помеченных значком ByRef, в комнатке дыры. Вместо доски Площадь – дыра с надписью Площадь, которая ведет в класс прямо к доске S1, и учитель может просунуть в дыру свою длинную руку и писать прямо на доске S1 в классе! Абсолютно то же самое с досками Периметр и P1. Поэтому в рассматриваемый момент с досок S1 и P1 в комнатушку ничего не переписывается. В этом нет нужды, так как учитель все равно имеет полный доступ к этим двум доскам. Через дыры он видит, что там – нули.
Итак, учитель может распоряжаться 4 досками, 2 – в своей комнатке и 2 – в классе. Посмотрим, что происходит, когда учитель выполняет оператор
Площадь = Сторона1 * Сторона2
Он смотрит на доски своей комнатки, видит там 10 и 50, перемножает их, затем просовывает руку в дыру с надписью Площадь и записывает результат 500 на доску S1 в классе. Говорят: переменная Площадь является ссылкой на переменную S1 или что переменная Площадь ссылается
на переменную S1.
Мы привыкли, что в ячейках памяти хранятся числа или строки. А что же хранится в «дырявой» ячейке Площадь? Говорят, что в ней хранится ссылка на ячейку S1 или, еще говорят, – хранится адрес ячейки S1.
Если учитель во время работы что-нибудь напишет на доске со значком ByVal, то бесполезно ждать, что в классе об этом когда-нибудь узнают. Обратного переписывания с досок ByVal в комнатушке на соответствующие доски в классе никогда не происходит! Это значит, что если мы нечаянно пометим параметр Периметр не словом ByRef, а словом ByVal, то все усилия процедуры по вычислению периметра окажутся бесполезными: никто никогда снаружи не узнает вычисленного значения периметра. Проверьте и увидите, что программа в этом случае печатает периметр равный нулю.
Теперь скажем то же самое, но только другими словами: при помощи компьютерной терминологии. Говорят, что ByVal обеспечивает передачу параметров по значению, а ByRef – передачу параметров по ссылке. При передаче параметров по значению процедура работает не с теми ячейками, из которых передается информация (A1, B1), а с собственными ячейками (Сторона1, Сторона2), куда информация из A1, B1 копируется при обращении к процедуре. Процедура может как угодно менять информацию в своих ячейках Сторона1 и Сторона2, на чужих ячейках A1 и B1 это никак не скажется. Поэтому обычно никто из программистов и не старается этого делать.
При передаче параметров по ссылке процедура работает не с собственными ячейками (Площадь, Периметр), а непосредственно с теми ячейками, на которые они ссылаются (S1, P1). Это опасно, ведь процедура получает доступ к локальным переменным другой процедуры, а это не приветствуется, ведь эти переменные становятся беззащитными против ошибок в вызываемой процедуре. Поэтому программисты стараются внимательно следить, чтобы ненароком не записать в чужие переменные что-нибудь не то. И опасное слово ByRef употребляют только тогда, когда хотят передать вызывающей процедуре важные сведения, а в остальных случаях используют безопасное ByVal.
Вот пример, как неоправданное использование ByRef довело нас до беды:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Dim A1, B1, S1, P1 As Integer 'Две стороны, площадь и периметр прямоугольника
A1 = 10 : B1 = 50
Прямоугольник_опасный(A1, B1, S1, P1)
Debug.WriteLine(A1 & " " & B1 & " " & S1 & " " & P1)
End Sub
Sub Прямоугольник_опасный(ByRef Сторона1 As Integer, ByVal Сторона2 As Integer, _
ByRef Площадь As Integer, ByRef Периметр As Integer)
Площадь = Сторона1 * Сторона2
Периметр = 2 * Сторона1 + 2 * Сторона2
Сторона1 = 999
End Sub
Пояснения. Автор процедуры Прямоугольник_опасный для каких-то своих нужд написал оператор Сторона1 = 999. И все бы ничего, это его право, но он шапкозакидательски написал в заголовке ByRef Сторона1. В результате ни в чем не виноватая процедура Button2_Click вместо того, чтобы напечатать
10 50 500 120
напечатала
999 50 500 120
Достаточно в приведенной программе вместо ByRef Сторона1 снова написать ByVal Сторона1, и все опять будет нормально. Таким образом, передача параметров по значению – еще один способ повысить надежность программирования.
Важное исключение. Слово ByVal теряет свою способность к защите по отношению к массивам и объектам. Ни массивов, ни объектов мы еще не проходили. О причинах невозможности защиты поговорим в 27.2.4.
Далее. В вызывающем операторе на месте параметров, вызываемых по значению, могут стоять не только переменные, но и литералы, и выражения:
Прямоугольник(10, A1+40, S1, P1)
Разница только в том, что при вызове процедуры выражения сначала вычисляются, после чего вычисленные значения, как и положено, посылаются в ячейки параметров процедуры (Сторона1 и Сторона2).
А вот на месте параметров, вызываемых по ссылке, могут стоять только переменные! Никаких литералов и выражений!
Из механики работы параметров вытекает очень удобный факт: Когда мы пишем процедуру, нам не нужно заботиться о том, какие имена переменных будут использованы при обращении к процедуре, мы просто даем параметру любое пришедшее в голову подходящее имя. И наоборот, когда мы пишем обращение к процедуре, нам не нужно заботиться о том, какие имена имеют параметры в заголовке процедуры. В частности, мы можем использовать в обращении переменные, имеющие такие же имена, что и соответствующие параметры. Эффект затенения не даст им перепутаться.
Из чего состоит тело процедуры. Выражения
Тело процедуры состоит из операторов Dim и остальных операторов. Остальные операторы выполняются по порядку.
Из чего состоят операторы? Они могут включать в себя блоки других операторов, как это делают, например, операторы If и Do. Они могут включать в себя выражения.
Мы знаем с вами уже три вида выражений: арифметические (их значение – число), строковые (их значение – текстовая строка) и логические (они принимают одно из двух значений: True или False).
Выражение, стоящее отдельно, само по себе, встречаться не может. Выражения всегда являются частью операторов. Выражения мы встречаем справа (не слева!) от знака равенства в операторе присваивания, в условиях тех же операторов If и Do, внутри скобок при обращении к методам и функциям.
В простейшем случае выражение – это литерал:
Фрагмент | Где тут выражения | ||
a = 0 | 0 | ||
b = -7 | -7 | ||
s = "Привет" | "Привет" |
В простом случае выражение – это переменная:
a = c | c | ||
w = s | s – строковое выражение |
В обычном случае выражение – это литералы и переменные, соединенные знаками арифметических и других операций. В выражения входят скобки, а также математические и другие функции:
d = a + 1 | Здесь три выражения: a, 1 и a+1 | ||
w = s & "ик" | Здесь три выражения: s, "ик" и s & "ик" | ||
Debug.WriteLine(b - 3) | Здесь три выражения: b, 3 и b-3 | ||
b = Len(w) | Здесь два выражения: w и Len(w) | ||
b = a * (Len(w) – Rnd()) | Выражение a * (Len(w) – Rnd()) и в нем 5 выражений | ||
If c + 9 > a Then | Логическое выражение c+9>a, состоящее из двух арифметических |
Функции
Философия. Мы с вами уже сталкивались со стандартными функциями (например, Len(w), Abs(c+200) и др.). Стандартная функция – это некая скрытая программа, которая принимает свои параметры, указанные в скобках, в качестве исходных данных (аргументов), что-то делает с ними и в результате получает одну величину, которая и является значением функции. Много примеров функций вы найдете в 5.4.1 и по всей книге.
Когда мы видим оператор
b = a * (Len(w) – Abs(c+200))
то говорим, что при выполнении этого оператора компьютер обращается к функциям Len и Abs. А само упоминание этих функций в тексте оператора называем обращениями к этим функциям, чтобы отличить их от объявлений функций, которые являются солидными программами для вычисления значений этих функций, только скрытыми от нас. Здесь полная аналогия с обращением к процедуре пользователя. Обращение к процедуре – это коротенький оператор, а объявление процедуры может состоять из многих строк.
Обратите внимание, что обращения к стандартным функциям встречаются обычно в выражениях. Редко кто пишет отдельные операторы такого вида:
Len(w)
Abs(c + 20)
Да это и понятно. Ведь в этом случае неизвестно, как использовать вычисленную величину функции. Действительно, ну вот мы вычислили длину строки, а куда ее девать, как приспособить для какого-нибудь дела? Поэтому пишут так:
b = Len(w)
Здесь все понятно: мы в дальнейшем можем использовать, переменную b, которая несет в себе вычисленную величину функции, как хотим. Или, скажем, так:
Debug.WriteLine(Abs(c + 20))
Здесь тоже от абсолютной величины польза была: мы ее напечатали.
Сейчас я хочу, чтобы мы подошли к осознанию необходимости и удобства обладания собственными функциями – функциями пользователя. Функции пользователя вы создаете тогда, когда вам недостаточно функций из библиотеки классов .NET Framework. Например, вы хотите иметь функцию, аргументами которой были бы длины двух сторон прямоугольника, а значением – периметр этого прямоугольника. Но позвольте! – скажете вы – Мы только что создали процедуру, которая делает именно это, да еще и площадь вычисляет! Зачем нам нужна еще какая-то функция? А затем, что функция в данном случае удобнее и естественнее в использовании. Вы увидите, что удобнее создать и использовать две функции: Периметр и Площадь, чем одну процедуру. И не забывайте, кстати, что единую функцию Периметр_Площадь создать нельзя, так как функция может иметь только одно значение.
Пример 1. Вспомним задачу из 11.4.1. : «Известны стороны двух прямоугольников. Нужно напечатать периметр того прямоугольника, чья площадь больше.» Приведу целиком программу решения этой задачи, но уже с использованием не процедуры, как в 11.4.1. , а функций:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim A1, B1 As Integer 'Две стороны 1 прямоугольника
Dim A2, B2 As Integer 'Две стороны 2 прямоугольника
A1 = 10 : B1 = 50
A2 = 20 : B2 = 30
If Площадь(A1, B1) > Площадь(A2, B2) Then WriteLine(Периметр(A1, B1)) _
Else WriteLine(Периметр(A2, B2))
End Sub
Function Периметр(ByVal Сторона1 As Integer, ByVal Сторона2 As Integer) As Integer
Return 2 * Сторона1 + 2 * Сторона2
End Function
Function Площадь(ByVal Сторона1 As Integer, ByVal Сторона2 As Integer) As Integer
Площадь = Сторона1 * Сторона2
End Function
Пояснения. Оператор
If Площадь(A1, B1) > Площадь(A2, B2) Then WriteLine(Периметр(A1, B1)) _
Else WriteLine(Периметр(A2, B2))
содержит в себе два обращения к функции Площадь и два обращения к функции Периметр. Он хорош своей естественностью и понятностью. Действительно, он практически повторяет условие задачи:
«ЕСЛИ площадь первого прямоугольника больше площади второго,
ТО печатай периметр первого прямоугольника,
ИНАЧЕ печатай периметр второго».
Терминология. Как же устроены наши функции? Сначала о терминологии, которая очень похожа на терминологию процедур.
Все операторы, из которых состоит функция, без заголовка и конечной строки, будем называть телом функции. А вместе с этими строками – объявлением функции. Как видите, тела наших функций очень коротенькие, всего из одного оператора. Но операторов может быть сколько угодно, как и в процедуре.
Когда VB в процессе выполнения программы натыкается на функцию Периметр, он ищет в программе объявление функции с именем Периметр и начинает выполнять тело этой функции. Этот процесс называется вызовом функции или
обращением к функции . Говорят также, что управление передается функции. После выполнения тела функции VB возвращается к выполнению программы. Говорят, что управление возвращается к программе. Про значение функции говорят, что функция возвращает это значение в программу.
Отличия функций от процедур. Вы видите, что объявление функции очень похоже на объявление процедуры. Но функция в отличие от процедуры обладает некоторыми свойствами переменной величины. Объявление функции отличается от объявления процедуры следующими элементами:
В заголовке функции мы пишем не Sub (процедура), а Function (функция)
В заголовке функции после скобок с параметрами должен быть указан тип значения функции (так как у нас это численное значение имеет смысл площади или периметра, то я выбрал в обоих случаях Integer).
Внутри тела функции ей хотя бы раз должно быть присвоено какое-нибудь значение. Именно это значение функция и будет возвращать. Присвоение можно делать двумя способами:
Обычным оператором присваивания, в котором слева от знака равенства стоит имя функции, как если бы это была не функция, а обычная переменная (у нас этим занимается оператор Площадь = Сторона1 * Сторона2).
При помощи специального оператора Return (у нас этим занимается оператор Return 2 * Сторона1 + 2 * Сторона2).
В конечной строке мы пишем не End Sub (конец процедуры), а End Function (конец функции)
Напомню еще раз, что обращение к функции отличается от обращения к процедуре. Если обращение к процедуре – самостоятельный оператор, то обращение к функции – это обычно составная часть выражения.
Пример 2. Рассмотрим другой пример функции. Ее параметр (аргумент) – строка. Значение функции – та же строка, повторенная столько раз, сколько символов (букв) в исходной строке. Например, если параметр – «уж», то функция – «ужуж», параметр – «Вена», функция – «ВенаВенаВенаВена». Если длина параметра превышает 8 символов, значение функции таково – «Вы задали слишком длинное слово».
Вот программа:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim S As String
S = TextBox1.Text
TextBox1.Text = Размноженное_слово(S)
End Sub
Function Размноженное_слово( ByVal Slovo As String) As String
Dim i, Длина_слова As Integer
Длина_слова = Len(Slovo)
If Длина_слова > 8 Then
Return "Вы задали слишком длинное слово"
Else
Размноженное_слово = ""
For i = 1 To Длина_слова
Размноженное_слово = Размноженное_слово & Slovo
Next
End If
End Function
Пояснения. Здесь мы пишем в текстовом поле слово и щелчком по кнопке тут же получаем в том же текстовом поле результат – размноженное слово.
Посмотрим на объявление функции. В заголовке мы видим, что как параметр функции, так и сама функция имеют тип String. Процедуры и функции, как вы уже видели, могут содержать кроме параметров также и обычные локальные переменные (в нашем случае это i и Длина_слова). Тело функции состоит из нескольких операторов.
Механика работы этой функции проста. Переменная Размноженное_слово накапливает в себе слова, как сумматор сумму. Роль плюса играет знак &. На каждой итерации второй из двух операторов присваивания удлиняет Размноженное_слово на Slovo. А итераций столько, сколько букв в слове. «Обнуляет сумматор» оператор
Размноженное_слово = ""
Роль нуля исполняет пустая строка "" длиной в 0 символов.
Для возврата значений применяется в одном месте оператор Return и в двух местах – оператор присваивания Размноженное_слово = . . .. Не запутаемся ли? – Значение одно, операторов – три. Спрашивается, какой из этих операторов в действительности вернет значение? Чтобы ответить на этот вопрос, вам нужно знать, что
между операторами Return и присваивания, используемыми для возврата значения функции, имеется существенное различие. А именно: наткнувшись на оператор Return, компьютер выполняет его и на этом немедленно прекращает выполнение функции, возвращаясь в программу, ее вызвавшую. Ничего подобного при выполнении оператора присваивания не происходит – компьютер продолжает выполнять тело функции дальше.
Если вы внимательно разберете текст этой или любой другой функции, то увидите, что вернет значение оператор, последний по времени выполнения (а не в порядке записи). Какой именно? Все зависит от длины слова. Если оно длиннее 8 букв, то значение вернет оператор Return. Если его длина – от 1 до 8 букв, то второй из двух операторов присваивания. Если вы ничего не введете в текстовое поле, то – первый.
Обязательно прогоните программу в пошаговом режиме. Вам будет любопытно наблюдать, как постепенно удлиняется Размноженное_слово.
Побочный эффект. В теле функции можно писать сколько угодно любых операторов. А почему бы в таком случае не попытаться убить двух зайцев и не вычислять в теле функции Площадь еще и периметр? Ну и что, что он не будет возвращаемым значением функции! Зато тогда можно было бы обойтись одной функцией. Попробуем:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim A1, B1, П1 As Integer 'Две стороны и периметр 1 прямоугольника
Dim A2, B2, П2 As Integer 'Две стороны и периметр 2 прямоугольника
A1 = 10 : B1 = 50
A2 = 20 : B2 = 30
If Площадь(A1, B1, П1) > Площадь(A2, B2, П2) Then WriteLine(П1) Else WriteLine(П2)
End Sub
Function Площадь(ByVal Сторона1 As Integer, ByVal Сторона2 As Integer, ByRef Периметр As Integer) _
As Integer
Площадь = Сторона1 * Сторона2
Периметр = 2 * Сторона1 + 2 * Сторона2
End Function
Что мы выиграли и что проиграли? Сэкономили две строчки кода, но пришлось вводить дополнительные переменные и параметр. А самое главное – проиграли в простоте, единообразии и понятности. Функция получилась довольно нелепая.
Если функция кроме возвращаемого значения вычисляет что-то еще побочное, что мы используем в программе, то говорят, что функция имеет побочный эффект. Побочные эффекты довольно часто используются в программировании. Например, среди функций из библиотеки классов .NET Framework довольно много таких, возвращаемое значение которых никого не интересует, а интересует именно побочный эффект. Поэтому обращение к функции разрешено писать не только в выражениях, но и отдельным оператором. Например, так:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Dim A1, B1, П1 As Integer 'Две стороны и периметр прямоугольника
A1 = 10 : B1 = 50
Площадь(A1, B1, П1)
Debug.WriteLine(П1)
Math.Abs(-20)
End Sub
Как я уже упоминал, от площади в этом примере толк есть, а от абсолютной величины – нет.
Задание 65.
Напишите функцию с двумя строковыми параметрами. Функция должна выдать длину той строки, которая короче.
Константы
Вспомним процедуру рисования дерева из 11.2.2.:
Sub Дерево()
Dim x As Single = 200
Dim y As Single = 100
Dim Ширина_кроны As Single = 20
Dim Высота_кроны As Single = 60
Dim Длина_ствола As Single = 15
Гр.FillEllipse(Brushes.Green, x, y, Ширина_кроны, Высота_кроны)
Гр.DrawEllipse(Pens.Black, x, y, Ширина_кроны, Высота_кроны)
Гр.DrawLine(Pens.Black, x + Ширина_кроны / 2, y + Высота_кроны, _
x + Ширина_кроны / 2, y + Высота_кроны + Длина_ствола)
End Sub
При помощи пяти переменных мы можем управлять положением, размерами и формой дерева. Предположим, приходит ваш начальник, смотрит на дерево и говорит: «Хорошее дерево, особенно мне нравится ширина кроны. Приказываю, чтобы отныне все деревья, нарисованные этой процедурой, имели только такую ширину кроны и никакую другую, несмотря на дальнейшее развитие и усложнение процедуры.»
Как надежно выполнить приказ начальника? Ширина кроны задается оператором
Dim Ширина_кроны As Single = 20
Если не трогать этот оператор, можно ли быть уверенным, что ширина кроны в будущем не изменится? Нельзя, так как в будущем мы можем по забывчивости дописать в процедуру операторы типа Ширина_кроны = . . . Как бороться с забывчивостью? VB предлагает средство – так называемые константы. Константа – это переменная, которой мы задаем значение при объявлении, и которая неспособна это значение в дальнейшем менять. Чтобы отличить константу от обычной переменной, в операторе объявления мы вместо Dim пишем Const:
Const Ширина_кроны As Single = 20
Отныне, если мы попытаемся дописать в процедуру операторы, имеющие возможность изменить значение константы, типа Ширина_кроны =… , VB выдаст сообщение об ошибке.
Таким образом, при помощи объявления констант мы повышаем надежность программирования.
Имейте в виду, что термином «константа» до последнего времени часто обозначали два близких понятия: с одной стороны собственно константы, а с другой стороны литералы. Я буду стараться избегать возможной путаницы и литералы буду называть литералами. Литералы мы уже проходили. Напомню, что литералами называются те конкретные значения величин, которые мы видим в программе. Например, во фрагменте
a = 1 + 0.25
b = "Амазонка"
Debug.WriteLine("Волга")
If a > 3 Then . . .
литералы это 1 0.25 "Амазонка" "Волга" 3.
Кроме упомянутых выше констант, которые вы создаете сами, существует еще большое число констант, определенных в библиотеке классов .NET Framework. Объявлять их не надо, ими можно сразу пользоваться, если знать их имена и смысл. Например, такими константами являются математические константы PI и E, входящие в класс Math. Каждая из таких констант имеет конкретное значение (например, PI = 3.14159265358979323846). Мы могли бы вместо слова PI просто писать конкретное число 3.14159265358979323846, но названия запоминаются легче и записываются короче, чем числа.
Функции
Процедуры и функции – важное средство сделать вашу программу надежнее и понятнее.
Прежде чем перейти к функциям, нам нужно поподробнее поговорить о параметрах. До сих пор мы использовали параметры для того, чтобы сообщить процедуре значения исходных данных для ее работы, например, положение и размеры дерева, а уж процедура по этим данным рисует дерево нужных размеров и в нужном месте. Однако бывает необходимо, чтобы и процедура нам что-то сообщила.
Переменные объектного типа
В 6.1.2 мы видели, что переменные могут иметь объектный тип. Проиллюстрируем это на примере. Создайте проект и поместите на форму метку Label1 и кнопку. Введите такой код:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Dim A As Object
A = Label1
A.text = "Я метка"
A = Me
A.text = "Я форма"
A = Me.CreateGraphics
A.DrawLine(Pens.Black, 20, 20, 200, 200)
End Sub
Пояснения. Слово Object в операторе Dim A As Object говорит о том, что переменная A может иметь своим значением любой объект.
Следующий оператор A = Label1 присваивает переменной A значение существующего объекта – метки Label1. Это значит, что теперь все равно, как написать:
Label1.Text = "Я метка"
или
A.text = "Я метка"
Эффект будет одинаковый.
Получается, что через переменную A вы получили доступ ко всем свойствам и другим возможностям метки, доступным через имя Label1.
Обратите внимание, что когда вы набираете точку после буквы A, никакого привычного нам вразумительного списка свойств и методов не появляется и слово text приходится полностью набирать вручную. Дело в том, что состав этого списка определяется типом объекта, после которого вы набираете точку. Тип Object настолько универсален, что практически не существует свойств и методов, присущих одновременно всем объектам VB.
В следующем операторе A = Me ветреная и непостоянная A изменяет метке и принимает в качестве своего значения форму. С тем же результатом всевластия над возможностями формы. О метке забыто.
Оператор A = Me.CreateGraphics присваивает переменной A значение объекта класса Graphics. В результате через переменную A вы получили доступ ко всем возможностям рисования.
Тип Object – самый общий объектный тип. Есть более узкие объектные типы. Например, Control. Переменная, объявленная этим типом, уже не может быть любым объектом, но может, например, принимать значение любого элемента управления.
Типы Label и Button – еще более узкие типы. Так, переменная, объявленная типом Button, может быть любой кнопкой, но только кнопкой. Говорят, что эта переменная стала объектом класса Button.
Параметры объектного типа
В качестве параметров мы пока использовали переменные числовых и строкового типов. Параметр – это переменная. Переменная может иметь объектный тип. Значит и параметр может иметь объектный тип.
Как это понимать и какая от этого польза? Рассмотрим два примера.
Пример 1. Параметр типа Control. Для задания размеров элемента управления требуется два оператора – один для ширины, другой – для высоты. Предположим, вы хотите обойтись одним. Особого смысла в этом нет, но для иллюстрации подойдет. Создайте проект и поместите на форму метку и две кнопки. Введите такой код:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Настраиваем_размер(Button2, 80, 30)
Настраиваем_размер(Label1, 100, 120)
End Sub
Sub Настраиваем_размер(ByVal Элемент_упр As Control, ByVal Ширина As Integer, ByVal Высота As Integer)
Элемент_упр.Width = Ширина
Элемент_упр.Height = Высота
End Sub
Пояснения. Оператор
Настраиваем_размер(Button2, 80, 30)
делает ширину кнопки Button2 равной 80, а высоту – 30. А оператор
Настраиваем_размер(Label1, 100, 100)
делает ширину метки Label1 равной 100, а высоту – 120. Мы добились того, чего хотели. Как это у нас получилось?
Оба приведенные оператора являются вызовами процедуры пользователя Настраиваем_размер. Посмотрите на ее заголовок. Первый параметр – Элемент_упр – имеет тип Control – объектный тип элементов управления. Это значит, что он может, например, принимать значение любого элемента управления на форме.
Обязательно зайдите в пошаговый режим и обратите внимание, что переменная Элемент_упр, как и положено параметру, инициализируется, несмотря на то, что она не обычная, а объектная. Пока мы не зашли в процедуру Настраиваем_размер, она, естественно, не инициализирована и подсказка просто сообщает, что она объявлена, как мы и задали, типом System.Windows.Forms.Control. Здесь System.Windows.Forms – пространство имен, в которое входит класс Control. Но как только VB заходит в процедуру, переменная Элемент_упр становится объектом класса System.Windows.Forms.Button. При втором обращении – System.Windows.Forms.Label.
Пример 2. Параметр типа Graphics. Вам уже предлагалось в Задание 78 написать процедуру пользователя для рисования крестика. У той процедуры были привычные нам параметры: координаты и размер крестика. Сейчас же нам интересен другой параметр. Мы хотим посредством него управлять тем, на поверхности какого элемента управления (или на форме) рисовать крестик. Создайте проект и поместите на форму метку и кнопку. Чтобы не отвлекаться, забудем о параметрах для координат и размера крестика. Введите такой код:
Dim Графика_для_формы As Graphics
Dim Графика_для_метки As Graphics
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Графика_для_формы = Me.CreateGraphics
Графика_для_метки = Label1.CreateGraphics
Рисуем_крестик(Графика_для_формы)
Рисуем_крестик(Графика_для_метки)
End Sub
Sub Рисуем_крестик(ByVal Гр As Graphics)
Гр.DrawLine(Pens.Blue, 100, 110, 120, 110)
Гр.DrawLine(Pens.Blue, 110, 100, 110, 120)
End Sub
Пояснения. Крестики рисуются на форме и на метке. Как видите, здесь параметром процедуры пользователя является объект класса Graphics. Без этого параметра мы не смогли бы пользоваться процедурой Рисуем_крестик для рисования крестика на разных элементах управления. Получилось бы только на одном.
Пример 3. Функция объектного типа. Создайте проект с кнопкой, меткой и текстовым полем. Пусть нам наскучило обращаться к элементам управления, как положено, по именам, а «желаем» по номерам. Пронумеруем их в уме как-нибудь, например, кнопка – 1, метка – 2, текстовое поле – 3. Пусть возникла задача напечатать, на сколько кнопка превосходит по вертикальному размеру текстовое поле. В соответствии с нашим желанием нам не хочется писать так:
Debug.WriteLine(Button1.Height - TextBox1.Height)
а хочется писать что-нибудь вроде этого:
Debug.WriteLine(Элемент(1).Height - Элемент(3).Height)
Можем ли мы так сделать? Можем. Достаточно написать функцию:
Function Элемент( ByVal Номер As Integer) As Control
Select Case Номер
Case 1
Return Button1
Case 2
Return Label1
Case 3
Return TextBox1
End Select
End Function
Пояснения. Функция может возвращать значение почти любого типа, а не только простого. В том числе и объект. Наша функция Элемент имеет объектный тип Control. Это значит, что в зависимости от значения своего параметра Номер она может принять значение не числа и не строки, к чему мы привыкли, а элемента управления: Button1 или Label1, или TextBox1. Вы можете в обращении мысленно заменить Элемент(1) на Button1, а Элемент(3) – на TextBox1, тогда вам будет легче привыкнуть к записи. Самое приятное, что когда мы в обращении ставим точку после Элемент(1), то всплывает список компонентов. Это происходит потому, что VB знает, что функция имеет тип Control.
Функция Элемент дает нам замечательное преимущество работать с пронумерованными элементами управления в цикле. Например, вот как можно находить суммарную ширину элементов управления:
For i = 1 To 3
s = s + Элемент(i).Width
Next
В заключение признаюсь, что создатели VB давно уже поняли прелесть нумерации элементов управления и воплотили ее стандартными средствами – при помощи так называемых коллекций (см. 16.2).
Соответствие типов
Мы с вами уже привыкли, что сочиняя программу, нужно немножко заботиться о соответствии типов. Соответствие типов должно выполняться не только для параметров методов, но и в операторе присваивания, где тип выражения справа от знака равенства должен соответствовать типу переменной слева от него. Поэтому, например, следующий фрагмент VB выполнит гладко и без малейших вопросов:
Dim a As Integer
Dim b As Integer = 99
a = b + 2
А вот при попытке выполнить следующий фрагмент
Dim a As Integer
Dim s As String = "Привет"
a = s
VB выдаст ошибку:
Cast from string "Привет" to type 'Integer' is not valid.
Переводится сообщение так:
«Преобразование строки "Привет" в тип Integer незаконно».
Действительно, в какое число можно превратить строчку текста? Нонсенс.
А зачем VB вообще что-то преобразовывал? А затем, что есть такое правило выполнения оператора присваивания:
Вычислив в операторе присваивания выражение справа от знака равенства, VB смотрит, совпадает ли его тип с типом переменной слева от него. Если совпадает – хорошо, а если не совпадает, то пытается преобразовать его тип к нужному (тому, что слева). Если удается – хорошо, а если из соображений правильности, точности, безопасности или из каких-то других своих соображений VB посчитает, что такое преобразование незаконно или ненужно, он выдает сообщение об ошибке.
Рассмотрим фрагмент:
Dim a As Integer
Dim b As Double = 1
a = b + 0.48
Он будет выполнен гладко, несмотря на то, что справа – Double, а слева – Integer. VB не возражает в данном случае преобразовать дробное число в целое. Несмотря на колоссальную потерю в точности (1 вместо 1.48), никаких сообщений или предупреждений мы не увидим. Это программист сам виноват, что объявил переменную целым типом.
Так же гладко выполнится и следующий фрагмент:
Dim a As Integer = 99
Dim s As String
s = a
Здесь VB преобразует число 99 в строку “99”.
Соответствие типов требуется и при вызове процедур и функций с параметрами.
При обращении к процедуре VB смотрит, совпадает ли тип параметра в обращении к процедуре с типом соответствующего параметра в заголовке объявления процедуры. Если совпадает – хорошо, а если не совпадает, то пытается преобразовать его тип к нужному (тому, что в объявлении). Если удается – хорошо, а если нет, VB выдает сообщение об ошибке.
В подтверждение этих слов достаточно вспомнить Методы, «придирчивые» к типу параметров (6.2.8).
Чтобы не было проблем, старайтесь объявлять переменные и параметры, между которыми должно соблюдаться соответствие, одинаковыми типами.
Соответствие объектных типов
Все, что сказано в предыдущем подразделе о соответствии типов, полностью относится и к тому случаю, когда эти типы объектные.
При попытке ввести в окно кода следующий код
Dim A As Graphics
A = Label1
VB подчеркнет ошибку и подскажет:
Value of type 'System.Windows.Forms.Label' cannot be converted to 'System.Drawing.Graphics'
Переводится подсказка так:
«Значение типа Label не может быть преобразовано в тип Graphics».
Действительно, мы объявили переменную A объектным типом Graphics, а сами присвоили ей значение хоть и объектного типа Label, но не имеющего никакого отношения к графике. «Не садись не в свои сани».
Точно такая же подсказка, но уже по поводу параметров, появится, если мы к процедуре из 11.5.2:
Sub Рисуем_крестик(ByVal Гр As Graphics)
Гр.DrawLine(Pens.Blue, 100, 110, 120, 110)
Гр.DrawLine(Pens.Blue, 110, 100, 110, 120)
End Sub
обратимся с глупой ошибкой:
Рисуем_крестик(Label1)
а не как положено:
Рисуем_крестик(Графика_для_метки)
Неопределенные параметры, произвольное число параметров
VB позволяет создавать процедуры и функции с неопределенными параметрами, обращаться к которым можно, например, так:
Проц (5, , , 8, 3)
Ясно, что здесь не указаны значения двух параметров.
VB также позволяет создавать процедуры и функции с произвольным числом параметров. Про то, как и зачем это делается, вы можете прочесть во многих учебниках по VB, в частности, в книге Гарнаева (стр. 133) (см. Список литературы).
Что такое методы
В 2.1.2 я сказал, что классы состоят в основном из методов, свойств и событий. Когда мы ставим точку после имени класса или полученного из него объекта, перед нами возникает список компонентов (members) этого класса, то есть тех же методов, свойств и кое-чего другого.
С другой стороны, мы знаем, что любой стандартный класс – это программа, и поэтому, несмотря на то, что ее текста мы не видим, можете быть уверены: он включает в себя все то, что мы сами привыкли писать в окне кода: процедуры, функции, переменные, параметры, константы и пр.
Вы видите, что здесь терминология несколько другая. Как соотносятся методы, свойства, события с одной стороны с процедурами, функциями, переменными с другой? Нам сейчас нужно хоть немножко увязать терминологию списка компонентов и терминологию программы. Мы исходим из того, что встретиться в этом списке может только то, что встречается в тексте программы этого класса.
Расшифруем чуть-чуть понятие метода. Методы делятся на две категории:
Методы, возвращающие значение – это функции (Function). Мы недавно научились их писать.
Методы, не возвращающие значения – это привычные нам процедуры (Sub).
В дальнейшем мы будем писать собственные методы и они тоже будут или процедурами, или функциями. Поэтому в дальнейшем я часто буду называть процедуры и функции методами.
Что такое свойства (Property)? Пока мы можем думать о них, как о переменных.
В классах встречаются и константы (Const).
О событиях (Event) поговорим гораздо попозже 22.13.
Очевидно, что у методов, возвращающих значение, у свойств, у констант имеются значения, а значит они принадлежат к тому или иному типу.
Все, что я сказал сейчас относительно классов, относится частично и к структурам, и к модулям, и к другим объектам, входящим в состав пространств имен.
Пользуемся подсказкой, чтобы узнать объектные типы
Для новичка функции, переменные, параметры объектного типа непривычны. И мы будем привыкать к ним постепенно, по мере надобности. А чтобы не отвыкнуть, не успев привыкнуть, нам нужно научиться для начала обеспечивать совместимость, быстро узнавать забытый тип и т.д. Ведь классов, их свойств и методов (да еще и параметров этих методов) в библиотеке классов .NET Framework огромное количество, всех не упомнишь. И если VB жалуется, например, на несоответствие типу параметра для забытого нами метода какого-то класса, то мы должны хотя бы иметь возможность где-то узнать, что это за тип.
Рассмотрим на простейшем примере, как для этого пользоваться подсказкой. Пусть мы, например, хотим покрасить метку в красный цвет, но забыли, как это делается. В результате мы не знаем, как правильно написать – так:
Label1.BackColor = Color.Red
так:
Label1.BackColor = Brushes.Red
или так:
Label1.BackColor = Pens.Red
VB путем подчеркивания вежливо подсказывает нам, что два последних варианта неверны. Но мы не понимаем, почему. И хотим знать, чтобы не ошибаться в дальнейшем. Такое желание достойно уважения и его необходимо удовлетворить.
Источник многих ошибок – несоответствие типов. Поэтому нам для начала нужно точно знать тип каждого элемента в написанных строках. Потренируемся. Начнем с правильной строки.
Label1.BackColor = Color.Red. Поставим мышь на слово Label1. Всплывает подсказка:
Friend Dim WithEvents Label1 As System.Windows.Forms.Label
Что это такое? Не обращаем внимания на слова Friend и WithEvents. Остается
Dim Label1 As System.Windows.Forms.Label
Это не что иное, как объявление метки. Мы его не делали, VB сделал его сам в скрытой части окна кода (см. 6.1.3). Мы видим, что метка Label1 имеет тип System.Windows.Forms.Label, то есть принадлежит классу Label, входящему в пространство имен System.Windows.Forms.
Поставим мышь на слово BackColor. Всплывает подсказка:
Public Overridable Overloads Property BackColor() As System.Drawing.Color
Не обращаем внимания на слова Public Overridable Overloads. Остается
Property BackColor() As System.Drawing.Color
Слово Property означает «свойство». Это не что иное, как подсказка о типе свойства BackColor. Мы видим, что свойство метки BackColor имеет тип System.Drawing.Color, то есть является структурой Color, входящей в пространство имен System.Drawing.
Не задавайте мне сейчас вопросов, вроде «Как может цвет быть какой-то там структурой?» или других вопросов о «смысле». Вам даже не нужно понимать, похожа ли структура на объект. Понимание таких вещей придет к нам позднее. Сейчас нам важно уяснить следующее: Некоторый элемент (в нашем случае – свойство BackColor) может принадлежать
одному классу или объекту (Label1), а его значение может иметь тип совсем другого класса, объекта или чего-нибудь еще (структура Color). Всегда помните эту разницу. Это нормально. Это в порядке вещей.
Поставим мышь на слово Color. Всплывает подсказка:
Structure Color
Здесь все понятно.
Далее. У структуры Color есть свойства и одно из них стоит после точки. Поставим мышь на слово Red. Всплывает подсказка:
Public Shared Overloads ReadOnly Property Red() As System.Drawing.Color
Не обращаем внимания на слова Public Shared Overloads ReadOnly. Получается
Property Red() As System.Drawing.Color
Мы видим, что Red – это свойство структуры Color и оно же имеет тип Color, то есть тип той же самой структуры. А почему бы и нет, Семен Семеныч Семенов? Опять же, пока важно не это.
Оператор присваивания Label1.BackColor = Color.Red присваивает свойству BackColor объекта Label1 свойство Red структуры Color. Мы только что убедились, что типы обоих свойств совпадают, а это значит, что совместимость типов соблюдена и присвоение прошло без осложнений.
Label1.BackColor = Brushes.Red. Теперь посмотрим, к чему придирается VB в операторе Label1.BackColor = Brushes.Red. Что касается левой части оператора, то там все то же, что и в правильной строке. Посмотрим на подчеркнутую правую часть.
Поставим на нее мышь. Всплывает подсказка, относящаяся ко всей подчеркнутой части ошибочного оператора и в ней мы скоро разберемся. Но нам сначала хотелось бы узнать, что это за Brushes такие. Предположим, что мы это забыли.
Чтобы вспомнить, мы можем воспользоваться средствами помощи или Object Browser, как мы это делали в 6.2.8. Я поступлю проще – напишу какой-нибудь правильный оператор, в котором выражение Brushes.Red не будет подчеркнуто, и поставлю мышь на слово Brushes. Всплывает подсказка:
Class Brushes
Все ясно. Это класс с именем Brushes.
Поставим мышь на слово Red. Всплывает подсказка:
Public Shared Overloads ReadOnly Property Red() As System.Drawing.Brush
Она единственным (самым правым) словом отличается от аналогичной подсказки для свойства Red структуры Color. Но это слово решает все.
Мы видим, что в данном случае Red – это свойство класса Brushes, и оно имеет тип Brush. Класс Brushes и класс Brush – это разные классы и вы не должны их путать, несмотря на то, что их похожее написание провоцирует спекуляции на тему некоей смысловой связи этих классов. Чтобы правильно пользоваться обоими классами, нет никакой необходимости вникать в наличие или отсутствие этой связи.
Мы видим, что свойство Red принадлежит классу Brushes, а само имеет значение совсем другого типа – класса Brush. Тоже в порядке вещей. Привыкайте.
Главный вывод таков. Неправильный оператор присваивания пытается присвоить свойству BackColor, имеющему тип Color, свойство Red, имеющее тип Brush. Типы не совпадают и VB бессилен их совместить.
Поставим мышь на подчеркнутую правую часть. Всплывает подсказка
Value of type 'System.Drawing.Brush' cannot be converted to 'System.Drawing.Color'
Переводится подсказка так:
«Значение типа Brush не может быть преобразовано в тип Color».
Как видите, для понимания причин несовместимости нам совсем не понадобилось изучать класс Brush и даже знакомиться с ним.
Label1.BackColor = Pens.Red. Аналогично можете убедиться в неправильности третьей строки, где свойство Red знакомого нам класса Pens имеет неведомый нам тип Pen, то есть его значение принадлежит классу Pen.
Получается не очень логичная с точки зрения здравого смысла вещь: Красный – он и в Африке красный. Зачем же тогда свойство Red разных классов имеет разный тип? Поймете, когда будете писать свои классы.
Параметры методов
В предыдущем подразделе мы разбирались в причинах несовместимости объектных типов в операторе присваивания. Сейчас мы рассмотрим несовместимость объектных типов в параметрах.
Вспомним еще раз Методы, «придирчивые» к типу параметров (6.2.8). Проделаем еще раз то, что мы делали там. Пусть мы, например, забыли параметры метода DrawLine. В результате мы не знаем, как правильно написать – так:
Граф.DrawLine(Pens.Red, 0, 0, 100, 100)
так:
Граф.DrawLine(Brushes.Red, 0, 0, 100, 100)
или так:
Граф.DrawLine(Color.Red, 0, 0, 100, 100)
VB путем подчеркивания подсказывает нам, что два последних варианта неверны. Но нам надо знать, почему. Мы грешим на несовместимость типов и решаем проверить, каким типам принадлежат первый параметр в обращении к методу и первый параметр в объявлении метода.
Граф.DrawLine(Pens.Red, 0, 0, 100, 100). Рассмотрим сначала правильную строку. Поставим мышь на слово Red. Подсказка говорит нам, что свойство Red знакомого нам класса Pens имеет неведомый нам тип Pen, то есть его значение принадлежит классу Pen. Хорошо. Неважно, что класс неведомый, важно, чтобы тип первого параметра тоже был Pen и в объявлении метода.
Как добраться до объявления? Предлагаю 3 способа. Первые два заключаются в использовании системы помощи VB и Object Browser. Я разобрал эти способы в 6.2.8 и тех, кто их совсем забыл, отсылаю туда. Остальным напомню.
1 способ. Поставьте текстовый курсор на название метода DrawLine в окне кода и нажмите клавишу F1. Перед вами возникнет окно помощи, в котором вы увидите заголовки нескольких вариантов метода – процедуры Sub DrawLine. Во всех вариантах вы увидите, что тип интересующего нас первого параметра – Pen. Не путайте с Pens. Все в порядке.
2 способ. Найдите в Object Browser класс Graphics и выделите. В правой панели вы увидите его свойства и методы. Среди них вы найдете и 4 варианта метода DrawLine с указанием параметров.
3 способ я рассмотрю в следующем подразделе.
Граф.DrawLine(Brushes.Red, 0, 0, 100, 100). Теперь рассмотрим вторую строку, ошибочную. Поставим на нее мышь. Перед нами возникает длинная подсказка об ошибке:
Overload resolution failed because no accessible 'DrawLine' can be called with these arguments:
'Public Overloads Sub DrawLine(pen As System.Drawing.Pen, x1 As Integer, y1 As Integer, x2 As Integer, y2 As Integer)': Value of type 'System.Drawing.Brush' cannot be converted to 'System.Drawing.Pen'.
'Public Overloads Sub DrawLine(pen As System.Drawing.Pen, x1 As Single, y1 As Single, x2 As Single, y2 As Single)': Value of type 'System.Drawing.Brush' cannot be converted to 'System.Drawing.Pen'.
Переводится она так:
«Ошибка произошла потому, что ни один из вариантов процедуры DrawLine не может быть вызван с такими аргументами (параметрами):
В варианте процедуры 'Public Overloads Sub DrawLine(pen As System.Drawing.Pen, x1 As Integer, y1 As Integer, x2 As Integer, y2 As Integer)' значение типа 'System.Drawing.Brush' не может быть преобразовано в 'System.Drawing.Pen'.
В варианте процедуры 'Public Overloads Sub DrawLine(pen As System.Drawing.Pen, x1 As Single, y1 As Single, x2 As Single, y2 As Single)' значение типа 'System.Drawing.Brush' не может быть преобразовано в 'System.Drawing.Pen'.»
Мне кажется, здесь все ясно. Мы ошибочно попытались написать в обращении первым параметром Brushes.Red. Но VB выдал ошибку, поскольку тип свойства Red класса Brushes есть Brush, а для первого параметра требуется Pen.
Аналогичное сообщение вы получите для третьей строки.
IntelliSense
У VB есть средство IntelliSense, помогающее вводить программный текст в окно кода. Его основная цель – сократить время написания операторов, по ходу ввода предлагая программисту подсказки, правильные варианты написания текста и дописывая за него слова.
Перечислю основные возможности IntelliSense:
Список компонентов. Мы с вами уже давно и широко пользуемся этой возможностью. Как только мы ставим точку после имени объекта, перед нами возникает прокручивающийся список всех подходящих компонентов этого объекта, из которого мы мышкой или с клавиатуры выбираем нужный. Когда нужный компонент выбран, рядом с ним возникает подсказка, поясняющая его смысл и значение. Мы нажимаем клавишу Tab или Enter – и компонент появляется в тексте, что избавляет нас от необходимости вводить его вручную.
Подсказка по параметрам – пример 1. Она, непрошенная, сопровождает вас во время ввода параметров процедур и функций. Безусловно, вам она уже, наверное, надоела, а некоторые, возможно, в ней уже разобрались и извлекают из нее пользу. Это, кстати, и есть третий способ добраться до объявления процедуры или функции, упомянутый в предыдущем подразделе. Пришла пора разобрать эту подсказку. Разберем ее сначала на примере вызова функции вычисления абсолютной величины.
Введите текст, приведенный на Рис. 11.2.
Рис. 11.2
Как только вы введете открывающую скобку, перед вами возникнет подсказка, приведенная на рисунке. Смысл ее не очень понятен, но в ее левом углу мы видим, что имеется семь вариантов написания этой функции, а мы сейчас смотрим на подсказку к 1-му варианту. Щелкая мышкой по стрелкам или с клавиатуры прокрутим список подсказок, пока не доберемся до чего-нибудь более подходящего (я имею в виду 3-й вариант – Рис. 11.3).
Рис. 11.3
В верхней части подсказки мы видим заголовок объявления функции.
Abs (value As Integer) As Integer
Это как раз то, что нужно, чтобы знать правильные типы функции и ее параметров. Функция имеет имя Abs. Слово value – это имя первого параметра. Почему выбрано именно такое имя, не имеет значения. Если вы помните, вы можете для параметров выбирать любые имена. Вы видите, что в этом варианте параметр должен иметь тип Integer. Функция в этом случае, как видите, будет обязана иметь тот же самый тип Integer.
В нижней части подсказки мы видим пояснение для параметра value: Говорится, что это число в диапазоне таком-то (далее идет пояснение диапазона для профессионалов, нам это не нужно, так как мы и без того знаем точное значение этого диапазона).
Введите в качестве параметра какое-нибудь число, переменную или выражение и закройте скобку. Подсказка исчезнет.
Подсказка по параметрам – пример 2. Мы хотим нарисовать залитый эллипс при помощи функции FillEllipse. Введите текст, приведенный на Рис. 11.4 и выберите 2-й вариант подсказки.
Рис. 11.4
Внутри скобок мы видим 4 запятые. Значит, параметров – 5. Первый из них такой:
brush As System.Drawing.Brush
Слово brush – это имя первого параметра. Начинающего на первых порах может дезориентировать тот факт, что выбрано именно такое имя. Ведь оно совпадает с именем класса Brush. Сказано же: разные вещи называй по-разному! Но опытные программисты знают, что значение и роль какого-нибудь элемента в программной строке зависит не только от его имени, но и от положения в этой строке. Поэтому и не возражают против таких «тезок». Ну, а новичкам приходится привыкать.
Вы видите, что первый параметр должен иметь тип System.Drawing.Brush. Это значит, что он должен быть объектом класса Brush, принадлежащего пространству имен System.Drawing.
Внизу подсказки мы видим пояснение параметра brush: Говорится, что этот объект определяет характеристики заливки.
В дальнейшем мы сами будем создавать кисти. А сейчас воспользуемся готовым объектом – Brushes.Blue (см. Рис. 11.5). Он имеет подходящий тип.
Рис. 11.5
Как только мы ввели запятую после Brushes.Blue, подсказка изменилась. Теперь полужирным выделен не первый параметр, который мы только что ввели, а второй, который нужно вводить. Вообще правило такое: полужирным выделяется тот параметр, на котором стоит текстовый курсор. И подсказка внизу – для него же. Это удобно.
Имя второго параметра – x. Его тип в этом варианте – Single. Читаем внизу пояснение: x – координата верхнего левого угла воображаемого прямоугольника, описанного вокруг эллипса.
Введите в качестве параметра какое- нибудь число, переменную или выражение и поставьте запятую. Подсказка снова изменяется, настроившись на третий параметр. И так далее.
Подсказка в подсказке. Попробуем в качестве какого-нибудь параметра этой функции ввести выражение, содержащее другую функцию – Math.Abs(-20) + 10. Как только мы откроем скобку после Abs, VB услужливо сменит подсказку для процедуры FillEllipse на подсказку для функции Abs (см. Рис. 11.6).
Рис. 11.6
Когда вы закроете скобку после функции Abs, вернется старая подсказка. Правда, у меня не сохранялся номер варианта старой подсказки.
Всплывающая подсказка. Ей мы широко пользовались. Если поставить мышь на любое имя в окне кода, всплывет подсказка о типе элемента с этим именем.
Дописываю слово. Начните писать какое-нибудь имя, например, имя пространства имен Microsoft. Написав Mic, вы устали и хотите, чтобы VB вместо вас дописал это слово. Нажмите комбинацию клавиш Alt-стрелка направо или Ctrl-пробел – и VB допишет или предложит варианты.
Выделение парных скобок. Когда вы пишете длинное выражение со многими вложенными скобками, бывает трудно глазами отыскать пару для данной скобки, что затрудняет правильный ввод выражений. VB приходит вам на помощь и в момент ввода скобки выделяет полужирным ее и ее пару.
Переменные и параметры объектного типа
Я уже говорил, что объекты пронизывают весь VB. Нам пора привыкать к ним, как к значениям переменных величин и параметров.
Точка
Точка – это структура типа Point, принадлежащего пространству имен System.Drawing. Давайте создадим Точку.
Что представляет собой структура Точка? Мы знаем, что точка-пиксель на плоскости (на экране) задается парой координат (X и Y). Так вот: основное назначение структуры Point в том и состоит, чтобы хранить эти две координаты точки и удобно их выдавать программисту по первому требованию. Поставим текстовый курсор на слово Point и нажмем F1. Сработает система помощи и на экране вы увидите небогатый список свойств и методов структуры Point. Среди них – X и Y. Остальные нас не интересуют. Щелкнем по свойству X – и увидим, что это свойство имеет тип Integer.
Для создания структуры Point достаточно написать:
Dim Точка As Point
Вопрос: если мы создали структуру Точка и нигде не задавали ей значения X и Y, то чему они равны? Посмотрим:
Dim Точка As Point
Точка.X = 20
Debug.WriteLine(Точка)
Кстати, еще одна новость – Debug.WriteLine(Точка). До сих пор мы при помощи Debug.WriteLine печатали числа и строки. А как распечатается структура? Не вылетит ли с экрана точка и не погонится ли за нами?
Вот что будет напечатано:
{X=20,Y=0}
VB решил, что распечатка структуры – это распечатка в фигурных скобках ее основных свойств. Можно вообразить, что Точка Point – это просто пара чисел. Попробуйте для интереса распечатать объекты: кнопку и форму.
Итак, X=20, Y=0. Этого следовало ожидать. Каждое из свойств структуры при ее инициализации инициализируется в соответствии со своим типом данных.
Кроме Точки типа Point в VB имеется Точка типа PointF, которая отличается от первой тем, что ее координаты имеют тип не Integer, а Single.
Кому нужна Точка? Это выяснится чуть позже.