Основы объектного программирования в классах на C# 3.0

Структуры

Разбить на страницы
Показывать лекцию целиком

Проект к данной лекции Вы можете скачать здесь.

Развернутые и ссылочные типы

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

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

T x = new T();

Напомню, как выполняется этот оператор, создающий объект класса Т. Объектам нужна память, чтобы с ними можно было работать. Рассмотрим две классические стратегии выделения памяти и связывания объекта, создаваемого в памяти, и сущности, объявленной в тексте. Есть два типа памяти - стек ( stack ) и куча ( heap ). Сущности x в стеке всегда отводится память, но какая память - зависит от того, к развернутому или ссылочному типу относится класс Т.

Определение 1.Если класс T относится к развернутому типу, то в стеке сущности x отводится память, необходимая объекту класса T. Говорят, что объект разворачивается на памяти, жестко связанной с сущностью x. Эту память сущность x ни с кем не разделяет.

Определение 2.Если класс T относится к ссылочному типу, то память объекту отводится в куче, а в стеке сущности x отводится дополнительная память, содержащая ссылку на объект.

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

Развернутые и ссылочные типы порождают две различные семантики. Рассмотрим присваивание:

y = x;

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

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

Рассмотрим операцию проверки объектов на эквивалентность:

if (x == y)

Для развернутых типов объекты x и y эквивалентны, если эквивалентны значения всех их полей. Для ссылочных типов объекты x и y эквивалентны, если эквивалентны значения ссылок.

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

public class A { ... }

Если объявление класса задается с использованием служебного слова struct, то такой класс относится к развернутым типам, которые также называют значимыми типами или value типами.

public struct B { ... }

Напомню: к значимым типам относятся все встроенные арифметические типы, булевский тип, перечисления, структуры. К ссылочным типам относятся массивы, строки, классы. Так можно ли в C# спроектировать свой собственный класс так, чтобы он относился к значимым типам? Ответ на этот вопрос - положительный, хотя и с рядом оговорок. Для того чтобы класс отнести к значимым типам, его достаточно объявить как структуру.

Классы и структуры

Структура - это частный случай класса. Исторически структуры используются в языках программирования раньше классов. В языках PL/1, C, Pascal они представляли собой только совокупность данных (полей класса), но не включали ни методов, ни событий. В языке С++ возможности структур были существенно расширены, и они стали настоящими классами, хотя и c некоторыми ограничениями. В языке C# сохранен именно такой подход к структурам.

