C# Курс

C# ООП: классы, интерфейсы, наследование

Самоучитель Python 3, собранный из материалов данного сайта.Предназначен в основном для тех, кто хочет изучить язык программирования Python с нуля.

Введение: Путешествие в мир объектов

Привет, юный программист! 👋 Сегодня мы начинаем самое интересное — знакомство с объектно-ориентированным программированием (ООП) в C#. Представь, что ты строишь город из LEGO. Обычное программирование — это когда ты собираешь всё из отдельных кирпичиков, а ООП — это когда у тебя есть готовые домики, машинки и деревья, которые ты просто соединяешь друг с другом. Удобно, правда?

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

Давай начнём с простого: представь, что ты описываешь собаку. У неё есть характеристики (имя, возраст, порода) и действия (лаять, бегать, есть). В ООП характеристики становятся свойствами, а действия — методами. Класс — это как чертёж собаки, а объект — конкретная собака по имени Бобик.

🎯 Цель урока: Научиться создавать свои классы, понимать разницу между классом и объектом, использовать конструкторы и методы.

1. Классы и объекты: фундамент ООП

Что такое класс?

Класс — это как шаблон или форма для печенья. Если у тебя есть форма в виде звезды, ты можешь сделать много звёздочек из теста. Класс — это форма, а объекты — это те самые звёздочки. В программировании класс описывает, какие данные (свойства) и действия (методы) будут у всех объектов этого класса.

Давай создадим простой класс Собака:

// Объявляем класс "Собака"
class Собака
{
    // Свойства (характеристики)
    public string Имя;      // Имя собаки
    public int Возраст;     // Возраст в годах
    public string Порода;   // Порода

    // Метод (действие)
    public void Лаять()
    {
        Console.WriteLine("Гав-гав!");
    }
}

Разберём код:

  • class Собака — мы создаём новый тип данных, как конструктор LEGO
  • public string Имя — свойство «Имя» типа строка (текст)
  • public int Возраст — свойство «Возраст» типа целое число
  • public void Лаять() — метод, который выводит звук лая

Ключевое слово public означает, что к этим членам класса можно обращаться откуда угодно (как к открытой двери). Если поставить private, доступ будет только внутри класса (как секретная комната).

Что такое объект?

Объект — это конкретный экземпляр класса. Если класс — это чертёж дома, то объект — сам дом, построенный по этому чертежу. Чтобы создать объект, нужно использовать ключевое слово new:

// Создаём объект "мояСобака" на основе класса "Собака"
Собака мояСобака = new Собака();

// Заполняем свойства
мояСобака.Имя = "Бобик";
мояСобака.Возраст = 3;
мояСобака.Порода = "Такса";

// Вызываем метод
мояСобака.Лаять();  // Выведет: Гав-гав!

// Выводим информацию о собаке
Console.WriteLine($"{мояСобака.Имя} - {мояСобака.Порода}, {мояСобака.Возраст} года");
// Выведет: Бобик - Такса, 3 года

Обрати внимание: new Собака() создаёт новый объект в памяти. Можно создать много собак:

Собака собака1 = new Собака();
собака1.Имя = "Шарик";
собака1.Возраст = 5;

Собака собака2 = new Собака();
собака2.Имя = "Мурка";  // Да, собака с кошачьим именем, почему нет?
собака2.Возраст = 2;

Console.WriteLine($"{собака1.Имя} и {собака2.Имя} дружат!");
// Выведет: Шарик и Мурка дружат!

Жизненная аналогия

Представь, что ты работаешь в кондитерской. У тебя есть форма для печенья (класс). С её помощью ты делаешь много печенек (объекты). У каждой печеньки есть форма (свойство), но они могут быть разного цвета (другие свойства). Ты можешь их продавать (метод).

Класс — это форма. Объект — печенька. Одна форма — много печенек. Вот и вся магия!

2. Конструкторы: как создавать объекты с готовыми данными

В прошлом примере мы сначала создавали объект, а потом заполняли свойства. Но что, если мы хотим задать всё сразу при создании? Для этого существуют конструкторы — специальные методы, которые вызываются автоматически при создании объекта.

Конструктор имеет то же имя, что и класс, и не возвращает никакого значения (даже void не пишем).

