Модул 4

Масиви и Колекции (Списъци)

Как да съхраняваме и обработваме много стойности наведнъж — фиксирани масиви, динамични списъци List<T>, индексация от нула и обхождане на колекции с foreach.

Теория

Когато трябва да пазим много стойности от един тип (оценките на цял клас, имената на студентите), отделни променливи не вършат работа. Масивът (Array) е наредена редица от елементи с фиксирана дължина, зададена при създаването му.

Достъпваме елементите по индекс в квадратни скоби: students[0] е първият елемент. Индексите започват от 0, така че последният елемент е на позиция Length - 1. Излизане извън границите хвърля IndexOutOfRangeException — една от най-честите грешки при начинаещи.

Списъкът List<T> е "умен масив" с променлив размер: Add() добавя, Remove() маха, Contains() проверява дали елемент съществува. T в ъгловите скоби е типът на елементите — List<int>, List<string>. За да го ползваме, ни трябва using System.Collections.Generic;.

За обхождане на колекция най-удобен е цикълът foreach — той минава през всеки елемент поред, без да се занимаваме с индекси. Когато обаче ни трябва самият индекс (или искаме да променяме елементи), използваме класическия for.

Три шаблона покриват повечето задачи с колекции: сума/средно (акумулатор от Модул 3), максимум/минимум (започнете с първия елемент и сравнявайте) и търсене с флаг (bool found = false; → вдигнете флага при намиране). Научите ли ги, задачите започват да си приличат.

Съществуват и двумерни масиви — таблици от стойности: int[,] grid = new int[3, 4]; (3 реда × 4 колони), достъп с grid[ред, колона]. Обхождат се с вложени цикли. За начало е достатъчно да знаете, че ги има.

Златни правила

Индексацията започва от 0

Индексите в масивите и списъците винаги започват от 0. Първият елемент е на индекс 0, а последният — на Дължината - 1.

Масивът е с фиксиран размер

Веднъж създаден, масивът (Array) не може да променя броя на елементите си. Ако трябва да добавяте/махате — използвайте List<T>.

Списъците са динамични

Колекцията List<T> е динамична — можете свободно да добавяте (Add) и премахвате (Remove) елементи по всяко време.

Length срещу Count

Масивът има свойство Length, списъкът — свойство Count. Лесно се бъркат — компилаторът ще ви подскаже.

Бърз справочник

Масив срещу Списък

ХарактеристикаМасив (Array)Списък (List)
Декларацияint[] nums = new int[5];List<int> nums = new List<int>();
РазмерФиксиран (nums.Length)Динамичен (nums.Count)
Добавянеnums[0] = 5; (само на съществуваща позиция)nums.Add(5);
ПремахванеНевъзможно (само презапис)nums.Remove(5); / nums.RemoveAt(0);
Проверка за елементръчно с цикъл (или Array.IndexOf)nums.Contains(5)

Полезни методи на List<T>

МетодКакво прави?Пример
Add()Добавя елемент в краяgrades.Add(6);
Remove()Премахва първото срещане на стойностnames.Remove("Иван");
RemoveAt()Премахва елемента на даден индексnames.RemoveAt(0);
Contains()Проверява дали стойността съществува (bool)if (names.Contains("Иван"))
IndexOf()Връща индекса на стойност (или -1)int i = names.IndexOf("Иван");
Clear()Изтрива всички елементиnames.Clear();

Код за анализ и пренаписване

Масив, списък и обхождане с foreach

Program.cs
using System;
using System.Collections.Generic;

namespace ModuleFour
{
    class Program
    {
        static void Main(string[] args)
        {
            // Масив
            string[] students = { "Иван", "Мария", "Георги" };
            Console.WriteLine($"Първият студент е: {students[0]}");

            // Списък
            List<int> grades = new List<int>();
            grades.Add(5);
            grades.Add(6);
            grades.Add(4);

            // Foreach цикъл - идеален за преминаване през колекции
            Console.WriteLine("Оценки:");
            foreach (int grade in grades)
            {
                Console.WriteLine(grade);
            }
        }
    }
}

Какво се случва тук?

  • Масивът students е създаден със синтаксис за инициализация { ... } — размерът (3) се определя автоматично от броя на елементите.
  • students[0] връща "Иван" — първият елемент е на индекс 0. students[3] би хвърлил IndexOutOfRangeException, защото валидните индекси са 0, 1 и 2.
  • List<int> grades = new List<int>(); създава празен списък — за разлика от масива, не указваме размер.
  • Add() добавя елементи в края, списъкът расте сам. След трите извиквания grades.Count е 3.
  • foreach (int grade in grades) се чете: "за всяка оценка grade в списъка grades". Променливата grade поема стойността на текущия елемент при всяка итерация — без индекси, без граници, без грешки.
  • Редът using System.Collections.Generic; най-отгоре е задължителен, за да работи List<T>.

Трите класически шаблона: максимум, средно, търсене

Program.cs
using System;

namespace ModuleFourExtra
{
    class Program
    {
        static void Main(string[] args)
        {
            int[] grades = { 4, 5, 6, 3, 5, 6 };

            // Шаблон 1 и 2: максимум + сума в едно обхождане
            int max = grades[0];
            int sum = 0;
            foreach (int grade in grades)
            {
                if (grade > max)
                {
                    max = grade;
                }
                sum += grade;
            }
            double average = (double)sum / grades.Length;
            Console.WriteLine($"Най-висока: {max}, Средна: {average:F2}");

            // Шаблон 3: търсене с флаг
            Console.Write("Търсена оценка: ");
            int target = int.Parse(Console.ReadLine());

            bool found = false;
            for (int i = 0; i < grades.Length; i++)
            {
                if (grades[i] == target)
                {
                    Console.WriteLine($"Намерена на индекс {i}!");
                    found = true;
                    break; // спираме при първото съвпадение
                }
            }

            if (!found)
            {
                Console.WriteLine("Няма такава оценка.");
            }
        }
    }
}

