Главная | Обратная связь | Поможем написать вашу работу!
МегаЛекции

Пользовательские типы данных в С.




 

Сегодняшнее занятие мы начнем со знакомства c пользовательскими типами данных в С, затем будем учиться работать с динамической памятью. В С совсем немного возможностей по построению пользовательских типов данных. Точнее, сами возможности практически безграничны, но вот "базовых кирпичиков", из которых все строится, всего 4. Это структуры (structures), объединения, или союзы (unions), битовые поля (bit fields) и перечисления (enumerations). Причем последний тип, перечисление, в С не очень популярен - никаких дополнительных преимуществ, кроме улучшения читаемости программы, он не дает. А наиболее распространен первый тип - структуры. Давайте с него и начнем.

структуры

Структуры. Представьте, что вам надо написать библиотеку для работы с комплексными числами. Ясно, что и результаты функций, и их параметры будут состоять из реальной и мнимой частей. Можно, конечно, для представления комплексного числа пользоваться массивом из двух double: /* Complex number as array */ double cmplx[2]; /* [0] - real part, [1] - img */ Можно, но не очень удобно - хотя бы потому, что вместо массива комплексных чисел вам придется работать с двумерным массивом вещественных /* Array of complex numbers-arrays */ double cmplxarr[10][2]; /* [][0] - real part. [][1] - img */ Поэтому в таких случаях гораздо удобнее построить свой тип данных, чтобы переменная такого типа вела себя как одно целое. Для этого надо определить структуру: /* complex.h */ struct COMPLEX { double re; double im; }; или то же самое, но короче /* complex.h */ struct COMPLEX { double re, im; }; Как видите, для определения структуры сначала ставят ключевое слово struct, затем название типа (это не название переменной, а именно название пользовательского типа), а затем в фигурных скобках определяют так называемые поля - таким же образом, как вне структуры вы бы определяли переменные. Так, в нашей структуре для комплексных чисел есть два поля типа double с именами re и im. Обычно, если программа состоит из нескольких файлов, то такое описание типа выносят в файл-заголовок (поэтому я и написал в комментарии complex.h). А потом включают его в те файлы, где таким типом хотят пользоваться. Написав подобное определение, мы теперь можем пользоваться нашим новым типом данных - создавать переменные, указатели на него, массивы, и так далее. Только название нашего нового типа будет 'struct COMPLEX', а не COMPLEX. Вот примеры использования нового типа для создания различных объектов: #include "complex.h" struct COMPLEX number1, number2; /* два числа */ struct COMPLEX arr[10]; /* массив */ struct COMPLEX *ptr; /* указатель */ Кстати, если структура понадобилась только в одном файле, то можно даже совместить описание типа и создание переменных, вот так: struct COMPLEX { double re, im; } number1, *ptr; struct COMPLEX number2; Разумеется, проделывать такое в файле-заголовке не следует, иначе транслятор попытаться создать переменные с одинаковыми именами везде, куда вы этот заголовок включите. Ограничений на типы используемых полей очень немного - это могут быть встроенные типы, массивы, указатели, другие структуры. Нельзя, правда, включить в структуру ее саму в качестве поля. А вот указатель на эту же структуру можно, и этим часто пользуются при описании элемента дерева, связного списка и т.п. Вот пример структуры с разными полями: /* Структура - элемент односвязного списка */ struct LIST_ELM { char info[100]; /* поле-массив char */ /* Поле-указатель на предыдущий элемент */ struct LIST_ELM *prev; }; Прямо при создании структур (имеются в виду переменные, а не само описание типа) их можно инициализировать - задавать полям начальные значения. Форма записи при этом следующая (на примере LIST_ELM): struct LIST_ELM first = { "First item", NULL }; struct LIST_ELM second = { "Second item", &first }; Как видите, в фигурных скобках мы просто перечисляем инициализирующие значения подходящего типа. Разумеется, если бы поле само было структурой или массивом, то для его инициализации нам пришлось бы поставить вложенный список значений в фигурных скобках: struct USELESS { char *p; int a[2]; double d; }; struct USELESS useless = { NULL, { 1,2 }, 3.14 }; Я показал вам, как определять структуры, создавать и инициализировать переменные такого типа. Осталось еще научиться с ними работать. Операторы для работы с создаваемыми пользователем типами - один из признаков объектно-ориентированного языка, и, когда мы доберемся до С++, мы научимся проделывать подобные вещи. Но пока мы стараемся держаться в рамках С, а в нем не предусмотрено операций для работы с пользовательскими типами данных, и относится это не только к структурам. Поэтому с каждым полем вам придется работать, как с самостоятельной переменной. Но прежде всего надо научиться получать доступ к нужному полю. Для этого в С есть два специальных оператора -. (точка) и -> (стрелочка, составленная из минуса и знака "больше"). Точка используется, когда у вас есть сама структура, а стрелочка - если вы работаете с указателем на структуру. Вот как это выглядит в программе (на примере определенного раньше типа COMPLEX): #include "complex.h" struct COMPLEX number, *ptr; /* Работа со полями структуры - точка */ number.re = 1.0; number.im = number.re; /* Работа с полями через указатель - стрелочка */ ptr = &number; ptr->re=1.0; ptr->im=ptr->re; Разумеется, во втором случае можно было бы использовать комбинацию звездочки (доступ через указатель) и точки (*ptr).re=1.0; (*ptr).im=(*ptr).re; но в варианте со стрелочкой транслятору приходится разбираться не с двумя операторами, а с одним. А уж о читаемости программы и говорить не приходится, особенно если поля у вас сами являются указателями на структуры. Попробуйте переписать без стрелочки строку p->next->q1->i=0; и вы сразу это оцените. Вы, наверное, догадались из этих примеров - если отвлечься от формы записи, то можно считать, что поля структур ведут себя, как обычные переменные соответствующего типа. Вы можете ставить поля в выражения, передавать их функциям, получать адрес поля и так далее. Здесь у вас не должно быть особых трудностей, поскольку вы уже видели подобное поведение у элементов массива. Но все-таки структуры придуманы прежде всего для того, чтобы работать с разнородной информацией, как с одним целым. Пока все преимущества, которые мы видели - поля переменной сгруппированы в одном объекте и доступны через имя этой переменной. Что еще можно делать со структурой? Разумеется, передавать в функцию в качестве параметра и возвращать результат. Однако тут есть одна особенность. Если структура несёт в себе большое колличество данных, то передача её как аргумента (передача по значению) приведёт к копированию всех этих данных из одного места в другое. Чтобы избежать подобных накладных расходов эффективней передать указатель на структуру. Вот как это выглядит на практике: #include <stdio.h> #include "complex.h" /* Заносим комплексное значение по указанному адресу */ void set_complex(struct COMPLEX *n, double re, double im) { n->re = re; n->im = im; } /* Складываем два числа, возвращаем результат */ struct COMPLEX *add_complex(struct COMPLEX *n1, struct COMPLEX *n2) { static struct COMPLEX result; result.re = n1->re + n2->re; result.im = n1->im + n2->im; return &result; } main() { struct COMPLEX number1, number2, *ptr; set_complex(&number1, 1.0, 1.0); set_complex(&number2, 1,0, 0.0); ptr=add_complex(&number1, &number2); printf("%g %g\n", ptr->re, ptr->im); } Между прочим, попробуйте догадаться, почему я в функции add_complex для хранения результата использовал статическую структуру, а не автоматическую.

 

