ToxicSpider
Бритуля - Богиня
Условие:

Напишите программу, которая подсчитывает количество больших и маленьких букв в предложении. Результаты подсчета выведите на экран.

Запустите программу с фразой "I LiKe C++"

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

Подсказка: убедитесь, что вы помните о том, как строится стринг.

Для крутых:
Если вы считаете, что абсолютно поняли не только этот материал, но и все предыдущие, то напишите цикл подсчета так, чтобы вместо "шагающей переменной" i был использован указатель на char. Это задание на 5+ ))))


Простой код:




Код "для крутых":




Результат:




Примечания:

1. Если у вас получился другой результат, но вы это заметили еще в процессе теста программы, до того, как увидели мой вариант - это довольно неплохо, потому, что это означает, что вы проверяете работу программы, а это - залог успеха. У вас просто баг: вы посчитали пробелы и значок "плюс" в пользу одного из типа букв.

2. Если у вас получился другой результат, но вы даже не обратили на это внимание, пока не прочли эти строки - это ОЧЕНЬ плохо, так как не проверять собственную программу - грубейшее упущение, которое, если вы будете программистом, будет больно вас бить по ушам, пока вы не научитесь этому простому правилу.

3. И, наконец, САМЫЙ печальный результат, это если вы написали ваш цикл так:

for ( int i=0; i < 10; i++)

а вот это 10 вы просто высчитали из конкретной строки. Это ОЧЕНЬ плохо! Такое решение просто нельзя допускать! Вы написали программу, которая работает с КОНКРЕТНОЙ строкой, а это НИКОГДА не может быть, не то что "хорошим"... это не может быть "хоть малость приемлемым" решением! Не повторяйте такую ошибку никогда! Программирование - эта та профессия, в которой нужно включать голову, а не работать на автомате!

4. Второй код я определил, как "код для крутых". Это вовсе не означает, что таким образом передвигаться по массиву круче, чем первым способом. Я стараюсь избегать такой способ, потому что игры с указателями - потенциально опасное место. Я назвал этот способ так, потому что если вы додумались до него - это означает, что то, что я рассказывал до сих пор - прочно сидит в голове, а это - шикарно! У вас прекрасный потенциал!



Вот мы и подошли в плотную к изучению "процедурного" программирования. Я отношусь к тому типу людей, которые считают, что понять "объектно-ориентированное" программирование без минимальных знаний процедурного - сложно, поэтому, считаю, что этот шаг необходим.

Вы спросите: "а чем мы занимались до сих пор?" Отвечу: до сих пор мы учили азбуку и наши программы - это как первые ложки фруктового пюре, которое дают полугодичному ребенку, чтобы он просто почувствовал разные вкусы.

На самом деле, "азбуку" мы еще не совсем закончили, просто оттягивать изучения функций далее уже нельзя, поэтому, приступим.

Что такое функция? Функция это блок кода, которым можно пользоваться много раз. Это непонятное определение, поэтому я приведу пример.

Давайте вспомним домашнее задание с лекции 9 "Указатели". Я напомню условие:

Условие:

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

Программа должна:
1. Распечатать содержимое всех переменных, включая указатели (ВАЖНО! кроме содержимого указателя на char!)
2. Распечатать адреса всех переменных, включая указатели (ВАЖНО! кроме адреса переменной char!)
3. Только для указателей: распечатать содержимое переменных, на которые указывают указатели (через указатели)
4. Только для указателей: изменить содержимое переменных, на которые они указывают (через указатели)
5. Распечатать все заново, то есть повторить с 1 по 3 пункт.


Думаю, все это помнят. Так же все помнят, что мы переписали заново все это огромное количество кода, которое нужно было для решения с 1-го по 3-й пункт, 2 раза. Теперь представьте ситуацию, что в место этого мы делаем так (для примера)

int num = 10;
float f = 11.5;

и так далее, объявляем все эти переменные...

int * p_num = & num;
float * p_f = &f;

и так далее, объявляем все указатели....

потом мы пишем примерно такую строчку:
"распечатать на экран все переменные"

потом мы меняем то, что нужно было по условию:
*p_num = 12;
*p_f = 33.56;


и опять пишем ТОЛЬКО ОДНУ строчку:
"распечатать на экран все переменные"


А где-то ниже, ВСЕГО ОДИН РАЗ мы описываем, что означает наша инструкция "распечатать на экран все переменные":

