Тема: Основы программирования на C#
Структура программы
Инструкции Базовым строительным блоком программы являются инструкции (statement). Инструкция представляет некоторое действие, например, арифметическую операцию, вызов метода, объявление переменной и присвоение ей значения. В конце каждой инструкции в C# ставится точка с запятой (;). Данный знак указывает компилятору на конец инструкции. Например:
| 1 | Console.WriteLine("Привет"); |
Данная строка представляет вызов метода Console.WriteLine, который выводит на консоль строку. В данном случае вызов метода является инструкцией и поэтому завершается точкой с запятой.
Набор инструкций может объединяться в блок кода. Блок кода заключается в фигурные скобки, а инструкции помещаются между открывающей и закрывающей фигурными скобками:
| 1 2 3 4 | { Console.WriteLine("Привет"); Console.WriteLine("Добро пожаловать в C#"); } |
В данном блоке кода две инструкции, которые выводят на консоль определенную строку.
Одни блоки кода могут содержать другие блоки:
| 1 2 3 4 5 6 | { Console.WriteLine("Первый блок"); { Console.WriteLine("Второй блок"); } } |
Метод Main Точкой входа в программу на языке C# является метод Main. При создании проекта консольного приложения в Visual Studio, например, создается следующий метод Main:
| 1 2 3 4 5 6 7 | class Program { static void Main(string[] args) { // здесь помещаются выполняемые инструкции } } |
По умолчанию метод Main размещается в классе Program. Название класса может быть любым. Но метод Main является обязательной частью консольного приложения. Если мы изменим его название, то программа не скомпилируется.
По сути и класс, и метод представляют своего рода блок кода: блок метода помещается в блок класса. Внутри блока метода Main располагаются выполняемые в программе инструкции.
Регистрозависимость C# является регистрозависимым языком. Это значит, в зависимости от регистра символов какое-то определенные названия может представлять разные классы, методы, переменные и т.д. Например, название обязательного метода Main начинается именно с большой буквы: "Main". Если мы назовем метод "main", то программа не скомпилируется, так как метод, который представляет стартовую точку в приложении, обязательно должен называться "Main", а не "main" или "MAIN".
Комментарии Важной частью программного кода являются комментарии. Они не являются собственно частью программы, при компиляции они игнорируются. Тем не менее комментарии делают код программы более понятным, помогая понять те или иные его части.
есть два типа комментариев: однострочный и многострочный. Однострочный комментарий размещается на одной строке после двойного слеша //. А многострочный комментарий заключается между символами /* текст комментария */. Он может размещаться на нескольких строках. Например:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | using System; namespace HelloApp { /* программа, которая спрашивает у пользователя имя и выводит его на консоль */ class Program { // метод Main - стартовая точка приложения static void Main(string[] args) { Console.Write("Введите свое имя: "); string name = Console.ReadLine(); // вводим имя } } } |
Программы верхнего уровня Начиная с версии C# 9.0 (.NET 5) добавлена возможность создавать программы верхнего уровня. То есть, если у нас программа состоит из одного метода Main, то мы можем убрать из определения программы объявление пространства имен (namespace HelloApp), объявление класса (class Program) и объявление метода Main (static void Main(string[] args)) и оставить только директивы using с подключаемыми пространствами имен и собственно исполняемые инструкции.
Например, выше в предыдущем листинге кода программа запрашивала ввод имени пользователя. Фактически все тело программы состоит из метода Main, поэтому мы ее можем сократить в C# 9.0 следующим образом:
| 1 2 3 4 5 | using System; Console.Write("Введите свое имя: "); string name = Console.ReadLine(); // вводим имя Console.WriteLine($"Привет {name}"); // выводим имя на консоль |
Результат работы программы будет тот же, что и в предыдущем случае.
Переменные
Для хранения данных в программе применяются переменные. Переменная представляет именнованную область памяти, в которой хранится значение определенного типа. Переменная имеет тип, имя и значение. Тип определяет, какого рода информацию может хранить переменная.
Перед использованием любую переменную надо определить. Синтаксис определения переменной выглядит следующим образом:
Вначале идет тип переменной, потом ее имя. В качестве имени переменной может выступать любое произвольное название, которое удовлетворяет следующим требованиям:
имя может содержать любые цифры, буквы и символ подчеркивания, при этом первый символ в имени должен быть буквой или символом подчеркивания
в имени не должно быть знаков пунктуации и пробелов
имя не может быть ключевым словом языка C#. Таких слов не так много, и при работе в Visual Studio среда разработки подсвечивает ключевые слова синим цветом.
Хотя имя переменой может быть любым, но следует давать переменным описательные имена, которые будут говорить об их предназначении.
Например, определим простейшую переменную:
В данном случае определена переменная name, которая имеет тип string. то есть переменная представляет строку. Поскольку определение переменной представляет собой инструкцию, то после него ставится точка с запятой.
При этом следует учитывать, что C# является регистрозависимым языком, поэтому следующие два определения переменных будут представлять две разные переменные:
| 1 2 | string name; string Name; |
После определения переменной можно присвоить некоторое значение:
| 1 2 | string name; name = "Tom"; |
Так как переменная name представляет тип string, то есть строку, то мы можем присвоить ей строку в двойных кавычках. Причем переменной можно присвоить только то значение, которое соответствует ее типу.
В дальнейшем с помощью имени переменной мы сможем обращаться к той области памяти, в которой хранится ее значение.
Также мы можем сразу при определении присвоить переменной значение. Данный прием называется инициализацией:
Отличительной чертой переменных является то, что в программе можно многократно менять их значение. Например, создадим небольшую программу, в которой определим переменную, поменяем ее значение и выведем его на консоль:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | using System; namespace HelloApp { class Program { static void Main(string[] args) { string name = "Tom"; // определяем переменную и инициализируем ее Console.WriteLine(name); // Tom name = "Bob"; // меняем значение переменной Console.WriteLine(name); // Bob Console.Read(); } } } |
Консольный вывод программы:
Tom
Bob
Литералы
Литералы представляют неизменяемые значения (иногда их еще называют константами). Литералы можно передавать переменным в качестве значения. Литералы бывают логическими, целочисленными, вещественными, символьными и строчными. И отдельный литерал представляет ключевое слово null.
Логические литералы Есть две логических константы - true (истина) и false (ложь):
| 1 2 | Console.WriteLine(true); Console.WriteLine(false); |
Целочисленные литералы Целочисленные литералы представляют положительные и отрицательные целые числа, например, 1, 2, 3, 4, -7, -109. Целочисленные литералы могут быть выражены в десятичной, шестнадцатеричной и двоичной форме.
С целыми числами в десятичной форме все должно быть понятно, так как они используются в повседневной жизни:
| 1 2 3 | Console.WriteLine(-11); Console.WriteLine(5); Console.WriteLine(505); |
Числа в двоичной форме предваряются символами 0b, после которых идет набор из нулей и единиц:
| 1 2 3 | Console.WriteLine(0b11); // 3 Console.WriteLine(0b1011); // 11 Console.WriteLine(0b100001); // 33 |
Для записи числа в шестнадцатеричной форме применяются символы 0x, после которых идет набор символов от 0 до 9 и от A до F, которые собственно представляют число:
| 1 2 3 | Console.WriteLine(0x0A); // 10 Console.WriteLine(0xFF); // 255 Console.WriteLine(0xA1); // 161 |
Вещественные литералы Вещественные литералы представляют вещественные числа. Этот тип литералов имеет две формы. Первая форма - вещественные числа с фиксированной запятой, при которой дробную часть отделяется от целой части точкой. Например:
Также вещественные литералы могут определяться в экспоненциальной форме MEp, где M — мантисса, E - экспонента, которая фактически означает "*10^" (умножить на десять в степени), а p — порядок. Например:
| 1 2 | Console.WriteLine(3.2e3); // по сути равно 3.2 * 103 = 3200 Console.WriteLine(1.2E-1); // равно 1.2 * 10-1 = 0.12 |
Символьные литералы Символьные литералы представляют одиночные символы. Символы заключаются в одинарные кавычки.
Символьные литералы бывают нескольких видов. Прежде всего это обычные символы:
Специальную группу представляют управляющие последовательности.
Управляющая последовательность представляет символ, перед которым ставится обратный слеш. И данная последовательность интерпретируется определенным образом. Наиболее часто используемые последовательности:
'\n' - перевод строки
'\t' - табуляция
'\' - обратный слеш
И если компилятор встретит в тексте последовательность \t, то он будет воспринимать эту последовательность не как слеш и букву t, а как табуляцию - то есть длинный отступ.
Также символы могут определяться в виде шестнадцатеричных кодов, также заключенный в одинарные кавычки.
Еще один способ определения символов представляет использования шестнадцатеричных кодов ASCII. Для этого в одинарных кавычках указываются символы '\x', после которых идет шестнадцатеричный код символа из таблицы ASCII. Коды символов из таблицы ASCII можно посмотреть здесь.
Например, литерал '\x78' представляет символ "x":
| 1 2 | Console.WriteLine('\x78'); // x Console.WriteLine('\x5A'); // Z |
И последний способ определения символьных литералов представляет применение кодов из таблицы символов Unicode. Для этого в одинарных кавычках указываются символы '\u', после которых идет шестнадцатеричный код Unicode. Например, код '\u0411' представляет кириллический символ 'Б':
| 1 2 | Console.WriteLine('\u0420'); // Р Console.WriteLine('\u0421'); // С |
Строковые литералы Строковые литералы представляют строки. Строки заключаются в двойные кавычки:
| 1 2 3 | Console.WriteLine("hello"); Console.WriteLine("фыва"); Console.WriteLine("hello word"); |
Если внутри строки необходимо вывести двойную кавычку, то такая внутренняя кавычка предваряется обратным слешем:
| 1 | Console.WriteLine("Компания \"Рога и копыта\""); |
Также в строках можно использовать управляющие последовательности. Например, последовательность '\n' осуществляет перевод на новую строку:
| 1 | Console.WriteLine("Привет \nмир"); |
При выводе на консоль слово "мир" будет перенесено на новую строку:
Привет
мир
null
null представляет ссылку, которая не указывает ни на какой объект. То есть по сути отсутствие значения.
Типы данных
Как и во многих языках программирования, в C# есть своя система типов данных, которая используется для создания переменных. Тип данных определяет внутреннее представление данных, множество значений, которые может принимать объект, а также допустимые действия, которые можно применять над объектом.
В языке C# есть следующие примитивные типы данных:
bool: хранит значение true или false (логические литералы). Представлен системным типом System.Boolean
| 1 2 | bool alive = true; bool isDead = false; |
byte: хранит целое число от 0 до 255 и занимает 1 байт. Представлен системным типом System.Byte
| 1 2 | byte bit1 = 1; byte bit2 = 102; |
sbyte: хранит целое число от -128 до 127 и занимает 1 байт. Представлен системным типом System.SByte
| 1 2 | sbyte bit1 = -101; sbyte bit2 = 102; |
short: хранит целое число от -32768 до 32767 и занимает 2 байта. Представлен системным типом System.Int16
| 1 2 | short n1 = 1; short n2 = 102; |
ushort: хранит целое число от 0 до 65535 и занимает 2 байта. Представлен системным типом System.UInt16
| 1 2 | ushort n1 = 1; ushort n2 = 102; |
int: хранит целое число от -2147483648 до 2147483647 и занимает 4 байта. Представлен системным типом System.Int32. Все целочисленные литералы по умолчанию представляют значения типа int:
| 1 2 3 | int a = 10; int b = 0b101; // бинарная форма b =5 int c = 0xFF; // шестнадцатеричная форма c = 255 |
uint: хранит целое число от 0 до 4294967295 и занимает 4 байта. Представлен системным типом System.UInt32
| 1 2 3 | uint a = 10; uint b = 0b101; uint c = 0xFF; |
long: хранит целое число от –9 223 372 036 854 775 808 до 9 223 372 036 854 775 807 и занимает 8 байт. Представлен системным типом System.Int64
| 1 2 3 | long a = -10; long b = 0b101; long c = 0xFF; |
ulong: хранит целое число от 0 до 18 446 744 073 709 551 615 и занимает 8 байт. Представлен системным типом System.UInt64
| 1 2 3 | ulong a = 10; ulong b = 0b101; ulong c = 0xFF; |
float: хранит число с плавающей точкой от -3.4*1038 до 3.4*1038 и занимает 4 байта. Представлен системным типом System.Single
double: хранит число с плавающей точкой от ±5.0*10-324 до ±1.7*10308 и занимает 8 байта. Представлен системным типом System.Double
decimal: хранит десятичное дробное число. Если употребляется без десятичной запятой, имеет значение от ±1.0*10-28 до ±7.9228*1028, может хранить 28 знаков после запятой и занимает 16 байт. Представлен системным типом System.Decimal
char: хранит одиночный символ в кодировке Unicode и занимает 2 байта. Представлен системным типом System.Char. Этому типу соответствуют символьные литералы:
| 1 2 3 | char a = 'A'; char b = '\x5A'; char c = '\u0420'; |
string: хранит набор символов Unicode. Представлен системным типом System.String. Этому типу соответствуют строковые литералы.
| 1 2 | string hello = "Hello"; string word = "world"; |
object: может хранить значение любого типа данных и занимает 4 байта на 32-разрядной платформе и 8 байт на 64-разрядной платформе. Представлен системным типом System.Object, который является базовым для всех других типов и классов .NET.
| 1 2 3 | object a = 22; object b = 3.14; object c = "hello code"; |
Например, определим несколько переменных разных типов и выведем их значения на консоль:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | using System; namespace HelloApp { class Program { static void Main(string[] args) { string name = "Tom"; int age = 33; bool isEmployed = false; double weight = 78.65; Console.WriteLine($"Имя: {name}"); Console.WriteLine($"Возраст: {age}"); Console.WriteLine($"Вес: {weight}"); Console.WriteLine($"Работает: {isEmployed}"); } } } |
Для вывода данных на консоль здесь применяется интерполяция: перед строкой ставится знак $ и после этого мы можем вводить в строку в фигурных скобках значения переменных. Консольный вывод программы:
Имя: Tom
Возраст: 33
Вес: 78,65
Работает: False
Использование суффиксов
При присвоении значений надо иметь в виду следующую тонкость: все вещественные литералы рассматриваются как значения типа double. И чтобы указать, что дробное число представляет тип float или тип decimal, необходимо к литералу добавлять суффикс: F/f - для float и M/m - для decimal.
| 1 2 3 4 5 | float a = 3.14F; float b = 30.6f; decimal c = 1005.8M; decimal d = 334.8m; |
Подобным образом все целочисленные литералы рассматриваются как значения типа int. Чтобы явным образом указать, что целочисленный литерал представляет значение типа uint, надо использовать суффикс U/u, для типа long - суффикс L/l, а для типа ulong - суффикс UL/ul:
| 1 2 3 | uint a = 10U; long b = 20L; ulong c = 30UL; |
Использование системных типов
Выше при перечислении всех базовых типов данных для каждого упоминался системный тип. Потому что название встроенного типа по сути представляет собой сокращенное обозначение системного типа. Например, следующие переменные будут эквивалентны по типу:
| 1 2 | int a = 4; System.Int32 b = 4; |
Неявная типизация
Ранее мы явным образом указывали тип переменных, например, int x;. И компилятор при запуске уже знал, что x хранит целочисленное значение.
Однако мы можем использовать и модель неявной типизации:
| 1 2 3 4 5 | var hello = "Hell to World"; var c = 20; Console.WriteLine(c.GetType().ToString()); Console.WriteLine(hello.GetType().ToString()); |
Для неявной типизации вместо названия типа данных используется ключевое слово var. Затем уже при компиляции компилятор сам выводит тип данных исходя из присвоенного значения. В примере выше использовалось выражение Console.WriteLine(c.GetType().ToString());, которое позволяет нам узнать выведенный тип переменной с. Так как по умолчанию все целочисленные значения рассматриваются как значения типа int, то поэтому в итоге переменная c будет иметь тип int или System.Int32
Эти переменные подобны обычным, однако они имеют некоторые ограничения.
Во-первых, мы не можем сначала объявить неявно типизируемую переменную, а затем инициализировать:
| 1 2 3 4 5 6 7 | // этот код работает int a; a = 20; // этот код не работает var c; c= 20; |
Во-вторых, мы не можем указать в качестве значения неявно типизируемой переменной null:
| 1 2 | // этот код не работает var c=null; |
Так как значение null, то компилятор не сможет вывести тип данных.
double или decimal
Из выше перечисленного списка типов данных очевидно, что если мы хотим использовать в программе числа до 256, то для их хранения мы можем использоват переменные типа byte. При использовании больших значений мы можем взять тип short, int, long. То же самое для дробных чисел - для обычных дробных чисел можно взять тип float, для очень больших дробных чисел - тип double. Тип decimal здесь стоит особняком в том плане, что несмотря на большую разрядность по сравнению с типом double, тип double может хранить большее значение. Однако значение decimal может содержать до 28 знаков после запятой, тогда как значение типа double - 15-16 знаков после запятой.
Decimal чаще находит применение в финансовых вычислениях, тогда как double - в математических операциях. Общие различия между этими двумя типами можно выразить следующей таблицей:
| | Decimal | Double |
| Наибольшее значение | ~1028 | ~10308 |
| Наименьшее значение (без учета нуля) | 10-28 | ~10-323 |
| Знаков после запятой | 28 | 15-16 |
| Разрядность | 16 байт | 8 байт |
| Операций в секунду | сотни миллионов | миллиарды |
4