Битовые поля. Вот, собственно, и все, что я собирался сказать о структурах. Перейдем теперь к битовым полям. Прежде, чем показать, как они выглядят, приведу характерный пример, где они требуются. Представьте себе, что вы пишете программу для работы с контроллером КАМАК. И в документации на контроллер написано что-нибудь в таком духе: "CSR (cтатусный регистр) доступен по адресу 0xD8000. Для генерации цикла КАМАК номер функции (0..31) нужно занести в биты 1-5 статусного регистра, а в бит 0 занести 1. После завершения цикла бит 0 будет очищен контроллером.". И вам надо выполнить 8 функцию КАМАК, а затем дождаться завершения цикла. Разумеется, все это можно проделать с помощью операций битовой арифметики: volatile char *csr = (char*)0xD8000; /* Заслать в биты 1..5 функцию 8, взвести бит 0 */ *csr = (8<<1)|1 /* Дождаться, пока контроллер очистит бит 0 */ while ((*csr & 1)!= 0); Такое, конечно, сработает. Но писать долго, читать непонятно. Неплохо бы иметь возможность работать с группами битов как с переменными. Вот именно для этого и предусмотрены битовые поля. Само по себе определение битового поля очень похоже на определение структуры, только после каждого поля указывается его размер в битах. И в качестве типа поля указывать можно только int, signed или unsigned. Вот как мы могли бы описать статусный регистр из примера выше: struct CSR { unsigned busy: 1; /* Бит, запускающий цикл */ unsigned f: 5; /* 5 битов под функцию */ int unused: 3; /* Три неиспользуемых старших бита */ }; Теперь, создав переменную такого типа, мы будем иметь в ней три "маленьких" целых числа - unused с длиной 3 бита и диапазоном -4..3, беззнаковое f длиной 5 бит и диапазоном 0..31, и беззнаковое busy из одного бита, то есть, с диапазоном 0..1. Причем все эти переменные окажутся упакованными в один байт. И теперь мы можем проделать со статусным регистром требуемую работу в гораздо более понятной форме: volatile struct CSR *mycsr = (struct CSR *)0xD8000; /* Заслать в биты 1..5 функцию 8 */ csr->f = 8; /* взвести бит 0 */ csr->busy = 1; /* Дождаться, пока контроллер очистит бит 0 */ while (csr->busy); Можно еще более усовершенствовать определение нашего битового поля - не давать имя первой, неиспользуемой, группе битов, благо С это позволяет: struct CSR { unsigned busy: 1; /* Бит, запускающий цикл */ unsigned f: 5; /* 5 битов под функцию */ int: 3; /* Три неиспользуемых старших бита */ }; При этом 3 бита под безымянное поле все равно будут выделены, так что нужные нам поля f и busy окажутся на прежних местах. Хочу вас сразу предупредить. Единственный сюжет, когда стоит использовать битовые поля - это именно работа с внешними устройствами. Ну, может быть изредка еще и перепаковка данных из одного кода в другой. Не стоит пытаться в расчетной задаче экономить с их помощью память - в скорости работы программы вы точно проиграете, а выигрыш по памяти будет скорее всего грошовым.

 