Чем следует руководствоваться, делая выбор между структурой и классом? Полагаю, можно пользоваться следующими правилами:

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

    Структуры

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

    Синтаксис структур

    Синтаксис объявления структуры аналогичен синтаксису объявления класса:

    [атрибуты][модификаторы]struct имя_структуры[:список_интерфейсов]
    {тело_структуры}

    Какие изменения произошли в синтаксисе заголовка структуры в сравнении с синтаксисом класса? Их немного. Перечислим их:

  • ключевое слово class изменено на слово struct ;
  • список родителей, который мог включать имя родительского класса, заменен списком, допускающим только имена интерфейсов. Для структур не может быть задан родитель (класс или структура). Заметьте, структура может наследовать интерфейсы;
  • для структур не применимы модификаторы abstract и sealed. Причиной является отсутствие механизма наследования.
  • Все, что может быть вложено в тело класса, может быть вложено и в тело структуры: поля, методы, конструкторы и прочее, включая классы и интерфейсы.

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

    Рассмотрим ограничения, накладываемые на структуры.

    Структуры и наследование

    Самое серьезное ограничение связано с ограничением наследования. Для структуры не может быть задан родительский класс или родительская структура. У структуры не может быть наследников. Конечно, всякая структура, как и любой класс в C#, является наследником класса object, наследуя все свойства и методы этого класса. Структура может наследовать один или несколько интерфейсов, реализуя методы этих интерфейсов.

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

    y = x;

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

    x = (T)y;

    Для структур, принадлежащих к значимому типу, подобное присваивание между родителем и потомком было бы невозможным. Родителю нельзя присвоить объект потомка, поскольку у родителя меньше полей, чем у потомка, поэтому можно было бы присвоить лишь часть полей. А уж в обратную сторону операция присваивания была бы принципиально невозможна. И это одна из причин, по которой решились отказаться от наследования для значимых типов в языке C#.

    Структуры и инициализация полей

    Второе серьезное ограничение связано с процессом создания объектов и их инициализацией.

    Рассмотрим объявление:

    int[] x;
    int y = x[0];

    Это объявление корректно. А теперь рассмотрим похожее объявление:

    int u; 
    int v = u;

    Это объявление некорректно, возникнет ошибка периода компиляции, сообщающая, что переменная v не инициализирована и не может быть использована в вычислениях. В чем разница? Сущность x - это массив и, следовательно, относится к ссылочным типам. При создании объектов ссылочного типа все поля объектов инициализируются некоторыми значениями. В данном конкретном случае все элементы целочисленного типа получат значение по умолчанию, равное нулю. Сущность u арифметического типа относится к развернутому типу. Все поля объектов развернутого типа должны быть инициализированы явно или стандартным конструктором по умолчанию в момент создания объекта. Разная семантика инициализации приводит к следующим ограничениям на структуры.

  • Если в классе поля можно объявлять с инициализацией, то поля структуры не допускают инициализацию при их объявлении. Объявление с инициализацией полей означало бы их неявную инициализацию, а потому запрещено.
  • Стандартный конструктор по умолчанию у структур имеется, он в стеке создает объект, инициализируя поля созданного объекта значениями по умолчанию. Этот конструктор нельзя заменить собственным конструктором без аргументов.
  • Класс Rational или структура Rational

    Вернемся к классу Rational, спроектированному в предыдущей лекции. Очевидно, что его вполне разумно представить в виде структуры. Наследование для него необязательно. Семантика присваивания развернутого типа больше подходит для рациональных чисел, чем ссылочная семантика, ведь рациональные числа - это еще один подкласс арифметического класса. В общем, класс Rational - прямой кандидат в структуры. Зададимся вопросом, насколько просто объявление класса превратить в объявление структуры? Достаточно ли заменить слово class словом struct? В данном случае заменой одного слова не обойтись. Нужно справиться и с другими вышеперечисленными ограничениями на структуры. Сделать это не трудно. И я приведу объявление структуры SRational, оставляя читателю возможность самому найти все различия между описаниями класса Rational и описанием структуры. Замечу, что, следуя правилам хорошего стиля, требующего давать полям содержательные имена, изменены имена полей. Для русскоязычных читателей английские названия, возможно, менее приятны, чем аббревиатуры m и n.

    /// <summary>
    /// Структура SRational.
    /// определяет новый тип данных - рациональные числа и основные
    /// операции над ними - сложение, умножение, вычитание и деление.
    /// Рациональное число задается парой целых чисел
    /// (numerator,denominator) и изображается
    /// обычно в виде дроби numerator/denominator. 
    /// Число numerator называется числителем,
    /// denominator - знаменателем. Для рационального числа существует
    /// множество его представлений, например, 1/2, 2/4, 3/6, 6/12.
    /// Среди всех представлений
    /// можно выделить то, в котором числитель и знаменатель взаимно
    /// несократимы. Такой представитель будет храниться в полях класса.
    /// </summary>
    public struct SRational
    {
        const string NONE_EXIST =
                "Не существует рационального  числа " +
                "со знаменателем, равным нулю!";
        //Поля класса. Числитель и знаменатель рационального числа.
        int numerator, denominator;       
        /// <summary>
        /// Конструктор класса. Создает рациональное число
        /// эквивалентное numerator/denominator, 
        /// но со взаимно несократимыми числителем и знаменателем. 
        /// Если denominator = 0, то выбрасывается исключение, 
        /// сообщающее о невозможности создать
        /// рациональное число со знаменателем 0
        /// </summary>
        /// <param name="numerator">числитель</param>
        /// <param name="denominator">знаменатель</param>
        public SRational(int numerator, int denominator)
        {
            if (denominator == 0) 
                throw new SRationalException(NONE_EXIST);
            if (numerator == 0) 
            { 
                this.numerator = 0; 
                this.denominator = 1; 
                return; 
            }             
            //приведение знака
            if (denominator < 0) 
            { 
                denominator = -denominator; 
                numerator = -numerator; 
            }
            //приведение к несократимой дроби
            int m = numerator, n = denominator;
            {
                int p = 0;
                m = Math.Abs(m); n = Math.Abs(n);
                do
                {
                    p = m % n; m = n; n = p;
                } while (n != 0);
            }//Nod
            this.numerator = numerator / m; 
            this.denominator = denominator / m;            
        }       
    
        /// <summary>
        /// Представление рационального числа 
        /// в виде строки
        /// </summary>
        /// <returns>строка в формате numerator/denominator
        /// </returns>
        public override string ToString()
        {
            return numerator + "/" + denominator;
        }
        public SRational Plus(SRational a)
        {
            int u, v;
            u = numerator * a.denominator + denominator * a.numerator;
            v = denominator * a.denominator;
            return (new SRational(u, v));
        }//Plus
        public static SRational operator +(SRational r1, SRational r2)
        {
            return (r1.Plus(r2));
        }
        public SRational Minus(SRational a)
        {
            int u, v;
            u = numerator * a.denominator - denominator * a.numerator; 
            v = denominator * a.denominator;
            return (new SRational(u, v));
        }//Minus
        public static SRational operator -(SRational r1, SRational r2)
        {
            return (r1.Minus(r2));
        }
        public SRational Mult(SRational a)
        {
            int u, v;
            u = numerator * a.numerator; 
            v = denominator * a.denominator;
            return (new SRational(u, v));
        }//Mult
        public static SRational operator *(SRational r1, SRational r2)
        {
            return (r1.Mult(r2));
        }
        public SRational Divide(SRational a)
        {
            int u, v;
            u = numerator * a.denominator; 
            v = denominator * a.numerator;
            return (new SRational(u, v));
        }//Divide
        public static SRational operator /(SRational r1, SRational r2)
        {
            return (r1.Divide(r2));
        }
        //Константы класса 0 и 1 - ZERO и ONE
        public static readonly SRational ZERO, ONE;
        SRational(int num, int den, string t)
        {
            numerator = num; denominator = den;
        }//Закрытый конструктор
        static SRational()
        {
            ZERO = new SRational(0, 1, "");
            ONE = new SRational(1, 1, "");
        }//Статический конструктор
        //Операции отношения
        public static bool operator ==(SRational r1, SRational r2)
        {
            return ((r1.numerator == r2.numerator)  
                (r1.denominator == r2.denominator));
        }
        public static bool operator !=(SRational r1, SRational r2)
        {
            return ((r1.numerator != r2.numerator) || 
                (r1.denominator != r2.denominator));
        }        
        public static bool operator <(SRational r1, SRational r2)
        {
            return (r1.numerator * r2.denominator < 
                r2.numerator * r1.denominator);
        }
        public static bool operator >(SRational r1, SRational r2)
        {
            return (r1.numerator * r2.denominator > 
                r2.numerator * r1.denominator);
        }
        public static bool operator <(SRational r1, double r2)
        {
            return ((double)r1.numerator / r1.denominator < r2);
        }
        public static bool operator >(SRational r1, double r2)
        {
            return ((double)r1.numerator / r1.denominator > r2);
        }
        public override bool Equals(object obj)
        {
            return this == (SRational)obj;
        }
        public override int GetHashCode()
        {
            return numerator + denominator;
        }
    }
    /// <summary>
    /// Класс, задающий исключения при работе
    /// с рациональными числами.
    /// </summary>
    public class SRationalException : Exception
    {
        public SRationalException() { }
        public SRationalException(string message) :
            base(message) { }
        public SRationalException(string message,
            Exception e)
            : base(message, e) { }
    }

    Все ранее построенные примеры работы с классом Rational применимы и при работе со структурой SRational и будут давать эквивалентные результаты за одним исключением. При вызове конструктора без аргументов для класса Rational создается корректное рациональное число 0/1. Для структуры конструктор по умолчанию создает некорректное число 0/0. Эту ситуацию никак нельзя исправить, поскольку этот конструктор нельзя переопределить. Единственный выход - не пользоваться этим конструктором при работе со структурами.

    Построим пример работы с объектами структуры SRational:

    public void TwoSemantics()
       {
           SRational sr1 = new SRational(1, 3), 
              sr2 = new SRational(3, 5);
           SRational sr3, sr4;
           sr3 = sr1 + sr2; sr4 = sr3;
           if (sr3 > 1) sr3 = sr1 + sr3 + SRational.ONE; 
           else sr3 = sr2 + sr3 - SRational.ONE;
           Console.WriteLine("Структура SRational");
           Console.WriteLine("sr1 = " + sr1.ToString());
           Console.WriteLine("sr2 = " + sr2.ToString());
           Console.WriteLine("sr3 = " + sr3.ToString());
           Console.WriteLine("sr4 = " + sr4.ToString());
       }

    В этом примере используются константы, работает статический конструктор, закрытый конструктор, перегруженные операции сравнения, арифметические выражения над рациональными числами. В результате вычислений r3 получит значение 8/15, r4 - 14/15. Заметьте, аналогичный пример для класса Rational даст те же результаты. Для класса Rational и структуры Rational нельзя обнаружить разницу между ссылочным и развернутым присваиванием. Это связано с особенностью класса Rational - он по построению относится к неизменяемым ( immutable ) классам, аналогично классу string. Операции этого класса не изменяют поля объекта, а каждый раз создают новый объект. В этом случае можно считать, что объекты класса обладают присваиванием развернутого типа.

    Встроенные структуры

    Как уже говорилось, все значимые типы языка реализованы структурами. В библиотеке FCL имеются и другие встроенные структуры. Рассмотрим в качестве примера структуры Point, PointF, Size, SizeF, Rectangle, находящиеся в пространстве имен System.Drawing (при работе с консольным проектом это пространство нужно подключать). Данные структуры активно используются при работе с графическими объектами. Структуры Point, PointF определяют точки на плоскости. Свойства X и Y позволяют задавать координаты точки, целочисленные или вещественные. Для структур Size и SizeF свойства Height и Width задают высоту и ширину. Структуры Point и Size позволяют сформировать прямоугольную область - структуру Rectangle. Конструктору прямоугольника можно передать в качестве аргументов две структуры: точку, задающую координаты левого верхнего угла прямоугольника, и размер - высоту и ширину прямоугольника.

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

    public void TestPointAndSize()
      {
        Point pt1 = new Point(3,5), pt2 = new Point(7,10), pt3;
        PointF pt4 = new PointF(4.55f,6.75f);
        Size sz1 = new Size(10,20), sz2;
        SizeF sz3 = new SizeF(10.3f, 20.7f);
        pt3 = Point.Round(pt4);
        sz2 = new Size(pt1);
        Console.WriteLine ("pt1: " + pt1);
        Console.WriteLine ("sz2 = new Size(pt1): " + sz2);
        Console.WriteLine ("pt4: " + pt4);
        Console.WriteLine ("pt3 = Point.Round(pt4): " + pt3);
        pt1.Offset(5,7);
        Console.WriteLine ("pt1.Offset(5,7): " + pt1);    
        Console.WriteLine ("pt2: " + pt2);
        pt2 = pt2+ sz2;
        Console.WriteLine ("pt2= pt2+ sz2: " + pt2);
       }//TestPointAndSize

    Результаты его выполнения показаны на рис. 2.1.

    (рис 2.1) Операции над точками и размерами

    Отметим, что метод ToString, определенный для этих структур, выдает строку со значениями полей в приемлемой для восприятия форме.

    Еще раз о двух семантиках присваивания

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

    public void TestClassAndStructPoint()
      {
       string s = "Структуры: присваивание развернутого типа!";
       Console.WriteLine(s);
       Point pt1 = new Point(3, 5), pt2;
       pt2 = pt1;
       Console.WriteLine("pt1: " + pt1);
       Console.WriteLine("pt2 = pt1: " + pt2);
       pt1.Offset(5, 7);
       Console.WriteLine("pt1.Offset(5,7): " + pt1);
       Console.WriteLine("pt2: " + pt2);
       s = "Классы: присваивание ссылочного типа!";
       Console.WriteLine(s);
       CPoint cpt1 = new CPoint(3, 5), cpt2;
       cpt2 = cpt1;
       Console.WriteLine("cpt1: " + cpt1);
       Console.WriteLine("cpt2=cpt1: " + cpt2);
       cpt1.Offset(5, 7);
       Console.WriteLine("cpt1.Offset(5,7): " + cpt1);
       Console.WriteLine("cpt2: " + cpt2);
      }

    Результаты вычислений показаны на рис. 2.2.

    (рис 2.2) Две семантики присваивания

    Действия над объектами Point и CPoint выполняются аналогичные, а результаты получаются разные: в конце вычислений pt1 и pt2 различны, а cpt1 и cpt2 совпадают. Объяснение этому понятно: в первом случае присваивание структур pt2 и pt1 - значимое, а для объектов cpt1 и cpt2 - ссылочное.

    Архитектура Решения

    Как обычно, для демонстрации примеров данной лекции построено Решение с именем лекции Ch2_2. В Решение включен единственный консольный проект с именем StructuresAndClasses. К проекту присоединены две DLL с именами ClassSamples и Geometrical_Figures, включенные в Решение Ch2_1, которое приведено в предыдущей лекции. Классы CPoint и SRational из этих библиотек используются в консольном проекте. Консольный проект содержит традиционный класс Testing, большое число его методов представляют собой различные тесты, которые иллюстрируют работу с различными объектами классов из DLL, присоединенных к консольному проекту.

    Задачи

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

  • Создать DLL с классом Point, описывающим точку на плоскости, заданную декартовыми и полярными координатами. Среди методов класса Point предусмотреть метод Distance, вычисляющий расстояние до заданной точки. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с классом Point.
  • Создать DLL с классами Point и Line, описывающими точку и линию на плоскости. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL. Среди методов класса Line предусмотреть метод, определяющий принадлежность заданной точки линии.
  • Создать DLL с классами Point и Square, описывающими точку и квадрат на плоскости со сторонами, параллельными осям координат. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL. Среди методов класса Square предусмотреть метод, определяющий принадлежность заданной точки квадрату.
  • Создать DLL с классами Point и Circle, описывающими точку и круг на плоскости. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL. Среди методов класса Circle предусмотреть метод, определяющий принадлежность заданной точки кругу.
  • Создать DLL с классами Point и Rectangle, описывающими точку и прямоугольник на плоскости со сторонами, параллельными осям координат. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL. Среди методов класса Rectangle предусмотреть метод, определяющий принадлежность заданной точки прямоугольнику.
  • Создать DLL с классами Point и Rhomb, описывающими точку и ромб на плоскости с осями, параллельными осям координат. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL. Среди методов класса Rhomb предусмотреть метод, определяющий принадлежность заданной точки ромбу.
  • Создать DLL с классами Point и Tetragon, описывающими точку и четырехугольник на плоскости. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL. Среди методов класса Tetragon предусмотреть метод, определяющий принадлежность заданной точки четырехугольнику.
  • Создать DLL с классами Point и Triangle, описывающими точку и треугольник на плоскости. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL. Среди методов класса Triangle предусмотреть метод, определяющий принадлежность заданной точки треугольнику.
  • Создать DLL с классами Account и Accounts, описывающими счет в банке и множество счетов клиентов, хранящихся в файле. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL.
  • Создать DLL с классами Lexeme и AriphmeticExpression, описывающими лексему и арифметическое выражение. Среди методов класса AriphmeticExpression предусмотреть метод, представляющий выражение в виде массива лексем. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL.
  • Создать DLL с классами Lexeme и LogicExpression, описывающими лексему и логическое выражение. Среди методов класса LogicExpression предусмотреть метод, представляющий выражение в виде массива лексем. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL.
  • Создать DLL с классами Lexeme и Assignment, описывающими лексему и оператор присваивания. Среди методов класса Assignment предусмотреть метод, представляющий оператор в виде массива лексем. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL.
  • Создать DLL с классами Student и Exam, описывающими студента и экзамен. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL.
  • Создать DLL с сервисным классом Poisk, содержащего методы линейного и бинарного поиска. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL.
  • Создать DLL с сервисным классом Sorting, содержащего методы сортировки. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL.
  • Создать DLL с интерфейсным классом InputArray, позволяющий вводить одномерный массив с проверкой корректности ввода элементов. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL.
  • Проекты

  • Дано множество точек P1, P2, $$\dots$$ Pn со случайными координатами. Построить множество точек P1, P2, $$\dots$$ Pm ( m <= n ), обход которых позволяет построить многоугольник без пересечений, если это возможно. Уметь вычислять площадь многоугольника, определять, является ли он выпуклым, принадлежит ли заданная точка внутренней области (границам) многоугольника. Построить реализацию в виде класса Polygon, включенного в состав DLL. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с объектами класса Polygon.
  • Практикум 3. Разработка программ в Visual Studio 2010

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

    Страницы:

    Проект к данной лекции Вы можете скачать здесь.

    Развернутые и ссылочные типы

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

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

    T x = new T();

    Напомню, как выполняется этот оператор, создающий объект класса Т. Объектам нужна память, чтобы с ними можно было работать. Рассмотрим две классические стратегии выделения памяти и связывания объекта, создаваемого в памяти, и сущности, объявленной в тексте. Есть два типа памяти - стек ( stack ) и куча ( heap ). Сущности x в стеке всегда отводится память, но какая память - зависит от того, к развернутому или ссылочному типу относится класс Т.

    Определение 1.Если класс T относится к развернутому типу, то в стеке сущности x отводится память, необходимая объекту класса T. Говорят, что объект разворачивается на памяти, жестко связанной с сущностью x. Эту память сущность x ни с кем не разделяет.

    Определение 2.Если класс T относится к ссылочному типу, то память объекту отводится в куче, а в стеке сущности x отводится дополнительная память, содержащая ссылку на объект.

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

    Развернутые и ссылочные типы порождают две различные семантики. Рассмотрим присваивание:

    y = x;

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

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

    Рассмотрим операцию проверки объектов на эквивалентность:

    if (x == y)

    Для развернутых типов объекты x и y эквивалентны, если эквивалентны значения всех их полей. Для ссылочных типов объекты x и y эквивалентны, если эквивалентны значения ссылок.

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

    public class A { ... }

    Если объявление класса задается с использованием служебного слова struct, то такой класс относится к развернутым типам, которые также называют значимыми типами или value типами.

    public struct B { ... }

    Напомню: к значимым типам относятся все встроенные арифметические типы, булевский тип, перечисления, структуры. К ссылочным типам относятся массивы, строки, классы. Так можно ли в C# спроектировать свой собственный класс так, чтобы он относился к значимым типам? Ответ на этот вопрос - положительный, хотя и с рядом оговорок. Для того чтобы класс отнести к значимым типам, его достаточно объявить как структуру.

    Классы и структуры

    Структура - это частный случай класса. Исторически структуры используются в языках программирования раньше классов. В языках PL/1, C, Pascal они представляли собой только совокупность данных (полей класса), но не включали ни методов, ни событий. В языке С++ возможности структур были существенно расширены, и они стали настоящими классами, хотя и c некоторыми ограничениями. В языке C# сохранен именно такой подход к структурам.

    Чем следует руководствоваться, делая выбор между структурой и классом? Полагаю, можно пользоваться следующими правилами:

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

    Структуры

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

    Синтаксис структур

    Синтаксис объявления структуры аналогичен синтаксису объявления класса:

    [атрибуты][модификаторы]struct имя_структуры[:список_интерфейсов]
    {тело_структуры}

    Какие изменения произошли в синтаксисе заголовка структуры в сравнении с синтаксисом класса? Их немного. Перечислим их:

  • ключевое слово class изменено на слово struct ;
  • список родителей, который мог включать имя родительского класса, заменен списком, допускающим только имена интерфейсов. Для структур не может быть задан родитель (класс или структура). Заметьте, структура может наследовать интерфейсы;
  • для структур не применимы модификаторы abstract и sealed. Причиной является отсутствие механизма наследования.
  • Все, что может быть вложено в тело класса, может быть вложено и в тело структуры: поля, методы, конструкторы и прочее, включая классы и интерфейсы.

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

    Рассмотрим ограничения, накладываемые на структуры.

    Структуры и наследование

    Самое серьезное ограничение связано с ограничением наследования. Для структуры не может быть задан родительский класс или родительская структура. У структуры не может быть наследников. Конечно, всякая структура, как и любой класс в C#, является наследником класса object, наследуя все свойства и методы этого класса. Структура может наследовать один или несколько интерфейсов, реализуя методы этих интерфейсов.

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

    y = x;

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

    x = (T)y;

    Для структур, принадлежащих к значимому типу, подобное присваивание между родителем и потомком было бы невозможным. Родителю нельзя присвоить объект потомка, поскольку у родителя меньше полей, чем у потомка, поэтому можно было бы присвоить лишь часть полей. А уж в обратную сторону операция присваивания была бы принципиально невозможна. И это одна из причин, по которой решились отказаться от наследования для значимых типов в языке C#.

    Структуры и инициализация полей

    Второе серьезное ограничение связано с процессом создания объектов и их инициализацией.

    Рассмотрим объявление:

    int[] x;
    int y = x[0];

    Это объявление корректно. А теперь рассмотрим похожее объявление:

    int u; 
    int v = u;

    Это объявление некорректно, возникнет ошибка периода компиляции, сообщающая, что переменная v не инициализирована и не может быть использована в вычислениях. В чем разница? Сущность x - это массив и, следовательно, относится к ссылочным типам. При создании объектов ссылочного типа все поля объектов инициализируются некоторыми значениями. В данном конкретном случае все элементы целочисленного типа получат значение по умолчанию, равное нулю. Сущность u арифметического типа относится к развернутому типу. Все поля объектов развернутого типа должны быть инициализированы явно или стандартным конструктором по умолчанию в момент создания объекта. Разная семантика инициализации приводит к следующим ограничениям на структуры.

  • Если в классе поля можно объявлять с инициализацией, то поля структуры не допускают инициализацию при их объявлении. Объявление с инициализацией полей означало бы их неявную инициализацию, а потому запрещено.
  • Стандартный конструктор по умолчанию у структур имеется, он в стеке создает объект, инициализируя поля созданного объекта значениями по умолчанию. Этот конструктор нельзя заменить собственным конструктором без аргументов.
  • Класс Rational или структура Rational

    Вернемся к классу Rational, спроектированному в предыдущей лекции. Очевидно, что его вполне разумно представить в виде структуры. Наследование для него необязательно. Семантика присваивания развернутого типа больше подходит для рациональных чисел, чем ссылочная семантика, ведь рациональные числа - это еще один подкласс арифметического класса. В общем, класс Rational - прямой кандидат в структуры. Зададимся вопросом, насколько просто объявление класса превратить в объявление структуры? Достаточно ли заменить слово class словом struct? В данном случае заменой одного слова не обойтись. Нужно справиться и с другими вышеперечисленными ограничениями на структуры. Сделать это не трудно. И я приведу объявление структуры SRational, оставляя читателю возможность самому найти все различия между описаниями класса Rational и описанием структуры. Замечу, что, следуя правилам хорошего стиля, требующего давать полям содержательные имена, изменены имена полей. Для русскоязычных читателей английские названия, возможно, менее приятны, чем аббревиатуры m и n.

    /// <summary>
    /// Структура SRational.
    /// определяет новый тип данных - рациональные числа и основные
    /// операции над ними - сложение, умножение, вычитание и деление.
    /// Рациональное число задается парой целых чисел
    /// (numerator,denominator) и изображается
    /// обычно в виде дроби numerator/denominator. 
    /// Число numerator называется числителем,
    /// denominator - знаменателем. Для рационального числа существует
    /// множество его представлений, например, 1/2, 2/4, 3/6, 6/12.
    /// Среди всех представлений
    /// можно выделить то, в котором числитель и знаменатель взаимно
    /// несократимы. Такой представитель будет храниться в полях класса.
    /// </summary>
    public struct SRational
    {
        const string NONE_EXIST =
                "Не существует рационального  числа " +
                "со знаменателем, равным нулю!";
        //Поля класса. Числитель и знаменатель рационального числа.
        int numerator, denominator;       
        /// <summary>
        /// Конструктор класса. Создает рациональное число
        /// эквивалентное numerator/denominator, 
        /// но со взаимно несократимыми числителем и знаменателем. 
        /// Если denominator = 0, то выбрасывается исключение, 
        /// сообщающее о невозможности создать
        /// рациональное число со знаменателем 0
        /// </summary>
        /// <param name="numerator">числитель</param>
        /// <param name="denominator">знаменатель</param>
        public SRational(int numerator, int denominator)
        {
            if (denominator == 0) 
                throw new SRationalException(NONE_EXIST);
            if (numerator == 0) 
            { 
                this.numerator = 0; 
                this.denominator = 1; 
                return; 
            }             
            //приведение знака
            if (denominator < 0) 
            { 
                denominator = -denominator; 
                numerator = -numerator; 
            }
            //приведение к несократимой дроби
            int m = numerator, n = denominator;
            {
                int p = 0;
                m = Math.Abs(m); n = Math.Abs(n);
                do
                {
                    p = m % n; m = n; n = p;
                } while (n != 0);
            }//Nod
            this.numerator = numerator / m; 
            this.denominator = denominator / m;            
        }       
    
        /// <summary>
        /// Представление рационального числа 
        /// в виде строки
        /// </summary>
        /// <returns>строка в формате numerator/denominator
        /// </returns>
        public override string ToString()
        {
            return numerator + "/" + denominator;
        }
        public SRational Plus(SRational a)
        {
            int u, v;
            u = numerator * a.denominator + denominator * a.numerator;
            v = denominator * a.denominator;
            return (new SRational(u, v));
        }//Plus
        public static SRational operator +(SRational r1, SRational r2)
        {
            return (r1.Plus(r2));
        }
        public SRational Minus(SRational a)
        {
            int u, v;
            u = numerator * a.denominator - denominator * a.numerator; 
            v = denominator * a.denominator;
            return (new SRational(u, v));
        }//Minus
        public static SRational operator -(SRational r1, SRational r2)
        {
            return (r1.Minus(r2));
        }
        public SRational Mult(SRational a)
        {
            int u, v;
            u = numerator * a.numerator; 
            v = denominator * a.denominator;
            return (new SRational(u, v));
        }//Mult
        public static SRational operator *(SRational r1, SRational r2)
        {
            return (r1.Mult(r2));
        }
        public SRational Divide(SRational a)
        {
            int u, v;
            u = numerator * a.denominator; 
            v = denominator * a.numerator;
            return (new SRational(u, v));
        }//Divide
        public static SRational operator /(SRational r1, SRational r2)
        {
            return (r1.Divide(r2));
        }
        //Константы класса 0 и 1 - ZERO и ONE
        public static readonly SRational ZERO, ONE;
        SRational(int num, int den, string t)
        {
            numerator = num; denominator = den;
        }//Закрытый конструктор
        static SRational()
        {
            ZERO = new SRational(0, 1, "");
            ONE = new SRational(1, 1, "");
        }//Статический конструктор
        //Операции отношения
        public static bool operator ==(SRational r1, SRational r2)
        {
            return ((r1.numerator == r2.numerator)  
                (r1.denominator == r2.denominator));
        }
        public static bool operator !=(SRational r1, SRational r2)
        {
            return ((r1.numerator != r2.numerator) || 
                (r1.denominator != r2.denominator));
        }        
        public static bool operator <(SRational r1, SRational r2)
        {
            return (r1.numerator * r2.denominator < 
                r2.numerator * r1.denominator);
        }
        public static bool operator >(SRational r1, SRational r2)
        {
            return (r1.numerator * r2.denominator > 
                r2.numerator * r1.denominator);
        }
        public static bool operator <(SRational r1, double r2)
        {
            return ((double)r1.numerator / r1.denominator < r2);
        }
        public static bool operator >(SRational r1, double r2)
        {
            return ((double)r1.numerator / r1.denominator > r2);
        }
        public override bool Equals(object obj)
        {
            return this == (SRational)obj;
        }
        public override int GetHashCode()
        {
            return numerator + denominator;
        }
    }
    /// <summary>
    /// Класс, задающий исключения при работе
    /// с рациональными числами.
    /// </summary>
    public class SRationalException : Exception
    {
        public SRationalException() { }
        public SRationalException(string message) :
            base(message) { }
        public SRationalException(string message,
            Exception e)
            : base(message, e) { }
    }

    Все ранее построенные примеры работы с классом Rational применимы и при работе со структурой SRational и будут давать эквивалентные результаты за одним исключением. При вызове конструктора без аргументов для класса Rational создается корректное рациональное число 0/1. Для структуры конструктор по умолчанию создает некорректное число 0/0. Эту ситуацию никак нельзя исправить, поскольку этот конструктор нельзя переопределить. Единственный выход - не пользоваться этим конструктором при работе со структурами.

    Построим пример работы с объектами структуры SRational:

    public void TwoSemantics()
       {
           SRational sr1 = new SRational(1, 3), 
              sr2 = new SRational(3, 5);
           SRational sr3, sr4;
           sr3 = sr1 + sr2; sr4 = sr3;
           if (sr3 > 1) sr3 = sr1 + sr3 + SRational.ONE; 
           else sr3 = sr2 + sr3 - SRational.ONE;
           Console.WriteLine("Структура SRational");
           Console.WriteLine("sr1 = " + sr1.ToString());
           Console.WriteLine("sr2 = " + sr2.ToString());
           Console.WriteLine("sr3 = " + sr3.ToString());
           Console.WriteLine("sr4 = " + sr4.ToString());
       }

    В этом примере используются константы, работает статический конструктор, закрытый конструктор, перегруженные операции сравнения, арифметические выражения над рациональными числами. В результате вычислений r3 получит значение 8/15, r4 - 14/15. Заметьте, аналогичный пример для класса Rational даст те же результаты. Для класса Rational и структуры Rational нельзя обнаружить разницу между ссылочным и развернутым присваиванием. Это связано с особенностью класса Rational - он по построению относится к неизменяемым ( immutable ) классам, аналогично классу string. Операции этого класса не изменяют поля объекта, а каждый раз создают новый объект. В этом случае можно считать, что объекты класса обладают присваиванием развернутого типа.

    Встроенные структуры

    Как уже говорилось, все значимые типы языка реализованы структурами. В библиотеке FCL имеются и другие встроенные структуры. Рассмотрим в качестве примера структуры Point, PointF, Size, SizeF, Rectangle, находящиеся в пространстве имен System.Drawing (при работе с консольным проектом это пространство нужно подключать). Данные структуры активно используются при работе с графическими объектами. Структуры Point, PointF определяют точки на плоскости. Свойства X и Y позволяют задавать координаты точки, целочисленные или вещественные. Для структур Size и SizeF свойства Height и Width задают высоту и ширину. Структуры Point и Size позволяют сформировать прямоугольную область - структуру Rectangle. Конструктору прямоугольника можно передать в качестве аргументов две структуры: точку, задающую координаты левого верхнего угла прямоугольника, и размер - высоту и ширину прямоугольника.

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

    public void TestPointAndSize()
      {
        Point pt1 = new Point(3,5), pt2 = new Point(7,10), pt3;
        PointF pt4 = new PointF(4.55f,6.75f);
        Size sz1 = new Size(10,20), sz2;
        SizeF sz3 = new SizeF(10.3f, 20.7f);
        pt3 = Point.Round(pt4);
        sz2 = new Size(pt1);
        Console.WriteLine ("pt1: " + pt1);
        Console.WriteLine ("sz2 = new Size(pt1): " + sz2);
        Console.WriteLine ("pt4: " + pt4);
        Console.WriteLine ("pt3 = Point.Round(pt4): " + pt3);
        pt1.Offset(5,7);
        Console.WriteLine ("pt1.Offset(5,7): " + pt1);    
        Console.WriteLine ("pt2: " + pt2);
        pt2 = pt2+ sz2;
        Console.WriteLine ("pt2= pt2+ sz2: " + pt2);
       }//TestPointAndSize

    Результаты его выполнения показаны на рис. 2.1.

    (рис 2.1) Операции над точками и размерами

    Отметим, что метод ToString, определенный для этих структур, выдает строку со значениями полей в приемлемой для восприятия форме.

    Еще раз о двух семантиках присваивания

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

    public void TestClassAndStructPoint()
      {
       string s = "Структуры: присваивание развернутого типа!";
       Console.WriteLine(s);
       Point pt1 = new Point(3, 5), pt2;
       pt2 = pt1;
       Console.WriteLine("pt1: " + pt1);
       Console.WriteLine("pt2 = pt1: " + pt2);
       pt1.Offset(5, 7);
       Console.WriteLine("pt1.Offset(5,7): " + pt1);
       Console.WriteLine("pt2: " + pt2);
       s = "Классы: присваивание ссылочного типа!";
       Console.WriteLine(s);
       CPoint cpt1 = new CPoint(3, 5), cpt2;
       cpt2 = cpt1;
       Console.WriteLine("cpt1: " + cpt1);
       Console.WriteLine("cpt2=cpt1: " + cpt2);
       cpt1.Offset(5, 7);
       Console.WriteLine("cpt1.Offset(5,7): " + cpt1);
       Console.WriteLine("cpt2: " + cpt2);
      }

    Результаты вычислений показаны на рис. 2.2.

    (рис 2.2) Две семантики присваивания

    Действия над объектами Point и CPoint выполняются аналогичные, а результаты получаются разные: в конце вычислений pt1 и pt2 различны, а cpt1 и cpt2 совпадают. Объяснение этому понятно: в первом случае присваивание структур pt2 и pt1 - значимое, а для объектов cpt1 и cpt2 - ссылочное.

    Архитектура Решения

    Как обычно, для демонстрации примеров данной лекции построено Решение с именем лекции Ch2_2. В Решение включен единственный консольный проект с именем StructuresAndClasses. К проекту присоединены две DLL с именами ClassSamples и Geometrical_Figures, включенные в Решение Ch2_1, которое приведено в предыдущей лекции. Классы CPoint и SRational из этих библиотек используются в консольном проекте. Консольный проект содержит традиционный класс Testing, большое число его методов представляют собой различные тесты, которые иллюстрируют работу с различными объектами классов из DLL, присоединенных к консольному проекту.

    Задачи

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

  • Создать DLL с классом Point, описывающим точку на плоскости, заданную декартовыми и полярными координатами. Среди методов класса Point предусмотреть метод Distance, вычисляющий расстояние до заданной точки. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с классом Point.
  • Создать DLL с классами Point и Line, описывающими точку и линию на плоскости. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL. Среди методов класса Line предусмотреть метод, определяющий принадлежность заданной точки линии.
  • Создать DLL с классами Point и Square, описывающими точку и квадрат на плоскости со сторонами, параллельными осям координат. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL. Среди методов класса Square предусмотреть метод, определяющий принадлежность заданной точки квадрату.
  • Создать DLL с классами Point и Circle, описывающими точку и круг на плоскости. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL. Среди методов класса Circle предусмотреть метод, определяющий принадлежность заданной точки кругу.
  • Создать DLL с классами Point и Rectangle, описывающими точку и прямоугольник на плоскости со сторонами, параллельными осям координат. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL. Среди методов класса Rectangle предусмотреть метод, определяющий принадлежность заданной точки прямоугольнику.
  • Создать DLL с классами Point и Rhomb, описывающими точку и ромб на плоскости с осями, параллельными осям координат. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL. Среди методов класса Rhomb предусмотреть метод, определяющий принадлежность заданной точки ромбу.
  • Создать DLL с классами Point и Tetragon, описывающими точку и четырехугольник на плоскости. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL. Среди методов класса Tetragon предусмотреть метод, определяющий принадлежность заданной точки четырехугольнику.
  • Создать DLL с классами Point и Triangle, описывающими точку и треугольник на плоскости. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL. Среди методов класса Triangle предусмотреть метод, определяющий принадлежность заданной точки треугольнику.
  • Создать DLL с классами Account и Accounts, описывающими счет в банке и множество счетов клиентов, хранящихся в файле. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL.
  • Создать DLL с классами Lexeme и AriphmeticExpression, описывающими лексему и арифметическое выражение. Среди методов класса AriphmeticExpression предусмотреть метод, представляющий выражение в виде массива лексем. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL.
  • Создать DLL с классами Lexeme и LogicExpression, описывающими лексему и логическое выражение. Среди методов класса LogicExpression предусмотреть метод, представляющий выражение в виде массива лексем. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL.
  • Создать DLL с классами Lexeme и Assignment, описывающими лексему и оператор присваивания. Среди методов класса Assignment предусмотреть метод, представляющий оператор в виде массива лексем. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL.
  • Создать DLL с классами Student и Exam, описывающими студента и экзамен. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL.
  • Создать DLL с сервисным классом Poisk, содержащего методы линейного и бинарного поиска. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL.
  • Создать DLL с сервисным классом Sorting, содержащего методы сортировки. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL.
  • Создать DLL с интерфейсным классом InputArray, позволяющий вводить одномерный массив с проверкой корректности ввода элементов. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с DLL.
  • Проекты

  • Дано множество точек P1, P2, $$\dots$$ Pn со случайными координатами. Построить множество точек P1, P2, $$\dots$$ Pm ( m <= n ), обход которых позволяет построить многоугольник без пересечений, если это возможно. Уметь вычислять площадь многоугольника, определять, является ли он выпуклым, принадлежит ли заданная точка внутренней области (границам) многоугольника. Построить реализацию в виде класса Polygon, включенного в состав DLL. Построить Windows-проект, предоставляющий пользователю интерфейс для работы с объектами класса Polygon.
  • Практикум 3. Разработка программ в Visual Studio 2010

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

    Вернуться к учебному плану