Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Domnin_Lab_9-12 / ДОКУМЕНТЫ_9-12 / ЛАБ_11_C# / Интерфейсы_структуры_и_методы2

.doc
Скачиваний:
16
Добавлен:
02.02.2015
Размер:
298.5 Кб
Скачать

ЛАБ.РАБ_№ 11

Интерфейсы, структуры и перечисления

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

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

Перечисления – списки именованных целочисленных констант

ИНТЕРФЕЙСЫ

Рассматривается подход- что класс должен делать, а не как он должен это делать. Пример – абстрактный метод определяет сигнатуру для метода, но не выполняет его реализацию. В производном классе каждый абстрактный метод, определенный базовым классом, реализуется по-своему, т.е. абстрактный метод задает интерфейс для метода, но не способ реализации. В С# c помощью ключевого слова interface полностью отделяется интерфейс класса от его реализации. После определения интерфейса ег может реализовать любое количество классов и наоборот один класс может реализовать множество интерфейсов. Реализуя интерфейс, С# в полной мере использует полиморфизм – “один интерфейс – много методов”.

Упрощенная форма объявления интерфейса:

interface имя {

тип_врзврата имя_метода1(список_параметров);

тип_врзврата имя_метода2(список_параметров);

// ……….

тип_врзврата имя_методаN(список_параметров);

}

Здесь:

имя – имя интерфейса, любое, но лучше целевое, ряд – series, лучше-ISeries,

имя_методаNнабор public-методов (без указания спецификатора), которые будут реализованы каждым классом, включающих интерфейс,

тип_врзврата - используются встроенные типы,

список_параметров –

Пример описания интерфейса для класса, генерирующего ряд чисел:

public interface ISeries {

int getNext(); // Возвращает следующее число ряда.

void reset(); // Выполняет перезапуск.

void setStart(int x); // Устанавливает начальное значение.

}

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

Интерфейсы не могут:

- иметь членов данных,

- определять конструкторы, деструкторы или операторные методы,

- быть объявленными статически (ни один член интерфейса).

Формат записи класса, который реализует интерфейс совпадает с форматом описания производного класса и имеет вид:

class мя_класса : имя_интерфейса {

// тело класса

}

Класс должен реализовать интерфейс в полном объеме, со всеми методами. При реализации нескольких интерфейсов их имена разделяются запятыми. При наследовании базового класса его имя должно стоять перед списком интерфейсов (через запятые). Реализация методов должна быть открытая, как и их описание в интерфейсе. Сигнатура типа в реализации метода должна полностью совпадать с сигнатуой типа в описании интерфейса.

Ниже приводться первая программа с интерфейсами

1

// Интерфейс. Назначение.Структура.Первая программа.Ее структура

// Создание интерфейса - возможен отдельный файл ISeries.cs

using System;

public interface ISeries

{

int getNext(); // Возврат следующего и предыдущего чисел ряда

void reset(); // Выполнение перезапуска

void setStart(int x); // Установка начального значения

}

// Реализация класса интерфейса - возможен отдельный файл AllFive.cs

class AllFive : ISeries

{

int start;

int val;

public AllFive()

{

start = 0;

val = 0;

}

public int getNext()

{

val += 5;

return val;

}

public void reset()

{

val = start;

}

public void setStart(int x)

{

start = x;

val = start;

}

} // Включены все (!!!) методы интерфейса

// Основной класс выполнения задачи - возможен отдельный файл FirstSeries

class FirstSeries

{

public static void Main()

{

AllFive ob = new AllFive();

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

Console.WriteLine(" Следующее значение равно " + ob.getNext());

Console.WriteLine("\n Переход в исходное состояние ");

ob.reset();

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

Console.WriteLine(" Следующее значение равно " + ob.getNext());

Console.WriteLine("\n Начинаем с числа 100 ");

ob.setStart(1000);

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

Console.WriteLine(" Следующее значение равно " + ob.getNext());

Console.WriteLine("\n\n\n ");

}

}