Объединения. Третий из определяемых пользователем типов данных - это объединение, или союз. Определяется этот тип с помощью другого ключевого слова - union, например, так. union DOUBLE_UCHAR8 { double d; unsigned char uc[8]; }; Как видите у союза тоже есть поля. Вот только ведут себя эти поля совсем иначе, не так, как в структуре. Дело в том, что все поля союза располагаются по одному адресу.

 

Перечисления. И, наконец, последний тип - перечисление (enumeration). По-моему, наименее популярный - это можно понять даже по соотношению частоты определения перечислений и структур в той же стандартной библиотеке С. Этот тип позволяет создавать синонимы для последовательности целых чисел, а затем эти синонимы использовать в качестве символических констант. Или использовать сам этот тип в качестве параметра функции. Например, мы можем написать enum BOOLEAN { FALSE, TRUE }; А потом использовать вместо 0 и 1 имена FALSE и TRUE. Или использовать имя нашего типа для задания параметра: void f(enum BOOLEAN flag) { if (flag==FALSE) printf("flag==FALSE\n"); else printf("flag==TRUE\n"); } main() { f(FALSE); f(TRUE); }

 


 

Операторы и операции языка

Операции языка СИ (C)

 

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

 

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

Знак операции Назначение операции

() Вызов функции

[ ] Выделение элемента массива

. Выделение элемента записи

-> Выделение элемента записи

! Логическое отрицание

~ Поразрядное отрицание

- Изменение знака

++ Увеличение на единицу

