×
Ви дійсно бажаєте відкрити доступ до тестування за курсом C# 5.0 Стартовый на 40 днів?
ВІДЕОУРОК № 3. Переменные и типы данных в C#
Здравствуйте. Тема нашего сегодняшнего урока – переменные и типы данных. Разберем более подробно понятие переменной. Разберем новое понятие что же такое константы. И так же более подробно разберем понятие типов данных. Также рассмотрим арифметические операторы, операторы сравнения. Будем учится применять переменные и константы. Мы поймем когда и какие типы нужно использовать при создании переменной. Изучим арифметические операции над значениями переменных. Научимся сравнивать значения переменных. И в конце урока посмотрим как производится форматирование строк. Вот это вкратце тема нашего сегодняшнего урока.
Давайте мы с вами вспомним, что же такое переменная. Мы уже видим знакомый нам слайд. Переменная, это область памяти, которая хранит в себе некоторое значение, которое в последствии мы можем изменить. Обратите внимание на планку ОЗУ. Мы создаем переменную с именем а типа byte и присваиваем ей значение 2. И мы уже помним, что 2 десятичное представлено в двоичном представлении. Мы видим, что здесь все лампочки горят в пол накала. И только вторая лампочка горит в полный накал. Также вспомним, как создается переменная. Мы должны задать переменной некий идентификатор. Некое friendly name – дружественное имя, по которому мы и будем обращаться к этой ячейке. Зачем мы это делаем? Что бы не запоминать вот такие сложные адреса в памяти. Но бывает так, что если вы дизассемблируете программу, ко конечно же там имен переменных уже нет в компилируемых языках. И приходится работать непосредственно с адресами. Так вот, мы создаем переменную с именем а типа byte и присваиваем ей значение 2. И в результате у нас в памяти выделится некий байт с определенным адресом. И здесь загорятся лампочки в соответствии с двоичным представлением того числа, которое мы присваиваем этой переменной. Также давайте посмотрим на этот слайд. Что мы здесь делаем. Мы здесь создаем переменную а типа short. А short это скольки байтовая? Это двухбайтовая переменная. И присваиваем ей значение 256. Обратите внимание на двоичное представление числа 256. Вы помните, что можно воспользоваться калькулятором для вычисления и преобразования чисел. Можно это сделать вручную, но я думаю, что сегодня никто не будет сидеть и на листочке это считать. Значит мы видим, что число 256 в двоично представлении имеет вот такой вид. И получается? что младший байт, обратите внимание, он весь зануленный. Лампочки горят в пол накала. И только самый нулевой бит второго байта установлен в единицу. Вот так выглядит число 256 в памяти. Также давайте еще вспомним какие числа у нас могут быть. Это однобайтовые. Также мы видим, что один байт выделяется и для булевых значений, для хранения результатов высказываний. Мы помним какие они бывают? True или false. True если высказывание истинное, false – если ложное. Далее два байта, их мы называем машинным словом, их можно выделить использовав либо short либо ushort. Short для хранения знаковых чисел, то есть те числа, которые могут иметь плюс или минус. Ushort используется для хранения чисел без знака, то есть для натуральных чисел. И так же два байта выделяется если мы создаем символьную переменную типа char – это сокращение от character. А почему 2 байта? Потому что в операционной системе Windows каждый символ хранится в формате Юникод, а формат Юникод требует для кодировки одного символа два байта. Если мы используем типы int и uint – unsigned int, или float, то у нас выделяется в памяти 4 байта. Если мы хотим выделить 8 байт для хранения большого числа мы используем long, ulong и double. C числами float и double работает арифметический сопроцессор. Давайте еще посмотри в общем структуру предопределенных типов данных. Мы уже знакомы с sbyte, byte, short, ushort, int, uint, long, ulong, bool, char, decimal, float, double и string. Обратите внимание, мы уже практически познакомились со всеми базовыми типами кроме двух: object и dynamic. Но с этими типамы вы будете знакомится на следующем курсе. Так же хотелось немного поговорить о правилах именования переменных. То есть существует в различных компаниях свой code convention, и этот code convention или код стандарт по именованию имеется и в общем виде. Даже Microsoft на своем ресурсе MSDN тоже описывает некие стандарты и даже больше, не только стандарты именования тех же переменных, функций. Имена переменных должны быт понятны и передавать смысл хранимого значения. Понятно зачем же мы заменили числа на понятные frienly names, только для того, чтобы описать, что у нас лежит в том или ином байте или нескольких байтах. Обратите внимание. Переменная а, она мне ни о чем не говорит, я не понимаю, что за число 256 здесь хранится. Вот если бы здесь было написано price – цена, тогда я понимаю, что мы здесь храним цену какого-то товара. Или если бы здесь был написан какой-нибудь count – это счетчик и уже в зависимости от контекста использования этого счетчика мы бы понимали по ходу чтения нашей программы к чему этот счетчик относится. И важно понимать, что правильное именование – это очень важно. Почему? Потому что ваши программы будут читать другие программисты и ваша программа должна быть им понятна. Это хороший стиль. И давайте посмотрим сами правила именования переменных. И вот мы видим, что в именах можно использовать алфавитные символы и символы подчеркивания. Можно конечно алфавитно-цифровые использовать, но это мы посмотрим дальше. Вот правильные названия: myVariable, my_Variable, _myVariable. Обратите внимание, мы используем алфавитно-цифровые имена. У нас идут уже цифры и символы алфавита. Обратите внимание, мы не можем называть нашу переменную именем, где первая позиция – цифра. Обратите внимание, также нельзя использовать в качестве имен зарезервированные ключевые слова. Мы не можем дать имя переменной int, например, или byte, или short. Почему? Потому что они зарезервированы и компилятор просто не поймет, что вы имели ввиду. Поэтому имеются зарезервированные ключевые слова, которые нельзя использовать в чистом виде в качестве имен, но если мы к ним что-то припишем, обратите внимание, intMyVar, то так можно. Но это будет не красиво. Потому что считается, что нет смысла в имя переменной включать ее тип. Это все равно, что вас зовут Иван, а я говорю, что давайте назовем его Человекоиван. Почему нет. Или Мальчикочеловекоиван. А зачем? И так понятно, что я человек. Это глупое имя. Так же и здесь, это странно, когда мы в имя переменной включаем ее тип. Далее. Мы можем использовать символ @ - эго еще называют собакой. И мы его используем толь в начале имени. Зачем он используется, мы дальше с вами посмотрим. Важно понимать, что язык C# чувствителен к регистру. Это значит, что переменная а с именем а-маленькое и с именем А-большая – это разные переменные. Это важно помнить. Вот допустим Visual Basic не чувствительный к регистру. C# чувствителен к регистру, поэтому если вы напишете в разном регистре, то это будут разные переменные. Потому мы и можем использовать такое именование, как здесь. Также можно сказать о стиле именования. Есть стиль Camel Case, обратите внимание, что мы здесь делаем. Первое слово с маленькой буквы, второй с большой и все последующие тоже. Такой стиль напоминает горбы верблюда. А когда первое слово с большой буквы, этот стиль называется Pascal Case. Рекомендуется все переменные именовать в стиле Camel Case. Именно с маленькой буквы. Такой код стандарт. А имена функций, методов, с которыми мы познакомимся дальше, рекомендуется именовать с большой буквы. Поэтому, когда мы будем читать код мы сразу будем знать где переменная, а где метод. Ключевые слова. Мы уже говорили, что не можем использовать в качестве имен переменных ключевые слова языка. Смотрите как их здесь мало. В отличии от естественных языков эти несколько десятков языков выучить очень легко. Тем более, мы практически и не будем учить их нацелено. Они выучатся сами по ходу изучения курса. Вы их сами запомните. Обратите внимание, это просто английские слова, которые имеют определенный смысл в контексте языковых конструкций, которые вы будете использовать в своей программе. Например, мы смотрим какое-нибудь слово, например goto. Как оно звучит – иди туда, так и в нашей программе goto говорит перейди на такую-то сроку программы и начинай выполнять с того места. Мы не можем использовать goto в качестве имени переменной, например. Но так же у нас есть contextual keywords – это контекстные ключевые слова. Эти ключевые слова мы можем использовать в качестве имен переменных, имен функций. К ним компилятор относится не так строго. Он уже не ругается, не подчеркивает красным, не говорит, что не понимает. Но с обычными ключевыми словами у вас будет проблемы, если вы их в чистом виде будете использовать в качестве имен переменных. Соглашения по именованию. Мы уже говорили о Camel Case, о Pascal Case. Вот стиль именования в Pascal Case. Camel Case – каждое слово, исключая первое, начинается с большой буквы. Uppercase – все символы в верхнем регистре. Не рекомендуется использовать венгерскую нотацию и начинать идентификатор с символа нижнего подчеркивания и также использовать в имени имя типа. Многие программисты любят использовать венгерскую нотацию и определенный вид переменных начинать с нижнего подчеркивания. Но Microsoft не рекомендует этого делать. В code convention Microsoft этого нет, но во многих компаниях присутствует задокументирован вот такой стиль. А вот такой вообще не рекомендуется. Мы в имя переменной еще включили тип этой переменной. Вот это называется чистая венгерская нотация. Она была придумана одним венгром, который работал в Microsoft. Он работал, кажется, в MFC проекте. И когда разрабатывались библиотеки MFC, там активно использовалась венгерская нотация, когда мы в имена всех переменных вставляли тип этих переменных. Запомните пожалуйста соглашения по именованию. Это просто. А дальше мы с вами переходи к рассмотрению еще одного интересного понятия, такого как константы.
Давайте посмотрим, что это такое. Смотрите, константа – это область памяти, которая хранит в себе некоторое значение, которое нельзя изменить. Интересно. До этого на слайдах было. Переменная – это область памяти, которая хранит в себе некоторое значение, которое можно изменить. А константа? Это область памяти, которая хранит в себе значение, которое нельзя изменить. А как нам сделать константу? Давайте посмотрим. Мы видим, что мы здесь создаем константу типа byte с именем а, значит мы говорим, что нам нужна неизменяемая область памяти размером в один байт. Мы выделяем один байт. Именуем эту область как а. И присваиваем ей значение 2. Еще раз. Мы создаем константу типа byte с именем а и присваиваем ей значение 2. Константа – это неизменяемая область памяти. Многие говорят, что константа – это неизменяемая переменная. Так говорить нельзя. Потому что переменная меняющаяся. Как можно сказать – неизменяющаяся переменная. Не говорите, что константа – это неменяющаяся переменная. Константа – это область памяти, которая никогда не меняется или хранит в себе неизменяемое значение. Попытка присвоить константе новое значение приводит к ошибке уровня компиляции. Если мы при написании нашей программы, где в коде пытается обратится к константе на запись, компилятор нам подчеркнет красным. Этого делать нельзя. А было бы интересно посмотреть в коде как выглядят константы. Заходим в первый пример.
1: using System; 2: 3: // Константа (Constant) - это область памяти, которая хранит в себе некоторое значение, которое нельзя изменить. 4: 5: namespace Constant 6: { 7: class Program 8: { 9: static void Main() 10: { 11: // На 13-й строке, создаем константу с именем pi, типа double и присваиваем ей значение 3.141 12: 13: const double pi = 3.141; 14: 15: // На 17-й строке, выводим значение константы - pi, на экран. 16: 17: Console.WriteLine(pi); 18: 19: // Попытка присвоения константе нового значения, приводит к ошибке уровня компиляции! 20: 21: //pi = 2.71828183; 22: 23: // Задержка. 24: Console.ReadKey(); 25: } 26: } 27: }
На 13 строке мы создаем константу типа double с именем pi – 3,14. Смотрите, что мы здесь сделали. На 13 строке мы создали константу типа double с именем pi и присвоили ей 3,14. А давайте на 17 строке выведем эту константу на экран. Давайте посмотрим на работу это й программы. Выполняемся. А давайте на 21 строке все-таки попробуем ей что-то присвоить. Вы видите, что произошло. Подчеркнулось красным. Ошибка. The left-hand side of an assignment must be a variable, property or indexer. Это не может быть константа. Что такое переменная мы знаем. А что такое свойства и индексаторы мы еще познакомимся, не переживайте. Нам очень важно научится читать вот эти волшебные сообщения об ошибках, потому что они будут нас сопровождать постоянно в нашей жизни. И поэтому вы не переживайте. Если появились ошибки, зайдите в Error List, посмотрите, если не знаете, что это за ошибка, поищите в интернете, может кто-то уже сталкивался с такими ошибками. Потому что ошибки бывают и более сложные, а не такие тривиальные как эта. Чтобы исправить эту ошибку мы должны не обращаться к этой константе. Но, хотелось бы поговорить об еще одной хитрости с константами. На самом деле, константы – это очень хитрые сущности. Мы их создаем вот таким образом, а как же она будет выглядеть в действительности, там внутри, если мы заглянем внутрь и посмотрим, как работает программа. Дело в том, что есть одна очень интересная программка, она называется .NET Reflector. Этот .NET Reflector берет существующую программу и дизассемблирует ее. Давайте посмотрим, как это выглядит. Нажимаем Отрыть. Вот он наш пример с константами. Вот наш exe-файл, который мы собирались отдавать бабушке. Берем открываем его. Заходим. О! Наш Program. Expand Method. И смотрите, вот она наша программа. Вот она дизассемблирована наша программа. Тот же метод Main, та же константа 3,14. Но обратите внимание, эта константа уже не представлена отдельной конструкцией. Она не представлена именем. У нее нет имени, она уже встроена как параметр этого метода. То есть она представляет собой непосредственное значение. Или еще как высокоуровневые программисты называют – литерал. Наша константа представлена здесь как непосредственное значение, или литерал. Вот такую особенность запомните, потому что работа с константами таит в себе небольшие опасности. Но об этом мы будем говорить позже. Мы сейчас не будем затрагивать dll библиотеки или еще какие-то сложные сущности, потому что мы на этом курсе получаем удовольствие, мы просто знакомимся с базовым синтаксисом языка C#. Константы мы рассмотрели. Перейдем дальше.
Следующее что мы должны рассмотреть с вами это кастинг. Или еще это называется, как преобразование значений типов. Кастинг – это преобразования значений определенных типов. Преобразование с одного типа в другой. Ну, например, у меня есть натуральное число, и я хочу из натурального числа превратить в вещественное. Зачем? Чтобы с этим числом работал FPU, потому что с натуральными числами работает ALU CPU. А вот с вещественными числами работает более быстрое устройство – сопроцессор или другие вспомогательные устройства. Получается, что кастинг – это преобразование значения. Преобразовать собаку в кошку. Превращение. Превратить строку в число, число в строку. Кастинг – преобразование значение переменной одного типа в значение другого типа. Обратите внимание! Бывает всего две разновидности кастинга. Это явный кастинг – explicit, и неявный кастинг – implicit. Что это значит? Явный кастинг, это когда я машине говорю: «Пожалуйста, преврати мне строку в число!» А неявный кастинг – это когда машина это сама делает. Если я не знаю такого понятия как кастинг, я может и не пойму почему машина так сделала. Так вот, кастинг бывает явным и неявным. Поэтому в зависимости от явности или неявности кастинга, кастинг может быть либо опасным, либо безопасным. Давайте мы посмотри что это такое. Неявный кастинг – безопасный. У нас имеется один байт в памяти, в нем хранится число 10. Мы создаем переменную а типа byte и присваиваем ей значение 10. Вот выделился ящичек с лампочками. И теперь мы видим, что здесь у нас находится некое число. Мы создаем переменную b типа short. Мы выделяем два байта. Обратите внимание! У нас в памяти есть один байт и два байта, две области. И я теперь хочу значение переменной а присвоить переменной b. Переменной b мы присваиваем значение переменной а. А что в ней хранится. А вот число 10. Оно попадает прямо в переменную b. Новое значение переменной b. Вот это двухбайтовая переменная b. Но важно помнить, если бы у нас здесь было отрицательное число, то вот эти лидирующие нули – это знак положительного числа. Если бы у нас было отрицательное число. То здесь бы стояли единицы. Но мы пока не будем это рассматривать, потому что это у нас еще впереди. Работа с положительными и отрицательными числами. Мы сконцентрируемся именно на кастинге. А еще в программировании на C# есть не только понятие кастинга, а есть еще каст. Не путайте, кастинг и каст – это не одно и то же. Запомните, кастинг, это преобразование значения типа. Это превращение собаки в кошку, слона в муху и мухи в слона. А вот каст – это приведение типов. Это мы будем рассматривать на следующем курсе. Поэтому мы сконцентрируемся на кастинге. Преобразование значения типов. Еще раз, у нас имеется переменная вот с таким значением, переменная b пока пустая. Мы берем значение этой переменной помещаем в переменную b. В результате мы видим вот такую комбинацию лампочек. То есть переменная b равна 10. Если мы в коде так написали, то 10 попало бы в переменную b, а переменная b двухбайтовая, и поэтому значение переменной а попадает в крайний правый байт. А этот наполняется лидирующими нулями. Положительные числа все начинаются с нуля, а отрицательные с 1. Но мы пока не говорим о знаковых числах. А теперь смотрим, когда же у нас используется опасный кастинг. У нас имеется переменная с именем а типа short, и это переменная равна 256. Таким образом, число, хранящееся в этой переменной занимает собой именно два байта. Вы видите, что единица перескочила во второй байт. Дальше мы создаем однобайтовую переменную b и зануляем ее. Теперь я хочу вот эти два литра жидкости влить в однолитровую баночку. Можно ли это сделать. Ну конечно два литра не поместятся в литровую баночку. Но тем не менее, я считаю, что второй литр воды мне вообще не нужен и никак на меня не влияет. Сложно представить, но можно. Компилятор говорит, что он не позволяет. Я теперь в скобках явно компилятору пишу, что я хочу в однобайтовую переменную b поместить значение их переменной а. И смотрите вот это – подпись программиста. Я Александр в здравом уме, знаю, что я делаю, я понимаю, что у меня отвалится половина числа и что она мне не нужна. И вот когда запускают спутник с моей программой, он пошел по другой траектории, врезался в другой спутник разбился. Меня вызываю на ковер. Мне говорят, что я поставил свою подпись, что я в здравом уме понимаю, что отвалится пол числа. Зачем вы так сделали? Зачем вы убили точность или потеряли пол числа и наша ракета, которая стоит миллиард долларов пошла в другую сторону. Вот она видите какая хитрость. Почему этот явный кастинг называется опасным, потому что мы теряем часть числа. Поэтому нас и обязывают ставить эту подпись. То есть использовать оператор явного преобразования значения типа. И теперь нам было бы хорошо посмотреть в программный код, как выглядит кастинг в программном коде. У нас имеется пример. Преобразование типа – это преобразование переменной одного типа в значение другого типа.
1: using System; 2: 3: // Преобразование типа (Casting или Type conversion) - это преобразование значения переменной одного типа в значение другого типа. 4: // Выделяют явное (explicit) и неявное (implicit) преобразование типов. 5: 6: namespace Casting 7: { 8: class Program 9: { 10: static void Main() 11: { 12: // ---------------------------------- Переменные --------------------------------------------- 13: 14: #region Неявное (безопасное) преобразование типа: 15: 16: // Неявное преобразование значения типа - byte в тип int. (преобразование меньшего типа в больший) 17: byte a = 10; // 0000 1010 - 1 байт 18: int b = 0; // 0000 0000 0000 0000 0000 0000 0000 0000 - 4 байта 19: b = a; // 0000 0000 0000 0000 0000 0000 0000 1010 - 4 байта 20: 21: Console.WriteLine(b); 22: 23: // Неявное преобразование значения типа - int в тип float. (преобразование целого типа в вещественный) 24: int c = 255; 25: float d = 0f; 26: d = c; 27: 28: Console.WriteLine(d); 29: 30: #endregion 31: 32: #region Явное (опасное) преобразование типа: 33: 34: // Явное преобразование значения типа - int в тип byte. 35: // (преобразование большего типа в меньший, приводит к потере части результата) 36: 37: int e = 256; // 0000 0000 0000 0000 0000 0001 0000 0000 - 4 байта 38: byte f = 0; // 0000 0000 - 1 байт 39: f = (byte)e; // 0000 0000 - 1 байт 40: // f = e; // ОШИБКА. 41: 42: Console.WriteLine(f); 43: 44: // Явное преобразование значения типа - float в тип int. 45: // (преобразование вещественного типа в целый, приводит к потере точности результата) 46: 47: float g = 10.5F; 48: int h = 0; 49: h = (int)g; 50: // h = g; // ОШИБКА. 51: 52: Console.WriteLine(h); 53: 54: #endregion 55: 56: // ---------------------------------- Константы ---------------------------------------------- 57: 58: #region Кастинг констант 59: 60: // Возможно неявное преобразование значения константы типа - int в тип byte, 61: // при инициализации переменной значением константы, 62: // если значение константы не превышает максимально допустимого значения переменной. 63: 64: const int i = 255; // 0000 0000 0000 0000 0000 0000 1111 1111 - 4 байта 65: byte j = 0; // 0000 0000 - 1 байт 66: j = i; // 1111 1111 - 1 байт 67: 68: Console.WriteLine(j); 69: 70: // Возможно явное преобразование значения константы типа - float в тип byte, 71: // при инициализации переменной значением константы, 72: // если значение константы не превышает максимально допустимого значения переменной. 73: 74: const float k = 255; 75: byte l = 0; 76: l = (byte)k; 77: 78: Console.WriteLine(l); 79: 80: 81: // Невозможно ни явное, ни неявное преобразование значения константы, 82: // при инициализации переменной значением константы, 83: // если значение константы превышает максимально допустимый диапазон значения переменной. 84: 85: const int m = 256; // 0000 0000 0000 0000 0000 0001 0000 0000 - 4 байта 86: byte n = 0; // 0000 0000 - 1 байт 87: // n = m; // ОШИБКА. 88: // n = (byte)m; // ОШИБКА. 89: 90: Console.WriteLine(n); 91: 92: // Невозможно ни явное, ни неявное преобразование значения вещественной константы, 93: // при инициализации целочисленной переменной значением константы, 94: // если значение константы превышает максимально допустимый диапазон значения переменной. 95: 96: const float o = (float)256.5; // преобразование double в float 97: byte p = 0; 98: // p = o; // ОШИБКА. 99: // p = (byte)o; // ОШИБКА. 100: 101: Console.WriteLine(p); 102: 103: #endregion 104: 105: // Задержка. 106: Console.ReadKey(); 107: } 108: } 109: }
Выделяют явное и неявное преобразование значений типов. Здесь у нас имеется достаточно большое количество правил. Поэтому мы сейчас сосредоточимся и планомерно их посмотрим. Если вы забудете со временем немножко этих правил, ничего страшного, вы всегда сможете зайти и вспомнить их. Давайте посмотрим, на 17 строке мы создаем переменную а типа byte и присваиваем ей значение 10. Обратите внимание, вот его двоичное представление. На 18 строке мы создаем переменную b типа int и присваиваем ей значение 0. У нее во всех 4 байтах нули. Мы переменной b присваиваем значение переменной а. Видите вот этот столбик. Вот с каким байтом у нас происходит работа. Либо это самый крайний правый младший байт, потому что у нас идет запись числа справа на лево. Здесь же остаются лидирующие нули в трех байтах. Один байт разделяется на два полубайта. И каждый байт называется тетрадой. Это важно помнить. Это measurement, это измерение единиц информации в одном байте. И у нас здесь произошло неявное преобразование значения типа. Мы взяли литр жидкости и перелили в 4 литровую банку. Это не опасно, потому что мы знаем, что жидкость все равно не выльется. Смотрим дальше. На 24 строке мы создаем переменную с типа int и присваиваем ей значение 255. Здесь будут одни единички в самом крайнем байте. На 25 строке мы создаем переменную d типа float и зануляем ее. И теперь на 26 строке мы переменной d присваиваем значение переменной d. Что здесь происходит? Запускается хитрый программный механизм, который хватает целое число и начинает его пересчитывать в специальный формат, который состоит из мантиссы и характеристики. В Вещественных числах экспоненту не называют экспонентой, ее называют характеристикой, число разбивается на две части. Это такое сложное представление, что его сегодня разбирать нет смысла формат этого числа, при том что есть механизмы, которые занимаются преобразование целых чисел в вещественные и наоборот. Это есть целая минипрограмма, которая занимается преобразованием. Раньше приходилось даже самостоятельно рассчитывать эти числа когда работали на низком уровне с ассемблерами на устройствах с плавающей точкой и приходилось такие числа рассчитывать. Но сейчас мы этого не делаем. Смотрите, снова же безопасно. Целое число превратилось в дробное. Прекрасно, мы с ним можем работать. Здесь мы видим две формы неявного безопасного преобразования типов. Это когда мы меньшее число превращаем в большее или когда мы целое число превращаем в дробное, то есть у нас в результате в переменной d появилось 255,0. Ничего у нас здесь не потерялось. Все хорошо, все числа остались. А давайте посмотрим какие опасности нас подстерегают. У нас имеется на 37 строке переменная e типа int которой мы присваиваем значение 256. Обратите внимание, эта переменная занимает собой два байта минимум. Далее на 38 строке мы создаем однобайтовую переменную f и присваиваем ей значение 0. И теперь на 39 строке мы берем переменной f присваиваем значение переменной е, но здесь четыре литра, четыре байта, а здесь один. Но мы понимаем, что здесь только два литра в четырехлитровой. И мы пытаемся влить два литра в литровую банку. Что у нас произойдет? Выльется наружу за пределы литровой баночки. Но тем не менее на 39 строке мы с вами пишем, что я программист знаю, что я делаю. Я из четырехлитровой банки над вашим дорогим ноутбуком лью все в однолитровую банку. Ничего страшного. Лейте! Купим еще один, сказал миллионер. Вот она опасность. Мы понимаем, что здесь 100% что-то выльется. Мы готовы к потере трех байтов итогового результата. Без оператора явного преобразования типа, если мы такое сделаем, Visual Studio выдаст ошибку. Cannot implicitly convert type int to byte. Мы не можем явно преобразовать. Поэтому вернем комментарий. Мы не можем неявно преобразовать большее в меньшее. Если мы хотим это сделать, то мы должны использовать опасный кастинг. Мы здесь должны использовать явное преобразование значения типа. В каких еще случаях можно использовать явное преобразование. На 47 строке мы создаем переменную g типа float и присваиваем ей значение 10,5. Обратите внимание, это число имеет целую и дробную часть. Длина дробной части еще называется точностью. На 48 строке мы создаем такую же 4 байтовую переменную типа int с именем h. И float – 4 байта, и int – 4 байта. На 49 строке мы переменной h присваиваем значение переменной g. Но предварительно ее преобразовываем в целое. Преобразовать дробное число в целое – это значит потерять точность, это значит отбросить дробную часть. Мы потеряем точность. У нас в h будет лежать число 10. Представляете, если мы возьмем здесь 3,14 и отбросим дробную часть. Представляете какая погрешность. Мы даже до Луны никогда не долетим с такой точностью. Значит мы видим, что явной кастинг, явное преобразование значений типов используется в двух случаях: от большего к меньшему и от вещественного к целому. Отваливается дробная часть числа. Мы ее просто теряем. Она не то что округляется, она просто обрезается. Ее нет. Она не округляется. Ее просто нет. Запомните это. Значит еще раз смотрим. Два кастинга: неявное преобразование значений типа и явное преобразование значений типов. И мы видели, что мы здесь работали с кастингом с переменными. А вот идет кастинг для констант. Обратите внимание, на 64 строке мы создаем 4 байтовую константу и присваиваем ей значение 255. Далее на 65 строке мы создаем переменную j типа byte и зануляем ее. Обратите внимание, на 66 строке мы переменной j присваиваем значение константы i. Прочитайте внимательно здесь комментарий. Это правило. Коментарии в наших кодах – это правила. Во многих случаях – это даже определения, как нужно правильно произносить, как нужно правильно говорить, потому что информатика, она, как и математика, алгебра и геометрия, логика, дискретная математика, она полна определений. Это очень важно. Возможно неявное преобразование значения константы типа int в тип byte при инициализации переменной значением константы, если значение константы не превышает максимально допустимого значения этой переменной. Мы понимаем, что максимально допустимое значение, завершающее диапазон чисел, входящих в переменную byte – 255. У нас значение константы не превышает максимально допустимого значения, которое мы можем поместить в переменную j. И поэтому, на 66 строке у нас ошибки нет. Давайте попробуем превысить его. Если поставим 256, то здесь станет единица, а вот эти все биты занулятся. Давайте попробуем. Нет, смотрите, компилятор ругается. Он сам считает значение константы. Секрет простой. Констант не существует, это просто непосредственное значение, это мы их просто здесь используем по имени. Компилятор сам на 66 строке проверяет, он производит сравнение. А как он производит сравнение? Он идет в byte, берет оттуда MaxValue и начинает сравнивать. Если максимальное значение byte меньше, чем значение из константы, то выдается ошибка. Смотрим дальше. Значит на 68 строке мы выводим на экран значение переменной j. Теперь мы выводим на экран значение, которое в ней хранится, это 255. Если значение константы будет больше чем максимально допустимый диапазон значений переменной, в которое мы записываем это значение, то будет ошибка. Запомните это. Потому что иногда люди сталкиваются, сидят и не знают почему-же оно не работает. Следующий пример. На 74 строке мы создаем константу типа float с именем k и присваиваем ей значение 255. На 75 строке мы создаем переменную l типа byte и присваиваем ей значение 0. Обратите внимание, на 76 строке мы переменной l присваиваем значение переменной k, но используя оператор явного преобразования значения типа. Здесь мы использовали оператор неявного преобразования. На самом деле он здесь есть, мы просто его не видим. Он просто написан белым цветом. А здесь мы используем оператор явного преобразования значения типа. Возможно явное преобразование константы float в тип byte при инициализации переменной значением константы. Но только лишь значение константы не превышает максимально допустимого значения переменной. Точность потеряется? Давайте поиграем. Возьмем и присвоим константе 3,14. Смотрите все сработало. Но дробная часть то отбросится. На 85 строке мы создаем константу типа int с именем m и присваиваем ей значение 256. На 86 строке мы создаем байтовую переменную и зануляем ее. Обратите внимание на комментарий, если значение, хранящееся в константе превышает максимально допустимый диапазон значений той переменной в которую мы хотим записать это значение из константы, то нам уже не поможет никакой оператор. Ни неявного, ни явного преобразования значения типов. Это просто в языке C# запрещено. Смотрите, мы перепробовали все два способа. Вот больше эта константа и хоть убей. Все, она не влезет в байт и компилятор не позволяет этого сделать. Смотрим дальше. В данном случае то же самое происходит и с вещественными константами. Если у нас вещественная константа содержит в себе значение, которое превышает максимально допустимый диапазон значений определенного типа, то мы тоже не сможем выполнить вот такого присвоения. Если вы забудете все о чем мы говорили в комментариях описано в форме кратких определений, без неоднозначностей. Вот такие правила, которые относятся к константам. Еще раз – кастинг. Каст мы сейчас не рассматриваем. Кастинг – это преобразование мухи в слона, слона в муху. Еще раз, преобразование значения типа. А каст – это приведение типов. Не путайте кастинг и каст. Но уже сразу различайте их. А нам с вами осталось рассмотреть совсем немного. Работа с арифметическими операторами с различными операторами сравнения и еще с разными мелочами. Поэтому не переживайте. Это будет очень просто.
Заходим и смотрим следующий пример. Какие у нас имеются арифметические операторы.
1: using System; 2: 3: // Арифметические операторы (Arithmetic Operators) - +, −, *, /, % 4: 5: namespace Arithmetic 6: { 7: class Program 8: { 9: static void Main() 10: { 11: // Addition (+) 12: byte summand1 = 1, summand2 = 2; // Множественное объявление. 13: int sum = 0; 14: sum = summand1 + summand2; 15: 16: Console.WriteLine(sum); 17: 18: // Subtraction (-) 19: byte minuend = 5, subtrahend = 3; 20: int difference = 0; 21: difference = minuend - subtrahend; 22: 23: Console.WriteLine(difference); 24: 25: // Multiplication (*) 26: byte factor1 = 2, factor2 = 3; 27: int product = 0; 28: product = factor1 * factor2; 29: 30: Console.WriteLine(product); 31: 32: // Division (/) 33: byte dividend = 5, divisor = 2; 34: int quotient = 0, remainder = 0; 35: quotient = dividend / divisor; 36: 37: Console.WriteLine(quotient); 38: 39: // Remainder after division (%) 40: remainder = dividend % divisor; 41: 42: Console.WriteLine(remainder); 43: 44: // Delay. 45: Console.ReadKey(); 46: } 47: } 48: }
Плюс, минус, умножить, разделить и получить остаток от деления. Смотрим, на 12 строке мы создаем две переменные типа byte summand1 и summand2 присваиваем им значения 1 и 2. На 13 строке мы создаем переменную sum типа int и присваиваем ей значение 0. На 14 строке мы переменной sum присваиваем сумму значений переменных summand1 и summand2. Sum будет равно 3. Subtraction – вычитание. Мы создаем две переменных, которые хранят вычитаемое и уменьшаемое. На 20строке мы создаём переменную разность и на 21 строке мы переменной difference и присваиваем ей разность переменных уменьшаемого и вычитаемого. Difference будет равна 2. Далее на 26 строке мы создаем две переменные factor1 и factor2, первый множитель и второй множитель. И на 27 строке мы создаем переменную произведение. И на 28 строке переменной product присваиваем произведение двух множителей, первого и второго. Product равен 6. На 33 строке мы создаем две переменные – делимое и делитель и на 34 строке частное. И обратите внимание, reminder – остаток от деления. Когда мы делим натуральные числа… Смотрите, у меня есть 5 яблок. Я хочу их разделить на 2 части. Между собой и своим коллегой. То получается, что мне будет два яблока, коллеге будет два яблока и одно в остатке. Так вот этот остаток мы и помещаем в reminder. Соответственно на 35 строке мы переменной quotient присваиваем частное от деления divided на divisor. На 40 строке мы переменной reminder присваиваем именно остаток от деления и выводим это на экран. Видите, очень просто. У нас имеется пять знаков арифметических операций. Сложение, вычитание, умножение, деление, остаток от деления – это знак процентов. Это знак остатка от деления именно натурального числа на число. Следующий пример.
Возведение в степень.
1: using System; 2: using System.Collections.Generic; 3: using System.Linq; 4: using System.Text; 5: 6: // Math.Pow() - возведение числа в степень (1-ый аргумент - число, которое возводим в степень, 2-ой – степень, в которую возводим число) 7: 8: namespace MathPow 9: { 10: class Program 11: { 12: static void Main() 13: { 14: double x = 2, y = 8; 15: 16: double result = Math.Pow(x, y); 17: 18: Console.WriteLine(result); 19: 20: // Delay. 21: Console.ReadKey(); 22: } 23: } 24: }
Обратите внимание, на 14 строке мы создаем две переменные типа double с именами x и y и присваиваем им значения 2 и 8. На 16 строке мы создаем переменную result типа double и присваиваем ей возвращаемое значение метода pow. Обратите внимание, давайте выполнимся. 256. Мы указали 2 в 8 степени. То есть это число, которое мы максимально можем записать в байт. И поэтому у нас имеется такой класс. Вот как у нас була консоль – объектно-ориентированное представление жизненной сущности экрана и клавиатуры. Так вот Math – это объектно-ориентированное представление некоего математика. Мы говорим математику, чтобы он нам посчитал 2 в степени 8. 2 – основание степени, 8 – показатель. Result – это будет сама степень числа 2.
Следующий пример. А теперь мы хотим воспользоваться еще одной интересной функцией или методом нашего математика Math.
1: using System; 2: using System.Collections.Generic; 3: using System.Linq; 4: using System.Text; 5: 6: // Math.Sqrt() - математическая функция, которая извлекает квадратный корень 7: 8: namespace MathSqrt 9: { 10: class Program 11: { 12: static void Main() 13: { 14: double x = 256; 15: 16: double result = Math.Sqrt(x); 17: 18: Console.Write("Квадратный корень равен: "); 19: Console.WriteLine(result); 20: 21: // Delay. 22: Console.ReadKey(); 23: } 24: } 25: }
Sqrt – Square root – квадратный корень. Мы хотим извлечь квадратный корень из числа 256. И получается что на 16 строке мы создаем переменную result типа double и присваиваем ей возвращаемое значение метода Sqrt, который и извлечет корень квадратный из числа 256. Давай те выполнимся и посмотрим. Чему равен? Конечно же 16. Следующий пример – это comparsion.
1: using System; 2: 3: // Операции сравнения и проверки на равенство (<, <=, >, >=, ==, !=) 4: 5: namespace Comparison 6: { 7: class Program 8: { 9: static void Main() 10: { 11: byte value1 = 0, value2 = 1; 12: bool result = false; 13: 14: // Less than 15: result = value1 < value2; 16: Console.WriteLine(result); 17: 18: // Greater than 19: result = value1 > value2; 20: Console.WriteLine(result); 21: 22: // Less than or equal to 23: result = value1 <= value2; 24: Console.WriteLine(result); 25: 26: // Greater than or equal to 27: result = value1 >= value2; 28: Console.WriteLine(result); 29: 30: // Equals 31: result = value1 == value2; 32: Console.WriteLine(result); 33: 34: // Not equals 35: result = value1 != value2; 36: Console.WriteLine(result); 37: 38: // Delay. 39: Console.ReadKey(); 40: } 41: } 42: }
У нас также имеются операции сравнения и проверки на равенство либо неравенство. Больше, меньше, больше либо равно, меньше либо равно, равно, не равно. Знак равенства. Знак, состоящий из одного знака = - это присвоение, а это == - равенство. Возможно вы помните из логики, что восклицательный знак – это отрицание. Теперь смотрим, на 11 строке мы создаем две переменные value1 и value2 и присваиваем им значения 0 и 1 соответственно. И на 12 строке мы создаем булеву, не говорите булевскую, переменную с именем result и присваиваем ей значение false. Если мы ей ничего не присвоим, она по умолчанию будет все равно false. На 15 строке мы переменной result присваиваем возвращаемое значение оператора сравнения меньше. Он волшебный. Он берет одно число, второе число, сравнивает их и возвращает либо true, либо false. А почему? А потому что мы видим на 15 строке высказывание. Это высказывание. Я говорю, что у меня ног меньше чем у кошки. Вы говорите, что это правда. Если я скажу, что у меня ног больше чем у сороконожки, то вы скажете, что это ложь. Мы говорим, что значение value1 меньше чем значение value2. То есть 0 меньше 1. Это истинное высказывание. Значит в result мы запишем значение true. Мы помним, что булева переменная может принимать только два значения: true и false. И переменная result принимает в себя результат высказывания. И вот здесь я говорю, что переменная value1 меньше value2. Конечно же это правда. Дальше знак greater than. Мы переменной result присваиваем значение оператора больше чем. Я говорю, что значение переменной value1 больше чем значение value2. Это ложь. И поэтому result будет равен false. На 23 строке мы переменной result присваиваем возвращаемое значение оператора меньше или равно. Мы говорим, что value1 меньше либо равен value2. Это истина. На 27 строке мы говорим value1 больше либо равно value2. И не больше и не равно. Equals. Значения этих переменных не равны, потому здесь false. И на 35 строке мы говорим, что значения этих переменных не равны, и это истина. Я говорю, что количество моих ног не равно количеству ног кошки. Да, это истинное высказывание.
Следующий пример. Инкремент и декремент.
1: using System; 2: 3: // Операторы Инкремента и Декремента (Increment and Decrement Operators) 4: 5: namespace IncDec 6: { 7: class Program 8: { 9: static void Main() 10: { 11: Console.WriteLine("----- Постфиксный инкремент"); // Post-increment 12: 13: byte number1 = 0; 14: Console.WriteLine(number1++); // Сначала выводим на экран, потом увеличиваем на 1. 15: Console.WriteLine(number1); 16: 17: Console.WriteLine("----- Префиксный инкремент"); // Pre-increment 18: 19: byte number2 = 0; 20: Console.WriteLine(++number2); // Сначала увеличиваем на 1, потом выводим на экран. 21: 22: Console.WriteLine("----- Постфиксный декремент"); // Post-decrement 23: 24: sbyte number3 = 0; 25: Console.WriteLine(number3--); // Сначала выводим на экран, потом уменьшаем на 1. 26: Console.WriteLine(number3); 27: 28: Console.WriteLine("----- Префиксный декремент"); // Pre-decrement 29: 30: sbyte number4 = 0; 31: Console.WriteLine(--number4); // Сначала уменьшаем на 1, потом выводим на экран. 32: 33: // Delay. 34: Console.ReadKey(); 35: } 36: } 37: }
Это еще одно такое простое понятие. Смотрите, на 13 строке мы создаем переменную number1 типа byte и присваиваем значение 0. На 14 строке мы выводим на экран значение переменной number1 и здесь еще какие-то два плюса стоят. Называется инкремент. Инкремент – это приращение на единицу. В программировании настолько часто выполняется операция прибавления на 1, что разработчики языков программирования решили сделать сокращенную операцию. Чтобы не писать
number1 = number1 + 1;
Вот он аналог инкремента. И чтобы нам не писать такую длинную конструкцию мы просто используем ++. Но здесь есть еще одна сложность. Инкремент бывает двух видов. Это префиксный и постфиксный инкремент. Здесь у нас используется форма постфиксного инкремента. А вот ниже используется форма префиксного инкремента. Что это значит разберем. Переменная number1 равна 0. На 14 строке сначала выводится значение переменной number1 на экран, а только потом переменная увеличивается на 1. Сначала вывели на экран 0. Потом увеличили на 1. Давайте выполнимся. Сначала вывелся 0, потом мы ничего уже не увеличиваем, но так как переменная number1 была увеличена на 1. Она стала равна 1. Поэтому следующий вывод выводит на экран 1. Смотрим следующее – Number2. Мы ей присваиваем 0 и на 20 строке мы сначала увеличиваем переменную number2 на 1, а только потом мы ее выводим. Мы сначала увеличиваем, а только потом выводим ее значение. Значит у нас имеется такое понятие как инкремент – приращение на единицу и декремент – уменьшение на единицу. Как и инкремент – декремент имеет две формы. Постфиксную форму и префиксную форму. Теперь смотрим постфиксный декремент. У нас имеется на 24 строке переменная number3 типа sbyte, зануляем ее. Смотрите, мы сначала выводи на экран 0, а только потом уменьшаем ее на единицу, и на 26 строке мы выводим -1. Сначала вывели, потом уменьшили, потому что здесь используется постфиксная форма декремента. Далее, на 30 строке мы создаем еще одну переменную number4 типа sbyte, зануляем ее. Здесь мы используем префиксную форму декремента. Почему префиксную, потому что предлог, preposition. Предыдущая позиция. Просто? Просто. Значит два таких понятия, инкремент и декремент. Инкремент – увеличение значения переменной на 1. Декремент – уменьшение значения переменной на 1. Как инкремент, так и декремент имеет две формы. Это префиксная форма, то есть сначала произвести операцию а только потом использовать эту переменную, и постфиксная – сначала используем переменную, а только потом выполняем над ней операцию либо инкремента, либо декремента.
Следующий пример.
1: using System; 2: 3: namespace Assignment 4: { 5: class Program 6: { 7: static void Main() 8: { 9: // ПРАВИЛО: 10: // Все арифметические операции производимые над двумя значениями типа (byte, sbyte, short, ushort) 11: // в качестве результата, возвращают значение типа int. 12: 13: // Присвоение со сложением для типа byte. 14: byte variable1 = 0; 15: 16: //variable1 = variable1 + 5; // ОШИБКА: Попытка неявного преобразования значения результата, тип int в тип byte. 17: //variable1 = (byte)variable1 + 5; // ОШИБКА: Происходит преобразование типа byte в тип byte, раньше выполнения операции сложения. 18: 19: variable1 = (byte)(variable1 + 5); // Громоздкое решение. 20: 21: variable1 += 5; // Элегантное решение. 22: 23: //variable1 += 5000; // Ошибка. т.к. значение правой части выражения не должно превышать диапазон допустимых значений типа переменной 24: 25: // ПРАВИЛО: 26: // Для типов int, uint, long и ulong, не происходит преобразования типа результата арифметических операций. 27: 28: #region Операции присвоения с... 29: 30: // Присвоение со сложением. 31: int variable2 = 0; 32: variable2 = variable2 + 5; 33: variable2 += 5; 34: 35: // Присвоение с вычитанием. 36: uint variable3 = 0; 37: variable3 = variable3 - 5; 38: variable3 -= 5; 39: 40: // Присвоение с умножением. 41: long variable4 = 0; 42: variable4 = variable4 * 5; 43: variable4 *= 5; 44: 45: // Присвоение с делением. 46: ulong variable5 = 0; 47: variable5 = variable5 / 5; 48: variable5 /= 5; 49: 50: // Присвоение остатка от деления. 51: long variable6 = 0; 52: variable6 = variable6 % 5; 53: variable6 %= 5; 54: 55: #endregion 56: 57: // ПРАВИЛО: 58: // Для типов float и double, не происходит преобразования типа результата арифметических операций. 59: 60: // Присвоение со сложением. 61: float variable7 = 0; 62: variable7 = variable7 + 5; 63: variable7 += 5; 64: 65: // Присвоение с умножением. 66: double variable8 = 0; 67: variable8 = variable8 * 5; 68: variable8 *= 5; 69: 70: // Delay. 71: Console.ReadKey(); 72: } 73: } 74: }
Давайте посмотрим, что в этом примере. Обратите внимание, здесь тоже идет набор хитрых правил. Если бы мы были в каком-нибудь боевом виде спорта, я бы вам сказал: «Сгруппируйтесь!» Не пугайтесь, здесь все очень просто, просто нужно немного внимания. Первое правило. Все арифметические операции производимые над двумя значениями типа byte, sbyte, short, ushort в качестве результата возвращают значение типа int. Что это значит? Давайте посмотрим, на 14 строке мы создаем переменную variable1 типа byte и присваиваем ей значение 0. На 16 строке мы переменной variable1 присваиваем возвращаемое значение оператора +, который берет значение variable1, прибавляет ему значение 5 и должен был вернуть сюда 5? Давайте посмотрим, смотрите, ошибка. Что же такое? Cannot implicity covert type int to byte. Смотрите, он не может неявно преобразовать, сделать кастинг. Но если я вот здесь поставлю байт. Здесь у нас опять ошибка, я же поставил оператор преобразования типа. Я беру переменную variable1 типа byte преобразовываешь его к byte. Получаются те же яйца, только в профиль. Ошибка остается та же. Я сделал здесь просто глупое преобразование. А что же мне делать? Почему вообще такое происходит? Какого типа вообще эта 5. Все непосредственные значения типа int. И получается, что variable1 у нас байтовая а 5 типа int. Int был сколько байт? Правильно, 4. Если мы проводим операции на разнотипных значениях нам нужен кастинг. На 19 строке вот уже правильный вариант. Смотрите, что происходит. Мы берем однобайтовую переменную и прибавляем к ней 4 байтовую. В итоге оператор + возвращает 4 байта. И мы теперь эти 4 байта должны преобразовать в 1. Мы используем оператор явного преобразования значений типа. Смотрим еще один вариант, на 21 строке мы берем variable1 и используем оператор сокращенного сложения или оператор присвоения со сложением. Здесь происходит один в один то, что происходит на 19 строке. Просто более кратко. А что, у нас есть инкремент, увеличить на 1. Почему не было такого интересного оператора, который распадается на такую конструкцию, которую вы видите на 19 строке. Значит на 21 строке мы переменной variable1 присваиваем со сложением 5. Смотрите, не надо никаких кастингов, не надо ничего. Элегантное решение. На 23 строке опять мы видим какой компилятор умный и предусмотрительный. Умный в смысле того, что заложены такие алгоритмы проверки. На 23 строке мы переменной variable1 пытаемся присвоить значение, превышающее максимально допустимый диапазон значений, которые могут входить в эту переменную. Как в однобайтовую переменную поместить 5000? Это невозможно. Что сделает компилятор? Это значение не может быть конвертировано в byte. 500 тоже не могу, а 50? А 50 могу. А 255 могу. Но будет переполнение, потому что переменная variable1 уже содержит в себе значение. 255 – это непосредственное значение. А мы знаем, что почти нет разницы между константами и непосредственными значениями. Мы знаем, что этого нет. Вспоминайте пример с константами. Здесь когда-то была настоящая константа, но ее не стало, она превратилась в непосредственное значение. Правило. Для типов int, uint, long и ulong не происходит преобразования типа арифметических операций. То есть получается, что byte + byte = int. Sbyte + sbyte = int. Short + short = int. ushort + ushort = int. Это особенность адресации памяти, особенность работы регистров процессора, особенность оптимизации всей этой работы. Мы с вами с этим будем разбираться уже позже на других курсах. Как происходит выравнивание на границу слова, что там в памяти происходит, это мы разберем позже или об этом можно почитать у Джефри Рихтера. Ну а мы смотрим следующий блок. Операции присвоения с сложением, вычитанием, умножением, делением и присвоением остатка от деления. И мы с вами уже видели похожую операцию. На 31 строке мы создаем переменную типа int с именем variable2, четыре байта. На 32 строке мы ей присваиваем сумму значения переменной variable2 и 5. Обратите внимание, ошибки нет. Потому что, int + int = int. Если бы variable2 была байтовая, была бы ошибка. Это громоздкое решение, а на 33 строке решение элегантное. Присвоение со сложением. То же у нас имеется и на 38 – присвоение с вычитанием. Громоздкое решение, элегантное решение. 42 строка, умножение, присвоение. 43 строка, присвоение с умножением. Громоздкое решение. Элегантное решение. Присвоение с делением. Знак деления. Присвоение остатка от деления. Переходим дальше.
Как вы видите, мы с вами рассматриваем простейшие конструкции, которые вы все видели в первом, втором классе. Видите, даже первоклассник или второклассник может научится программировать. А теперь мы поговорим с вами об областях видимости.
1: using System; 2: 3: // Использование локальных областей и локальных переменных. 4: 5: namespace LocalVariables 6: { 7: class Program 8: { 9: static void Main() 10: { 11: // ПРАВИЛО: 12: // В коде можно создавать локальные области и в двух разных локальных областях хранить одноименные переменные. 13: 14: // Локальная область 1 15: { 16: int a = 1; 17: Console.WriteLine(a); 18: } 19: 20: // Локальная область 2 21: { 22: int a = 2; 23: Console.WriteLine(a); 24: } 25: 26: 27: // ПРАВИЛО: 28: // Если в коде имеются локальные области, то запрещается хранить одноименные переменные за пределами локальных областей. 29: 30: //int a = 3; // ОШИБКА: Переменная с таким именем уже существует в локальной области. 31: 32: // Delay. 33: Console.ReadKey(); 34: } 35: } 36: }
Что это значит? Смотрите, что мы делаем. На 15 и на 18 строках мы создаем некий блок. Мы не говорим слово «фигурные» скобки. Здесь – это операторные скобки. На 15 строке мы видим открывающуюся операторную скобку, на 18 строке – закрывающую операторную скобку. Они определяют некий блок. А блок чего? Что это такое на 16 и 17 строке? Это операторы языка. Все что заканчивается точкой с запятой, является оператором языка. У нас есть даже пустой оператор. Его можно ставить сколько угодно и программа будет выполнятся. Но зачем поступать так иррационально? Поэтому мы с вами так делать не будем. Значит, операторные скобки группируют несколько операторов. Сейчас мы посмотрим зачем их так группировать. Смотрите, на 16 строке в этом блоке мы создаем переменную а типа int и присваиваем ей значение 1. Затем выводим на экран. И во втором блоке мы создаем одноименную переменную. Два блока определяют некие локальные области. Представьте, что вот здесь стоит человечек, и здесь стоит человечек. Они не видят друг друга. Они находятся в разных коробках. Невозможно увидеть, что находится в локальной области из другой локальной области, так же невозможно увидеть, что находится в локальной области из внешней области. Смотрите, идет метод Main. По сути это глобальная область по отношению к этим локальным областям. Смотрите, что у нас позволено сделать. В разных локальных областях создали одноименные переменные. Если мы такое попытаемся сделать просто в коде… Давайте попробуем, создаем одну, другую… Мы не можем этого сделать. Давайте посмотрим, ошибка, такая переменная уже объявлена. Представьте, что у меня на вебинаре или на тренинге сидит два Ивана. Я отворачиваюсь к доске и обращаюсь «Иван». Они не понимают, к какому Ивану я обращаюсь. То есть у нас есть две одноименный сущности. Невозможно сделать так, чтобы в одной области видимости были две одинаковые одноименные сущности. Потому что мы не поймем к чему обращаться. И поэтому если у меня имеется два Ивана, то я должен как-то их идентифицировать. Тебя я буду называть Иван, а тебя Ваня. Мы создали две локальные области видимости, в которых создали две локальные переменные а. Коллизии никакой нет. Смотрим теперь правила. В коде можно создавать локальные области, и в локальных областях хранит одноименные переменные. На 27 строке еще одно правило. Если в коде имеются локальные области. То запрещается хранить одноименные переменные за пределами локальных областей. Давайте посмотрим. Ошибка. Переменная с таким именем уже существует в локальной области. Давайте посмотрим еще одно интересное правило работы локальной области с глобальной. Смотрите, если я на 13 строке создам переменную b и захочу из первой локальной области вывести это значение на экран. Используем сниппеты. Вы можете нажать комбинацию CTRL+K,X. Потом v, Enter, дальше cw Enter. Но это долго, проще через IntelliSense написать. Вы можете написать просто cw потом Tab и еще раз Tab. И теперь мы здесь хотим вывести значение переменной b. Смотрите, мы с вами видим переменную b из глобальной области видимости. Получается, что эти два человечка, которые сидят в этих ящиках, они видят все что находится вокруг. Это как мы видим звезды. То, что находится глобально относительно Земли. Но мы, например, не можем рассмотреть микробов. Не можем посмотреть в локальные области видимости. Поэтому звезды мы видим, микробов мы не видим. Запомните это правило. Из локальной области, мы видим то, что находится в глобальной области. Значит из локальной области мы можем заглянуть в глобальную, а из глобальной в локальную можем заглянуть? Нет, не можем. Если в коде имеются локальные области, то запрещается хранить одноименные переменные за пределами локальной области. Даже если в локальной области будет переменная с единственным именем, то к ней все равно нельзя будет обратится. Компилятор даже не позволяет нам этого сделать. А мы переходим с вами дальше.
1: using System; 2: 3: // Использование ключевых слов языка C# в качестве идентификаторов. 4: 5: namespace At 6: { 7: class Program 8: { 9: static void Main() 10: { 11: //int bool = 5; // Illegal 12: int @bool = 7; // Legal 13: Console.WriteLine(@bool); 14: 15: 16: // Символ @ не является частью идентификатора, поэтому, @myVariable - это тоже самое, что и myVariable. 17: string @myVariable = "Hello"; 18: Console.WriteLine(myVariable); 19: 20: 21: // Delay. 22: Console.ReadKey(); 23: } 24: } 25: }
Мы с вами уже видели в нашей презентации, что мы можем начинать переменные с @. Эта тема касается использования ключевых слов языка C# в качестве идентификаторов. Я часто работаю с ассемблером и меня интересует теория компиляторов и интерпретаторов. Я работаю с дизассемблерами с IDE, работаю с кодом как с данными, метаподходы. Метаинструменты. Когда эти инструменты представляют программные коды как данные. Приходится работать с анализом программных кодов. И мне нужны переменные с такими именами. И для меня вот int – это правильная сущность в моем бизнес контексте, в моих требованиях. Я понимаю, что врачам не надо создавать программу с именем int, потому что у них есть свои инструменты. Но я то работаю с кодом, и мне нужно представить тип. Что мне делать? Если я просто сделаю на 11 строке int bool, выдаст ошибку identifier expected, bool is a keyword. Он не позволяет мне этого сделать. На 12 строке вот оно решение моей проблемы. Я ставлю @ перед именем своей переменной. И заметьте у меня все работает. Но ведь можно поставить впереди нижнее подчеркивание. Можно и так сделать. На самом деле вот эта @ не является частью имени. Поэтому, если мы создадим переменную с именем myVariable и поставим в качестве префикса @, это будет тоже самое что и myVariable. Смотрите, на 17 строке мы создаем строковую переменную с именем @myVariable и присваиваем ей значение «Hello» И на 18 строке я использую эту переменную без @. Вы видите какая тонкость. Но я надеюсь вам не придется использовать @ в именах идентификаторов. Но может быть в редких случаях. Например, когда вы в своих программах будете именовать переменные ключевыми словами, например operator? То использование знака @ поможет избежать ошибки. Старайтесь не использовать ключевых слов. А мы идем дальше.
1: using System; 2: 3: // Проверка переполнения - (checked) 4: 5: namespace Checked 6: { 7: class Program 8: { 9: static void Main() 10: { 11: sbyte a = 127; 12: 13: // Проверять переполнение. 14: checked 15: { 16: a++; // ОШИБКА уровня компилятора 17: } 18: 19: // 127 + 1 = -128 20: Console.WriteLine(a); 21: 22: // Delay. 23: Console.ReadKey(); 24: } 25: } 26: }
Checked и Unchecked. На 11 строке мы создаем переменную а типа sbyte и присваиваем ей значение 127. Переменная со знаком. Давайте зайдем в нее и посмотрим. MaxValue = 127, MinValue = -128. То есть вот он диапазон чисел, который может хранится в этой переменной. Если мы возьмем число -128 по модулю и сложим 127 и 128 мы получим 255. Получается, что мы сюда записали максимально допустимое значение знаковой байтовой переменной. Если я к максимально допустимому значению 127 прибавлю 1, то что произойдет? Давайте откроем калькулятор и посчитаем. Потому что это – интересно. У меня имеется число десятичное 127, я его представляю в бинарном виде. Вы видите? Видите, как выглядит число в бинарном виде? Если я только прибавляю к нему единицу у меня произойдёт перенос. Давайте попробуем, + 1. Мы получили число -128. Вы видите, как работает эта арифметика. 127 + 1 = -128 А у нас произошло переполнение, перенос из предпоследнего разряда в последний. Это число представляет собой отрицательное число, потому что все знаковые числа, которые начинаются с 1 – это отрицательные числа. Если крайний бит будет равен 0 то число положительное. Это зависит от того как мы рассматриваем число. Если мы говорим, что число натуральное этот закон не работает. Но со знаковыми числами это важно помнить. Чтобы вам не вникать в машинную математику и в понятия двоичных дополнений можно помнить диапазоны этих 4 типов. Мы понимаем, что если мы к 127 прибавим 1. То у нас будет ошибка. Я ожидал получить 128. А у меня получается -128. Это уже ошибка. Это особенность машинной арифметики. И поэтому я хочу проконтролировать overflow. И для этого у меня имеется оператор checked вот с таким телом. Мы пишем checked – проверять, контролировать все что находится внутри блока оператора checked. И тут, смотрите, я пытаюсь увеличить 127 на 1. Здесь происходит переполнение, перенос из предпоследнего бита в последний. Давайте выполнимся. Смотрите! Overflow exeption! Когда у вас появляется такая ошибка, то первое, что надо сделать, это зайти в View Detail… Посмотреть детали, что же здесь произошло? Произошло переполнение. Конечно это же написано и в основном окне. Но дело в том, что у нас бывают еще и вложенные исключения. Но это мы будем разбирать уже на следующем курсе. Работу вот с такими исключительными ситуациями. Но если такое окно появилось и прочитав заголовок вы не можете понять, что произошло, заходите в просмотр деталей и смотрите то что здесь написано. В итоге, операция у меня не выполнилась. Мне бросилось исключение переполнения. Для знаковых чисел идет контроль этого бита. У нас имеется еще регистр флагов в процессоре, где идет 9 бит sign flag, который контролирует это переполнение. Вот благодаря ему это и получилось. Мы понимаем, что мы не можем так делать. А если отнять от -128 единицу. Давайте попробуем. Меняем значение переменной и ставим декремент. Выполняемся. Опять ошибка. -128-1 = -129 а минимально допустимое значение -128. А такие операции называются антипереполнением. Мы все будем называть просто переполнением. Значит, оператор checked позволяем контролировать переполнение и антипереполнение числа. Заходим в следующий пример.
1: using System; 2: 3: // Запрет проверки переполнения - (unchecked) 4: 5: namespace UnChecked 6: { 7: class Program 8: { 9: static void Main() 10: { 11: sbyte a = 127; 12: 13: // Проверять переполнение. 14: unchecked 15: { 16: a++; // Логическая ошибка 17: } 18: 19: // 127 + 1 = -128 20: Console.WriteLine(a); 21: 22: // Delay. 23: Console.ReadKey(); 24: } 25: } 26: }
Unchecked – это значит не проверять. А по умолчанию оно и так не проверяет. Смотрите, мы к 127 прибавляем 1 и получаем -128 в консоли, а если мы закомментируем операцию unchecked и программа выполняется, как и в прошлый раз. Зачем его использовать? Сейчас перейдем в следующий пример.
1: using System; 2: 3: // Комбинация проверки и запрета проверки переполнения. 4: 5: namespace CheckedUnChecked 6: { 7: class Program 8: { 9: static void Main() 10: { 11: sbyte a = 126; 12: sbyte b = 127; 13: 14: // Проверять переполнение. 15: checked 16: { 17: a++; 18: 19: // Не проверять переполнение. 20: unchecked 21: { 22: b++; 23: } 24: 25: a++; 26: } 27: 28: // Delay. 29: Console.ReadKey(); 30: } 31: } 32: }
Для того чтобы мы могли комбинировать checked и unchecked. На 11 и 12 строке мы создаем две переменные типа sbyte и присваиваем им значения 126 и 127. На 15 строке мы создаем конструкцию checked. Но он проверяет переполнение переменной а, но я хочу отключить переменную b. И мы внустри checked создаем локальную область, в которой не будет происходить проверки на переполнение. Проверка переполнения или отказ от проверки переполнения. Смотрим далее.
Теперь мы уже ближе и ближе подходим к работе со строками.
1: using System; 2: 3: // Сцепление строк. (Конкатенация) 4: 5: namespace Concatenation 6: { 7: class Program 8: { 9: static void Main() 10: { 11: // 1 вариант. 12: string word1 = "Привет "; 13: string word2 = "Мир!"; 14: string phrase = word1 + word2; 15: Console.WriteLine(phrase); 16: 17: // 2 вариант. 18: Console.WriteLine("Hello " + "World!"); 19: 20: // Delay. 21: Console.ReadKey(); 22: } 23: } 24: }
Мы помним, что формат строки – это двойные кавычки. На 12 строке мы создаем строковую переменную word1 и присваиваем ей значение «Привет ». На 13 строке мы создаем переменную word2 и присваиваем ей значение «Мир». На 14 строке мы создаем переменную phrase и присваиваем ей значение… Знак +. Знак + когда используется со строками обозначает уже не сложение, а сцепление, не склеивание, не сложение, не смыкание, а именно сцепление строк – конкатенация. На 14 строке у нас происходит сцепление двух строк, конкатенация двух строк. «Привет » в переменной word1 и «Мир!» в переменной word2. Значит на 14 строке мы переменной phrase присваиваем конкатенацию строковых значений переменных word1 и word2. И на 15 строке мы выведем на экран «Привет Мир!» Выполняемся. Но на 18 строке обратите внимание, мы выводим на экран конкатенацию двух строк «Hello » и «World!» Здесь мы производим конкатенацию над строковыми литералами. Или над строковыми непосредственными значениями. Это была конкатенация – простая операция. Смотрим дальше. Ага! String format. А сейчас мы должны обязательно перейти в нашу презентацию и посмотреть на терминологию. Потому что у всех этих скобочек есть свои правильные названия. Смотрите, строки, форматированный вывод. Обратите внимание, у нас имеется строка. Метод WriteLine в данном случае он имеет два параметра. Один это строка, а второй – это какое-то значение, которое мы хотим вставить внутрь это й строки. Смотрите как у нас составляется строка, мы пишем «Это число», а дальше идут служебные символы. Так вот этот символ {0} называется маркером подстановки. И теперь мы вместо маркера подстановки подставляем единицу. В итоге, когда мы выполним эту строку, мы выведем на экран вот эту единицу. Обратите внимание, у нас следующая строка. Позиция элемента подстановки начинается с 0. То есть параметры, которые идут за строкой, те значения, которые мы хотим подставить нумеруются с нуля. Смотрим на строку. Первый маркер нулевой, значит вместо него будет подставлена 1, а двойка в маркер с цифрой 1. Маркеры подстановки можно располагать в любом порядке.
1: using System; 2: 3: namespace StringFormat 4: { 5: class Program 6: { 7: static void Main() 8: { 9: int a = 1; 10: Console.WriteLine("Это число {0}", a); 11: 12: int b = 2, c = 3; 13: Console.WriteLine("Это числа {0} и {1}", b, c); 14: Console.WriteLine("Это числа наоборот {1} и {0}", b, c); 15: 16: // Delay. 17: Console.ReadKey(); 18: } 19: } 20: }
На 10 строке вывод на экран, где используется маркер подстановки, после строки идет параметр, который называется элемент подстановки. Значит мы видим, что выт этот элемент подстановки значение переменной а мы подставим в строку. Давайте выполнимся. Вот число 1. Смотрите, здесь элементы подстановки b и с. У b нулевой индекс, у с первый индекс. Если поменять маркеры подстановки, то числа будут выводится в обратном порядке. Это просто. Смотрим дальше.
Далее у нас имеются флаги форматирования.
1: using System; 2: 3: // Флаги форматирования строк. 4: 5: namespace FlagFormating 6: { 7: class Program 8: { 9: static void Main() 10: { 11: Console.WriteLine("C format: {0:C}", 99.9); // Вывод в денежном формате. 12: Console.WriteLine("F format: {0:##}", 99.935); // Вывод значений с фиксированой точностью. 13: Console.WriteLine("N format: {0:N}", 99999); // Стандартное числовое форматироваание. 14: Console.WriteLine("X format: {0:X}", 255); // Вывод в шеснадцатиричном формате. 15: Console.WriteLine("D format: {0:D}", 0xFF); // Вывод в десятичном формате. 16: Console.WriteLine("E format: {0:E}", 9999); // Вывод в экспоненциальном формате. 17: Console.WriteLine("G format: {0:G}", 99.9); // Вывод в общем формате. 18: Console.WriteLine("P format: {0:P}", 99.9); // Вывод в процентном формате. 19: 20: // Delay. 21: Console.ReadKey(); 22: } 23: } 24: }
У нас имеется маркер подстановки и через две точки мы ему пишем в каком формате будет выводится это число. Сейчас я выделил элементы подстановки. А вот это маркеры подстановки. И оказывается, что у маркеров подстановки имеются свои флаги форматирования. Флаг С – currency – валюта. Давайте выполнимся и посмотрим, как же оно выводит. А денежном формате оно добавило знак доллара. У меня стоит американская локаль, потому у меня такая валюта стоит. Флаг F – округление числа. N – разбиение числа на классы. И т.д. Там процентные форматы и др. Символы форматирования, которые используются вместе с маркером подстановки. Смотрим далее.
Оператор SizeOf. Что на помогает делать этот оператор? Он помогает нам определить размер типа. Оператор SizeOf позволяет получить размер значения в байтах для указанного типа. Оператор SizeOf можно применять только к типам byte, sbyte, short, ushort, int, uint, long, ulong, float, double, decimal, char, bool.
1: using System; 2: 3: // Оператор sizeof - позволяет получить размер значения в байтах для указанного типа. 4: // Оператор sizeof можно применять только к типам: 5: // byte, sbyte, short, ushort, int, uint, long, ulong, float, double, decimal, char, bool. 6: // Возвращаемые оператором sizeof значения имеют тип int. 7: 8: namespace Sizeof 9: { 10: class Program 11: { 12: static void Main() 13: { 14: int doubleSize = sizeof(double); 15: Console.WriteLine("Размер типа double: {0} байт.", doubleSize); 16: 17: Console.WriteLine("Размер типа int: {0} байт.", sizeof(int)); 18: Console.WriteLine("Размер типа bool: {0} байт.", sizeof(bool)); 19: Console.WriteLine("Размер типа long: {0} байт.", sizeof(long)); 20: Console.WriteLine("Размер типа short: {0} байт.", sizeof(short)); 21: 22: // Delay. 23: Console.ReadKey(); 24: } 25: } 26: }
Использование этого оператора мы увидим дальше по курсу, как и где используется оператор SizeOf. Зачем нам нужно вот так определять типы? Оператор возвращает int значение, которое будет содержать в себе размер типа double. А размер double равен 8 байт. Давайте выполнимся. А зачем он используется пусть вам будет интересно. Дальше, когда будем изучать, тогда покажу только эти примеры. Ага! Размер double – 8 байт. Int – 4 байта. Bool – 1 байт. Long – 8 байт. Short – 2 байта. Видите, вот что делает оператор SizeOf. Представьте себе, что у нас бывают машины 32 битные и 64 битные. Дело в том, что раньше int в старых программах был двухбайтовым. Поэтому, когда появились 32 битные машины, то int стал не машинным словом, а двойным машинным словом. Это уже определено Windows, но мы же пишем программы не только для операционной системы Windows. Мы можем писать и под Android, и под MacOS и под другие ОС. Возможно сделаем сами свою виртуальную машину. И не понятно, под какую адресацию памяти мы это будем делать и под какую разрядность процессора. И поэтому этот оператор будет помогать понят на какой платформе работает программа. Смотрите это в наших вебинарах. У нас такое тоже может появится. У насть курсы под Android, там много все интересного. Смотрим дальше.
Неявно типизированные переменные.
1: using System; 2: 3: // Неявно типизированные локальные переменные. 4: 5: namespace Var 6: { 7: class Program 8: { 9: static void Main() 10: { 11: // Неявно типизированная локальная переменная переменная. 12: var myVar = 7; 13: 14: Console.WriteLine(myVar); 15: 16: // Неявно типизированные локальные переменные не допускают множественного объявления. 17: // var a = 1, b = 2, c = 3; 18: 19: // Неявно типизированные локальные переменные должны быть инициализированы. 20: // var a; 21: 22: // Константа не может быть неявно типизированная. 23: // const var myVar = 3.14; 24: 25: // Delay. 26: Console.ReadKey(); 27: } 28: } 29: }
На 12 строке мы создаем переменную с именем myVar типа var и присваиваем ей значение 7. Давайте наведем на var. Что же это за тип непонятный. Смотрите, мне подсказывается, что это тип Int32. Var – неявно типизированные локальные переменные. А что это значит? Смотрите, если я сделаю 7.3f – все работает. Что поменялось? Теперь тип стал показываться Single. А если уберу f, тогда тип будет Double. А если я поставлю здесь двойные кавычки. Смотрите, тип стал String. Это начало динамической типизации, но не путайте, это еще не динамическая типизация. Это еще не то что мы с вами рассматривали там слово dynamic. Это не оно, это просто неявно типизированная локальная переменная. Локальная почему? А потому что она хранится в локальной области метода Main. Это тоже локальная область по отношению к какому-то еще не понятному классу Program. Зачем их сделали? Для того, чтобы если программист не знает какого типа сделать переменную, то нужно использовать var. Никогда так не говорите. Программист не знает какого типа сделать переменную? Такой программист что должен делать? Идти мыть посуду! А дело в том, что этот тип зародился вместе из одной интересной конструкцией – анонимными типами. Мы их будем изучать чуть попозже. На другом курсе. Но программисты поняли, что иногда у нас бывают очень сложные и длинные типы. И они начали использовать var. Для того чтобы не только работать с анонимными типами, но и работать с длинными именами типов. Потому что на следующем курсе мы научимся создавать свои типы данных. Поэтому для такого случая. Когда мы хотим сделать int переменную то ставить тип var не всегда целесообразно. Но есть такое понятие, как сокрытие информации. Сокрытие информации разделяется на три части. Первая – сокрытие реализации, второе – сокрытие реализации частей программных систем. Это сокрытие целых больших кусков кода. Последнее – это сокрытие типов данных. Мы сейчас не будем долго говорить об этом. Я так закинул удочку, вам стало интересно, потому что в дальнейшем мы с вами сокрытие информации будем изучать и как инкапсуляцию, и как инкапсуляцию сокрытия кусков больших программных систем и сокрытие типов данных. Это интересно. Смотрите в наших следующих курсах. И на 14 строке мы выводим значение переменной myVar. То есть она становится волшебной, и приобретает тот тип, значение которого мы и пытаемся присвоить этой переменной. Но здесь тоже есть несколько правил. На 17 строке мы пытаемся создать множественное объявление. Не смотря на то, что типы одинаковые, оно конечно показывает, что это структура Int32, но все равно нельзя делать множественное объявление неявно типизированным. Мы так же не можем неявно типизированные локальные переменные оставлять без первичной инициализации. Почему? Потому что здесь угадай то, не знаю что. Если вверху было понятно на что похоже это значение, а здесь вообще не на что опереться этой переменной. Это как потребовать емкость и не знать, что там будет внутри. И последнее правило – константа не может быть неявно типизированной. Вот такие правила при работе с неявно типизированными локальными переменными. Мы с ними познакомились, чтобы вы знали, что есть такая разновидность переменных а уже как они правильно используются. Но запомните, не надо использовать неявную типизацию, когда вы не знаете какой тип у переменной должен быть. Здесь нужно либо выучить типы, либо обрести другую профессию, которая не связана с программированием. А мы идем дальше.
Comparsion. Сравнение значений разных типов.
1: using System; 2: 3: // Сравнение значений разных типов. 4: 5: namespace Comparison 6: { 7: class Program 8: { 9: static void Main() 10: { 11: bool result = false; 12: 13: int a = 1; 14: float b = 2.0f; 15: result = a < b; // Сравнение значения типа int, со значением типа float - допустимо. 16: 17: string c = "Hello"; 18: //result = c < a; // Сравнение значения типа int, со значением типа string - не допустимо. 19: 20: // Delay. 21: Console.ReadKey(); 22: } 23: } 24: }
Такой вопрос тоже часто встречается. А могу ли я сравнивать int с float. Или float и double. Или byte с double. Допустимы ли такие сравнения. На 11 строке мы создаем булеву переменную result и присваиваем ей значение false. Мы понимаем, что в эту переменную мы будем записывать результаты сравнений, результаты высказываний. На 13 строке мы создаем переменную а типа int и присваиваем ей значение 1. На 14 строке мы создаем переменную b типа float и присваиваем ей значение 2. Если убрать f из числа то будет ошибка, потому что по умолчанию литерал с точкой воспринимается как double. На 14 строке мы создали переменная b типа float и присвоили ей значение 2. И на 15 строке мы переменной result присваиваем возвращаемое значение оператора меньше. Мы говорим, что a