Рис 1 Результаты работы с первой программой с интерфейсами

В данной программе соблюдены все рекомендации и проблем не возникло.

Рассмотрим второй пример работы с интерфейсами, когда все получаемые числа включают только цифры 5. Здесь рассматриваются числа вида 5, 55, 555, 5555, ……5555555555, 55555555555555, 55 555 555 555 555 555 555 – если работать с типом ulong , то предельно допустимое число меньше последнего и при попытке получить последнее число из пяторок возникнет переполнение. При работе с такими длинными числами прийдется потрудиться над их представлением на экране.

Для решения задачи применяем две технологии: первая – без использования интерфейсов, вторая – с применением интерфейсов. Первая технология кажется значительно проще, но она не обладает теми большими преимуществами работы с интерфейсами, которые были рассмотрены выше.

2 - приведен текст программы без работы с интерфейсами

//Проба для типа ulong числа, содержащие только пятерки

using System;

class proba {

public static void Main()

{

ulong s = 0, sum=0;

int i;

Console.WriteLine("\n i\ts\t\t\tsum"); // \t -

for (i = 0; i < 20; i++)

{

s = s * 10 + 5; // Получаем все допустимые искомые числа

sum = sum + s; // Получаем сумму этих чисел

// Внимательно рассмотрите организацию вывода результатов на экран

Console.WriteLine("{0,4}{1,22}{2,2}{3}", i, s," ", sum);

}

Console.WriteLine("\n\n\n ");

}

}

Рис 2 Для типа ulong получены все числа, включающие только пятерки

и их суммы ( при i = 20 наступает переполнение).

Разработку текста программы c интерфейсами для рассматриваемой задачи рассмотрим поэтапно.

1 За основу структуры текста раздела interfase принимаем рассмотренный в задаче № 1 раздел interfase с уточнением особенности нашей задачи. Из текста примера №2 видно, что получаем сумму двух рядов – s и sum. Поэтому в раздел interfase добавляем два метода – getNexts() (для ряда s) и getNextsum() (для ряда sum). Получаем конечный текст программы этого блока в виде:

public interface ISeries

{

long getNexts(); // Возврат следующего

long getNextsum();

void reset(); // Выполнение перезапуска

void setStart(int x); // Установка начального значения

}

  1. Класс со всеми интерфейсными методами назовем AllFive. В этом классе располагаются начальные значения полей данных, методы последующих значений рядов чисел (getNexts() и getNextsum() ), повторного выполнения (reset()), выполнения с предварительно введенного начального значения (retStart(ulong x)). Основной класс (class AllFirst : ISeries), кроме функции Main(), включает выделение памяти и организацию выводов всех результатов. Предлагаю студентам самостоятельно рассмотреть организацию вывода результатов. Ниже приводится полный текст программы.

3

using System;

// Реализация класса интерфейса - возможен отдельный файл AllFive.cs

class AllFive : ISeries

{ int start; // Начальное значение

int vals; // Возвращается следующее число ряда s.

int valsum; // Возвращает следующее число ряда sum.

public AllFive()

{ start = 0;

vals = 0;

valsum = 0;

}

public int getNexts()

{ vals *= 10;

vals = vals + 5;

//Console.WriteLine("vals2="+vals);

return vals;

}

public int getNextsum()

{ valsum = valsum +vals;

//Console.WriteLine("valsum="+valsum);

return valsum;

}

public void reset()

{ vals = start;

valsum = start;

//Console.WriteLine("vals1="+vals);

}

public void setStart(ulong x)

{

valsum = x;

start = x;

vals = start;

// Console.WriteLine("vals3="+vals);

}

} // Включены все (!!!) методы интерфейса

//Основной класс выполнения задачи - возможен отдельный файл FirstSeries

class FirstSeries