-- Уменьшение на единицу

& Взятие адреса

* Обращение по адресу

(тип)

Преобразование типа (т.е. (float) a)

sizeof()

Определение размера в байтах

* Умножение

/ Деление

% Определение остатка от деления

+ Сложение

- Вычитание

<< Сдвиг влево

>> Сдвиг вправо

< Меньше, чем

<= Меньше или равно

> Больше, чем

>= Больше или равно

= = Равно

!= Не равно

& Поразрядное логическое "И"

^ Поразрядное исключающее "ИЛИ"

| Поразрядное логическое "ИЛИ"

&& Логическое "И"

|| Логическое "ИЛИ"

?: Условная (тернарная) операция

= Присваивание

+=, - =, *=, /=, %=, <<=, >>=, &=, |=, ^=

Бинарные операции (например, а *= b (т.е. a = a * b) и т.д.)

, Операция запятая

Оператор в языке Си (C)

Для исключения путаницы в понятиях "операция" и "оператор", отметим, что оператор - это наименьшая исполняемая единица программы.

Различают унарные и бинарные операции. У первых из них один операнд, а у вторых - два. Начнем их рассмотрение с операций, отнесенных к первой из следующих традиционных групп:

Арифметические операции.

Логические операции и операции отношения.

Операции с битами.

Арифметические операции задаются следующими символами (табл. 2): +, -, *, /, %. Последнюю из них нельзя применять к переменным вещественного типа. Например:

 

a = b + c;

x = y - z;

r = t * v;

s = k / l;

p = q % w;

Логические операции отношения задаются следующими символами (см. табл. 2): && ("И"), || ("ИЛИ"),! ("НЕ"), >, >=, <, <=, = = (равно),!= (не равно). Традиционно эти операции должны давать одно из двух значений: истину или ложь. В языке СИ (C)принято следующее правило: истина - это любое ненулевое значение; ложь - это нулевое значение. Выражения, использующие логические операции и операции отношения, возвращают 0 для ложного значения и 1 для истинного. Ниже приводится таблица истинности для логических операций.

x

y

x&&y

x||y

!x

 

 

 

 

 

 

Битовые операции можно применять к переменным, имеющим типы int, char, а также их вариантам (например, long int). Их нельзя применять к переменным типов float, double, void (или более сложных типов). Эти операции задаются следующими символами: ~ (поразрядное отрицание), << (сдвиг влево), >> (сдвиг вправо), & (поразрядное "И"), ^ (поразрядное исключающее "ИЛИ"), | (поразрядное "ИЛИ").

Примеры: если a=0000 1111 и b=1000 1000, то

~a = 1111 0000,

a << 1 = 0001 1110,

a >> 1 = 0000 0111,

a & b = 0000 1000,

a ^ b = 1000 0111,

a | b = 1000 1111.

В языке предусмотрены две нетрадиционные операции инкремента (++) и декремента (--). Они предназначены для увеличения и уменьшения на единицу значения операнда. Операции ++ и -- можно записывать как перед операндом, так и после него. В первом случае (++n или --n) значение операнда (n) изменяется перед его использованием в соответствующем выражении, а во втором (n++ или n--) - после его использования. Рассмотрим две следующие строки программы:

a = b + c++;

a1 = b1 + ++c1;

Предположим, что b = b1 = 2, c = c1 = 4. Тогда после выполнения операций: a = 6, b = 2, c = 5, a1 = 7, b1 = 2, c1 = 5.

Широкое распространение находят также выражения с еще одной нетрадиционной тернарной или условной операцией?:. В формуле

y = x? a: b;

y = a, если x не равно нулю (т.е. истинно), и y = b, если х равно нулю (ложно). Следующее выражение

y = (a>b)? a: b;

позволяет присвоить переменной у значение большей переменной (а или b), т.е. y = max(a, b).

Еще одним отличием языка является то, что выражение вида а = а + 5; можно записать в другой форме: a += 5;. Вместо знака + можно использовать и символы других бинарных операций (см. табл. 2).

