Файл: 1_C#_Введение_в_программирование_Уч_пособ_(Марченко-2005).pdf

ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 11.12.2025

Просмотров: 1349

Скачиваний: 3

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

XXX xxx = new XXX(); xxx.mmm = 0; f1(xxx);

f1(ref xxx);

//По возвращении из метода это уже другая ссылка!

//Под именем xxx – другой объект.

}//======================================

}//=============================================

Передача параметров. Ссылка и ссылка на ссылку как параметры

Параметры ВСЕГДА передаются по значению. Это означает, что в области активации создаётся копия ЗНАЧЕНИЯ параметра. При этом важно, ЧТО копируется в эту область. Для ссылочных типов возможны два варианта:

можно скопировать адрес объекта (ссылка как параметр),

можно скопировать адрес переменной, которая указывает на объект (ссылка на ссылку как параметр).

Впервом случае параметр обеспечивает изменение полей объекта. Непосредственное изменение значения этой ссылки (возможно в результате

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

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

Следующий программный код демонстрирует специфику передачи параметров.

using System; namespace Ref_RefRef

{

///<summary>

///Ссылка и ссылка на ссылку.

///</summary>

class WorkClass

{

public int x; public WorkClass()

{

x = 0;

}

public WorkClass(int key)

{

x = key;

}

public WorkClass(WorkClass wKey)

{

x = wKey.x;

}

}

class ClassRef

{

static void Main(string[] args)

{

WorkClass w0 = new WorkClass(); Console.WriteLine(“on start: {0}”, w0.x); //_: 0 f0(w0);

Console.WriteLine(“after f0: {0}”, w0.x); //0: 1 f1(w0);

Console.WriteLine(“after f1: {0}”, w0.x); //1: 1 f2(ref w0);

Console.WriteLine(“after f2: {0}”, w0.x); //2: 10 f3(ref w0);

31

Console.WriteLine(“after f3: {0}”, w0.x); //3: 3

// Ещё один объект...

WorkClass w1 = new WorkClass(w0); ff(w0, ref w1);

if (w0 == null) Console.WriteLine(“w0 == null”); else Console.WriteLine(“w0 != null”); // !!!

if (w1 == null) Console.WriteLine(“w1 == null”); // !!!

else Console.WriteLine(“w1 != null”);

}

static void f0(WorkClass wKey)

{

wKey.x = 1;

Console.WriteLine(“ in f0: {0}”, wKey.x);

}

static void f1(WorkClass wKey)

{

wKey = new WorkClass(2); Console.WriteLine(“ in f1: {0}”, wKey.x);

}

static void f2(ref WorkClass wKey)

{

wKey.x = 10;

Console.WriteLine(“ in f2: {0}”, wKey.x);

}

static void f3(ref WorkClass wKey)

{

wKey = new WorkClass(3); Console.WriteLine(“ in f3: {0}”, wKey.x);

}

static void ff(WorkClass wKey, ref WorkClass refKey)

{

wKey = null; refKey = null;

}

}

}

Сравнение значений ссылок

Имеют место быть следующие варианты:

Ссылка может быть пустой (ref0 == null || ref1 != null),

Разные ссылки могут быть настроены на разные объекты (ref0 != ref1),

Разные ссылки могут быть настроены на один объект (ref0 == ref1).

Четвёртого не дано (больше-меньше в условиях управляемой памяти) не имеет никакого смысла.

this в нестатическом методе

Первичное выражение this в теле нестатического метода ссылается на объект, “от имени” которого был произведён вызов данного метода. Если метод возвращает ссылку, this можно использовать в качестве возвращаемого значения оператора return.

Свойства

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

32


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

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

Для задания свойств в языке C# используется специальный синтаксис и предполагает описание способов получения и установки значения — они называются get accessor и set accessor.

Наличие accessor’ов определяет доступность свойства для чтения и записи. При обращении к значению свойства вызывается механизм чтения (get accessor), при изменении значения вызывается механизм записи (set accessor).

class TestClass

{

int xVal; // Переменная объекта.

//Свойство, обслуживающее переменную объекта.

//Предоставляет возможность чтения и записи значений

//поля xVal.

public int Xval

{

//Эти значения реализованы в виде блоков программного кода,

//обеспечивающих получение и изменение значения поля.

//get accessor.

get

{

//Здесь можно расположить любой код.

//Он будет выполняться после обращения к свойству для

//прочтения значения.

//Например, так. Здесь получается,

//что возвращаемое значение зависит от количества

//обращений к свойству.

xVal++; return xVal;

}

set

{

//set accessor.

//Здесь можно расположить любой код.

//Он будет выполняться после обращения к свойству для

//записи значения.

xVal = value;

}

}

}

class Class1

{

static void Main(string[] args)

{

//Создали объект X. TestClass X = new TestClass();

//Обратились к свойству для записи в поле Xval

//значения. Обращение к свойству располагается

//СЛЕВА от операции присвоения. В свойстве Xval

//будет активизирован блок кода set.

X.Xval = 100;

//Обратились к свойству для чтения из поля Xval

//значения. Обращение к свойству располагается

//СПРАВА от операции присвоения. В свойстве Xval

//будет активизирован блок кода get.

int q = X.Xval;

}

}

33


Поскольку объект для доступа к данным Get концептуально равнозначен чтению значения переменной, хорошим стилем программирования считается отсутствие заметных побочных эффектов при использовании объектов для доступа к данным

Get.

Значение свойства не должно зависеть от каких-либо обстоятельств, в частности, от количества обращений к объекту. Если доступ к свойству порождает (как в нашем случае) заметный побочный эффект, свойство, согласно рекомендациям по соблюдению “хорошего стиля”, желательно реализовывать как метод.

Обработка исключений

Пусть в классе объявляются методы A и B.

При этом из метода A вызывается метод B, который выполняет свою работу,

возможно, возвращает

результаты. В теле

метода A есть точка вызова метода

B,

и точка

возврата,

в которой оказывается управление после успешного

возвращения из метода B.

анализирует полученные результаты

и

Если всё

хорошо,

метод A, возможно,

продолжает свою работу непосредственно из точки возврата.

Если при выполнении метода B возникла исключительная ситуация (например, целочисленное деление на 0), возможно, что метод A узнает об этом, анализируя возвращаемое из B значение. Таким может быть один из сценариев “обратной связи”, при котором вызывающий метод узнаёт о результатах деятельности вызывающего метода.

Недостатки этого сценария заключаются в том, что:

метод B может в принципе не возвращать никаких значений,

среди множества возвращаемых методом B значений невозможно выделить подмножество значений, которые можно было бы воспринимать как уведомление об ошибке,

работа по подготовке уведомления об ошибке требует неоправданно больших усилий.

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

Некорректная ситуация в ходе выполнения программы (деление на нуль, выход за пределы массива) рассматривается как исключительная ситуация, на которую метод, в котором она произошла, реагирует ГЕНЕРАЦИЕЙ ИСКЛЮЧЕНИЯ, а не обычным возвращением значения, пусть даже изначально ассоциированного с ошибкой.

Среда выполнения создает объект для представления исключения при его возникновении. Одновременно с этим прерывается обычный ход выполнения программы. Происходит так называемое разматывание стека, при котором управление НЕ оказывается в точке возврата и если ни в одном из методов, предшествующих вызову, не было предпринято предварительных усилий по ПЕРЕХВАТУ ИСКЛЮЧЕНИЯ, приложение аварийно завершается.

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

Все языки программирования, использующие среду выполнения, обрабатывают исключения одинаково. В каждом языке используется форма try/catch/finally для структурированной обработки исключений.

Следующий пример демонстрирует основные принципы организации генераторов и перехватчиков исключений.

using System;

//Объявление собственного исключения.

//Наследуется базовый класс Exception. public class xException:Exception

{

//Собственное исключение имеет специальную строчку

//и в этом её отличие.

public string xMessage;

34


//Кроме того, в поле её базового элемента

//также фиксируется особое сообщение.

public xException(string str):base("xException is here...")

{

xMessage = str;

}

}

//Объявление собственного исключения.

//Наследуется базовый класс Exception. public class MyException:Exception

{

//Собственное исключение имеет специальную строчку

//и в этом её отличие.

public string MyMessage;

//Кроме того, в поле её базового элемента

//также фиксируется особое сообщение.

public MyException(string str):base("MyException is here...")

{

MyMessage = str;

}

}

public class StupidCalcule

{

public int Div(int x1, int x2)

{

//Вот здесь метод проверяет коррактность операндов и с помощью оператора

//throw возбуждает исключение.

if (x1 != 0 && x2 == 0)

throw new Exception("message from Exception: Incorrect x2!"); else if (x1 == 0 && x2 == 0)

throw new MyException("message from MyException: Incorrect x1 && x2!"); else if (x1 == -1 && x2 == -1)

throw new xException("message from xException: @#$%*&^???");

// Если же ВСЁ ХОРОШО, счастливый заказчик получит ожидаемый результат. return (int)(x1/x2);

}

}

public class XXX

{

public static void Main()

{

int ret;

StupidCalcule sc = new StupidCalcule();

//Наше приложение специально подготовлено к обработке исключений!

//Потенциально опасное место (КРИТИЧЕСКАЯ СЕКЦИЯ) ограждено

//(заключено в блок try)

try

{

// Вызов...

ret = sc.Div(-1,-1);

//Если ВСЁ ХОРОШО - будет выполнен оператор Console.WriteLine.

//Потом операторы блока finally, затем операторы за пределами

//пределами блоков try, catch, finally.

Console.WriteLine("OK, ret == {0}.", ret.ToString());

}

catch (MyException e)

{

// Здесь перехватывается MyException. Console.WriteLine((Exception)e); Console.WriteLine(e.MyMessage);

}

// Если этот блок будет закомментирован -

35


//возможные исключения типа Exception и xException

//окажутся неперехваченными. И после блока

//finally приложение аварийно завершится.

catch (Exception e)

{

//А перехватчика xException у нас нет!

//Поэтому в этом блоке будут перехватываться

//все ранее неперехваченные потомки исключения Exception.

//Это последний рубеж.

//Ещё один вариант построения последнего рубежа

//вылядит так:

//catch

//{ // Операторы обработки - сюда. //}

Console.WriteLine(e);

}

finally

{

//Операторы в блоке finally выполняются ВСЕГДА, тогда как

//операторы, расположенные за пределами блоков try, catch, finally,

//могут и не выполниться вовсе.

Console.WriteLine("finally block is here!");

}

//Вот если блоки перехвата исключения окажутся не

//соответствующими возникшему исключению - нормальное

//выполнение приложения будет прервано - и мы никогда не увидим

//этой надписи.

Console.WriteLine("Good Bye!");

}

}

Массив. Объявление

Массив – множество однотипных элементов. Это тоже ТИП. Любой массив наследует классу (является производным от класса – о принципе наследования позже) System.Array.

Существует несколько способов сознания группировок однотипных объектов:

объявление множества однотипных элементов в рамках перечисления (класса, структуры),

определение собственно массива.

Принципиальная разница состоит в следующем:

доступ к данным-членам перечисления, класса, массива производится ПО имени данного-члена (элементы перечисления, класса или структуры ИНОГДА могут быть одного типа, но каждый член всегда имеет собственное имя),

доступ к элементу массива осуществляется по индексу (элементы массива ВСЕГДА однотипны, располагаются в contiguous memory) при этом допускается случайный доступ.

Многомерные массивы. Массив размерности (или ранга) N (N определяет число измерений массива) – это Массив массивов (или составляющих массива) ранга N-1. Составляющие массива – это массивы меньшей размерности, являющиеся элементами данного массива. Составляющая массива – это либо массив, либо элемент массива.

ОбъявлениеМассива ::= ИмяТипа СписокСпецификаторовРазмерности ИмяМассива [ИнициализацияМассива]; ИмяТипа ::= Идентификатор ИмяМассива ::= Идентификатор

СписокСпецификаторовРазмерности ::= [СписокСпецификаторовРазмерности] СпецификаторРазмерности ::= [СписокНеявныхСпецификаторов] СпецификаторРазмерности ::= [] СписокНеявныхСпецификаторов

СписокНеявныхСпецификаторов ::= [СписокНеявныхСпецификаторов ,] НеявныйСпецификатор НеявныйСпецификатор ::= ПУСТО | РАЗДЕЛИТЕЛЬ

36