Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
komu-nibud_da_prigoditsya.doc
Скачиваний:
47
Добавлен:
11.12.2015
Размер:
787.97 Кб
Скачать

Xhtml 1.0css 2.1ie 7ie 8ie 9Cr 8Op 12Sa 5Fx 4

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"

"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head>

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

<title>Идентификаторы</title>

<style type="text/css">

#A, .a {

border: none;

background: #f0f0f0;

color: green;

padding: 5px;

}

.b {

border: 1px solid red;

color: red;

padding: 0;

}

</style>

</head>

<body>

<p id="A" class="b">Стиль идентификатора</p>

<p class="a b">Стиль классов a и b</p>

<p class="b">Стиль класса b</p>

</body>

</html>

Для первого абзаца устанавливается стиль от идентификатора A и класса b, свойства которых противоречат друг другу. При этом стиль класса будет игнорироваться из-за особенностей каскадирования и специфичности. Для второго абзаца стиль задаётся через классы a и bодновременно. Приоритет у классов одинаковый, значит, в случае противоречия будут задействованы те свойства, которые указаны в стиле ниже. К последнему абзацу применяется стиль только от класса b. На рис. 21.1 показан результат применения стилей.

Рис. 21.1. Использование стилей для текста

Специфичность в каскадировании начинает играть роль при разрастании стилевого файла за счёт увеличения числа селекторов, что характерно для больших и сложных сайтов. Чтобы стиль применялся корректно, необходимо грамотно управлять специфичностью селекторов путем использования идентификаторов, повышения важности через !important, порядком следования свойств.

Классы

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

Пример 21.3. Использование классов

.r, .b {

padding: 10px;

background: #FCE3EE;

}

.r {

border-radius: 8px;

-webkit-border-radius: 8px;

-moz-border-radius: 8px

}

.b { border: 1px solid #ED1C24; }

.n { border: none; }

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

Пользовательские типы данных – структуры, их создание и применение.

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

Сегодняшнее занятие мы начнем со знакомства 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) ;

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

Само по себе определение битового поля очень похоже на определение структуры, только после каждого поля указывается его размер в битах. И в качестве типа поля указывать можно только intsigned или 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

0

0

0

0

1

0

1

0

1

1

1

0

0

1

0

1

1

1

1

0

Битовые операции можно применять к переменным, имеющим типы 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. Первый из них записывается следующим образом:

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]