Другие операции из табл. 2 будут описаны в последующих параграфах.

 

Операторы цикла

Циклы организуются, чтобы выполнить некоторый оператор или группу операторов определенное число раз. В языке СИ (C)три оператора цикла: for, while и do - while. Первый из них формально записывается, в следующем виде:

for (выражение_1; выражение_2; выражение_3) тело_цикла

Тело цикла составляет либо один оператор, либо несколько операторов, заключенных в фигурные скобки {... } (после блока точка с запятой не ставится). В выражениях 1, 2, 3 фигурирует специальная переменная, называемая управляющей. По ее значению устанавливается необходимость повторения цикла или выхода из него.

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

Примеры:

for (i = 1; i < 10; i++)

{...

}

for (сh = 'a'; ch!= 'p';) scanf ("%c", &ch);

 

/* Цикл будет выполняться до тех пор, пока с клавиатуры

 

не будет введен символ 'p' */

 

Любое из трех выражений в цикле for может отсутствовать, однако точка с запятой должна оставаться. Таким образом, for (;;) {...} - это бесконечный цикл, из которого можно выйти лишь другими способами.

 

В языке СИ (C)принято следующее правило. Любое выражение с операцией присваивания, заключенное в круглые скобки, имеет значение, равное присваиваемому. Например, выражение (а=7+2) имеет значение 9. После этого можно записать другое выражение, например: ((а=7+2)<10), которое в данном случае будет всегда давать истинное значение. Следующая конструкция:

((сh = getch()) == 'i')

позволяет вводить значение переменной сh и давать истинный результат только тогда, когда введенным значением является буква 'i'. В скобках можно записывать и несколько формул, составляющих сложное выражение. Для этих целей используется операция запятая. Формулы будут вычисляться слева направо, и все выражение примет значение последней вычисленной формулы. Например, если имеются две переменные типа char, то выражение

 

z = (х = у, у = getch());

 

определяет следующие действия: значение переменной у присваивается переменной х; вводится символ с клавиатуры и присваивается переменной у; z получает значение переменной у. Скобки здесь необходимы, поскольку операция запятая имеет более низкий приоритет, чем операция присваивания, записанная после переменной z. Операция запятая находит широкое применение для построения выражений цикла for и позволяет параллельно изменять значения нескольких управляющих переменных.

Допускаются вложенные конструкции, т.е. в теле некоторого цикла могут встречаться другие операторы for.

Оператор while формально записывается в таком виде:

while (выражение) тело_цикла

Выражение в скобках может принимать ненулевое (истинное) или нулевое (ложное) значение. Если оно истинно, то выполняется тело цикла и выражение вычисляется снова. Если выражение ложно, то цикл while заканчивается.

Оператор do-while формально записывается следующим образом:

do {тело_цикла} while (выражение);

Основным отличием между циклами while и do - while является то, что тело в цикле do - while выполняется по крайней мере один раз. Тело цикла будет выполняться до тех пор, пока выражение в скобках не примет ложное значение. Если оно ложно при входе в цикл, то его тело выполняется ровно один раз.

Допускается вложенность одних циклов в другие, т.е. в теле любого цикла могут появляться операторы for, while и do - while.

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

Операторы условных и безусловных переходов

Для организации условных и безусловных переходов в программе на языке СИ (C)используются операторы: if - else, switch и goto. Первый из них записывается следующим образом:

if (проверка_условия) оператор_1; else оператор_2;

Если условие в скобках принимает истинное значение, выполняется оператор_1, если ложное - оператор_2. Если вместо одного необходимо выполнить несколько операторов, то они заключаются в фигурные скобки. В операторе if слово else может отсутствовать.

В операторе if - else непосредственно после ключевых слов if и else должны следовать другие операторы. Если хотя бы один из них является оператором if, его называют вложенным. Согласно принятому в языке СИ (C)соглашению слово else всегда относится к ближайшему предшествующему ему if.

Оператор switch позволяет выбрать одну из нескольких альтернатив. Он записывается в следующем формальном виде:

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