{ public static void Main()

{ AllFive ob = new AllFive();

// AllFive ob1 = new AllFive();

Console.WriteLine("\n i\ts\t\t\tsum");

for (int i = 0; i < 12; i++)

Console.WriteLine("{0,4}{1,20}{2,2}{3}", i, ob.getNexts(), " ", ob.getNextsum());

Console.WriteLine("\n Переход в исходное состояние ");

ob.reset();

for (int i = 0; i < 12; i++)

Console.WriteLine("{0,4}{1,20}{2,2}{3}", i, ob.getNexts(), " ", ob.getNextsum());

Console.WriteLine("\n Начинаем с 0 или из одних пятерок, но не более 8, начнем с 5555 ");

ob.setStart(5555);

for (int i = 0; i < 12; i++)

Console.WriteLine("{0,4}{1,20}{2,2}{3}", i, ob.getNexts(), " ", ob.getNextsum());

Console.WriteLine("\n\n\n ");

}

}

Рис 3а

Рис 3b

Рис 3c

Рис 3 Демонстрация трех режимов работы

а – начальный запуск текста программы с интерфейсами;

b – повторный запуск в режиме reset();

c – запуск при начальном вводе числа 5555

Применение интерфейсных ссылок

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

№ 4

// Работа с интерфейсными ссылками

using System;

// Определение интерфейса

public interface ISeries

{ ulong getNext(); // Возвращение следующего числа ряда

ulong getNextsum(); // Возвращение следкющего числа суммы

void reset(); // Выполнение перезапуска

void setStart(ulong x); // Установка начального значения

}

// Реализация класса интерфейса - возможен отдельный файл AllFive.cs

class AllFive : ISeries

{

ulong start; // Начальное значение

ulong vals; // Возвращается следующее число ряда s.

public AllFive()

{ start = 0;

val = 0;

}

public ulong getNext()

{ vals *= 10;

vals = vals + 5;

//Console.WriteLine("vals2="+vals);

return vals;

}

} // Включены все (!!!) методы интерфейса

// Реализация класса интерфейса - возможен отдельный файл AllSeven.cs

class AllSeven : ISeries

{ ulong start; // Начальное значение

ulong vals; // Возвращается следующее число ряда s.

ulong valsum; // Возвращает следующее число ряда sum.

public AllSeven()

{ start = 0;

vals = 0;

valsum = 0;

}

public ulong getNext()

{ vals *= 10;

vals = vals + 7;

//Console.WriteLine("vals2="+vals);

return vals;

}

public ulong getNextsum()

{

valsum = valsum + vals;

//Console.WriteLine("valsum="+valsum);

return valsum;

}

public void reset()

{ vals = start;

valsum = start;

//Console.WriteLine("vals1="+vals);

}

public void setStart(ulong x)

{ valsum = x;

start = x;

vals = start;

// Console.WriteLine("vals3="+vals);

}

}

// Основной класс выполнения задачи - возможен отдельный файл FirstSeries

class FirstSeries

{ public static void Main()

{ AllFive ob5 = new AllFive();

AllSeven ob7 = new AllSeven();

ISeries ob;

Console.WriteLine("\n i\ts5\t\t\tsum5\t\ts7\t\tsum7");

for (int i = 0; i < 16; i++)

{

ob = ob5;

Console.Write("{0,2}{1,18}{2,2}{3,16}", i, ob.getNext(), " ", ob.getNextsum());

ob = ob7;

Console.WriteLine("{0,2}{1,19}{2,2}{3,17}", " ", ob.getNext(), " ", ob.getNextsum());

}

Console.WriteLine("\n\n\n ");

}

}

Рис 4 Применение интерфейсной ссылки ob при построении рядов чисел из

пятерок и семерок (s5 и s7) и их сумм (sum5 и sum7), прии этом

для i-го ряда i=k

sum(k) =∑ si

Применение интерфейсных свойств

Свойства и методы определяются без тела. Формат спецификации свойства имеет вид

// Интерфейсное свойство

тип имя {

get;

set;

}

