Условный оператор if else.

Позволяет выполнять код при выполнении определенного условия. Конструкция else оператора позволяет определить действия, которые нужно выполнить, если результатом вычисления выражения в if будет false.

Форма сокращенного оператора if:

if (Условное Выражение) Действие; else Действие;

Пример:

if (a > b && a > c) {

message = "Самое большое значение из трех: " + a;

} else if (b > c) {

message = "Самое большое значение из трех: " + b;

} else {

message = "Самое большое значение из трех: " + c;

}

Условный оператор switch

Оператор switch является расширенным оператором ветвления, который позволяет в зависимости от значения выражения перейти к выполнению определенного кода.

В операторе switch вы указываете выражение, возвращающее некоторое значение и один или несколько фрагментов кода, которые будут выполняться в зависимости от результата выражения. Он аналогичен применению нескольких операторов if/else, но если в последних вы можете указать несколько условий (возможно, не связанных между собой), то в операторе switch содержится лишь один условный оператор, за которым следуют блоки, которые нужно выполнять. Вот его синтаксис:

switch (выражение){

case константа 1:

//блок операторов первой константы;

break;

[сase константа 2:

//блок операторов второй константы;

break;

]

...

[default:

//операторы, выполняющиеся в том случае,

//когда значение выражения не совпало

//ни с одним из перечисленных значений констант;

break;

]

}

 

Выражение-переключатель должно иметь тип sbyte, byte, short, ushort, int, uint, long, ulong, char string или же должно быть явно преобразовано в один из этих типов.

Пример:

//Название фигуры на плоскости полученной объединением n точек

if (n > 0) {

switch(n) {

case 1:

message = "Это точка";

break;

case 2:

message = "Это линия";

break;

case 3:

message = "Это треугольник";

break;

default:

message = "Это многоугольник";

break;

}

} else {

message = "Кол-во точек должно быть больше 0";

}

В отличие от языка С++, оператор switch языка С# не поддерживает передачу управления вниз (fall-through). Передача управления вниз означает, что при отсутствии break будет выполняться следующий оператор case, содержащийся в switch. Передача управления вниз нужна, когда у вас две case-метки и вторая метка представляет операцию, которая должна выполняться в любом случае. В С# это реализовано с помощью объединение case-меток, в этом случае нужно расположить case-метки одну за другой, например:

switch(host) {

case "127.0.0.1":

case "localhost":

message = "Это локальный сервер";

break;

default:

message = "Это удаленный сервер";

break;

}

ЛЕКЦИЯ 4

Циклы

Цикл - это–блок команд, который исполняется многократно заданное количество раз или до тех пор, пока не будет выполнено условие выхода из цикла.

С циклами тесно связаны такие понятия как тело цикла, итерация, счетчик, условие выхода.

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

Выражение определяющее, будет в очередной раз выполняться итерация, или цикл завершится, называется условием выхода или условием окончания цикла (либо условие продолжения в зависимости от того, как интерпретируется его истинность — как признак необходимости завершения или продолжения цикла).

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

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

• Циклы с предусловиями (сначала происходит проверка условия и только потом выполняется тело цикла)

• Циклы с постусловием (сначала выполняется тело цикла и только потом происходит проверка условия)

В C# к уже знакомым вам циклам for, while и do-while добавляется еще один: foreach – для работы с членами массивов, списков, коллекций.

FOR. Цикл с предусловием. Смысловое предназначение - выполнять последовательность действий заданное количество раз.

Задается следующим образом:

for (инициализация_переменной; условие; выражение)

{

Действие;

}

Пример:

//поиск суммы простых чисел 1..20

for (int i = 1; i < 20; i++) {

bool flag = false; // признак наличия чисел меньше i, на которые оно делится без остатка

for (int j = 2; j <= i / 2; j++) { // проверка всех чисел 2..i/2 (числа больше i/2 не могут быть делителем)

if (i % j == 0) { // если j является делителем i, значит i != простое число

flag = true;

break;

}

}

if (!flag) { // делитель не найден, i простое число

sum += i;

}

}

