Предыдущая лекция | &nbsp; | Следующая лекция
:----------------:|:----------:|:----------------:
[Основные элементы языка.](./t3l1.md) | [Содержание](../readme.md#тема-4-программирование-на-языке-c-основы) | [Массивы как структурированный тип данных.](./t3l1_3.md)

# Основы языка `C#`

**Содержание:**

* [Операторы отношения и логические операторы](#Операторы_отношения_и_логические_операторы)
* [Условные операторы](#Условные_операторы)
* [Циклы](#Циклы)
* [Операторы перехода](#Операторы_перехода)

## Операторы отношения и логические операторы

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

Ниже перечислены операторы отношения:

Оператор | Значение
:-------:|--------
`==`     | Равно
`!=`     | Не равно
`>`      | Больше
`<`      | Меньше
`>=`     | Больше или равно
`<=`     | Меньше или равно

К числу логических относятся операторы, приведенные ниже:

Оператор | Значение
:--------:|-------
`&` | И
`\|` | ИЛИ
`^` | Исключающее ИЛИ
`&&` | Укороченное И
`\|\|` | Укороченное ИЛИ
`!` | НЕ

Результатом выполнения оператора отношения или логического оператора является логическое значение типа **bool**.

В целом, объекты можно сравнивать на равенство или неравенство, используя операторы отношения `==` и `!=`. А операторы сравнения `<`, `>`, `<=` или `>=` могут применяться только к тем типам данных, которые поддерживают отношение порядка. Следовательно, операторы отношения можно применять ко всем числовым типам данных. Но значения типа **bool** могут сравниваться только на равенство или неравенство, поскольку истинные (`true`) и ложные (`false`) значения не упорядочиваются. Например, сравнение `true > false` в `C#` не имеет смысла.

Рассмотрим пример программы, демонстрирующий применение операторов отношения и логических операторов:

```cs
short d = 10, f = 12;
bool var1 = true, var2 = false;

if (d < f) 
    Console.WriteLine("d < f");
if (d <= f)
    Console.WriteLine("d <= f");
if (d != f)
    Console.WriteLine("d != f");

// Следующее условие не выполнится
if (d > f)
    Console.WriteLine("d > f");

// Сравниванием переменные var1 и var2
if (var1 & var2)
    Console.WriteLine("Данный текст не выведется");
if (!(var1 & var2))
    Console.WriteLine("!(var1 & var2) = true");
if (var1 | var2)
    Console.WriteLine("var1 | var2 = true");
if (var1 ^ var2)
    Console.WriteLine("var1 ^ var2 = true");

Console.ReadLine();
```

![](../img/03007.png)

### Укороченные логические операторы

В **C#** предусмотрены также специальные, *укороченные*, варианты логических операторов `И` и `ИЛИ`, предназначенные для получения более эффективного кода. Поясним это на следующих примерах логических операций. Если первый операнд логической операции `И` имеет ложное значение (`false`), то ее результат будет иметь ложное значение независимо от значения второго операнда. Если же первый операнд логической операции `ИЛИ` имеет истинное значение (`true`), то ее результат будет иметь истинное значение независимо от значения второго операнда. Благодаря тому что значение второго операнда в этих операциях вычислять не нужно, *экономится время и повышается эффективность кода*.

Укороченная логическая операция `И` выполняется с помощью **оператора &&**, а укороченная логическая операция `ИЛИ` — с помощью **оператора ||**. Этим укороченным логическим операторам соответствуют обычные логические операторы `&` и `|`. Единственное отличие укороченного логического оператора от обычного заключается в том, что второй его операнд вычисляется только по мере необходимости.

Укороченные логические операторы иногда оказываются более эффективными, чем их обычные аналоги. Так зачем же нужны обычные логические операторы `И` и `ИЛИ`? Дело в том, что в некоторых случаях требуется вычислять оба операнда логической операции `И` либо `ИЛИ` из-за возникающих побочных эффектов. Пример:


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

```cs
if (f != 0 && (d % f) == 0)
    Console.WriteLine("{0} делится нацело на {1}",d,f);
```

В этом случае так же используется укороченный оператор но при этом возникнет исключительная ситуация т.к. первый оператор сравнения содержит деление на `0`

```cs
if ((d % f) == 0 && f != 0)
    Console.WriteLine("{0} делится нацело на {1}", d, f);
```

При использовании целостного оператора `&` в любом случае возникнет исключительная ситуация, т.к. второе выражение будет вычислено несмотря на то, что первое выражение вернуло `false`

```cs
if (f != 0 & (d % f) == 0)
    Console.WriteLine("{0} делится нацело на {1}", d, f);
```

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

При использовании обычного оператора `|`, в данной конструкции переменная `i` будет инкреминироваться

```cs
if (b | (++i < 10))
    Console.WriteLine("i равно {0}", i);   // i = 1
```

При использовании укороченного оператора `||` значение `i` останется прежним

```cs
if (b || (++i < 10))
    Console.WriteLine("i равно {0}", i);  // i = 0
```

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

![](../img/03008.png)

## Условные операторы

Условные операторы позволяют управлять *потоком выполнения* программы, чтобы не выполнялась каждая строка кода, как она следует в программе. Давайте рассмотрим все условные операторы языка `C#`:

### Оператор if

Для организации условного ветвления язык `C#` унаследовал от `С` и `С++` конструкцию `if...else`. Ее синтаксис должен быть интуитивно понятен для любого, кто программировал на процедурных языках:

```cs
if (условие)
  оператор (операторы)
else
  оператор (операторы)
```

Если по каждому из условий нужно выполнить более одного оператора, эти операторы должны быть объединены в блок с помощью фигурных скобок `{...}`. (Это также касается других конструкций `C#`, в которых операторы могут быть объединены в блок — таких как циклы `for` и `while`.)

Стоит обратить внимание, что в отличие от языков `С` и `С++`, в `C#` условный оператор `if` может работать только с булевскими выражениями, но не с произвольными значениями вроде `-1` и `0`.

В операторе `if` могут применяться сложные выражения, и он может содержать операторы `else`, обеспечивая выполнение более сложных проверок. Синтаксис похож на применяемый в аналогичных ситуациях в языках `С` (`С++`) и `Java`. При построении сложных выражений в `C#` используется вполне ожидаемый набор логических операторов. Давайте рассмотрим следующий пример:

```cs
string myStr;
Console.WriteLine("Введите строку: ");

myStr = Console.ReadLine();

if (myStr.Length < 5)
    Console.WriteLine(
        "\nВ данной строке меньше 5 символов");
else if ((myStr.Length >= 5) && (myStr.Length <= 12)) 
    Console.WriteLine(
        "\nВ данной строке {0} символов",myStr.Length);
else 
    Console.WriteLine(
        "\nВ данной строке больше 12 символов");

Console.ReadLine();
```

![](../img/03013.png)

Как видите количество `else if`, добавляемых к единственному `if`, не ограничено. Один момент, который следует отметить касательно `if`: фигурные скобки применять не обязательно, если в условной ветви присутствует только один оператор, как показано в исходном примере.

### Оператор switch

Вторым оператором выбора в `C#` является оператор **switch**, который обеспечивает многонаправленное ветвление программы. Следовательно, этот оператор позволяет сделать выбор среди нескольких альтернативных вариантов дальнейшего выполнения программы. Несмотря на то что многонаправленная проверка может быть организована с помощью последовательного ряда вложенных операторов `if`, во многих случаях более эффективным оказывается применение оператора **switch**. Этот оператор действует следующим образом. Значение выражения последовательно сравнивается с константами выбора из заданного списка. Как только будет обнаружено совпадение с одним из условий выбора, выполняется последовательность операторов, связанных с этим условием. Ниже приведена общая форма оператора **switch**:

```cs
switch(выражение) {
    case константа1:
        последовательность операторов
        break;
    case константа2:
        последовательность операторов
        break;
    case константаЗ:
        последовательность операторов
        break;

    ...

    default:
        последовательность операторов
        break;
}
```

Хотя оператор `switch...case` должен быть знаком программистам на `С` и `С++`, в `C#` он немного безопаснее, чем его эквивалент `С++`. В частности, он запрещает "сквозные" условия почти во всех случаях. Это значит, что если часть **case** вызывается в начале блока, то фрагменты кода за последующими частями **case** не могут быть выполнены, если только не используется явно оператор **goto** для перехода к ним. Компилятор обеспечивает это ограничение за счет того, что требует, чтобы за каждой частью **case** следовал оператор **break**, в противном случае он выдает ошибку.

Важно отметить, что заданное выражение в операторе **switch** должно быть **целочисленного типа** (**char**, **byte**, **short** или **int**), **перечислимого** или же **строкового**. А выражения других типов, например с плавающей точкой, в операторе **switch** не допускаются. Зачастую выражение, управляющее оператором **switch**, просто сводится к одной переменной. Кроме того, константы выбора должны иметь тип, совместимый с типом выражения. В одном операторе **switch** не допускается наличие двух одинаковых по значению констант выбора.

Давайте на примере рассмотрим использование оператора **switch**:

```cs
Console.WriteLine("Введите язык (C#, VB или C++)");
string myLanguage = Console.ReadLine();

sw1(myLanguage);

Console.ReadLine();
// Данный метод выводит выбор пользователя
static void sw1(string s)
{
    switch (s)
    {
        case "C#":
            Console.WriteLine("Вы выбрали язык C#");
            break;
        case "VB":
            Console.WriteLine("Вы выбрали язык Visual Basic");
            break;
        case "C++":
            Console.WriteLine("Вы выбрали язык С++");
            break;
        default:
            Console.WriteLine("Такой язык я не знаю");
            break;
    }
}
```

![](../img/03014.png)

### Тернарный оператор

Тернарный оператор (`?`) относится к числу самых примечательных в `C#`. Он представляет собой условный оператор и часто используется вместо определенных видов конструкций `if-else`. Ниже приведена общая форма этого оператора (в квадратных скобках не обязательная часть):

```
[var Result =] Условие ? Выражение_если_условие_истинно : Выражение_если_условие_ложно;
```

Здесь **Условие** должно относиться к типу **bool** а *Выражение_если_условие_истинно* и *Выражение_если_условие_ложно* — к одному и тому же типу. Обратите внимание на применение двоеточия и его местоположение в операторе `?`. Значение выражения `?` определяется следующим образом. Сначала вычисляется *Условие*. Если оно истинно, то вычисляется *Выражение_если_условие_истинно*, а полученный результат определяет значение всего выражения `?` в целом. Если же *Условие* оказывается ложным, то вычисляется *Выражение_если_условие_ложно*, и его значение становится общим для всего выражения `?`:

```cs
int b, c;
c = -4;

b = (c >= 0) ? c : c*c;
```

Присваивать переменной результат выполнения оператора `?` совсем не обязательно. Например, значение, которое дает оператор `?`, можно использовать в качестве аргумента при вызове метода. А если все выражения в операторе `?` относятся к типу **bool**, то такой оператор может заменить собой условное выражение в цикле или операторе `if`. Давайте рассмотрим пример использования тернарного оператора:

```cs
int result;

// Реализуем функцию модуля числа
for (int i = 5; i >= -5; i--)
{
    result = i >= 0 ? i : -i;
    Console.Write("{0}\t", result);
}

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

// Выбор четных чисел
for (int i = 0; i < 10; i++)
{
    if (i % 2 == 0 ? true : false)
        Console.Write("{0}\t",i);
}

Console.ReadLine();
```

![](../img/03012.png)

## Циклы

В **C#** имеются четыре различных вида циклов (`for`, `while`, `do...while` и `foreach`), позволяющие выполнять блок кода повторно до тех пор, пока удовлетворяется определенное условие. В этой лекции мы познакомимся с циклами **for** и **while**.

### Цикл for

Цикл for в `C#` предоставляет механизм итерации, в котором определенное условие проверяется перед выполнением каждой итерации. Синтаксис этого оператора показан ниже:

```cs
for (инициализатор; условие; итератор)
  оператор (операторы)
```

Здесь:

**инициализатор** это выражение, вычисляемое перед первым выполнением тела цикла (обычно инициализация локальной переменной в качестве счетчика цикла). Инициализация, как правило, представлена оператором присваивания, задающим первоначальное значение переменной, которая выполняет роль счетчика и управляет циклом;

**условие** это выражение, проверяемое перед каждой новой итерацией цикла (должно возвращать true, чтобы была выполнена следующая итерация);

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

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

Стоит отметить, что цикл **for** отлично подходит для повторного выполнения оператора или блока операторов заранее известное количество раз. Давайте рассмотрим практическое применение цикла **for** на следующем примере:

```cs
// Данный метод выводит таблицу умножения
// размерностью b x b
static void tab(byte b)
{
    Console.WriteLine("Таблица умножения {0} x {0}\n", b);
    // Этот цикл проходит по строкам
    for (int i = 1; i <= b; i++)
    {
        // Этот цикл проходит по столбцам
        for (int j = 1; j <= b; j++)
            Console.Write("{0}\t", j * i);
        Console.WriteLine();
    }
    Console.WriteLine();
}

tab(8);

// Давайте разберем нестандартные возможности цикла for
// ************************************************* //

// Применение нескольких переменных управления циклом
for (byte i = 0, j = 20; i <= j; i += 5, j -= 5)
    Console.WriteLine("i = {0}, j = {1}",i,j);
Console.WriteLine();

// Использование условного выражения в цикле
bool b = false;
for (byte i = 1, j = 100; !b; i++, j--)
    if (i < Math.Sqrt(j))
        Console.WriteLine("Число {0} меньше квадратного корня из {1}", i, j);
    else b = true;

// Отсутствие части цикла
int k = 0;
for (; k < 10; )
{
    k++;
    Console.Write(k);
}
Console.WriteLine("\n");

// Цикл без тела
int sum = 0;
for (int i = 1; i <= 10; sum += ++i);

Console.WriteLine("Значение суммы: {0}",sum);

Console.ReadLine();
```

![](../img/03015.png)

### Цикл while

Подобно **for**, **while** также является циклом с предварительной проверкой. Синтаксис его аналогичен, но циклы **while** включают только одно выражение:

```cs
while(условие)
    оператор (операторы);
```

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

Как и в цикле **for**, в цикле **while** проверяется условное выражение, указываемое в самом начале цикла. Это означает, что код в теле цикла может вообще не выполняться, а также избавляет от необходимости выполнять отдельную проверку перед самим циклом.

Пример:

```cs
// Пример возведения числа в несколько степеней
byte l = 2, i = 0;
int result = 1;

while (i < 10)
{
    i++;
    result *= l;
    Console.WriteLine("{0} в степени {1} равно {2}",l,i,result);
}

Console.ReadLine();
```

![](../img/03016.png)

### Цикл `do ... while`

Цикл `do...while` в **C#** — это версия **while** с постпроверкой условия. Это значит, что условие цикла проверяется после выполнения тела цикла. Следовательно, циклы `do...while` удобны в тех ситуациях, когда блок операторов должен быть выполнен как минимум однажды. Ниже приведена общая форма оператора цикла `do...while`:

```cs
do {
    операторы;
} while (условие);
```

При наличии лишь одного **оператора** фигурные скобки в данной форме записи необязательны. Тем не менее они зачастую используются для того, чтобы сделать конструкцию `do...while` более удобочитаемой и не путать ее с конструкцией цикла `while`. Цикл `do...while` выполняется до тех пор, пока условное выражение истинно. В качестве примера использования цикла `do...while` можно привести следующую программу, расчитывающую факториал числа:

```cs
try
{
    // Вычисляем факториал числа
    int i, result = 1, num = 1;

    Console.WriteLine("Введите число:");
    i = int.Parse(Console.ReadLine());

    Console.Write("\n\nФакториал {0} = ", i);
    do
    {
        result *= num;
        num++;
    } while (num <= i);

    Console.Write(result);
}
catch (FormatException ex)
{
    Console.WriteLine("Вы ввели не число. {0}",ex.Message);
}
finally
{
    Console.ReadLine();
}
```

<!-- TODO перенести к массивам -->

### Цикл foreach

Цикл **foreach** служит для циклического обращения к элементам **коллекции**, представляющей собой группу объектов. В **C#** определено несколько видов коллекций, каждая из которых является **массивом**. Ниже приведена общая форма оператора цикла foreach:

```cs
foreach (тип имя_переменной_цикла in коллекция) 
    оператор;
```

Здесь **тип имя_переменной_цикла** обозначает **тип** и **имя переменной** управления циклом, которая получает значение следующего элемента коллекции на каждом шаге выполнения цикла **foreach**. А **коллекция** обозначает циклически опрашиваемую коллекцию, которая здесь и далее представляет собой массив. Следовательно, тип переменной цикла должен соответствовать типу элемента массива. Кроме того, тип может обозначаться ключевым словом **var**. В этом случае компилятор определяет тип переменной цикла, исходя из типа элемента массива. Это может оказаться полезным для работы с определенного рода запросами. Но, как правило, тип указывается явным образом.

Оператор цикла **foreach** действует следующим образом. Когда цикл начинается, первый элемент массива выбирается и присваивается переменной цикла. На каждом последующем шаге итерации выбирается следующий элемент массива, который сохраняется в переменной цикла. Цикл завершается, когда все элементы массива окажутся выбранными.

Цикл **foreach** позволяет проходить по каждому элементу коллекции (объект, представляющий список других объектов). Формально для того, чтобы нечто можно было рассматривать как коллекцию, это нечто должно поддерживать интерфейс **IEnumerable**. Примерами коллекций могут служить **массивы**, классы коллекций из пространства имен **System.Collection**, а также пользовательские классы коллекций.

Пример использования цикла **foreach**:

```cs
// Объявляем два массива
int[] myArr = new int[5];
int[,] myTwoArr = new int[5, 6];
int sum = 0;

Random ran = new Random();

// Инициализируем массивы
for (int i = 1; i <= 5; i++)
{
    myArr[i-1] = ran.Next(1, 20);
    for (int j = 1; j <= 6; j++)
        myTwoArr[i - 1, j - 1] = ran.Next(1, 30);
}

// Вычисляем квадрат каждого элемента одномерного массива
foreach (int fVar in myArr)
    Console.WriteLine("{0} в квадрате равно {1}",fVar,fVar*fVar);

Console.WriteLine();
// Вычислим сумму элементов многомерного массива
foreach (int fTwoVar in myTwoArr)
    sum += fTwoVar;

Console.WriteLine("Сумма элементов многомерного массива: {0}",sum);

Console.ReadLine();
```

## Операторы перехода

Язык **C#** предлагает несколько операторов, позволяющих немедленно перейти на другую строку программы. Давайте их рассмотрим.

### Оператор goto

Имеющийся в **C#** оператор **goto** представляет собой оператор безусловного перехода. Когда в программе встречается оператор **goto**, ее выполнение переходит непосредственно к тому месту, на которое указывает этот оператор. Он уже давно "вышел из употребления" в программировании, поскольку способствует созданию "макаронного" кода. Хотя в некоторых случаях он оказывается удобным и дает определенные преимущества, если используется благоразумно. Главный недостаток оператора **goto** с точки зрения программирования заключается в том, что он вносит в программу беспорядок и делает ее практически неудобочитаемой. Но иногда применение оператора **goto** может, скорее, прояснить, чем запутать ход выполнения программы.

Для выполнения оператора **goto** требуется метка — действительный в **C#** идентификатор с двоеточием. Метка должна находиться в том же методе, где и оператор **goto**, а также в пределах той же самой области действия.

Пример использования оператора **goto**:

```cs
    // Обычный цикл for выводящий числа от 1 до 5
    Console.WriteLine("Обычный цикл for:");
    for (int i = 1; i <= 5; i++)
        Console.Write("\t{0}",i);

    // Реализуем то же самое с помощью оператора goto
    Console.WriteLine("\n\nА теперь используем goto:");
    int j = 1;
link1:     
    Console.Write("\t{0}",j);
    j++;
    if (j <= 5) goto link1; 


    Console.ReadLine();
```

Репутация оператора **goto** такова, что в большинстве случаев его применение категорически осуждается. Вообще говоря, он, конечно, не вписывается в рамки хорошей практики объектно-ориентированного программирования.

### Оператор break

С помощью оператора **break** можно организовать немедленный выход из цикла в обход любого кода, оставшегося в теле цикла, а также минуя проверку условия цикла. Когда в теле цикла встречается оператор **break**, цикл завершается, а выполнение программы возобновляется с оператора, следующего после этого цикла. Оператор **break** можно применять в любом цикле, предусмотренном в **C#**.

```cs
// В данном цикле выведутся числа от 1 до 5 вместо 100
for (int i = 1; i < 100; i++)
    if (i <= 5)
        Console.WriteLine(i);
    else 
        break;

Console.ReadLine();
```

Обратите внимание если оператор **break** применяется в целом ряде вложенных циклов, то он прерывает выполнение только самого внутреннего цикла.

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

### Оператор continue

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

В циклах `while` и `do...while` оператор **continue** вызывает передачу управления непосредственно условному выражению, после чего продолжается процесс выполнения цикла. А в цикле **for** сначала вычисляется итерационное выражение, затем условное выражение, после чего цикл продолжается:

```cs
// Выводим числа кратные 5
for (byte i = 1; i <= 100; i++)
{
    if (i % 5 != 0) continue;
    Console.Write("\t{0}", i);
}

Console.ReadLine();
```

Оператор **continue** редко находит удачное применение, в частности, потому, что в **C#** предоставляется богатый набор операторов цикла, удовлетворяющих большую часть прикладных потребностей. Но в тех особых случаях, когда требуется преждевременное прерывание шага итерации цикла, оператор **continue** предоставляет структурированный способ осуществления такого прерывания.

### Оператор return

Оператор **return** организует возврат из метода. Его можно также использовать для возврата значения. Имеются две формы оператора **return**: одна — для методов типа **void**, т.е. тех методов, которые не возвращают значения, а другая — для методов, возвращающих конкретные значения.

Для немедленного завершения метода типа **void** достаточно воспользоваться следующей формой оператора **return**:

```cs
return;
```

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

Для возврата значения из метода в вызывающую часть программы служит следующая форма оператора **return**:

```cs
return значение;
```

Давайте рассмотрим применение оператора **return** на конкретном примере:

```cs
int result = Sum(230);
Console.WriteLine("Сумма четных чисел от 1 до 230 равна: " + result);

Console.ReadLine();

// Метод, возращающий сумму всех четных чисел
// от 1 до s
static int Sum(int s)
{
    int mySum = 0;
    for (int i = 1; i <= s; i++)
        if (i % 2 == 0)
            mySum += i;
    return mySum;
}
```

---

## Контрольные вопросы

1. Операторы отношения
1. Логические операторы (обычные и укороченные)
1. Условные операторы
1. Тернарный оператор
1. Циклы
1. Оператор break
1. Оператор continue
1. Оператор return

---

Предыдущая лекция | &nbsp; | Следующая лекция
:----------------:|:----------:|:----------------:
[Основные элементы языка.](./t3l1.md) | [Содержание](../readme.md#тема-4-программирование-на-языке-c-основы) | [Массивы как структурированный тип данных.](./t3l1_3.md)