распечатать на экран все переменные означает:
cout << num << endl;
cout << f << endl;


и так далее.



Что делает компьютер: он доходит до строки, в которой написано: "распечатать на экран все переменные" и понимает, что где-то, в другом месте кода, у него есть подробное описание что это означает... он ищет и находит, выполняет все по этому "рецепту" и выполнив всю инструкцию до конца, возвращается обратно, туда, откуда он пошел искать инструкцию, переходит на строку ниже, где ему надо поменять значения переменных, по нашему условию. Меняет, после этого он опять встречает "распечатать на экран все переменные" и опять лезет в инструкцию....

Думаю, что идея понятна. Теперь поговорим, какие преимущества дает такой подход. Их масса!

Во-первых код выглядит значительно проще! Потому что вместо того, чтобы читать все эти строки распечатки - программист понимает: "ага, вот тут происходит распечатка". Если он сомневается - он лезет в саму функцию распечатки (то, что мы назвали описанием) и ОДИН РАЗ смотрит на нее! Далее, везде, где он встречает строку "распечатать на экран все переменные", он уже знает что именно происходит. То есть, за всей этой шелухой деталей не теряется суть программы: он легче видит картину в целом.

Во-вторых - это избавляет нас от необходимости писать один и тот же код много раз! Я живу по принципу: если в программе есть кусок кода, который мне надо будет ниже в точности повторить - я выношу этот кусок в отдельную функцию.

А в-третьих - представьте, что мы не пользуемся функциями, а копируем и вставляем кусок кода туда, где он нужен. Внезапно выясняется, что в этом куске - баг. Тогда мы идем по коду, ищем этот кусок во всех местах и исправляем ВО ВСЕХ. А вот когда это функция - она написана только в одном месте, поэтому мы и исправляем ее один раз!


Я люблю сравнение функций с автоматом кока-колы. Человек, подходящий к автомату, чтобы купить себе баночку, точно НЕ знает, как именно этот автомат работает. Он не знает, каков механизм внутри, как автомат считает деньги, как он дает сдачу, как выбирает именно тот напиток, который заказали....

Он знает только одно: вот сюда надо бросить монеты, а вон из той дырки выпадет баночка. И этих знаний ему вполне достаточно, чтобы этим автоматом пользоваться. Функция работает по тому же самому принципу: она что-то получает, производит с этим определенные действия и отдает результат. То, что получает функция, называется "параметры", а то, что отдает - "возвращаемое значение". Она как бы возвращает результат тому, кто к ней обратился. Пишется это все так:

в начале пишут ТИП возвращаемого значения, потом ИМЯ функции, по которому мы будем обращаться к ней, после, в круглых скобках, пишут принимаемые ею ПАРАМЕТРЫ и разделяют их запятой. В конце всего этого дела идет блок, в котором и происходит вся работа функции:


float foo (int num, char letter) { блок }

Где:

float - тип возвращаемого значения
foo - имя функции
int num - первый параметр, его тип и его имя
char letter - второй параметр, тип и имя

В блоке подробненько описан алгоритм функции, то есть - ее механизм.


Как обращаются к функции? То есть - процесс купли баночки колы:

int main ( ){

int num = 5;
char letter = 'c';


// тут какой нибудь код, не относящийся к делу

float f = foo(num, letter); // обращение в функции, передача параметров по имени, после чего в переменную f будет положен результат ее работы

// тут какой нибудь код, не относящийся к делу

}


Ну, хорош трепаться, как гласит русская пословица: лучше один раз увидеть, чем 100 раз услышать, поэтому, сразу пример. Предупреждаю заранее, он может показаться немного пугающим, а самое главное - непривычным, но когда мы "перемоем ему косточки", то будет ясно, что не так страшен черт, как его рисуют.


Код:







Результат:




Давайте начнем с того, что найдем функцию main. Мы ведь теперь поняли, что main - это функция. Это главная функция программы. Когда операционная система запускает программу - она запускает функцию main и уже из нее - запускается все остальное.

Вглядевшись в main мы УЖЕ видим огромное преимущество использования функций в программировании: по-моему - так гораздо понятней, что делает программа, или я ошибаюсь? Программа создает массив из трех чисел, заполняет весь массив числами и распечатывает его на экран. После этого, она ищет самое большое число из тех, что лежат в массиве, распечатывает его на экран и после этого - распечатывает вновь весь массив.