{

case константа_1: операторы_1;

break;

case константа_2: операторы_2;

brea;

...............

default: операторы_default;

}

 

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

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

Допускаются вложенные конструкции switch.

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

 

goto метка;

 

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

 

Турбо отладчик полностью поддерживает синтаксис выражений языка СИ (C). Выражение состоит из смеси операций, строк, переменных

 

и констант.

Циклические операторы

С# включает достаточно большой набор циклических операторов, таких как for, while, do...while, а также цикл с перебором каждого элемента foreach. Кроме того, С# поддерживает операторы перехода и возврата, например goto, break, continue и return.

Оператор goto

 

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

Он используется следующим образом:

1. Создается метка в коде программы Label 1.

2. Организуется переход на эту метку goto Labe1.

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

 

using System;

 

public class Labels

{

public static int Main()

{

int i = 0;

Label:

Console.WriteLine("i: {0 } ", i);

i = i + 1;

if (i < 10)

goto Label;

return 0;

}

}

 

 

Здесь мы выводим на экран строку со значением i десять раз (от 0 до 9). Инструкция goto помогает повторить выполнение одних и тех же инструкций определенное число раз. В этой программе число повторов определяется инструкцией if(i < 10). Значит, до тех пор пока переменная I будет иметь значение меньшее, чем 10, goto будет переносить нас на метку label:, а значит, вывод строки на экран будет повторяться. То есть с использованием goto мы можем организовать циклический повтор операций в программе.

Именно это явление привело к созданию альтернативного метода организации циклов, такого как while, do..while, for или foreach. Большинство программистов понимают, что использование goto в программе лучше заменять чем-нибудь другим, что приведет к созданию программного кода, более структурированного и понятного, нежели инструкции goto.

 

Цикл while

 

Эта циклическая инструкция работает по принципу: «Пока выполняется условие — происходит работа». Ее синтаксис выглядит следующим образом:

 

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

{

инструкция;

}

 

 

Как и в других инструкциях, выражение — это условие, которое оценивается как булево значение. Если результатом проверки условия является истина, то выполняется блок инструкций, в противном случае в результате выполнения программы while игнорируется. Рассмотрим пример, приведенный выше, только с использованием while:

 

 

using System;

 

public class WhileCycle

{

public static int Main()

{

int i = 0;

while (i < 10)

{

Console.WriteLine("i: {0}", i);

++i;

}

return 0;

}

}

 

По своей функциональности и та, и другая реализация программы работают абсолютно одинаково, но логика работы несколько изменилась. Заметьте, что цикл while проверяет значение i перед выполнением блока statement. Это гарантирует, что цикл не будет выполняться, если проверяемое условие ложно. Таким образом, если первоначально i примет значение 10 и более, цикл не выполнится ни разу. Инструкция while является вполне самостоятельной, а в данном примере ее можно прочитать подобно предложению: «пока i меньше 10, выводим сообщение на экран и увеличиваем i».

 

Цикл do... while

 

Бывают случаи, когда цикл while не совсем удовлетворяет вашим требованиям. Например, вы хотите проверять условие не в начале, а в конце цикла. В таком случае лучше использовать цикл do...while.

do

{

Инструкция

}

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

 

Подобно while, выражение — это условие, которое оценивается как булево значение.

Это выражение можно прочитать как: «выполнить действие; если выполняется условие — повторить выполнение еще раз». Заметьте разницу между этой формулировкой и формулировкой работы цикла while. Разница состоит в том, что цикл do...while выполняется всегда минимум один раз, до того как произойдет проверка условия выражения.

 

using System;

 

public class DoWhile

{

public static int Main()

{

int i = 0;

do

{

Console.WriteLine("i: {0}", i);

++i;

}

while (i < 10);

return 0;

}

}

 

 

На этом примере видно, что если первоначально i примет значение 10 и более, цикл выполнится. Затем произойдет проверка условия while (i < 10), результатом которой станет ложь (false), и повтора выполнения цикла не произойдет. То есть он выполнится один раз. Как вы помните, при таких же начальных условиях while не выполнился ни разу.


 

Строки и работа с ними

