Модул 9

Работа с низове (Strings)

Текстът е навсякъде — имена, съобщения, файлове. Как се търси, реже, заменя и сглобява текст в C#: най-важните методи на string, защо низовете са неизменими и кога ни трябва StringBuilder.

Теория

Низът (string) е последователност от символи (char). Можем да достъпваме отделните символи по индекс, точно както при масив: name[0] е първата буква, а name.Length е дължината. Индексацията започва от 0 — както при масивите.

Ключова особеност: низовете в C# са неизменими (immutable). Никой метод не променя самия низ — всички връщат нов низ. Затова name.ToUpper(); сам по себе си не прави нищо видимо; трябва да запазим резултата: name = name.ToUpper();. Това е грешка №1 при работа с текст.

Богатият набор от методи покрива почти всичко: Contains проверява дали текст се съдържа, IndexOf намира позиция, Substring изрязва част, Replace заменя, Split разделя по разделител (връща масив!), Trim маха празните места по краищата. Комбинирането им решава повечето текстови задачи.

Понеже всяка операция създава нов низ, слепването на много парчета в цикъл (result += ...) е бавно — при всяко завъртане се копира всичко отначало. За сглобяване на дълъг текст използваме StringBuilder (от System.Text): той трупа парчетата ефективно и накрая ToString() връща готовия низ.

Вътре в низовете действат escape последователности: \n е нов ред, \t — табулация, \\ — обратна наклонена черта, \" — кавичка в низа. Ако не искате тълкуване (типично при пътища до файлове), сложете @ пред низа — дословен низ: @"C:\new\file.txt".

Отделните символи са от тип char (единични кавички!) и си имат свои помощници: char.ToUpper(c), char.IsDigit(c), char.IsLetter(c). Комбинацията "вземи символ по индекс + обработи го" е в основата на много текстови задачи.

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

Низовете са неизменими

Методите на string НЕ променят оригинала — те връщат нов низ. Винаги запазвайте резултата: name = name.Trim(); (само name.Trim(); не прави нищо).

Сравнявайте с == или Equals

За низове == сравнява съдържанието (за разлика от някои други езици). За сравнение без значение на главни/малки букви: a.Equals(b, StringComparison.OrdinalIgnoreCase) или сравнете двете страни след ToLower().

Split връща масив

text.Split(' ') връща string[] — резултатът се обхожда с цикъл или се достъпва по индекс. Идеален за обработка на изречения дума по дума.

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

Преди да работите с потребителски вход, проверете го със string.IsNullOrEmpty(input) или string.IsNullOrWhiteSpace(input) — пазят и от null, и от празен текст.

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

Най-важните методи на string

МетодКакво прави?Пример (резултат)
LengthБрой символи (свойство, без скоби!)"Иван".Length4
ToUpper() / ToLower()Връща нов низ с главни / малки букви"Ivan".ToUpper()"IVAN"
Contains()Съдържа ли се текстът (връща bool)"банан".Contains("ана")true
IndexOf()Позиция на първото срещане (или -1)"банан".IndexOf("н")2
Substring(start, len)Изрязва част от низа"програма".Substring(0, 4)"прог"
Replace(old, new)Заменя всички срещания"a-b-c".Replace("-", "+")"a+b+c"
Split(разделител)Разделя на масив от части"а б в".Split(' ')["а","б","в"]
Trim()Маха празните места в началото и края" здравей ".Trim()"здравей"
StartsWith() / EndsWith()Започва / завършва ли с текста"file.txt".EndsWith(".txt")true

Полезни статични методи и StringBuilder

ИнструментЗа какво служи?Пример
string.IsNullOrEmpty()Проверка за null или празен низif (string.IsNullOrEmpty(input))
string.IsNullOrWhiteSpace()Като горното + само интервалиif (string.IsNullOrWhiteSpace(input))
string.Join()Слепва колекция с разделителstring.Join(", ", names)
new StringBuilder()Ефективно сглобяване на дълъг текстvar sb = new StringBuilder();
sb.Append() / sb.AppendLine()Добавя текст / текст + нов редsb.AppendLine("ред");
sb.ToString()Връща готовия низstring result = sb.ToString();

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

Обработка на изречение и StringBuilder

Program.cs
using System;
using System.Text;

namespace ModuleNine
{
    class Program
    {
        static void Main(string[] args)
        {
            string sentence = "  C# е страхотен език за програмиране  ";

            // Почистване и нормализиране
            sentence = sentence.Trim();
            Console.WriteLine($"Дължина: {sentence.Length} символа");
            Console.WriteLine($"С главни букви: {sentence.ToUpper()}");

            // Търсене
            if (sentence.Contains("C#"))
            {
                Console.WriteLine($"Позиция на 'език': {sentence.IndexOf("език")}");
            }

            // Разделяне на думи
            string[] words = sentence.Split(' ');
            Console.WriteLine($"Брой думи: {words.Length}");

            // Сглобяване на дълъг текст със StringBuilder
            StringBuilder sb = new StringBuilder();
            foreach (string word in words)
            {
                sb.AppendLine($"- {word}");
            }
            Console.WriteLine(sb.ToString());
        }
    }
}

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

  • sentence.Trim() връща нов низ без празните места по краищата — и ние го записваме обратно в sentence. Без присвояването оригиналът щеше да си остане с интервалите (неизменимост!).
  • Length е свойство (без скоби) и брои всички символи, включително интервалите.
  • Contains връща bool — идеален за if. IndexOf връща позицията на първото срещане, броена от 0, или -1, ако текстът липсва.
  • Split(' ') нарязва изречението по интервалите и връща масив string[] — оттам нататък важи всичко от Модул 4 (обхождане, индекси, Length).
  • StringBuilder се ползва в три стъпки: създаваме го, трупаме с Append/AppendLine (тук — в цикъл), и накрая ToString() дава готовия резултат. За няколко реда разликата не личи, но при хиляди итерации е огромна.
  • using System.Text; най-отгоре е нужен за StringBuilder.