Я советую остановиться на коде и пока вы не прочли мои объяснения, немного изучить его. Там, в коде есть мои комментарии, которые помогут вам в этом. Мы никуда не спешим, надо взять паузу и перевести дыхание)))) все новое - всегда пугает)))


Многие наверняка задались вопросом: почему функция main написана последней? Этому, конечно, есть объяснение и я расскажу чуть ниже причины такого расположения функций. Мы пока рассмотрим каждую в отдельности и начнем именно с main.

Функция main объявляет массив из трех чисел, после заполняет его. Обратите внимание, что на данном этапе, нам не нужно знать, как именно она получает значения! Это как раз и есть наш автомат с колой, о котором мы знаем: сюда кинь денежку, отсюда выпадет баночка. Нам нужно лишь "позвать" функцию get_num и "подставить" что-то, куда функция вернет результат.

Это можно сравнить с автоматом колы, из которого не выпадает баночка, а выливается кола и нужно подставить стаканчик, иначе, если стаканчика не будет, кола выльется и утечет в канализацию, то есть - денежку мы кинули, а колу не выпили. Причем - все без обмана и винить можно только себя - колу нам дали, мы просто не взяли ее.

Теперь перенесемся на минутку в функцию get_num и посмотрим, как именно происходит получение значения. Функция написана второй сверху. Давайте рассмотрим: как мы видим, в скобках ничего не написано, а это значит, что функция не принимает параметров, они ей не нужны. Тип возвращаемого значения - int

Сейчас, мы словно смотрим на чертеж автомата с колой и можем подробно рассмотреть весь механизм. Первым делом, объявляется переменная, в которую мы положим возвращаемое значение. Как правило, я называю ее ret, просто в силу привычки. Код, написанный ниже - нам уже знаком. Мы будем крутиться в цикле, пока пользователь не введет нужное нам значение. Здесь есть маленькое дополнение: в конце цикла есть блок if, который известит пользователя об ошибке, если такая была.

Далее, мы видим слово return и после него - наша переменная. Это означает: "верни то, что лежит в этой переменной". Сразу возникают вопросы: кому верни? Тому, кто позвал тебя, в данном случае - это была функция main. Да, отлично, скажут некоторые, но по окончанию блока переменная ret перестанет существовать! Это правда, НО! Прежде чем мы уничтожим наш "ящичек" ret, мы скопируем то, что в нем лежит и это содержимое "упадет" в руки тому, кто его ловит "на том конце".

Это как лимон: прежде, чем выкинуть его на помойку - мы выдавим сок в стакан с чаем. Как только алгоритм встречает на своем пути слово return - он покидает функцию и если есть что вернуть - возвращает туда, откуда пришел в эту функцию. Вроде понятно, да?


Итак, мы снова вернулись в main. В наш массив, в определенное отделение нашего длинного ящичка, попало то, что мы получили в функции get_num. Это наш "стакан". Пойдя на повторный шаг цикла - мы подставим следующее деление, и так, пока не заполним весь массив.

Выйдя из цикла for мы попадем на следующую строку: здесь идет вызов другой функции, которая, судя по названию, умеет распечатать на экран массив. Как мы видим, до вызова функции нет никакой переменной, в которую бы вернулось значение. Вывод, который можно сделать: либо из функции ничего не возвращается, либо, нам не нужно то, что она возвращает (такое тоже бывает). Однако, эта функция, в отличии от предыдущей - принимает параметры.

Мы положили туда массив и его размер. Ожидаемо то, что функция принимает 2 параметра: int * и int. Давайте найдем "чертеж" этого "автомата с колой". Он написан чуть выше. Обратите внимание: тип возвращаемого значения обозначен как void - это новое слово, и в программировании оно означает "ничего", то есть наша функция ничего не возвращает.

Примечание: Когда функция ничего не возвращает, программист обязан написать в типе возвращаемого значения слово void. Если программист не напишет ничего, то это означает, что функция возвращает int, это, как бы, "тип по-умолчанию". Я не знаю, зачем так сделали, скорее всего у этого есть своя история... НО! Я НАСТОЯТЕЛЬНО НЕ советую пользоваться этим. Пишите ВСЕГДА тип возвращаемого значения, чтобы код был читабелен не только для вас, но и для других программистов, которые не знают эту фишку, по каким-то причинам....