class Собака
{
    public string Имя;
    public int Возраст;
    public string Порода;

    // Конструктор с параметрами
    public Собака(string имя, int возраст, string порода)
    {
        Имя = имя;           // Записываем переданное имя в свойство
        Возраст = возраст;   // То же для возраста
        Порода = порода;     // И для породы
    }

    // Конструктор без параметров (по умолчанию)
    public Собака()
    {
        Имя = "Неизвестно";
        Возраст = 0;
        Порода = "Неизвестна";
    }

    public void Лаять()
    {
        Console.WriteLine($"{Имя} говорит: Гав-гав!");
    }
}

// Использование:
// Создаём с параметрами
Собака бобик = new Собака("Бобик", 3, "Такса");
бобик.Лаять();  // Выведет: Бобик говорит: Гав-гав!

// Создаём без параметров (используется второй конструктор)
Собака неизвестная = new Собака();
Console.WriteLine(неизвестная.Имя);  // Выведет: Неизвестно

Видишь, как удобно? Можно сразу передать все данные, и объект готов к использованию. А если данных нет — конструктор по умолчанию задаёт стандартные значения.

Ещё пример с классом Игрок для игры:

class Игрок
{
    public string Никнейм;
    public int Уровень;
    public int Здоровье;

    // Конструктор
    public Игрок(string никнейм)
    {
        Никнейм = никнейм;
        Уровень = 1;         // Новый игрок всегда 1 уровня
        Здоровье = 100;      // Полное здоровье
    }

    public void Атаковать(int урон)
    {
        Здоровье -= урон;
        Console.WriteLine($"{Никнейм} получил {урон} урона. Осталось здоровья: {Здоровье}");
    }
}

// Создаём игрока
Игрок герой = new Игрок("СуперКодер");
Console.WriteLine($"{герой.Никнейм}, уровень {герой.Уровень}, здоровье {герой.Здоровье}");
// Выведет: СуперКодер, уровень 1, здоровье 100

герой.Атаковать(30);  // Выведет: СуперКодер получил 30 урона. Осталось здоровья: 70

3. Методы: действия объектов

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

Давай создадим класс Калькулятор, который умеет складывать и вычитать:

class Калькулятор
{
    // Метод, который принимает два числа и возвращает сумму
    public int Сложить(int a, int b)
    {
        int результат = a + b;
        return результат;  // Возвращаем результат
    }

    // Метод для вычитания
    public int Вычесть(int a, int b)
    {
        return a - b;  // Можно сразу вернуть
    }

    // Метод, который ничего не возвращает, но выводит на экран
    public void ПоказатьРезультат(string операция, int результат)
    {
        Console.WriteLine($"Результат {операции}: {результат}");
    }
}

// Использование:
Калькулятор кальк = new Калькулятор();
int сумма = кальк.Сложить(10, 5);
кальк.ПоказатьРезультат("сложения", сумма);  // Выведет: Результат сложения: 15

int разность = кальк.Вычесть(10, 5);
кальк.ПоказатьРезультат("вычитания", разность);  // Выведет: Результат вычитания: 5

Обрати внимание на ключевое слово return — оно возвращает значение из метода. Если метод объявлен как void, он ничего не возвращает (просто выполняет действия).

Методы с параметрами по умолчанию

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

class Приветствие
{
    public void СказатьПривет(string имя = "Друг")
    {
        Console.WriteLine($"Привет, {имя}!");
    }
}

Приветствие привет = new Приветствие();
привет.СказатьПривет("Анна");   // Выведет: Привет, Анна!
привет.СказатьПривет();         // Выведет: Привет, Друг!

4. Свойства: полный контроль над данными

Свойства — это не просто переменные, а специальные конструкции, которые позволяют контролировать доступ к данным. Они могут проверять значения или выполнять код при чтении/записи.

Давай создадим класс Счет для банковского счёта:

class Счет
{
    private decimal баланс;  // Приватное поле (скрыто от внешнего мира)

    // Свойство для доступа к балансу
    public decimal Баланс
    {
        get { return баланс; }              // Чтение
        private set { баланс = value; }     // Запись (только внутри класса)
    }