Только для чтения или только для записи используется свойство или только get или только set.

Приводится пример применения свойства для получения следующего элемента ряда в задаче из предыдущего раздела.

5

// Применение свойства в интерфейсе

using System;

// Определение интерфейса

public interface ISeries {

// Интерфейсное свойство

ulong next {

get ; // Возвращает следующее число ряда.

set ; // Устанавливает следующее число ряда.

}

}

// Реализация интерфейса Iseries.

class AllFive:ISeries {

ulong val;

public AllFive()

{ val = 0;

}

// получаем или устанавливаем значение ряда.

public ulong next

{

get

{

val *= 10;

val = val + 5;

return val;

}

set

{

val = value;

}

}

}

// Основной класс выполнения задачи

class FirstSeries

{

public static void Main()

{ AllFive ob = new AllFive();

AllFive ob1 = new AllFive();

Console.WriteLine("\n i\t\ts5\t\tНачиная с s=55555\n");

// Получаем доступ к ряду через свойство

ob1.next = 5555;

for (int i = 0; i < 20; i++)

Console.WriteLine("{0,3}{1,25}{2,25}", i, ob.next, ob1.next);

Console.WriteLine("\n\n\n ");

}

}

Рис 5 Применение свойства в интерфейсе

Применение индексатора в интерфейсе

Формат объявления интерфейсного индексатора

// Интерфейсный индексатор

тип_элемента this[int индекс] {

get;

set;

}

где get – метод – при применении индексатора только для чтения;

set – метод - при применении индексатора только для записи;

Рассмотрим пример применения индексатора только для чтения элементов ряда чисел для интерфейса ISeries.

6

// Интерфейс c индексатором.

using System;

public interface ISeries

{ // Интерфейсное свойство

int next

{ get; // Возвращает следующее число ряда

set; // Устанавливает следующее число ряда

}

// Интерфейсный индексатор

int this[int index]

{ get; // Возврат заданного члена ряда

}

}

// Реализация класса интерфейса

class AllFive : ISeries

{ int vals;

public AllFive()

{ vals = 0;

}

// С помощью свойств устанавливаем или получаем значения

public int next

{ get

{ vals *= 10;

vals = vals + 5;

return vals;

}

set

{ vals = value;

}

}

// Значение получается с помощью индексатора

public int this[int index] {

get

{ vals = 0;

for (int i = 0; i < index; i++)

{ vals *= 10;

vals = vals + 5;

}

return vals;

}

}

}

// Основной класс выполнения задачи

class FirstSeries

{

public static void Main()

{ AllFive ob = new AllFive();

Console.WriteLine("\n i\t\ts");

// Получаем доступ к ряду посредством свойства

Console.WriteLine("\n Получаем значения с помощью свойства");

for (int i = 0; i < 12; i++)

Console.WriteLine("{0,4}{1,20}{2,2}", i, ob.next, " ");

Console.WriteLine("\n Начать с 0 или \n с 55...( не более 8 пятерок),\n начнем с 5555 ");

ob.next = 5555;

for (int i = 0; i < 12; i++)

Console.WriteLine("{0,4}{1,20}{2,2}", i, ob.next, " ");

Console.WriteLine("\n Переход в исходное состояние ");

ob.next = 0;

// Получаем доступ к ряду с помощью индексатора

Console.WriteLine("\n Получаем значения с помощью индексатора");

for (int i = 0; i < 12; i++)

Console.WriteLine("{0,4}{1,20}{2,2}", i, ob[i], " ");

Console.WriteLine("\n\n\n ");

}

}

Рис 6 Результаты применения индексатора в интерфейсе

Наследование интерфейсов

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

7

// Наследование интерфейсов

using System;

public interface A

{

void metA();

}

public interface B : A

{ void metB();

void metB1();

}

public interface C : B

{ void metC();

}

class DA : A {

public void metA()

{ Console.WriteLine("\n DoOwn matA() ");

}

}

class DB : B