Другая ситуация, когда функция ничего не принимает, вот тогда скобки можно оставить пустыми. Однако, есть ряд программистов, которые и в этом случае любят указывать явно, что функция ничего не принимает и пишут в скобках слово void. Например я, на работе, делаю так всегда. То есть, если бы я писал функцию get_num, которую мы разбирали самой первой, на работе, то я описал бы ее так:

int get_num (void) { блок }


Окей, но вернемся к начатому: функция print_array. Там немного кода и он довольно простой, поэтому я не вижу смысла его подробно описывать, мы уже должны легко понимать такой код. Единственное, на что я обращу внимание - это на мой трюк с "тройным оператором" в строке cout: я написал так для красоты, чтобы после каждого числа, которое не является последним, ставилась запятая, а после последнего - точка. Как мы можем заметить из результата (картинка под кодом), что именно так и происходит.

Функция не возвращает ничего, поэтому в ней отсутствует слово return, по окончанию блока - алгоритм все равно вернется туда, откуда пришел. Однако, написав в конце return без параметра, мы бы не ошиблись, это легитимно.

Я хочу задержаться на одном нюансе: я не совсем хорошо написал эту функцию по той причине, что хотел вам показать главное - ее работу. Однако, обратите внимание: функция принимает указатель! Это - потенциальная опасность! То есть, если тот, кто позвал функцию - передал первым параметром NULL, то программа свалится, как только попытается обратиться к arr [i] (в строке cout)! То есть - функция не защищена! Это - упущение программиста! Если бы я писал эту функцию на работе, то написал бы ее так:



Вот здесь, инструкция return без параметра - наполнена огромным смыслом! Первое, что я делаю - проверяю указатель и если он указывает на NULL, то алгоритм вернется из функции и не доберется до опасного места! То есть - я сделал свою программу более устойчивой! Советую вам стремится к устойчивости программ. Нужно нащупать золотую середину: с одной стороны относится к функции, которую вы пишете, как к самой умной (а все вокруг - дураки, совершающие глупости), но с другой - не переборщить с этим делом, чтобы не превратить функцию из пары строк, в функцию в которой сотни строк и которая переполнена огромной кучей проверок. Именно умение найти этот компромисс и является важной составляющей в умении программировать, чему мы, собственно говоря и учимся)))

Отлично, осталось совсем чуть-чуть. Разберем функцию get_max. Что она делает? Она проходит по массиву и находит максимальное число. Функция принимает параметры int * и int, а возвращает int. По сути - это все, что нам нужно знать о функции. Представьте себе ситуацию: команда программистов получила задание. В команде всегда есть главный. Он распределяет работу: он единственный, который знает, что должна делать программа, то есть видит картину целиком. Остальным программистам это знание не нужно... почему?

Потому что главный создает основной алгоритм (архитектуру) и выделяет из него независимые куски. Такого человека иногда называют "архитектор". Потом он идет к программеру Васе и говорит: "Вася, мне нужно, чтобы ты написал функцию, которая ищет максимальное число. Я вызову ее, передам ей массив и размер, а ты мне вернешь максимальное число в массиве". "Хорошо" - говорит Вася, "я понял".

Вася идет и пишет функцию. Согласитесь, что ему вовсе не обязательно знать, откуда берется массив, который ему дадут и что будут потом делать с максимальным значением, которое он найдет. Ему нужно только написать сам поиск. С другой стороны, главному, который дал Васе задание, вовсе не обязательно знать, как именно происходит этот поиск. Он пользуется функцией Васи, как аппаратом кока-колы. Он знает, что если он позовет функцию Васи и передаст туда массив и размер, то он гарантированно получит на выходе - максимальное число в массиве. То есть - функция Васи для него - "черная коробка". Он не знает, как она работает, но он знает как ею пользоваться.

Но так как мы учимся, мы все-таки заглянем в механизм, который написал этот Вася. Опустим сейчас то, что Вася - олух и он не проверил указатель, за это он получит по шее, но далее Вася сделал все по-уму. Он изначально решил, что самое большое число - это первый элемент массива. Далее - он лишь проверяет свою догадку: он запускает цикл прохода по массиву, и начинает его с единицы, так как нулевое значение нет смысла сравнивать само с собой. Если в массиве только одно число, то есть его размер равен одному, то цикл for не сделает ни шага, потому что условие изначально будет ложным (i < size) в противном случае, он сравнит текущую ячейку с максимальным числом и если в текущей ячейке будет лежать бОльшее число, чем то, что лежит в max - он перепишет число из текущей ячейки в max и пойдет далее по массиву. Таким образом, в конце алгоритма он неизбежно найдет и вернет максимальное число.