    // Конструктор
    public Счет(decimal начальныйБаланс)
    {
        if (начальныйБаланс >= 0)
            баланс = начальныйБаланс;
        else
            баланс = 0;
    }

    // Метод для пополнения
    public void Пополнить(decimal сумма)
    {
        if (сумма > 0)
        {
            баланс += сумма;
            Console.WriteLine($"Счёт пополнен на {сумма}. Баланс: {баланс}");
        }
    }

    // Метод для снятия
    public void Снять(decimal сумма)
    {
        if (сумма > 0 && сумма <= баланс)
        {
            баланс -= сумма;
            Console.WriteLine($"Снято {сумма}. Баланс: {баланс}");
        }
        else
        {
            Console.WriteLine("Недостаточно средств или неверная сумма");
        }
    }
}

// Использование:
Счет мойСчет = new Счет(1000);
Console.WriteLine(мойСчет.Баланс);  // 1000

мойСчет.Пополнить(500);   // Счёт пополнен на 500. Баланс: 1500
мойСчет.Снять(200);       // Снято 200. Баланс: 1300
мойСчет.Снять(2000);      // Недостаточно средств или неверная сумма

// мойСчет.Баланс = 5000;  // Ошибка! Свойство Баланс имеет private set

Здесь value — это ключевое слово, которое означает значение, переданное при присваивании. Свойства защищают данные от некорректных изменений.

5. Статические члены: общее для всех объектов

Иногда нужно, чтобы свойство или метод принадлежали не конкретному объекту, а всему классу целиком. Например, общее количество созданных объектов. Для этого используется ключевое слово static.

class Пользователь
{
    public string Имя;
    public static int КоличествоПользователей = 0;  // Статическое поле

    public Пользователь(string имя)
    {
        Имя = имя;
        КоличествоПользователей++;  // Увеличиваем счётчик при создании
    }

    // Статический метод
    public static void ПоказатьКоличество()
    {
        Console.WriteLine($"Всего пользователей: {КоличествоПользователей}");
    }
}

// Создаём пользователей
Пользователь юзер1 = new Пользователь("Анна");
Пользователь юзер2 = new Пользователь("Иван");
Пользователь юзер3 = new Пользователь("Мария");

// Вызываем статический метод (через имя класса, не через объект!)
Пользователь.ПоказатьКоличество();  // Выведет: Всего пользователей: 3

// Статические члены доступны без создания объекта
Console.WriteLine(Пользователь.КоличествоПользователей);  // 3

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

class Математика
{
    public static double Пи = 3.14159;

    public static double ПлощадьКруга(double радиус)
    {
        return Пи * радиус * радиус;
    }
}

// Используем без создания объекта
double площадь = Математика.ПлощадьКруга(5);
Console.WriteLine($"Площадь круга: {площадь}");  // Площадь круга: 78.53975

6. Инкапсуляция: защита данных

Инкапсуляция — это принцип, по которому данные объекта скрыты от внешнего мира, а доступ к ним осуществляется только через специальные методы (геттеры и сеттеры) или свойства. Это как пульт от телевизора: ты нажимаешь кнопки, но не знаешь, как внутри устроены микросхемы.

Зачем это нужно? Чтобы никто случайно не испортил данные. Например, возраст не может быть отрицательным, а имя — пустым.

class Человек
{
    private string имя;   // Приватное поле
    private int возраст;  // Приватное поле

    // Свойство с проверкой
    public string Имя
    {
        get { return имя; }
        set
        {
            if (!string.IsNullOrEmpty(value))  // Проверяем, что имя не пустое
                имя = value;
            else
                Console.WriteLine("Имя не может быть пустым!");
        }
    }

    public int Возраст
    {
        get { return возраст; }
        set
        {
            if (value >= 0 && value <= 150)  // Проверяем возраст
                возраст = value;
            else
                Console.WriteLine("Некорректный возраст!");
        }
    }

    // Конструктор
    public Человек(string имя, int возраст)
    {
        Имя = имя;      // Используем свойства (с проверками)
        Возраст = возраст;
    }

    public void Представиться()
    {
        Console.WriteLine($"Привет, я {имя}, мне {возраст} лет.");
    }
}