{ public void metA()

{ Console.WriteLine("\n Do matA() ");

}

public void metB()

{ Console.WriteLine(" DoOwn matB() ");

}

public void metB1()

{ Console.WriteLine(" DoOwn matB1() ");

}

}

class DC : C

{ public void metA()

{ Console.WriteLine("\n Do matA() ");

}

public void metB()

{ Console.WriteLine(" Do matB() ");

}

public void metB1()

{ Console.WriteLine(" Do matB1() ");

}

public void metC()

{ Console.WriteLine(" DoOwn matC() ");

}

}

class Own {

public static void Main()

{ DA oa = new DA();

DB ob = new DB();

DC oc = new DC();

oa.metA();

ob.metA();

ob.metB();

ob.metB1();

oc.metA();

oc.metB();

oc.metB1();

oc.metC();

Console.WriteLine("\n\n\n ");

}

}

Рис 7 Результаты работы при совместном насследовании классов и интерфейсов.

Работа со структурами (struct)

В С# используются ссылочные типы – это классы, и не ссылочные типы, обращение к которым проводится по значениям. Переменные ссылочного типа защищены, контролируются, контролируется память, способы доступа, очистка мусора и др. Но работа с ссылочными типами требует больше ресурсов времени и памяти и чем проще задача тем более ощутимые эти накладные расходы. Поэтому активно используются и не ссылочные типы, причем это касается не только простых переменных, но и объектов. Известно, что ссылочные типы всегда используют оператор new. C# обычно для массивов автоматически выделяет больший объем памяти, чем требуется . Это позволяет не пользоваться оператором new, что сокращает временные затраты на инициализацию массивов и позволяет задавать их начальные значения в виде:

тип [] х = {4, 37, 50, 48,77}

Для уменьшения накладных расходов при работе с объектами в С# используется не ссылочный тип данных, который называется структурами. Структуры подобны классам, но относятся к типу значений. Для их объявлений используется ключевое слово struct иформа тх объявления такая

struct имя_структуры : интерфейсы {

// объявление членов

}

Некоторые особенности структур:

1 Структуры не наследуют другие структуры или классы (но наследуют класс obejct ), не используют модификаторы abstract, virtual, protected.

2 Допускают реализацию (указываются через запятые) нескольких интерфейсов.

3 Членами структур могут быть методы, поля, индексаторы, свойства, операторные методы и события.

4 Может использовать оператор new и быть инициализирован (без new инициализация выполняется вручную).

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

8

// Работа со структурой

using System;

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

// бирка новорожденного - TallyOfBirth - TOB - string

// фамипие мамы - surname - s - string

// время суток - time - t - string

// день - day - d - byte, string

// месяц - month - m - byte, string

// год - year - y - ushort, int

// м / д - boy/girl - b/g - char

// вес - weight - w - double

// длина - length - l - double

// особенности - peculiarity - p - string

// повезло - сокращенные имена значений не имеют повторений

struct TOB {

public string surname; // s

public string time; // t

public byte day; // d

public byte month; // m

public int year; // y

public char rod; // r = b / g

public double weight; // w

public double length; // l

public string peculiarity; // p;

public TOB(string s, string t, byte d, byte m, int y, char r, double w, double l, string p)

{

surname = s;

time = t;

day = d;

month = m;

year = y;

rod = r;

weight = w;

length = l;

peculiarity = p;

}

}

// Использование структуры TOB