Вот, видите, мы и разобрались во всем и согласитесь - это было не очень сложно. Самое время поговорить о хорошем тоне. Несколько правил:

1. Имена функций должны отображать то, что они делают. То есть, если функция находит максимальное число, то желательно ее назвать так, чтобы это было понятно из имени.

2. Функция должна быть "узкопрофильной", то есть функция, которая принимает число с клавиатуры, потом кладет его в массив, после ищет максимальное число - плохая функция. Она должна быть разбита на более мелкие: одна примет число, вторая - найдет наибольшее и т.д.

3. Старайтесь писать код как можно проще, избегайте сложных, неоправданных трюков. Оправдание сложному решению может быть только одно: мы, своим сложным решением, намного укоротим время, которое требуется, чтобы выполнить работу. Либо, если это важно - сократим занимаемое место в памяти.

4. Избегайте через чур длинных функций. Длинную функцию в сотни строк - ОЧЕНЬ сложно читать. Если у вас вышла через чур длинная функция - возможно, есть куски кода, из которых можно сделать отдельные функции и просто вызывать их.

5. Функция должна быть универсальной. Это НЕ противоречит пункту 2, где я заявил, что функция должна быть "узкопрофильной"! Объясню почему:
Возьмем мою функцию get_num, она узкопрофильная, то есть все, что она делает - получает значение с клавиатуры и проверяет его. Но она НЕ универсальная и это ее минус! Чтобы сделать ее универсальной, мне стоило "границы" легальных чисел принять как параметры, то есть:


Функция, которую написал я:




Функция, которую следовало бы написать:




Думаю, разница понятна.


Теперь, последняя вещь, которую бы мне хотелось осветить в этой лекции. Напоминаю свои слова, сказанные выше:
"Многие наверняка задались вопросом: почему функция main написана последней? Этому, конечно, есть объяснение и я расскажу чуть ниже причины такого расположения функций"

Самое время поговорить про причины этого. Дело в том, что когда компилятор компилирует программу и встречает вызов функции, ему необходимо знать, что такая функция есть и ею пользуются правильно. То есть, что типы переданных параметров - соответствуют тем, которые функция принимает. Что параметр, который функция возвращает - соответствует типу переменной, куда этот параметр кладут... НО! Если функция описана ниже, чем ее вызов - ему не с чем это сравнить, так как он еще не дошел до нее (он идет сверху вниз).

Этому есть 2 способа решений:

первый способ: описывать функции так, как сделал я, то есть функция foo, которая вызывает функцию bar, написана под описанием механизма функции bar. У этого решения есть минус: нужно постоянно следить за порядком: кто кого вызывает, чтобы знать какую из них написать под какой. Когда функций много - это не очень простая задача. Поэтому я пользуюсь вторым способом.

второй способ: функции пишутся в произвольном порядке, а сверху они "объявляются", то есть, сверху я всего лишь опишу как ее зовут, что она принимает и что возвращает, как бы сказав таким способом компилятору: "ниже в коде есть описание функции, которую зовут так-то, а пользуются - так-то" и тогда, встретив вызов функции ниже, он сравнивает ее с моим объявлением, проверяет, что параметры соответствуют и идет далее. Единственное требование хорошего тона, что если вы выбрали такой способ, то функция main должна быть написана самой первой.

Чтобы было понятней, я перепишу программу, описанную выше, вторым способом:







То, что написано выше функции main - называется "объявление функции", использование функции называется "вызов функции", а само описание механизма - либо "описание", либо, более профессиональный термин - "имплементация". А так как этот способ мне более по душе, то в моих примерах я буду писать так.


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



Домашнее задание


Напишите программу, которая просит у пользователя 5 чисел, от 1 до 50.
После того, как пользователь введет эти 5 чисел, высветите на экран меню, в котором будут такие возможности:
если пользователь ввел 1 - программа распечатывает весь массив
если пользователь ввел 2 - распечатывает минимальное из введенных им чисел
если пользователь ввел 3 - сумму всех чисел.

После того, как пользователь введет свой выбор действия, программа выполнит его требование, а потом покажет меню вновь. И так до тех пор, пока пользователь не введет ноль, что означает - конец работы.
При введенном нуле, программа завершается.

Удачи!

@темы: C++