// Использование:
Человек человек = new Человек("Анна", 25);
человек.Представиться();  // Привет, я Анна, мне 25 лет.

// Попробуем задать некорректные данные
человек.Возраст = -5;     // Некорректный возраст!
человек.Имя = "";         // Имя не может быть пустым!

Console.WriteLine(человек.Возраст);  // Выведет 25 (старое значение осталось)

7. Практические задания

Теперь твоя очередь! Попробуй выполнить эти задания самостоятельно. Если что-то не получается — перечитай урок или попроси помощи.

Задание 1: Класс «Книга»

Создай класс Книга со следующими элементами:

  • Свойства: Название (string), Автор (string), ГодИздания (int), КоличествоСтраниц (int)
  • Конструктор, который принимает все четыре параметра
  • Метод Описание(), который выводит информацию о книге в формате: «"Название" - Автор, Год издания, X страниц»
  • Метод Сравнить(Книга другаяКнига), который выводит, какая книга толще (больше страниц)

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

Книга книга1 = new Книга("Война и мир", "Лев Толстой", 1869, 1225);
Книга книга2 = new Книга("Маленький принц", "Антуан де Сент-Экзюпери", 1943, 96);

книга1.Описание();  // "Война и мир" - Лев Толстой, 1869, 1225 страниц
книга1.Сравнить(книга2);  // "Война и мир" толще, чем "Маленький принц"

Задание 2: Класс «Таймер»

Создай класс Таймер, который симулирует обратный отсчёт:

  • Приватное поле оставшиесяСекунды (int)
  • Свойство ОставшиесяСекунды с проверкой: значение не может быть отрицательным (если пытаются задать отрицательное, ставится 0)
  • Конструктор, который принимает начальное количество секунд
  • Метод Тик(), который уменьшает оставшиеся секунды на 1 и выводит сообщение: «Осталось: X секунд». Если время вышло, выводит «Время вышло!»
  • Метод Сбросить(int новыеСекунды), который устанавливает новое значение (с проверкой)

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

Таймер таймер = new Таймер(5);
таймер.Тик();  // Осталось: 4 секунд
таймер.Тик();  // Осталось: 3 секунд
// ... (вызываем ещё три раза)
таймер.Тик();  // Время вышло!

таймер.Сбросить(3);
таймер.Тик();  // Осталось: 2 секунд

Задание 3: Класс «Банкомат»

Создай класс Банкомат, который управляет выдачей денег:

  • Приватное поле баланс (decimal) — сколько денег в банкомате
  • Свойство Баланс только для чтения (только get)
  • Конструктор, который задаёт начальный баланс (не может быть отрицательным)
  • Метод Внести(decimal сумма) — добавляет деньги (сумма должна быть положительной)
  • Метод Снять(decimal сумма) — снимает деньги, если достаточно средств. Выводит сообщение об успехе или ошибке
  • Статическое поле ОбщаяСуммаВсехБанкоматов (decimal), которое увеличивается при создании каждого банкомата и при внесении денег
  • Статический метод ПоказатьОбщуюСумму(), который выводит общую сумму всех банкоматов

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

Банкомат банкомат1 = new Банкомат(10000);
Банкомат банкомат2 = new Банкомат(5000);

банкомат1.Внести(2000);
банкомат2.Снять(3000);  // Успешно

Банкомат.ПоказатьОбщуюСумму();  // Общая сумма всех банкоматов: 14000

Console.WriteLine(банкомат1.Баланс);  // 12000
Console.WriteLine(банкомат2.Баланс);  // 2000

Заключение

Поздравляю! 🎉 Ты сделал первый шаг в мир объектно-ориентированного программирования. Теперь ты знаешь:

  • Что такое классы и объекты
  • Как создавать конструкторы
  • Как писать методы и свойства
  • Зачем нужна инкапсуляция
  • Что такое статические члены

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

А пока — попробуй выполнить задания. Если что-то непонятно, перечитай примеры или напиши свой код — практика лучше всего помогает разобраться. Удачи!

💡 Совет: Попробуй придумать свой класс, например, «Смартфон», «ИгровойПерсонаж» или «Рецепт». Опиши его свойства и методы — это поможет закрепить материал.