While. Цикл с предусловием. Сначала выполняется проверка, если условие - истина, выполняется тело цикла. В результате проверки может оказаться, что тело такого цикла может ни разу не выполниться, используются в тех случаях, когда количество итераций заранее не известно. Задается следующим образом:

while (выражение)

{

Действие;

}

Пример:

//поиск кол-ва бит, необходимых для записи целого беззнакового числа

//максимально число, которое можно записать в n бит = 2^(n-1) - 1

int bits = 1;

while (2 << (bits - 1) < number) {

bits++;

}

do..while.Цикл с постусловием, т.е. тело такого цикла выполняется, по меньшей мере, один раз.

Задается следующим образом:

do

{

Действие;

}while(выражение);

Пример:

//поиск числа фибоначчи, большего 100

int fibonachi1 = 1;

int fibonachi2 = 1;

do {

int next = fibonachi1 + fibonachi2;

fibonachi1 = fibonachi2;

fibonachi2 = next;

} while (fibonachi2 < 100);

Новым видом цикла, которого нет в С++, является цикл foreach, удобный при работе с массивами, коллекциями и другими подобными контейнерами данных. Его синтаксис:

foreach(тип_идентификатор in контейнер)

{

Действие;

}

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

Серьезным недостатком циклов foreach в языке C# является то, что цикл работает только на чтение, но не на запись элементов. Так что наполнять контейнер элементами приходится с помощью других операторов цикла.

Пример:

//поиск максимального и минимального эл-тов

int max, min;

max = min = data[0];

foreach(int item in data) {

if (item > max) { // проверка на максимальность

max = item;

continue; // item больше всех предыдущих элементов data, незачем его проверять на минимальность

}

if (item < min) { //проверка на минимальность

min = item;

}

}

Goto - это оператор безусловного перехода. Может иметь одну из следующих форм:

• goto ‘идентификатор’,

• goto case ‘выражение-константа’,

• goto default.

При применении первой формы нужно указать метку в том месте программы куда нужно осуществить переход. Метка указывается просто - в нужном месте программы пишется имя метки с двоеточием на конце:

Exit: return 0;

Чтобы перейти на эту метку:

goto Exit;

Еще goto можно применять если нужно перейти к определенной ветви в блоке switch, это будет вторая и третья форма goto:

switch (key)

{

case 0:

//do smth

break;

case 1:

//do smth

goto case 2;

case 2:

//do smth

goto default;

default:

//do smth

break;

}

Массивы

Одномерные массивы.

Массив в С#, как и в С – это группа ячеек памяти, имеющих одно имя и тип данных. Программа может обращаться к любому элементу массива путем указания имени массива, за которым в квадратных скобках следует индекс (значение, указывающее на местоположение элемента в рамках массива) элемента. Если в качестве индекса программа использует целочисленное выражение, тогда сначала это выражение вычисляется для определения индекса

Синтаксис объявления одномерного массива следующий:

<Тип элементов массива>[] <имя массива>;

Мы видим, что данный синтаксис отличается от привычного вам «сишного». Квадратные скобки перекочевали от имени переменной – к имени типа, явно подчеркивая таким образом, что переменная имеет «массивный тип».

Примеры объявления одномерных массивов:

int[] myArr; // Объявление ссылки на массив целых чисел

string [] myStrings; // Объявление ссылки на массив строк

Все массивы в С# унаследованы от класса System.Array, который, в свою очередь, наследуется от класса System.Object. Это наследование означает, что все массивы являются объектами. «Объектность» массивов с одной стороны дает ряд преимуществ, с другой – имеет ряд недостатков. К преимуществам можно отнести: полученный в наследство немалый набор методов по работе с массивами, контроль выхода за границы массива и др.; к недостаткам – некоторое снижение быстродействия при работе с массивом вследствие того, что он размещается в «куче».

Учитывая, что массивы – это ссылочные типы, в приведенном выше примере создаются две пустые ссылки. Для дальнейшей работы необходимо выделить память под эти ссылки. Как вы уже и привыкли, память выделяется с помощью «new».

myArr = new int[10]; // Выделяем память под массив на 10 чисел

myStrings = new string[50]; // Выделяем память под массив на 50 строк

