Указатели на структуры
Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy — урок 20 из 107.
При работе со структурами вы часто будете использовать указатели, чтобы избежать копирования больших структур данных и иметь возможность изменять исходную структуру. Указатель на структуру хранит адрес памяти этого экземпляра структуры.
Вы можете создать указатель на структуру, используя оператор & для существующей структуры или используя оператор взятия адреса напрямую с литералом структуры.
type Person struct {
Name string
Age int
}
p := Person{Name: "Alice", Age: 30}
ptr := &p // указатель на существующую структуру
// Или создайте напрямую
ptr2 := &Person{Name: "Bob", Age: 25}Go предоставляет удобное сокращение при доступе к полям структуры через указатель. Вместо того чтобы писать (*ptr).Name, вы можете просто написать ptr.Name. Go автоматически разыменовывает указатель за вас.
ptr := &Person{Name: "Alice", Age: 30}
// Оба варианта работают одинаково
fmt.Println((*ptr).Name) // явное разыменование
fmt.Println(ptr.Name) // автоматическое разыменованиеЭто автоматическое разыменование также применяется при изменении полей. Изменения через указатель влияют на исходную структуру.
p := Person{Name: "Alice", Age: 30}
ptr := &p
ptr.Age = 31 // изменяет оригинал
fmt.Println(p.Age) // 31Указатели на структуры имеют важное значение в ООП-паттернах Go. Они позволяют методам изменять состояние получателя, обеспечивают эффективную передачу больших структур в функции и служат основой для создания связанных структур данных, таких как деревья и списки.
Задание
ЛегкоДавайте создадим систему банковских счетов, которая демонстрирует, как указатели на структуры позволяют эффективно изменять исходные данные. Вы создадите счет, который можно обновлять с помощью операций с указателями.
Вы организуете свой код в двух файлах:
account.go: Определите структуруBankAccountс полямиOwner(string),Balance(float64) иAccountNumber(string). Создайте функцию с именемNewAccount, которая принимает имя владельца и номер счета в качестве параметров и возвращает указатель на новыйBankAccountс начальным балансом 0. Также создайте функциюDeposit, которая принимает указатель*BankAccountи сумму, а затем добавляет эту сумму к балансу счета через указатель.main.go: Считайте информацию о счете из входных данных, создайте счет с помощью вашей функции-конструктора (которая возвращает указатель), внесите депозит, используя указатель, и выведите данные счета, чтобы подтвердить, что баланс был изменен.
Будут предоставлены следующие входные данные:
- Строка 1: Имя владельца
- Строка 2: Номер счета
- Строка 3: Сумма депозита (float)
Формат вывода:
Account: [AccountNumber]
Owner: [Owner]
Balance: [Balance]Например, для John Smith, ACC-12345 и 500.50 ваш вывод должен быть следующим:
Account: ACC-12345
Owner: John Smith
Balance: 500.50Помните, что ваша функция NewAccount должна возвращать *BankAccount, используя оператор &, а функция Deposit может изменять баланс напрямую через указатель, используя автоматическое разыменование в Go (например, account.Balance вместо (*account).Balance).
Шпаргалка
Указатель на структуру хранит адрес памяти экземпляра структуры. Создавайте указатели на структуры с помощью оператора &:
type Person struct {
Name string
Age int
}
p := Person{Name: "Alice", Age: 30}
ptr := &p // указатель на существующую структуру
// Или создайте напрямую
ptr2 := &Person{Name: "Bob", Age: 25}Go обеспечивает автоматическое разыменование при доступе к полям структуры через указатель. Вместо (*ptr).Name вы можете писать ptr.Name:
ptr := &Person{Name: "Alice", Age: 30}
// Оба варианта работают одинаково
fmt.Println((*ptr).Name) // явное разыменование
fmt.Println(ptr.Name) // автоматическое разыменованиеИзменения через указатель влияют на исходную структуру:
p := Person{Name: "Alice", Age: 30}
ptr := &p
ptr.Age = 31 // изменяет оригинал
fmt.Println(p.Age) // 31Указатели на структуры позволяют методам изменять состояние получателя, обеспечивают эффективную передачу больших структур в функции и служат основой для связанных структур данных.
Попробуйте сами
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Прочитать имя владельца
ownerName, _ := reader.ReadString('\n')
ownerName = strings.TrimSpace(ownerName)
// Прочитать номер счета
accountNumber, _ := reader.ReadString('\n')
accountNumber = strings.TrimSpace(accountNumber)
// Прочитать сумму депозита
amountStr, _ := reader.ReadString('\n')
amountStr = strings.TrimSpace(amountStr)
amount, _ := strconv.ParseFloat(amountStr, 64)
// TODO: Создать новый аккаунт, используя функцию NewAccount (возвращает указатель)
// TODO: Внести депозит, используя функцию Deposit с указателем на аккаунт
// TODO: Вывести данные аккаунта в требуемом формате:
// Account: [AccountNumber]
// Owner: [Owner]
// Balance: [Balance]
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП в Go
Внешние файлыРабочее пространство и модули GoПакеты и импортыЭкспортируемые и неэкспортируемые именаВведение в ООП в GoСтруктуры как классыОпределение методов структурПолучатели-указатели и получатели-значенияИнициализация структурФункции-конструкторыИтоги — Простой калькулятор4Интерфейсы
Введение в интерфейсыНеявная реализацияИнтерфейс как контрактПустой интерфейс (any)Утверждение типаПереключатель типовКомпозиция интерфейсовИнтерфейсы Stringer и ErrorПовторение: Калькулятор фигур7Инкапсуляция
Экспортируемые и неэкспортируемые поляИнкапсуляция на уровне пакетовГеттеры и сеттерыСокрытие информации в GoИтоги — Записи о студентах10Обобщения (Generics) (Go 1.18+)
Введение в GenericsПараметры типовОграничения типовОбобщенные структурыОбходной путь для обобщенных методовИтоги — Обобщенная коллекция13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн Abstract FactoryПаттерн ObserverПаттерн Strategy2Глубокое погружение в типы и структуры
Базовые и составные типыОпределение пользовательских типовТеги структурАнонимные структурыВложенные структурыНулевые значения и значения по умолчаниюПовторение — Контактная книга5Композиция вместо наследования
Почему в Go нет наследованияОсновы встраивания структурПродвижение методовВстраивание нескольких структурВстраивание против агрегацииЗатенение встроенных методовИтоги — Иерархия сотрудников8Обработка ошибок и ООП
Интерфейс errorПользовательские типы ошибокОбертывание ошибок (fmt.Errorf)Sentinel-ошибкиerrors.Is() и errors.As()Panic, Defer и RecoverИтоги — Парсер файлов11Стандартная библиотека и ООП
io.Reader и io.Writersort.InterfaceИнтерфейс fmt.Stringerencoding/json со структурамиИнтерфейс http.HandlerПовторение: модели REST API14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикMiddleware как Декоратор3Указатели и память
Основы указателей в GoУказатели на структурыПередача по значению и по ссылкеФункция new()Сборка мусора в GoПовторение: Конструктор связного списка6Полиморфизм в Go
Полиморфизм через интерфейсыУтиная типизация в GoПравила реализации интерфейсовПолиморфные коллекцииВнедрение зависимостейИтоги — Обработчик платежей9Конкурентность и ООП
Основы горутинКаналы и взаимодействиеБуферизованные и небуферизованные каналыОператор selectsync.Mutex и sync.RWMutexsync.WaitGroupПроектирование потокобезопасных структурПовторение — Worker Pool