class StructTOB {

public static void Main() {

int i1=0,i2=0,i3=0,l=9,i,j; // Вспомогательные переменные

int[] k = {0,0,0,0,0,0,0,0,0}; // По количеству контролируемых названий \n (в нашем примере их 3,но возможно до 9)

// Вызов явного конструктора

TOB tob1 = new TOB("Королева", "10:56", 9, 3, 2013, 'b', 9.56, 48.54, "9");

// Вызов конструктора по умолчанию

TOB tob2 = new TOB();

// Создание объекта без вызова конструктора

// При обращении ВНАЧАЛЕ ИНИЦИАЛИЗИРОВАТЬ

TOB tob3;

Console.WriteLine("\n fam = "+tob1.surname+"\n time = " + tob1.time + "\n day = " + tob1.day + "\n month = " + tob1.month + "\n year = " + tob1.year + "\n " + "rod = " + tob1.rod + "\n weight = " + tob1.weight + "\n length = " + tob1.length + "\n peculiarity = " + tob1.peculiarity + "\n "); // Отформатированная структура вывода

Console.WriteLine();

/* Провека свободных, не заполненных полей, /n используя конструктор по умолчанию */

Console.WriteLine(" i1 = "+i1+" i2 = "+i2+" i3 = "+i3);

if (tob2.surname == null) {i1++; j=0; k[j]=1;} else { j=0;k[j]=0;}

if (tob2.time == null) {i2++; j=1; k[j]=1;} else { j=1;k[j]=0;}

if (tob2.day == 0) {i3++; j=2; k[j]=1;} else { j=2;k[j]=0;}

Console.WriteLine(" i1 = "+i1+" i2 = "+i2+" i3 = "+i3+"\n ");

for ( i = 0; i < 9; i++)

if (k[i] == 1) Console.WriteLine(" i = " + i + " Не заполнено!");

Console.WriteLine(" ");

// Начальная инициализация tob3

tob3.surname = " Demidetscay";

Console.WriteLine(tob3.surname +"\n\n\n ");

}

}

Рис 8 Результаты выполнения инициализации элементов структуры

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

9 Сравнение выполнения операций со структурами, с операциями, с классами

// Копирование структур и классов

using System;

// Определение структуры STR и класса CLA

// Определение структуры

struct STR {

public int x;

}

// Определение класса CLA

class CLA {

public int xc;

}

// Выполнение присваивания структур и присваивания объектов

class STRCLAs

{ public static void Main() {

Console.WriteLine("\n Присваивание структур a = b ");

STR a;

STR b;

a.x = 555;

b.x = 777;

Console.WriteLine(" a.x {0}, b.x {1}", a.x, b.x);

// Присваивание структур

a = b;

b.x = 999;

Console.WriteLine(" a.x {0}, b.x {1}", a.x, b.x);

Console.WriteLine("\n Присваивание классов aс = bс");

CLA ac = new CLA();

CLA bc = new CLA();

ac.xc = 555;

bc.xc = 777;

Console.WriteLine(" ac.xc {0}, bc.xc {1}", ac.xc, bc.xc);

// Присваивание классов

ac = bc;

bc.xc = 999;

Console.WriteLine(" ac.xc {0}, bc.xc {1}\n\n\n", ac.xc, bc.xc);

}

}

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

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

Заключение: в С++ - struct и class – эквивалентны; различие в доступе по умолчанию – для класса – закрытые, для структур – открытые;

в С# struct – определяют типы значений, а class –определяет ссылочные типы.

Работа с перечислениями ( enumeration - сокращенно enum )

Перечисление – конечное множество именованных целочисленных констант. Возможные форматы описания перечислений:

enum имя { список_перечисления }

enum имя : тип { список_перечисления }

где: enum – опознаватель, ключ перечисления;

имя – указывается имя типа перечисления;

тип – базовый тип элементов сиска;

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

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

{ a, b, c, d =20, e, f, r, q=51, t, g, h=55, i, k, p, x, y }

{ 0, 1, 2, 20, 10, 11, 12, 51, 52, 53, 55, 3, 5, вперед, назад, влево }

Кажущая вольность сомнительна, т.к. элементы списка должны быть одного типа. Неявные преобразования между целочисленными и перечислимыми типами не определены, поэтому, как правило, требуется выволнять явное преобразование типа. Обычно используется тип int, но допускается применения любого целочисленного типа (byte, sbyte, short, ushort, uint, long, ulong), кроме типа char.