Лабораторная работа №6

РАБОТА СО СТРОКАМИ В ЯЗЫКЕ С

Цель работы: изучить базовые операции работы со строками.

Теоретические сведения

В языке С нет специального типа данных для строковых переменных. Для

этих целей используются массивы символов (тип char). Следующий пример

демонстрирует использование строк в программе:

char str_1[100] = {‘П’,’р’,’и’,’в’,’е’,’т’,’\0’};

char str_2[100] = “Привет”;

char str_3[] = “Привет”;

printf(“%s\n%s\n%s\n”,str_1,str_2,str_3);

В приведенном примере показаны три способа инициализации строковых

переменных. Первый способ является классическим объявлением массива,

второй и третий используются специально для строк. Причем в последнем

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

Анализируя первый и второй способы инициализации массива символов

возникает вопрос: каким образом язык С++ «знает» где заканчивается строка?

Действительно, массив str_2 содержит 100 элементов, а массив str_3 меньше

100, тем не менее длина строки и в первом и во втором случаях одна и та же.

Такой эффект достигается за счет использования специальных управляющих

кодов, которые говорят где заканчивается строка или где используется перенос

внутри одной строки и т.п. В частности символ ‘\0’ означает в языке С++ конец

строки и все символы после него игнорируются как символы строки.

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

специального символа.

char str1[10] = {‘Л’,’е’,’к’,’ц’,’и’,’я’,’\0’};

char str2[10] = {‘Л’,’е’,’к’,’ц’, ’\0’,’и’,’я’ };

char str3[10] = {‘Л’,’е’, ’\0’,’к’,’ц’,’и’,’я’ };

printf(“%s\n%s\n%s\n”,str1,str2,str3);

Результатом работы данного кода будет вывод следующих трех строк:

Лекция

Лекц

Ле

Из этого примера видно как символ конца строки ‘\0’ влияет на длину

строк. Таким образом, чтобы подсчитать длину строки (число символов)

необходимо считать символы до тех пор, пока не встретится символ ‘\0’ или не

будет достигнут конец массива. Функция вычисления размера строк уже

реализована в стандартной библиотеке языка С string.h и имеет следующий

синтаксис:

int strlen(char* str);

где char* str – указатель на строку (об указателях речь пойдет ниже).

Следующая программа показывает использование функции strlen().

Листинг 5. Пример использования функции strlen().

#include <stdio.h>

#include <string.h>

int main(void) {

char str[] = “Привет мир!”;

int length = strlen(str);

printf(“Длина строки = %d.\n”,length);

return 0;

}

Результатом работы программы будет вывод на экран числа 11. Учитывая,

что первый символ имеет нулевой индекс, то можно заметить, что данная

функция считает и символ ‘\0’.

Теперь рассмотрим правила присваивания одной строковой переменной

другой. Допустим, объявлены две строки

char str1[] = “Это первая строка”;

char str2[] = “Это вторая строка”;

и необходимо выполнить оператор присваивания

str1 = str2;

При такой записи оператора присваивания компилятор выдаст сообщение

об ошибке. Для того чтобы выполнить копирование необходимо перебирать по

порядку элементы одного массива и присваивать их другому массиву. Данная

функция реализована в библиотеке языка С string.h и имеет следующее

определение:

char* strcpy(char* dest, char* src);

Она выполняет копирование строки src в строку dest и возвращает строку

dest. В листинге 6 показано использование функции strcpy().

Листинг 6. Пример использования функции strcpy().

#include <stdio.h>

#include <string.h>

int main(void) {

char src[] = “Привет мир!”;

char dest[100];

strcpy(dest,src);

printf(“%s\n”,dest);

return 0;

}

Кроме операций вычисления длины строки и копирования строк важной

является операция сравнения двух строк между собой. В языке С две строки

считаются одинаковыми, если равны их длины и элементы одной строки равны

соответствующим элементам другой. Функция сравнения двух строк имеет вид:

int strcmp(char* str1, char* str2);

и реализована в библиотеке string.h. Данная функция возвращает нуль, если

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

использования данной функции.