Форматиране на име и инициали

Program.cs
using System;

namespace ModuleNineExtra
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.Write("Въведи име и фамилия: ");
            string fullName = Console.ReadLine().Trim();

            string[] parts = fullName.Split(' ');
            if (parts.Length < 2)
            {
                Console.WriteLine("Въведи и двете имена!");
                return;
            }

            string first = parts[0];
            string last = parts[parts.Length - 1];

            // Главна буква + останалото с малки (нормализиране)
            first = char.ToUpper(first[0]) + first.Substring(1).ToLower();
            last = char.ToUpper(last[0]) + last.Substring(1).ToLower();

            string initials = $"{first[0]}.{last[0]}.";

            Console.WriteLine($"Име: {first} {last}");
            Console.WriteLine($"Инициали: {initials}");
        }
    }
}

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

  • Trim() още на входа маха случайните интервали по краищата — навик, който спестява странни грешки по-късно.
  • Split(' ') реже по интервалите; проверката parts.Length < 2 пази от вход само с едно име (помните ли защитното програмиране?).
  • parts[parts.Length - 1] взима последната част — така "Иван Петров Георгиев" дава фамилия "Георгиев", а не средното име.
  • Нормализирането комбинира три умения: first[0] (символ по индекс) + char.ToUpper (главна буква) + Substring(1).ToLower() (останалото с малки). Резултатът: "иВАН" става "Иван".
  • Понеже низовете са неизменими, всяка от тези операции създава нов низ — и затова присвояваме резултата обратно (first = ...).

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

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

Резултатът от метода не е запазен

Грешно

string name = "иван";
name.ToUpper();
Console.WriteLine(name); // печата "иван"

Правилно

string name = "иван";
name = name.ToUpper();
Console.WriteLine(name); // печата "ИВАН"

Низовете са неизменими — ToUpper() връща нов низ, а оригиналът не се пипа. Без присвояване резултатът се губи. Това е грешка №1 при работа с текст.

Substring извън дължината

Грешно

string word = "куче";
string part = word.Substring(0, 10);
// ArgumentOutOfRangeException

Правилно

string word = "куче";
int len = Math.Min(10, word.Length);
string part = word.Substring(0, len);

Substring(start, length) гърми, ако поисканата дължина излиза извън низа. При променлива дължина на входа ограничете с Math.Min(искано, word.Length) или проверете предварително.

Сравнение, чувствително към регистъра

Грешно

if (input == "СТОП")
{
    // "стоп" и "Стоп" не минават
}

Правилно

if (input.ToLower() == "стоп")
{
    // всички варианти минават
}

== сравнява низовете точно, буква по буква, с регистъра. Нормализирайте едната или двете страни с ToLower()/ToUpper(), когато регистърът не бива да има значение.

Слепване с += в дълъг цикъл

Грешно

string result = "";
for (int i = 0; i < 10000; i++)
{
    result += i + ", "; // копира всичко всеки път

Правилно

StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++)
{
    sb.Append(i).Append(", ");
}
string result = sb.ToString();

Всяко += създава изцяло нов низ и копира досегашното съдържание — при хиляди итерации това е лавина от излишна работа. StringBuilder трупа в буфер и създава низа само веднъж накрая.

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

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

Задача 1: Брояч на думи и букви

Потребителят въвежда изречение. Програмата отпечатва броя на думите, броя на символите (без интервалите) и изречението с главни букви.

Задача 2: Цензура

Дадено е изречение и "забранена" дума. Заменете всички срещания на думата със звездички (по една за всяка буква) и отпечатайте резултата.

Задача 3: Палиндром

Проверете дали въведена дума е палиндром — чете се еднакво отпред и отзад (напр. "капак", "боб"). Главните и малките букви не трябва да имат значение.

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

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

1. Какво означава, че низовете в C# са неизменими (immutable)?

2. Какво връща "програма".IndexOf("z")?

3. Кога е по-добре да използваме StringBuilder вместо обикновено слепване с +?

4. Какво прави методът Split и какво връща?

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

Виж пълния речник на курса
Низ (string)
Последователност от символи. Достъп по индекс (name[0]), дължина чрез Length.
Символ (char)
Един единствен знак в единични кавички: 'A'. Помощници: char.ToUpper, char.IsDigit.
Неизменимост (immutability)
Низът не може да се променя след създаване — всички методи връщат нов низ.
Конкатенация
Слепване на низове с + или интерполация. За много слепвания в цикъл — StringBuilder.
Escape последователност
Специален запис в низ: \n нов ред, \t табулация, \\ черта, \" кавичка.
Дословен низ (@)
@"..." — низ без тълкуване на \; стандартът за файлови пътища.
StringBuilder
Клас от System.Text за ефективно сглобяване на дълъг текст: Append + накрая ToString().

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

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

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

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

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

  • Дръжте този справочник отворен и решавайте текстови задачи — методите се запомнят само с употреба, не със зубрене.
  • Помнете: индексите на символите започват от 0, а последният символ е на text.Length - 1 — същото правило като при масивите.
  • Когато слепвате повече от няколко низа в цикъл — спрете и помислете за StringBuilder.
  • Експериментирайте в конзолата: вземете едно изречение и го обработете с всеки метод от таблицата, за да видите какво връща.

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

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