После выделения памяти инициализация элементов происходит следующим образом: значения всех простых типов устанавливаются в «0», значения логического типа – в false, ссылки – в null.

Есть также возможность проинициализировать массив нужными значениями при объявлении:

int[] myArr = new int[10] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

int[] myArr = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

int[] myArr = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

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

Многомерные массивы.

Одномерные массивы (они же векторы) обеспечивают наилучшую производительность, за счет того, что с ними используются специальные команды IL языка. Но в случае необходимости можно использовать и многомерные массивы. Синтаксис многомерного массива еще больше отличается от «сишного» и более похож на «паскалевский».

<Тип элементов массива>[ , ,...] <имя массива>;

float[,] myArr; // Объявление ссылки на двумерный массив дробных

// чисел

myArr = new float[2, 3];// Выделение памяти под ссылку на двумерный

// массив дробных чисел

int[,] myArr = new int[2, 3] { { 1, 2, 3 }, { 3, 4, 6 } };

int[,] myArr = new int[,] { { 1, 2, 3 }, { 3, 4, 6 } };

int[,] myArr = { { 1, 2, 3 }, { 3, 4, 6 } };

myArr[1, 2] = 100;

Пример заполнения массива:

int[,] array = new int[5,10];

for (int i = 0; i < 5; i++) {

for (int j = 0; j < 10; j++) {

array[i,j] = i + j;

}

}

Рваные массивы

Кроме одномерных и многомерных массивов C# также поддерживает «рваные» (или вложенные) массивы. Синтаксис объявления такого массива снова отличается от предыдущих.

Итак, синтаксис объявления рваного массива выглядит так:

<Тип элементов массива>[][] <имя массива>;

Такой массив представляет собой массив массивов. Т.е. в каждой ячейке массива располагается вектор. За счет того, что при работе с вложенными массивами мы фактически работаем с векторами – то производительность такая же, как и при работе с обычными одномерными. При этом нужно учитывать, что все «подмассивы» не находятся в памяти рядом.

Пример:

//создать массив вида

// particialArray[0]: 0

// particialArray[1]: 0 1

// particialArray[2]: 0 1 2

// particialArray[3]: 0 1 2 3

int[][] particialArray = new int[4][];

for (int i = 0; i < 4; i++) {

particialArray[i] = new int[i + 1];

for (int j = 0; j < i + 1; j++) {

particialArray[i][j] = j;

}

}

Все массивы имеют методы:

• Метод GetLength возвращает количество элементов массива по заданному измерению;

• Методы GetLowerBound и GetUpperBound возвращают соответственно нижнюю и верхнюю границы массива по заданному измерению (например, если есть одномерный массив на 5 элементов, то нижняя граница будет «0», верхняя – «4»)

• Метод CopyTo копирует все элементы одного одномерного массива в другой, начиная с заданной позиции.

• Метод Clone производит поверхностное копирование массива. Копия возвращается в виде массива System.Object[]

• Статический метод BinarySearch производит бинарный поиск значения в массиве (в диапазоне массива)

• Статический метод Clear очищает массив (диапазон массива). При этом ссылочные элементы устанавливаются в null, логические – в false, остальные типы значений – в «0»

• Статический метод IndexOf – возвращает индекс первого вхождения искомого элемента в массиве (в диапазоне массива), в случае неудачи – возвращает «-1». Поиск производится от начала массива.

• Статический метод LastIndexOf – возвращает индекс первого вхождения искомого элемента в массиве (в диапазоне массива). Поиск производится с конца массива, в случае неудачи – возвращает «-1».

• Статический метод Resize изменяет размер массива.

• Статический метод Reverse – реверсирует массив (диапазон массива).

• Статический метод Sort – сортирует массив (диапазон массива). Также присутствуют методы расширения:

• Метод Sum – суммирует элементы массива

• Метод Average – подсчитывает среднее арифметическое элементов массива.

• Метод Contains – возвращает истину, если заданный элемент присутствует в массиве.

• Метод Max – возвращает максимальный элемент массива.

• Метод Min – возвращает минимальный элемент массива.

И на последок пара свойств:

• Свойство Length – возвращает длину массива

• Свойство Rank – возвращает количество измерений массива

Пример:

int[] myArr1 = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; // 1 2 3 4 5 6 7 8 9 10

int[] tempArr = (int[])myArr1.Clone();

Array.Reverse(myArr1, 3, 4) //1 2 3 7 6 5 4 8 9 10

myArr1 = tempArr; // 1 2 3 4 5 6 7 8 9 10

int[] myArr2 = new int[20]; // 0 0 0 0 0 0 0 0 ...

myArr1.CopyTo(myArr2, 5); // 0 0 0 0 0 1 2 3 4 5 6 7 8 9 10 0 0 0 ...

Array.Clear(myArr2, 0, myArr2.GetLength(0)); 0 0 0 0 0 ...

Array.Resize(ref myArr2, 10);// 0 0 0 0 0 0 0 0 0 0

myArr2 = new[] { 1, 5, 3, 2, 8, 9, 6, 10, 7, 4 };

Array.Sort(myArr2); // 1 2 3 4 5 6 7 8 9 10

Array.BinarySearch(myArr2, 5); // 4

myArr2.Max(); // 10

myArr2.Min(); // 1

myArr2.Average(); //5.5

int[,] myArr3 = { { 1, 2, 3 }, { 4, 5, 6 } };

myArr3.Rank; //2

ЛЕКЦИЯ 5

Строки

Сталкиваясь со строками повсеместно, было бы неправильно думать, что в C# нет удобного способа работы с ними. Именно поэтому и был создан класс System.String, от которого происходят все строки. Сам тип System.String – ссылочный. Из этого следует то, что строки размещаются в «куче» и имеют богатый набор методов. Несмотря на то, что строка это ссылка – создавать ее удобно без ключевого слова new.

string <имя> = значение;

Несмотря на такой простой способ создания строки, класс System.String имеет 8 конструкторов.

Пример:

string str1 = "Простая строка";

char[] chrArr={'П','р','о','с','т','а','я',' ','с','т','р','о','к','а'};

string str2 = new string(chrArr);

string str3 = new string(chrArr, 8, 6);

string str4 = new string('$', 10);

Так как часто возникает необходимость работы с путями к файлам или папкам – было введено понятие «буквальных» строк. Перед такими строками ставится символ «@» и все символы строкового литерала воспринимаются буквально, как они есть. Например:

string path = @"D:\Student\MyProjects\Strings\"

Методы класса System.String:

Индексатор – позволяет по индексу получить символ строки. Работает только в режиме чтения.

• Свойство Length – возвращает длину строки.

• Метод CopyTo – копирует заданное количество символов в массив char

• Методы Equals, Compare, CompareTo и оператор «==» – используются для сравнения строк. Некоторые методы могут принимать параметр типа StringComparison, который задает способ сравнения. Например, вариант CurrentCultureIgnoreCase используется для сравнения без учета регистра с текущими настройками культуры. Метод Compare – статический, поэтому вызывается из под класса. CompareTo как и в «сишном» варианте возвращает целое значение(«-1» – левое слово меньше правого, «0» – слова равны, «1» – левое слово больше).

• Методы StartsWith и EndsWith – проверяют, начинается (заканчивается) ли строка заданным строковым литералом.

• Метод IndexOf и LastIndexOf – возвращает индекс первого/последнего вхождения символа/подстроки в исходной строке.

• Методы IndexOfAny и LastIndexOfAny возвращает индекс первого/ последнего вхождения любого из перечисленных символов в исходной строке.

• Медод SubString получает подстроку из исходной. Все методы поиска включают перегруженные версии для поиска в заданном диапазоне с заданным способом сравнения.

• Метод Concatосуществляет конкатенацию (склеивание) строк. Удобная альтернатива данному методу – операции «+» и «+=».

• Методы ToLowerи ToUpper– возвращают строку в нижнем и верхнем регистре соответственно.

• Метод Replaceзаменяет все вхождения символа/подстроки на заданный символ/подстроку.

• Метод Contains– проверяет, входит ли заданный символ/подстрока в исходную строку.

• Метод Insert– вставляет подстроку в заданную позицию исходной строки.

• Метод Remove– удаляет заданный диапазон исходной строки.

