Чтение и запись текстовых файлов
Потоки для работы с текстовыми файлами представляют объекты, для которых не задан режим открытия ios::binary.
Для записи в файл к объекту ofstream или fstream применяется оператор << (как и при выводе на консоль) [5]:
#include <iostream>
#include <fstream>
int main()
{
std::ofstream out; // поток для записи
out.open("D:\\hello.txt"); // окрываем файл для записи
if (in.is_open())
{
out << "Hello World!" << std::endl;
}
Данный способ перезаписывает файл заново. Если надо дозаписать текст в конец файла, то для открытия файла нужно использовать режим ios::app [5]:
std::ofstream out("D:\\hello.txt", std::ios::app);
if (out.is_open())
{
out << "Welcome to CPP" << std::endl;
}
out.close();
Если надо считать всю строку целиком или даже все строки из файла, то лучше использовать встроенную функцию getline(), которая принимает поток для чтения и переменную, в которую надо считать текст [5]:
#include <iostream>
#include <fstream>
#include <string>
int main()
{
std::string line;
std::ifstream in("D:\\hello.txt"); // окрываем файл для чтения
if (in.is_open())
{
while (getline(in, line))
{
std::cout << line << std::endl;
}
}
in.close(); // закрываем файл
std::cout << "End of program" << std::endl;
return 0;
}
Переопределение операторов ввода и вывода
Операторы ввода >> и вывода << прекрасно работают для примитивных типов данных, таких как int или double. В то же время для использования их с объектами классов необходимо переопределять эти операторы.
Оператор <<
Обычно первый параметр оператора << представляет ссылку на неконстантный объект ostream. Данный объект не должен представлять константу, так как запись в поток изменяет его состояние. Причем параметр представляет именно ссылку, так как нельзя копировать объект класса ostream.
Второй параметр оператора определяется как ссылка на константу объекта класса, который надо вывести в поток.
Для совместимости с другими операторами переопределяемый оператор должен возвращать значение параметра ostream.
Также следует отметить, что операторы ввода и вывода не должны быть членами в классе, а определяются вне класса как обычные функции [5].
#include <iostream>
#include <string>
struct Person
{
std::string name;
int age;
};
std::ostream& operator << (std::ostream &os, const Person &p)
{
return os << p.name << " " << p.age;
}
int main()
{
Person tom;
tom.name = "Tom";
tom.age = 31;
std::cout << tom << std::endl;
return 0;
}
Оператор >>
Первый параметр оператора >>, как правило, представляет ссылку на объект istream, с которого осуществляется чтение. Второй параметр представляет ссылку на неконстантный объект, в который надо считать данные.
Обычно в качестве результата операторы возвращают ссылку на поток ввода istream из первого параметра [5].
#include <iostream>
#include <string>
struct Person
{
std::string name;
int age;
};
std::istream& operator >> (std::istream& in, Person& p)
{
in >> p.name >> p.age;
return in;
}
int main()
{
Person bob;
std::cout << "Input name and age: ";
std::cin >> bob;
std::cout << "Name: " << bob.name << "\tAge: " << bob.age << std::endl;
return 0;
}
Runtime_error: Division by zero!
The End...
Глава 2. C#
Язык C# появился в 2000 году, в результате работы большой группы разработчиков компании Microsoft, возглавляемой Андерсом Хейлсбергом (Anders Hejlsberg) для внедрения новых компонентных технологий и решений в области обмена сообщениями и данными, а также создания Internet-приложений (COM+, ASP+, ADO+, SOAP, Biztalk Framework) [2, 4]. В пособии использовались частично материалы их [4].
Переменные
Для хранения данных в программе применяются переменные. Переменная представляет именованную область памяти, в которой хранится значение определенного типа. Переменная имеет тип, имя и значение. Тип определяет, какого рода информацию может хранить переменная.
Перед использованием любую переменную надо определить. Синтаксис определения переменной выглядит следующим образом:
Вначале идет тип переменной, потом ее имя. В качестве имени переменной может выступать любое произвольное название, которое удовлетворяет следующим требованиям:
имя должно содержать не более 255 символов;
имя может содержать любые цифры, буквы и символ подчеркивания, при этом первый символ в имени должен быть буквой или символом подчеркивания;
в имени не должно быть знаков пунктуации и пробелов;
имя не может быть ключевым словом языка C#. Таких слов не так много, и при работе в Visual Studio среда разработки подсвечивает ключевые слова синим цветом.
Хотя имя переменой может быть любым, но следует давать переменным описательные имена, которые будут говорить об и предназначении.
Например, определим простейшую переменную:
string name;
В данном случае определена переменная name, которая имеет тип string, то есть переменная представляет строку. Поскольку определение переменной представляет собой инструкцию, то после него ставится точка с запятой.
При этом следует учитывать, что C# является регистрозависимым языком, поэтому следующие два определения переменных будут представлять две разные переменные:
string name;
string Name;
name = "Tom";
Так как переменная name представляет тип string, то есть строку, то мы можем присвоить ей строку в двойных кавычках. Причем переменной можно присвоить только то значение, которое соответствует ее типу.
В дальнейшем с помощью имени переменной мы сможем обращаться к той области памяти, в которой хранится ее значение.
Также мы можем сразу при определении присвоить переменной значение.
Данный примем называется инициализацией:
string name = "Tom";
Отличительной чертой переменных является то, что в программе можно многократно менять их значение. Например, создадим небольшую программу, в которой определим переменную, поменяем ее значение и выведем его на консоль:
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();
}
}
}
Численные литералы
Литералы представляют неизменяемые значения (иногда их еще называют константами). Литералы можно передавать переменным в качестве значения. Литералы бывают логическими, целочисленными, вещественными, символьными и строчными. И отдельный литерал представляет ключевое слово null.
Логические литералы
Есть две логических константы - true (истина) и false (ложь):
Console.WriteLine(true);
Console.WriteLine(false);
Целочисленные литералы представляют положительные и отрицательные целые числа, например, 1, 2, 3, 4, -7, -109. Целочисленные литералы могут быть выражены в десятичной, шестнадцатеричной и двоичной форме.
Числа в двоичной форме предваряются символами 0b, после которых идет набор из нулей и единиц:
Console.WriteLine(0b11); // 3
Console.WriteLine(0b1011); // 11
Console.WriteLine(0b100001); // 33
Для записи числа в шестнадцатеричной форме применяются символы 0x, после которых идет набор символов от 0 до 9 и от A до F, которые собственно представляют число:
Console.WriteLine(0x0A); // 10
Console.WriteLine(0xFF); // 255
Console.WriteLine(0xA1); // 16
Вещественные литералы
Вещественные литералы представляют вещественные числа. Этот тип литералов имеет две формы. Первая форма - вещественные числа с фиксированной запятой, при которой дробную часть отделяется от целой части точкой.
Также вещественные литералы могут определяться в экспоненциальной форме MEp, где M — мантисса, E - экспонента, которая фактически означает "*10^" (умножить на десять в степени), а p — порядок.
Символьные литералы
Символьные литералы представляют одиночные символы. Символы заключаются в одинарные кавычки.
Символьные литералы бывают нескольких видов. Прежде всего это обычные символы: ‘2’, ‘А’.
Специальную группу представляют управляющие последовательности Управляющая последовательность представляет символ, перед которым ставится обратный слеш. И данная последовательность интерпретируется определенным образом. Наиболее часто используемые последовательности:
'\n' - перевод строки;
'\t' – табуляция;
'\' - обратный слеш.
И если компилятор встретит в тексте последовательность \t, то он будет воспринимать эту последовательность не как слеш и букву t, а как табуляцию - то есть длинный отступ.
Также символы могут определяться в виде шестнадцатеричных кодов, также заключенный в одинарные кавычки.
Еще один способ определения символов представляет использования шестнадцатеричных кодов ASCII. Для этого в одинарных кавычках указываются символы '\x', после которых идет шестнадцатеричный код символа из таблицы ASCII.
Например, литерал '\x78' представляет символ "x":
Console.WriteLine('\x78'); // x
Console.WriteLine('\x5A'); // Z
И последний способ определения символьных литералов представляет применение кодов из таблицы символов Unicode. Для этого в одинарных кавычках указываются символы '\u', после которых идет шестнадцатеричный код Unicode. Например, код '\u0411' представляет кириллический символ 'Б':
Console.WriteLine('\u0420'); // Р
Console.WriteLine('\u0421'); // С
Строковые литералы
Строковые литералы представляют строки. Строки заключаются в двойные кавычки:
Console.WriteLine("hello");
Console.WriteLine("фыва");
Console.WriteLine("hello word");
Если внутри строки необходимо вывести двойную кавычку, то такая внутренняя кавычка предваряется обратным слешем:
Console.WriteLine("Компания \"Рога и копыта\"");
Также в строках можно использовать управляющие последовательности. Например, последовательность '\n' осуществляет перевод на новую строку:
Console.WriteLine("Привет \nмир");
При выводе на консоль слово "мир" будет перенесено на новую строку: null представляет ссылку, которая не указывает ни на какой объект. То есть по сути отсутствие значения.
Типы данных
Как и во многих языках программирования, в C# есть своя система типов данных, которая используется для создания переменных. Тип данных определяет внутреннее представление данных, множество значений, которые может принимать объект, а также допустимые действия, которые можно применять над объектом.
В языке C# есть следующие примитивные типы данных:
bool: хранит логическое значение.
byte: хранит целое число от 0 до 255 и занимает 1 байт.
sbyte: хранит целое число от -128 до 127 и занимает 1 байт
short: хранит целое число от -32768 до 32767 и занимает 2 байта.
ushort: хранит целое число от 0 до 65535 и занимает 2 байта.
int: хранит целое число от -2147483648 до 2147483647 и занимает 4 байта.
uint: хранит целое число от 0 до 4294967295 и занимает 4 байта.
long: хранит целое число от –9 223 372 036 854 775 808 до 9 223 372 036 854 775 807 и занимает 8 байт.
ulong: хранит целое число от 0 до 18 446 744 073 709 551 615 и занимает 8 байт.
float: хранит число с плавающей точкой от -3.4*1038 до 3.4*1038 и занимает 4 байта
double: хранит число с плавающей точкой от ±5.0*10-324 до ±1.7*10308 и занимает 8 байта.
decimal: хранит десятичное дробное число. Если употребляется без десятичной запятой, имеет значение от 0 до +/–79 228 162 514 264 337 593 543 950 335; если с запятой, то от 0 до +/–7,9228162514264337593543950335 с 28 разрядами после запятой и занимает 16 байт.
char: хранит одиночный символ в кодировке Unicode и занимает 2 байта. string: хранит набор символов Unicode.
object: может хранить значение любого типа данных и занимает 4 байта на 32-разрядной платформе и 8 байт на 64-разрядной платформе.
Например, определим несколько переменных разных типов и выведем их значения на консоль:
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}");
}
}
}
Для вывода данных на консоль здесь применяется интерполяция: перед строкой ставится знак $ и после этого мы можем вводить в строку в фигурных скобках значения переменных.
Использование суффиксов
При присвоении значений надо иметь в виду следующую тонкость: все вещественные литералы рассматриваются как значения типа double. И чтобы указать, что дробное число представляет тип float или тип decimal, необходимо к литералу добавлять суффикс: F/f - для float и M/m - для decimal.
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:
uint a = 10U;
long b = 20L;
ulong c = 30UL;
Операции
Арифметические операции
Операции представляют определенные действия над операндами - участниками операции. В качестве операнда может выступать переменной или какое-либо значение (например, число). Операции бывают унарными (выполняются над одним операндом), бинарными - над двумя операндами и тернарными - выполняются над тремя операндами. Рассмотрим все виды операций.
Бинарные арифметические операции:
+ (операция сложения двух чисел):
int x = 10;
int z = x + 12; // 22
- (операция вычитания двух чисел):
int x = 10;
int z = x - 6; // 4
* (операция умножения двух чисел):
int x = 10;
int z = x * 5; // 50
/ (операция деления двух чисел):
int x = 10;
int z = x / 5; // 2
double a = 10;
double b = 3;
double c = a / b; // 3.33333333
При делении стоит учитывать, что если оба операнда представляют целые числа, то результат также будет округляться до целого числа:
double z = 10 / 4; //результат равен 2
Несмотря на то, что результат операции в итоге помещается в переменную типа double, которая позволяет сохранить дробную часть, но в самой операции участвуют два литерала, которые по умолчанию рассматриваются как объекты int, то есть целые числа, и результат то же будет целочисленный.
Для выхода из этой ситуации необходимо определять литералы или переменные, участвующие в операции, именно как типы double или float:
double z = 10.0 / 4.0; //результат равен 2.5
% (операция получение остатка от целочисленного деления двух чисел):
double x = 10.0;
double z = x % 4.0; //результат равен 2
++ (операция инкремента)
Инкремент бывает префиксным: ++x - сначала значение переменной x увеличивается на 1, а потом ее значение возвращается в качестве результата операции.
И также существует постфиксный инкремент: x++ - сначала значение переменной x возвращается в качестве результата операции, а затем к нему прибавляется 1.
int x1 = 5;
int z1 = ++x1; // z1=6; x1=6
Console.WriteLine($"{x1} - {z1}");
int x2 = 5;
int z2 = x2++; // z2=5; x2=6
Console.WriteLine($"{x2} - {z2}");
-- (операция декремента или уменьшения значения на единицу). Также существует префиксная форма декремента (--x) и постфиксная (x--).
int x1 = 5;
int z1 = --x1; // z1=4; x1=4
Console.WriteLine($"{x1} - {z1}");
int x2 = 5;
int z2 = x2--; // z2=5; x2=4
Console.WriteLine($"{x2} - {z2}");
При выполнении сразу нескольких арифметических операций следует учитывать порядок их выполнения. Приоритет операций от наивысшего к низшему:
Инкремент, декремент
Умножение, деление, получение остатка
Сложение, вычитание
Для изменения порядка следования операций применяются скобки.
Рассмотрим набор операций:
nt a = 3;
int b = 5;
int c = 40;
int d = c---b*a; // a=3 b=5 c=39 d=25
Console.WriteLine($"a={a} b={b} c={c} d={d}");
Здесь мы имеем дело с тремя операциями: декремент, вычитание и умножение. Сначала выполняется декремент переменной c, затем умножение b*a, и в конце вычитание. То есть фактически набор операций выглядел так:
int d = (c--)-(b*a);
Но с помощью скобок мы могли бы изменить порядок операций, например, следующим образом:
int a = 3;
int b = 5;
int c = 40;
int d = (c-(--b))*a; // a=3 b=4 c=40 d=108
Console.WriteLine($"a={a} b={b} c={c} d={d}");
Поразрядные операции
Особый класс операций представляют поразрядные операции. Они выполняются над отдельными разрядами числа. В этом плане числа рассматриваются в двоичном представлении, например, 2 в двоичном представлении 10 и имеет два разряда, число 7 - 111 и имеет три разряда.
&(логическое умножение)
Умножение производится поразрядно, и если у обоих операндов значения разрядов равно 1, то операция возвращает 1, иначе возвращается число 0. Например:
int x1 = 2; //010
int y1 = 5;//101
Console.WriteLine(x1&y1); // выведет 0
int x2 = 4; //100
int y2 = 5; //101
Console.WriteLine(x2 & y2); // выведет 4
В первом случае у нас два числа 2 и 5. 2 в двоичном виде представляет число 010, а 5 - 101. Поразрядно умножим числа (0*1, 1*0, 0*1) и в итоге получим 000.
Во втором случае у нас вместо двойки число 4, у которого в первом разряде 1, так же как и у числа 5, поэтому в итоге получим (1*1, 0*0, 0 *1) = 100, то есть число 4 в десятичном формате.
| (логическое сложение)
Похоже на логическое умножение, операция также производится по двоичным разрядам, но теперь возвращается единица, если хотя бы у одного числа в данном разряде имеется единица. Например:
int x1 = 2; //010
int y1 = 5;//101
Console.WriteLine(x1|y1); // выведет 7 - 111
int x2 = 4; //100
int y2 = 5;//101
Console.WriteLine(x2 | y2); // выведет 5 – 101
^ (логическое исключающее ИЛИ)
Также эту операцию называют XOR, нередко ее применяют для простого шифрования:
int x = 45; // Значение, которое надо зашифровать - в двоичной форме 101101
int key = 102; //Пусть это будет ключ - в двоичной форме 1100110
int encrypt = x ^ key; //Результатом будет число 1001011 или 75
Console.WriteLine("Зашифрованное число: " +encrypt);
int decrypt = encrypt ^ key; // Результатом будет исходное число 45
Console.WriteLine("Расшифрованное число: " + decrypt);
Здесь опять же производятся поразрядные операции. Если у нас значения текущего разряда у обоих чисел разные, то возвращается 1, иначе возвращается 0. Таким образом, мы получаем из 9^5 в качестве результата число 12. И чтобы расшифровать число, мы применяем ту же операцию к результату.
~ (логическое отрицание или инверсия)
Еще одна поразрядная операция, которая инвертирует все разряды: если значение разряда равно 1, то оно становится равным нулю, и наоборот.
int x = 9;
Console.WriteLine(~x);
Операции сдвига
Операции сдвига также производятся над разрядами чисел. Сдвиг может происходить вправо и влево.
x<<y - сдвигает число x влево на y разрядов. Например, 4<<1 сдвигает число 4 (которое в двоичном представлении 100) на один разряд влево, то есть в итоге получается 1000 или число 8 в десятичном представлении.
x>>y - сдвигает число x вправо на y разрядов. Например, 16>>1 сдвигает число 16 (которое в двоичном представлении 10000) на один разряд вправо, то есть в итоге получается 1000 или число 8 в десятичном представлении.
Таким образом, если исходное число, которое надо сдвинуть в ту или другую строну, делится на два, то фактически получается умножение или деление на два. Поэтому подобную операцию можно использовать вместо непосредственного умножения или деления на два.
Операции присваивания
Операции присвоения устанавливают значение. В операциях присвоения участвуют два операнда, причем левый операнд может представлять только модифицируемое именованное выражение, например, переменную
В C# имеется базовая операция присваивания =, которая присвоивает значение правого операнда левому операнду: int number = 23;
Здесь переменной number присваивается число 23. Переменная number представляет левый операнд, которому присваивается значение правого операнда, то есть числа 23.
Также можно выполнять множественно присвоение сразу нескольких переменным одновременно:
int a, b, c;
a = b = c = 34;
Операции присвоения имеют низкий приоритет. И вначале будет вычисляться значение правого операнда и только потом будет идти присвоение этого значения левому операнду. Например:
int a, b, c;
a = b = c = 34 * 2 / 4; // 17
Сначала будет вычисляться выражение 34 * 2 / 4, затем полученное значение будет присвоено переменным.
Кроме базовой операции присвоения в C# есть еще ряд операций:
+=: (присваивание после сложения). Присваивает левому операнду сумму левого и правого операндов: выражение A += B равнозначно выражению A = A + B
-=: (присваивание после вычитания). Присваивает левому операнду разность левого и правого операндов: A -= B эквивалентно A = A - B
*=: (присваивание после умножения). Присваивает левому операнду произведение левого и правого операндов: A *= B эквивалентно A = A * B
/=: (присваивание после деления). Присваивает левому операнду частное левого и правого операндов: A /= B эквивалентно A = A / B
%=: (присваивание после деления по модулю). Присваивает левому операнду остаток от целочисленного деления левого операнда на правый: A %= B эквивалентно A = A % B
&=: (присваивание после поразрядной конъюнкции). Присваивает левому операнду результат поразрядной конъюнкции его битового представления с битовым представлением правого операнда: A &= B эквивалентно A = A & B
|=: (присваивание после поразрядной дизъюнкции). Присваивает левому операнду результат поразрядной дизъюнкции его битового представления с битовым представлением правого операнда: A |= B эквивалентно A = A | B
^=: (присваивание после операции исключающего ИЛИ). Присваивает левому операнду результат операции исключающего ИЛИ его битового представления с битовым представлением правого операнда: A ^= B эквивалентно A = A ^ B
<<=: (присваивание после сдвига разрядов влево). Присваивает левому операнду результат сдвига его битового представления влево на определенное количество разрядов, равное значению правого операнда: A <<= B эквивалентно A = A << B
>>=: (присваивание после сдвига разрядов вправо). Присваивает левому операнду результат сдвига его битового представления вправо на определенное количество разрядов, равное значению правого операнда: A >>= B эквивалентно A = A >> B
Применение операций присвоения:
int a = 10;
a += 10; // 20
a -= 4; // 16
a *= 2; // 32
a /= 8; // 4
a <<= 4; // 64
a >>= 2; // 16
2.2.4. Преобразование базовых типов данных
Применим операцию сложения к данным разных типов:
byte a = 4;
int b = a + 70;
Результатом операции является число 74, как и ожидается.
Но теперь попробуем применить сложение к двум объектам типа byte:
byte a = 4;
byte b = a + 70; // ошибка
Здесь поменялся только тип переменной, которая получает результат сложения - с int на byte. Однако при попытке скомпилировать программу мы получим ошибку на этапе компиляции. И если мы работаем в Visual Studio, среда подчеркнет вторую строку красной волнистой линией, указывая, что в ней ошибка.
При операциях мы должны учитывать диапазон значений, которые может хранить тот или иной тип. Но в данном случае число 74, которое мы ожидаем получить, вполне укладывается в диапазон значений типа byte, тем не менее мы получаем ошибку.
Дело в том, что операция сложения (да и вычитания) возвращает значение типа int, если в операции участвуют целочисленные типы данных с разрядностью меньше или равно int (то есть типы byte, short, int). Поэтому результатом операции a + 70 будет объект, который имеет длину в памяти 4 байта. Затем этот объект мы пытаемся присвоить переменной b, которая имеет тип byte и в памяти занимает 1 байт.
И чтобы выйти из этой ситуации, необходимо применить операцию преобразования типов:
byte a = 4;
byte b = (byte)(a + 70);
Операция преобразования типов предполагает указание в скобках того типа, к которому надо преобразовать значение.
Преобразования могут сужающие (narrowing) и расширяющие (widening). Расширяющие преобразования расширяют размер объекта в памяти.
byte a = 4;
int b = a + 70;
В операции сложения в данном случае участвуют два операнда типа int. Но один из операндов - переменная a представляет тип byte. Поэтому компилятор будет расширять переданное значение типа byte до типа int, то есть в памяти это значение получит 4 байта, а не 1.
Сужающие преобразования, наоборот, сужают значение до типа меньшей разядности.
byte a = 4;
byte b = (byte)(a + 70);
Полученное в результате операции значение типа int сужается до объекта типа byte и в памяти в место 4 байт получает только 1. Несмотря на то, что сумма (число 10) укладывается в диапазон типа byte, Visual Studio все равно отобразит ошибку.
Явные и неявные преобразования
Если в случае с расширяющими преобразованиями компилятор за нас выполнял все преобразования данных, то есть преобразования были неявными (implicit conversion), то при явных преобразованиях (explicit conversion) мы сами должны применить операцию преобразования (операция ()). Суть операции преобразования типов состоит в том, что перед значением указывается в скобках тип, к которому надо привести данное значение:
int a = 4;
int b = 6;
byte c = (byte)(a+b);
Условные выражения
Отдельный набор операций представляет условные выражения. Такие операции возвращают логическое значение, то есть значение типа bool: true, если выражение истинно, и false, если выражение ложно. К подобным операциям относятся операции сравнения и логические операции.
Операции сравнения
В операциях сравнения сравниваются два операнда и возвращается значение типа bool - true, если выражение верно, и false, если выражение неверно.
== (Cравнивает два операнда на равенство). Если они равны, то операция возвращает true, если не равны, то возвращается false:
int a = 10;
int b = 4;
bool c = a == b; // false
!= (сравнивает два операнда и возвращает true, если операнды не равны, и false, если они равны).
int a = 10;
int b = 4;
bool c = a != b; // true
bool d = a!=10; // false
< (операция "меньше чем"). Возвращает true, если первый операнд меньше второго, и false, если первый операнд больше второго:
int a = 10;
int b = 4;
bool c = a < b; // false
> (операция "больше чем"). Сравнивает два операнда и возвращает true, если первый операнд больше второго, иначе возвращает false:
int a = 10;
int b = 4;
bool c = a > b; // true
bool d = a > 25; // false
<= (операция "меньше или равно"). Сравнивает два операнда и возвращает true, если первый операнд меньше или равен второму. Иначе возвращает false.
int a = 10;
int b = 4;
bool c = a <= b; // false
bool d = a <= 25; // true
>= (операция "больше или равно"). Сравнивает два операнда и возвращает true, если первый операнд больше или равен второму, иначе возвращается false:
int a = 10;
int b = 4;
bool c = a >= b; // true
bool d = a >= 25; // false
Логические операции
| (операция логического сложения или логическое ИЛИ). Возвращает true, если хотя бы один из операндов возвращает true.
bool x1 = (5 > 6) | (4 < 6);
// 5 > 6 - false, 4 < 6 - true, поэтому возвращается true
bool x2 = (5 > 6) | (4 > 6);
// 5 > 6 - false, 4 > 6 - false, поэтому возвращается false
& (операция логического умножения или логическое И). Возвращает true, если оба операнда одновременно равны true.
bool x1 = (5 > 6) & (4 < 6);
// 5 > 6 - false, 4 < 6 - true, поэтому возвращается false
bool x2 = (5 < 6) & (4 < 6);
// 5 < 6 - true, 4 < 6 - true, поэтому возвращается true
|| (операция логического сложения). Возвращает true, если хотя бы один из операндов возвращает true.
bool x1 = (5 > 6) || (4 < 6);
// 5 > 6 - false, 4 < 6 - true, поэтому возвращается true
bool x2 = (5 > 6) || (4 > 6);
// 5 > 6 - false, 4 > 6 - false, поэтому возвращается false
&& (операция логического умножения). Возвращает true, если оба операнда одновременно равны true.
bool x1 = (5 > 6) && (4 < 6);
// 5 > 6 - false, 4 < 6 - true, поэтому возвращается false
bool x2 = (5 < 6) && (4 < 6);
// 5 > 6 - true, 4 > 6 - true, поэтому возвращается true
! (операция логического отрицания.) Производится над одним операндом и возвращает true, если операнд равен false. Если операнд равен true, то операция возвращает false:
bool a = true;
bool b = !a; // false
^ (операция исключающего ИЛИ). Возвращает true, если либо первый, либо второй операнд (но не одновременно) равны true, иначе возвращает false:
bool x5 = (5 > 6) ^ (4 < 6);
// 5 > 6 - false, 4 < 6 - true, поэтому возвращается true
bool x6 = (50 > 6) ^ (4 / 2 < 3);
// 50 > 6 - true, 4/2 < 3 - true, поэтому возвращается false
Здесь две пары операций | и || (а также & и &&) выполняют похожие действия, однако же они не равнозначны.
В выражении z=x|y; будут вычисляться оба значения - x и y.
В выражении же z=x||y; сначала будет вычисляться значение x, и если оно равно true, то вычисление значения y уже смысла не имеет, так как у нас в любом случае уже z будет равно true. Значение y будет вычисляться только в том случае, если x равно false
То же самое касается пары операций &/&&. В выражении z=x&y; будут вычисляться оба значения - x и y.
В выражении же z=x&&y; сначала будет вычисляться значение x, и если оно равно false, то вычисление значения y уже смысла не имеет, так как у нас в любом случае уже z будет равно false. Значение y будет вычисляться только в том случае, если x равно true
Поэтому операции || и && более удобны в вычислениях, так как позволяют сократить время на вычисление значения выражения, и тем самым повышают производительность. А операции | и & больше подходят для выполнения поразрядных операций над числами.
Условные конструкции
Условные конструкции - один из базовых компонентов многих языков программирования, которые направляют работу программы по одному из путей в зависимости от определенных условий.
Конструкция if/else
Конструкция if/else проверяет истинность некоторого условия и в зависимости от результатов проверки выполняет определенный код:
int num1 = 8;
int num2 = 6;
if(num1 > num2)
{ Console.WriteLine($"Число {num1} больше числа {num2}"); }
После ключевого слова if ставится условие. И если это условие выполняется, то срабатывает код, который помещен далее в блоке if после фигурных скобок. В качестве условий выступают ранее рассмотренные операции сравнения.
В данном случае у нас первое число больше второго, поэтому выражение num1 > num2 истинно и возвращает true, следовательно, управление переходит к строке
Console.WriteLine("Число {num1} больше числа {num2}");
Но что, если мы захотим, чтобы при несоблюдении условия также выполнялись какие-либо действия? В этом случае мы можем добавить блок else:
int num1 = 8;
int num2 = 6;
if(num1 > num2)
{ Console.WriteLine($"Число {num1} больше числа {num2}");}
else
{ Console.WriteLine($"Число {num1} меньше числа {num2}");}
Но при сравнении чисел мы можем насчитать три состояния: первое число больше второго, первое число меньше второго и числа равны. Используя конструкцию else if, мы можем обрабатывать дополнительные условия:
int num1 = 8;
int num2 = 6;
if(num1 > num2)
{ Console.WriteLine($"Число {num1} больше числа {num2}"); }
else if (num1 < num2)
{ Console.WriteLine($"Число {num1} меньше числа {num2}");}
else
{ Console.WriteLine("Число num1 равно числу num2");}
Также мы можем соединить сразу несколько условий, используя логические операторы:
int num1 = 8;
int num2 = 6;
if(num1 > num2 && num1==8)
{ Console.WriteLine($"Число {num1} больше числа {num2}");}
В данном случае блок if будет выполняться, если num1 > num2 равно true и num1==8 равно true.
Конструкция switch
Конструкция switch/case аналогична конструкции if/else, так как позволяет обработать сразу несколько условий:
Console.WriteLine("Нажмите Y или N");
string selection = Console.ReadLine();
switch (selection)
{
case "Y":
Console.WriteLine("Вы нажали букву Y");
break;
case "N":
Console.WriteLine("Вы нажали букву N");
break;
default:
Console.WriteLine("Вы нажали неизвестную букву");
break;
}
После ключевого слова switch в скобках идет сравниваемое выражение. Значение этого выражения последовательно сравнивается со значениями, помещенными после оператора сase. И если совпадение будет найдено, то будет выполняться определенный блок сase.
В конце каждого блока сase должен ставиться один из операторов перехода: break, goto case, return или throw. Как правило, используется оператор break. При его применении другие блоки case выполняться не будут.
Однако если мы хотим, чтобы, наоборот, после выполнения текущего блока case выполнялся другой блок case, то мы можем использовать вместо break оператор goto case:
int number = 1;
switch (number)
{
case 1:
Console.WriteLine("case 1");
goto case 5; // переход к case 5
case 3:
Console.WriteLine("case 3");
break;
case 5:
Console.WriteLine("case 5");
break;
default:
Console.WriteLine("default");
break;
}
Если мы хотим также обработать ситуацию, когда совпадения не будет найдено, то можно добавить блок default, как в примере выше.
Применение оператора return позволит выйти не только из блока case, но и из вызывающего метода. То есть, если в методе Main после конструкции switch..case, в которой используется оператор return, идут какие-либо операторы и выражения, то они выполняться не будут, а метод Main завершит работу.
Оператор throw применяется для выброса ошибок
Тернарная операция
Тернарную операция имеет следующий синтаксис: [первый операнд - условие] ? [второй операнд] : [третий операнд]. Здесь сразу три операнда. В зависимости от условия тернарная операция возвращает второй или третий операнд: если условие равно true, то возвращается второй операнд; если условие равно false, то третий. Например:
int x=3;
int y=2;
Console.WriteLine("Нажмите + или -");
string selection = Console.ReadLine();
int z = selection=="+"? (x+y) : (x-y);
Console.WriteLine(z);
Здесь результатом тернарной операции является переменная z. Если мы выше вводим "+", то z будет равно второму операнду - (x+y). Иначе z будет равно третьему операнду.
Циклы
Циклы являются управляющими конструкциями, позволяя в зависимости от определенных условий выполнять некоторое действие множество раз. В C# имеются следующие виды циклов:
for
foreach
while
do...while
Цикл for
Цикл for имеет следующее формальное определение:
for ([инициализация счетчика]; [условие]; [изменение счетчика])
{
// действия
}
Рассмотрим стандартный цикл for:
for (int i = 0; i < 9; i++)
{ Console.WriteLine($"Квадрат числа {i} равен {i*i}"); }
Первая часть объявления цикла - int i = 0 - создает и инициализирует счетчик i. Счетчик необязательно должен представлять тип int. Это может быть и другой числовой тип, например, float. И перед выполнением цикла его значение будет равно 0. В данном случае это то же самое, что и объявление переменной.
Вторая часть - условие, при котором будет выполняться цикл. Пока условное выражение возвращает true, будет выполняться цикл. В данном случае цикл будет выполняться, пока счетчик i не достигнет 9.
И третья часть - приращение счетчика на единицу. Опять же нам необязательно увеличивать на единицу. Можно уменьшать: i--.
В итоге блок цикла сработает 9 раз, пока значение i не станет равным 9. И каждый раз это значение будет увеличиваться на 1.
Нам необязательно указывать все условия при объявлении цикла. Например, мы можем написать так:
int i = 0;
for (; ;)
{ Console.WriteLine($"Квадрат числа {++i} равен {i * i}"); }
Формально определение цикла осталось тем же, только теперь блоки в определении у нас пустые: for (; i <;). У нас нет инициализированной переменной-счетчика, нет условия, поэтому цикл будет работать вечно - бесконечный цикл.
Мы также можем опустить ряд блоков:
int i = 0;
for (; i<9;)
{ Console.WriteLine($"Квадрат числа {++i} равен {i * i}"); }
Этот пример по сути эквивалентен первому примеру: у нас также есть счетчик, только создан он вне цикла. У нас есть условие выполнения цикла. И есть пр<