char str1[] = “Это первая строка”;

char str2[] = “Это вторая строка”;

if(strcmp(str1,str2) == 0) printf(“Срока %s равна строке

%s\n”,str1,str2);

else printf(“Срока %s не равна строке %s\n”,str1,str2);

В языке С имеется несколько функций, позволяющих вводить строки с

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

функция scanf(), которой в качестве параметра передается ссылка на массив

символов:

char str[100];

scanf(“%s”,str);

В результате выполнения этого кода, переменная str будет содержать

введенную пользователем последовательность символов. Кроме функции

scanf() также часто используют функцию gets() библиотеки stdio.h, которая в

качестве аргумента принимает ссылку на массив символов:

gest(str);

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

нажмет клавишу Enter, т.е. введет символ перевода строки ‘\n’. Затем она

записывает вместо символа ‘\n’ символ ‘\0’ и передает строку вызывающей

программе.

Для вывода строк на экран помимо функции printf() можно использовать

также функцию puts() библиотеки stdio.h, которая более проста в

использовании. Следующий пример демонстрирует применение данной

функции.

#define DEF “Заданная строка”

char str[] = “Это первая строка”;

puts(str);

puts(DEF);

puts(&str[4]);

 

Объектно-ориентированное программирование

Понятие об объектно-ориентированном программировании.

В основе объектно-ориентированного язык программирования лежат два основных понятия: объект и класс. Основными характеристическими свойствами этих понятий являются:

 

Инкапсуляция - комбинирование записей с процедурами и функциями, манипулирующими полями этих записей, формирует новый тип данных - объект (под записью понимается ᴨȇременная типа "запись").

 

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

 

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

Понятию “объект” сопоставляют ряд дополняющих друг друга определений. Ниже приведены некоторые из них.

 

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

 

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

 

Объект может быть охарактеризован структурой, его состоянием, поведением и индивидуальностью. Состояние объекта определяется ᴨȇречнем всех возможных (обычно статических) свойств и текущими значениями (обычно динамическими) каждого из этих свойств.

 

В дополнение к этим положениям объектно-ориентированное программирование в MATLAB допускает агрегирование объектов, т. е., объединение частей объектов или ряда объектов в одно целое.

 

Объект можно определить как некоторую структуру, принадлежащую к определенному классу. В MATLAB определены следующие семь основных классов объектов:

 

double — числовые массивы с элементами-числами двойной точности;

sparse — двумерные числовые или комплексные разреженные матрицы;

char — массивы символов;

struct — массивы структур (записей);

cell — массивы ячеек;

javaarray — массивы Ява;

functionjnandle — дескрипторы функций.

 

 

Понятие класса и его структура. Создание и применение.

 

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

Свойства инкапсуляции:

• совместное хранение данных и функций;

• сокрытие внутренней информации от пользователя;

• изоляция пользователя от особенностей реализации.

 

Общедоступные объекты описываются с помощью зарезервированного слова public и

характеризуются доступностью из произвольного места программы, для которого

определено пространство имен с описанием рассматриваемого объекта. При этом

элементы интерфейсов и перечислений являются общедоступными (public) объектами

по умолчанию.

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

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

из сборки с описанием объекта и описываются с помощью зарезервированного слова

internal.

Наконец, элементы классов и структур, в частности, поля, методы, свойства и вложенные

типы являются по умолчанию доступными из описаний соответствующих классов или

структур и описываются с помощью зарезервированного слова private.

 

Проиллюстрируем обсуждение использования модификаторов областей видимости

объектов (public и private) следующим содержательным примером фрагмента

программы на языке C#:

public class Stack

{

private int[] val;

// private используется и по умолчанию

private int top;

// private используется и по умолчанию

public Stack() {...}

public void Push(int x) {...}

public int Pop() {...}

}

Как видно из приведенного примера, фрагмент программы на языке C# содержит

Поделиться:





Воспользуйтесь поиском по сайту:



©2015 - 2024 megalektsii.ru Все авторские права принадлежат авторам лекционных материалов. Обратная связь с нами...