Какво се случва тук?

  • Максимумът започва от първия елемент (grades[0]), не от 0 — ако всички оценки бяха отрицателни числа, стартът от 0 щеше да даде грешен отговор.
  • Сумата и максимумът се събират в едно обхождане — няма нужда от два цикъла за две неща.
  • (double)sum / grades.Length — преобразуването е задължително: иначе 23 / 6 е целочислено и средната "оценка" става 3 вместо 3.83 (капанът от Модул 1!).
  • Търсенето ползва for вместо foreach, защото ни трябва индексът на намерения елемент.
  • Флагът found решава въпроса "а ако няма такъв елемент?" — проверката if (!found) след цикъла отпечатва съобщението само ако флагът така и не е бил вдигнат.
  • break спира търсенето при първото съвпадение — няма смисъл да обикаляме останалите елементи.

Внимавай! Чести грешки

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

Излизане извън границите

Грешно

int[] arr = { 1, 2, 3, 4, 5 };
for (int i = 0; i <= arr.Length; i++)
{
    Console.WriteLine(arr[i]); // гърми при i = 5
}

Правилно

int[] arr = { 1, 2, 3, 4, 5 };
for (int i = 0; i < arr.Length; i++)
{
    Console.WriteLine(arr[i]);
}

При 5 елемента валидните индекси са 0–4. <= пуска и i = 5IndexOutOfRangeException. Шаблонът е винаги i < arr.Length.

Премахване по време на foreach

Грешно

foreach (string name in names)
{
    if (name == "Иван")
    {
        names.Remove(name); // InvalidOperationException!
    }
}

Правилно

names.RemoveAll(name => name == "Иван");
// или: for цикъл отзад напред

Колекцията не може да се променя, докато foreach я обхожда. RemoveAll е най-краткото решение; иначе — for отзад напред или обхождане на копие (names.ToList()).

Length при списък (и Count при масив)

Грешно

List<int> grades = new List<int>();
Console.WriteLine(grades.Length); // няма такова свойство

Правилно

List<int> grades = new List<int>();
Console.WriteLine(grades.Count);

Масив → Length, списък → Count. Бъркането им е безобидно (компилаторът отказва веднага), но е сред най-честите грешки. Просто го запомнете като двойка.

Списък без new

Грешно

List<int> grades;
grades.Add(5); // CS0165: неинициализирана променлива

Правилно

List<int> grades = new List<int>();
grades.Add(5);

Декларацията само създава променливата-референция — самият списък се ражда чак с new List<int>(). Без него няма къде да се добавя.

Задачи за самостоятелна работа

Опитай първо сам — подсказката е там само ако наистина закъсаш.

Задача 1: Най-голямото число

Въведете 5 числа в масив и намерете най-голямото от тях.

Задача 2: Списък с уникални имена

Направете списък с имена. Нека потребителят да добавя имена. Ако въведе име, което вече съществува, отпечатайте "Името вече е добавено" и го премахнете.

Задача 3: Обърнат масив

Въведете 5 числа в масив и го обърнете наобратно БЕЗ готови методи (без Array.Reverse) — само с цикъл и размени. Отпечатайте резултата.

Мини-тест за проверка

Отговори си наум (или на глас!) и чак тогава разкрий отговора.

1. Кое свойство се използва за намиране размера на масив? А на списък?

2. Какъв ще е резултатът при опит за достъп до arr[5] при масив с точно 5 елемента?

3. Как се премахват елементи от списък, докато го обхождаме?

Речник на термините

Виж пълния речник на курса
Масив (Array)
Наредена редица от елементи от един тип с фиксирана дължина: int[] nums = new int[5];.
Списък (List<T>)
Динамична колекция с променлив размер — Add, Remove, Contains. Изисква using System.Collections.Generic;.
Индекс
Позицията на елемент в колекцията, броена от 0. Последният елемент е на индекс Length - 1.
Елемент
Една стойност от колекцията: arr[2] е третият елемент.
Генеричен тип <T>
T в List<T> е "заместител" за типа на елементите: List<int>, List<string>, List<Student>.
Инициализатор
Съкратен запис за създаване с готови стойности: int[] a = { 1, 2, 3 }; или new List<int> { 1, 2, 3 }.
Търсене с флаг
Шаблон: bool found = false; преди цикъла, вдигане при намиране, проверка след цикъла — отговаря и на въпроса "а ако го няма?".

Провери знанията

Бърз тест с избор от отговори върху термините на модула — с точки и моментална обратна връзка.

Провери знанията си

7 въпроса върху термините от модула. Показваме определение — ти избираш правилния термин.

Препоръки за този модул

  • Нарисувайте масива като редица от кутийки с номерата (индексите) под тях — нулевата индексация става очевидна.
  • Класически шаблон: обхождане с for (int i = 0; i < arr.Length; i++) — забележете <, не <=!
  • Не променяйте колекция (Add/Remove), докато я обхождате с foreach — ще получите изключение. Използвайте for или направете копие.
  • Изберете правилната структура: знаете точния брой → масив; броят се променя → List<T>.

Начален курс по C# — учебен наръчник и бърз справочник за студенти.

Натисни Ctrl K или / отвсякъде, за да търсиш.