• Методы PadLeftи PadRightдополняют исходную строку заданными символами слева/справа. Если символ не указывается, то дополнение происходит символом пробела. Первый параметр указывает на количество символов в строке, до которого она должна быть дополнена.

• Метод Splitразрезает строку по заданным символам разделителям. Возвращает массив получившихся в результате нарезания строк. Чтоб исключить из этого массива пробельные строки – нужно использовать данную функцию с параметром StringSplitOptions.RemoveEmptyEntries.

• Статический метод Join объединяет строки заданного массива в одну и чередует их с указанным символом-разделителем.

• Методы TrimLeftи TrimRightубирают пробельные (по умолчанию) и заданные символы соответственно с начала и конца строки. Метод Trim – делает тоже с обеих сторон строки.

• Статический метод Format– позволяет удобно сформатировать строку. Первый параметр – это форматная строка, которая содержит текст выводимый на экран. Если в эту строку необходимо вставить значения переменных, то место вставки помечается индексом в фигурных скобках, при необходимости, там же можно указать количество символов, занимаемых вставляемым элементом и его спецификатор формата. Сами вставляемые данные указываются следующими параметрами метода. Таким образом, синтаксис использования метода Format следующий:

String.Format("Печатаемый текст {индекс, размер:спецификатор}", данные);

Спецификаторы формата:

1. «С» – для числовых данных. Выводит символ местной валюты.

2. «D» – для целочисленных данных. Выводит обычное целое число.

3. «Е» – для числовых данных. Выводит число в экспоненциальной форме.

4. «F» – для числовых данных. Выводит число с фиксированной десятичной

точкой.

5. «G» – для числовых данных. Выводит обычное число.

6. «N» – для числовых данных. Выводит числа в формате локальных настроек.

7. «P» – для числовых данных. Выводит числа с символом «%».

8. «X» – для целочисленных данных. Выводит число в шестнадцатеричном

формате.

Пример работы со сторками:

string str = "Random string";

char first = str[0];//R

char last = str[str.Length - 1]; //g

string str2 = "RANDOM STRING";

bool testIs = str == str2; //false

bool testEquals = str.Equals(str2, StringComparison.CurrentCultureIgnoreCase); //true

int firstN = str.IndexOf('n');//2

int lastN = str.LastIndexOf('n');//11

string betweenNInclusive = str.Substring(firstN + 1, lastN - firstN - 1); //dom stri

str = str.Replace(' ', '_'); //Random_string

При работе со строками нужно учитывать тот факт, что в C# строки неизменны. То есть, невозможно внести в строку любые изменения не пересоздав ее. Но беспокоиться по этому поводу не стоит – строка создается и уничтожается автоматически, вам лишь нужно принять ссылку на нее и продолжать работать. При этом нужно понимать, что ссылочные переменные типа string могут менять объекты, на которые они ссылаются. А содержимое созданного string-объекта изменить уже невозможно.

Работа со строками в таком стиле может быть как удобна, так и не очень. К удобствам можно отнести то, что исходная строка не меняется и при случае мы можем к ней обратиться снова. К недостаткам – то, что при интенсивном изменении строки затрачивается много ресурсов (как памяти, так и «сборщика мусора» из-за чего может падать быстродействие).

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

Методы класса StringBuilder похожи на «стринговские» – поэтому остановимся на них кратко.

• Метод Append – добавляет к исходной строке данные любого из стандартных типов.

• Метод AppendFormat – добавляет к исходной строке строку, сформированную в соответствии со спецификаторами формата.

• Метод Insert – вставляет данные любого из стандартных типов в исходную строку.

• Метод Remove – удаляет из исходной строки диапазон символов.

• Метод Replace – заменяет символ/подстроку в исходной строке на указанный символ/подстроку.

• Метод CopyTo – копирует символы исходной строки в массив char

• Метод ToString – преобразовывает объект StringBuilder в String

Пример (сравнение работы string, StringBuilder):

string str = "Speed test";

for (int i = 0; i < 100000; i++) {

str += i;

}

//39854ms

var builder = new System.Text.StringBuilder("Speed test");

for (int i = 0; i < 100000; i++) {

builder.Append(i);

}

string result = builder.ToString();

//56ms