Объектно-ориентированное программирование в golang

Введение

Мир вокруг можно моделировать различными способами. Самым естественным из них является представление о нём, как о наборе объектов. У каждого объекта есть свои свойства.
Например, для человека это возраст, пол, рост, вес и т.д. Для велосипеда – тип, размер колёс, вес, материал, изготовитель и пр. Для товара в магазине – идентификационный номер,
название, группа, вес, цена, скидка и т.д.

У классов объектов набор этих свойств одинаковый: все собаки могут быть описаны, с той или иной точностью, одинаковым набором свойств, но значения этих свойств будут разные.

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

Вложенные структуры

Помимо определения нового типа для создания структуры вы можете также определить вложенную структуру. Подобные выполняемые на ходу определения структуры могут быть полезными в ситуациях, когда придумывание новых имен для типов структур будет пустой тратой времени. Например, тесты часто используют структуру для определения всех параметров, которые составляют конкретный тестовый случай. Было бы излишним работать с новыми именами, наподобие , когда структура используется только в одном месте.

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

Вывод для данного примера будет выглядеть следующим образом:

Вместо того, чтобы определять новый тип, описывающий структуру с ключевым словом , в данном примере вложенная определяется сразу же после короткого оператора присваивания . Мы определяем поля структуры, как и в предыдущих примерах, но теперь мы должны немедленно предоставить другую пару скобок и значения, которые будут присваиваться каждому полю. Использование структуры остается прежним, мы можем обратиться к именам полей с помощью записи с точкой. Чаще всего вы будете видеть вложенные структуры в тестах, так как часто используемые один раз структуры определяются для хранения данных и ожиданий для конкретного тестового случая.“”“

Accessing fields of a Struct

Accessing fields of the struct are really simple. We use the dot operator for that. When a nested structure is there we use dot inside the nested struct as well.

// declaration
type Car struct {
        name string
        cost int
}

var c Car = Car{"Mercedes", 3500000}
// accessing
carMake := c.name
carPrice := c.cost

Now, for a nested struct, we can do like this:

package main

import (
	"fmt"
)

type User struct {
	name string
}

type Service struct {
        name string
	user User
}

func main() {

	google := Service{
		name: "Google",
		user: User{
			name: "John Doe",
		},
	}

	// accessing from nested struct
	fmt.Println(google.user.name)  // prints "John Doe"
}

Передача объекта как ссылки

Итак, мы увидели, что обновления объекта никак не отразятся на функции, потому что в функцию не была передана ссылка. Для передачи объекта как ссылки можно вместо значений передать ссылку на объект: просто ставим в начале оператор взятия адреса . Чтобы принялась ссылка на объект, а не значение, вызываемую функцию тоже надо обновить.

func UpdateEmployee(empDetials *Employee) {  empDetails.Name = "Anshul";}var newEmployee = Employee{Name: "Mayank", Age: 40, Designation: "Developer"}UpdateEmployee(&newEmployee)// Исходный объект отправлен в обновлённую функцию...fmt.Println(newEmployee.Name)

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

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

Использование ключевого слова для создания объектов

Следующий способ создания объекта из структуры — использовать ключевое слово . При этом Golang создаёт новый объект типа и возвращает переменной его адрес в памяти. То есть, ключевое слово возвращает адрес этого объекта.

Используя ссылку, возвращаемую от ключевого слова , можно присваивать значения параметрам вновь созданного объекта. Все параметры по умолчанию принимают неявные значения, и для каждого конкретного типа это будет своё стандартное значение.

  1. Стандартным значением для булева типа будет .
  2. Стандартным значением для целочисленного типа будет .
  3. Стандартным значением для строкового типа будет (пустая строка).

Для лучшего понимания снова обратимся к коду:

type Employee struct {  Name string  Age int  Designation string  Salary int}var newEmployee = new(Employee)fmt.Println(newEmployee.Name)

Мы создаём новый объект с помощью ключевого слова , которое не позволяет передавать параметрам объекта стандартные значения. При создании объекта это делает за нас Golang. В нашем случае обращение к параметру вернёт пустую строку ().

В коде с помощью ключевого слова возвращается адрес вновь созданного объекта. Переменная выступает здесь в роли указателя на объект .

Указатели на структуру

Также возможно создать указатели на структуру. Посмотрите код ниже.

// hello.go

package main

import "fmt"

// Student structure
type Student struct {
	firstName, lastName string
	age                 int
}

func main() {
	stud7 := &Student{"Wade", "Wilson", 28}
	fmt.Println("First Name:", (*stud7).firstName)
	fmt.Println("Last Name:", (*stud7).lastName)
	fmt.Println("Age:", (*stud7).age)
}

Stud7 в вышеприведенной программе является указателем на структуру Student . (* Stud7) .firstName синтаксис для доступа к полю FirstName из stud7 структуры. Смотрите вывод ниже.

Golang дает нам возможность использовать stud7.firstName вместо явного разыменования (* stud7) .firstName для доступа к полю firstName .

Go struct simple example

The following is a Go struct simple example.

simple.go

package main

import "fmt"

type User struct {
    name       string
    occupation string
    age        int
}

func main() {

    u := User{"John Doe", "gardener", 34}

    fmt.Printf("%s is %d years old and he is a %s\n", u.name, u.age, u.occupation)
}

We define a struct with three fields.

type User struct {
    name       string
    occupation string
    age        int
}

We declare the struct.

u := User{"John Doe", "gardener", 34}

We initialize the struct.

fmt.Printf("%s is %d years old and he is a %s\n", u.name, u.age, u.occupation)

We print the contents of the struct.

$ go run simple.go
John Doe is 34 years old and he is a gardener

This is the output.

Go struct pointer

A pointer to the struct can be created with the operator or
the keyword. A pointer is dereferenced with the
operator.

pointer.go

package main

import "fmt"

type Point struct {
    x int
    y int
}

func main() {

    p := Point{3, 4}

    p_p := &p

    (*p_p).x = 1
    p_p.y = 2

    fmt.Println(p)
}

In the example, we create a pointer to the struct.

p_p := &p

The operator returns a pointer to the
structure.

(*p_p).x = 1
p_p.y = 2

A pointer is dereferenced with the operator. Go also allows to
use the dot operator directly.

Alternatively, we can create a pointer to a struct with the
keyword.

pointer2.go

package main

import "fmt"

type User struct {
    name       string
    occupation string
    age        int
}

func main() {

    u := new(User)
    u.name = "Richard Roe"
    u.occupation = "driver"
    u.age = 44

    fmt.Printf("%s is %d years old and he is a %s\n", u.name, u.age, u.occupation)
}

The example creates a new pointer to the struct with the
keyword.

Композиция структур в Golang

Отчет о погоде включает разнообразные данные вроде самых низких и высоких температур, указания текущего марсианского дня (его называют сол) и местности. Простым решением будет определение всех необходимых полей в единственной структуре , как показано в следующем примере:

Листинг 1

Go

type report struct {
sol int
high, low float64
lat, long float64
}

1
2
3
4
5

typereportstruct{

sol       int

high,low float64

lat,longfloat64

}

При разборе Листинга 1 можно заметить, что является миксом из несопоставимых данных. Он становится более громоздким при включении большего количества информации вроде скорости и направления ветра, давления, влажности, сезона, восхода и заката.

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

Листинг 2

Go

type report struct {
sol int
temperature temperature // Поле temperature является структурой типа temperature
location location
}

type temperature struct {
high, low celsius
}

type location struct {
lat, long float64
}

type celsius float64

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

typereportstruct{

sol         int

temperature temperature// Поле temperature является структурой типа temperature

location    location

}
 

typetemperaturestruct{

high,low celsius

}
 

typelocationstruct{

lat,longfloat64

}
 

typecelsius float64

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

Go

bradbury := location{-4.5895, 137.4417}
t := temperature{high: -1.0, low: -78.0}
report := report{sol: 15, temperature: t, location: bradbury}

fmt.Printf(«%+v\n», report) // Выводит: {sol:15 temperature:{high:-1 low:-78} location:{lat:-4.5895 long:137.4417}}

fmt.Printf(«a balmy %v° C\n», report.temperature.high) // Выводит: a balmy -1° C

1
2
3
4
5
6
7

bradbury=location{-4.5895,137.4417}

t=temperature{high-1.0,low-78.0}

report=report{sol15,temperaturet,locationbradbury}

fmt.Printf(«%+v\n»,report)// Выводит: {sol:15 temperature:{high:-1 low:-78} location:{lat:-4.5895 long:137.4417}}

fmt.Printf(«a balmy %v° C\n»,report.temperature.high)// Выводит: a balmy -1° C

Взгляните на Листинг 2

Обратите внимание, здесь сразу понятно, что параметры и касаются температур, в то время как те же самые поля из Листинга 1 не столь очевидны

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

Листинг 3

Go

func (t temperature) average() celsius {
return (t.high + t.low) / 2
}

1
2
3

func(ttemperature)average()celsius{

return(t.high+t.low)2

}

Тип и метод можно использовать независимо от отчета о погоде, как показано ниже:

Go

t := temperature{high: -1.0, low: -78.0}
fmt.Printf(«average %v° C\n», t.average()) // Выводит: -39.5° C

1
2

t=temperature{high-1.0,low-78.0}

fmt.Printf(«average %v° C\n»,t.average())// Выводит: -39.5° C

При создании отчета о погоде метод доступен через объединение с полем :

Go

report := report{sol: 15, temperature: t}
fmt.Printf(«average %v° C\n», report.temperature.average()) // Выводит: average -39.5° C

1
2

report=report{sol15,temperaturet}

fmt.Printf(«average %v° C\n»,report.temperature.average())// Выводит: average -39.5° C

Если вы хотите показать среднюю температуру напрямую через тип , нет нужды дублировать логику из Листинга 3. Вместо этого напишите метод, что встраивает реальную имплементацию:

Go

func (r report) average() celsius {
return r.temperature.average()
}

1
2
3

func(rreport)average()celsius{

returnr.temperature.average()

}

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

Вопрос для проверки:

Сравните Листинг 1 и Листинг 2. Какой код удобнее и почему?

Интерфейсы

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

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

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

Мы будет вызывать эту функцию так:

Интерфейсы также могут быть использованы в качестве полей:

Мы можем даже хранить в данные , определив в ней метод
:

Теперь может содержать , и даже другие
.

Go struct is a value type

Go structs are value types. When we assign a struct variable to another struct
variable, a new copy of the struct is created. Likewise, when we pass a struct
to another function, the function receives a new copy of the struct.

valuetype.go

package main

import "fmt"

type User struct {
    name       string
    occupation string
    age        int
}

func main() {

    u1 := User{"John Doe", "gardener", 34}

    u2 := u1

    u2.name = "Richard Roe"
    u2.occupation = "driver"
    u2.age = 44

    fmt.Printf("%s is %d years old and he is a %s\n", u1.name, u1.age, u1.occupation)
    fmt.Printf("%s is %d years old and he is a %s\n", u2.name, u2.age, u2.occupation)
}

In the example, we assign a struct to another struct. Changing the fields of the
new struct does not affect the original struct.

$ go run valuetype.go
John Doe is 34 years old and he is a gardener
Richard Roe is 44 years old and he is a driver

The two structs are distinct entities.

Динамическое выделение памяти для структур

Динамически выделять память под массив структур необходимо в том случае, если заранее неизвестен размер массива. Для определения размера структуры в байтах используется операция sizeof(ИмяСтруктуры).Пример Библиотека из 3 книг

12345678910111213141516171819202122232425262728293031323334

#include <stdio.h>#include <stdlib.h>#include <malloc.h>struct book{  char title;  char author;  int value;};int main(){  struct book *lib;  int i;  system(«chcp 1251»);  system(«cls»);  lib = (struct book*)malloc(3 * sizeof(struct book));  for (i = 0; i<3; i++)  {    printf(«Введите название %d книги : «, i + 1);    gets_s((lib + i)->title);    printf(«Введите автора %d книги : «, i + 1);    gets_s((lib + i)->author);    printf(«Введите цену %d книги : «, i + 1);    scanf_s(«%d», &(lib + i)->value);    getchar();  }  for (i = 0; i<3; i++)  {    printf(«\n %d. %s «, i + 1, (lib + i)->author);    printf(«%s %d», (lib + i)->title, (lib + i)->value);  }  getchar();  return 0;}

Язык Си

Методы

Не смотря на то, что программа стала лучше, мы все еще можем значительно её
улучшить, используя метод — функцию особого типа:

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

Это гораздо проще прочесть, нам не нужно использовать оператор (Go
автоматически предоставляет доступ к указателю на для этого метода), и
поскольку эта функция может быть использована только для мы можем
назвать её просто .

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

В будет написано:

Нулевое значение структуры в Go

Когда структура определена, и она явно не инициализируется каким-либо значением, свойствам структуры по умолчанию присваиваются их нулевые значения. Смотрите пример кода ниже.

// hello.go

package main

import (
	"fmt"
)

// Student struct
type Student struct {
	firstName, lastName string
	age                 int
}

func main() {
	var stud4 Student //zero valued structure
	fmt.Println("Student 4", stud4)
}

Вышеприведенная программа определяет stud4, но она не инициализируется никаким значением. Следовательно, firstName и lastName присваиваются нулевые значения строки, которая равна «», а возрасту присваиваются нулевые значения int, равные 0. Смотрите вывод ниже.

Примеры использования интерфейсов в коде на Golang

Вместе с Go вы можете начать имплементировать код и познавать интерфейсы по ходу дела. Любой код имплементирует интерфейс, даже тот код, что уже существует. Разберем тему на примере.

В следующем листинге выводится выдуманная дата, что состоит из дня года и часа дня.

Листинг 8

Go

package main

import (
«fmt»
«time»
)

// stardate возвращает выдуманное измерение времени для указанной даты.
func stardate(t time.Time) float64 {
doy := float64(t.YearDay())
h := float64(t.Hour()) / 24.0
return 1000 + doy + h
}

func main() {
day := time.Date(2012, 8, 6, 5, 17, 0, 0, time.UTC)
fmt.Printf(«%.1f Curiosity has landed\n», stardate(day)) // Выводит: 1219.2 Curiosity has landed
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

packagemain

import(

«fmt»

«time»

)
 
// stardate возвращает выдуманное измерение времени для указанной даты.

funcstardate(ttime.Time)float64{

doy=float64(t.YearDay())

h=float64(t.Hour())24.0

return1000+doy+h

}
 

funcmain(){

day=time.Date(2012,8,6,5,17,,,time.UTC)

fmt.Printf(«%.1f Curiosity has landed\n»,stardate(day))// Выводит: 1219.2 Curiosity has landed

}

Функция из Листинга 8 ограничивается земными датами. Чтобы исправить это, следующий листинг объявляет интерфейс, который может использовать но уже по своему усмотрению:

Листинг 9

Go

type stardater interface {
YearDay() int
Hour() int
}

// stardate возвращает выдуманное измерение времени.
func stardate(t stardater) float64 {
doy := float64(t.YearDay())
h := float64(t.Hour()) / 24.0
return 1000 + doy + h
}

1
2
3
4
5
6
7
8
9
10
11

typestardaterinterface{

YearDay()int

Hour()int

}
 
// stardate возвращает выдуманное измерение времени.

funcstardate(tstardater)float64{

doy=float64(t.YearDay())

h=float64(t.Hour())24.0

return1000+doy+h

}

Новая функция в Листинге 9 продолжает оперировать земными датами, потому что тип из стандартной библиотеке удовлетворяет требования интерфейса . Интерфейсы в Go являются удовлетворяемыми косвенным образом, что особенно полезно при работе с чужим кодом.

С находящимся на месте интерфейсом Листинг 9 может быть расширен с типом , что удовлетворяет требования интерфейса с методами и , как показано в следующем коде.

Листинг 10

Go

type sol int

func (s sol) YearDay() int {
return int(s % 668) // Марсианской год состоит из 668 дней
}

func (s sol) Hour() int {
return 0 // Неизвестный час
}

1
2
3
4
5
6
7
8
9

typesol int

func(ssol)YearDay()int{

returnint(s%668)// Марсианской год состоит из 668 дней

}
 

func(ssol)Hour()int{

return// Неизвестный час

}

Функция оперирует как земными датами, так и марсианскими днями, как показано в следующем листинге.

Листинг 11

Go

day := time.Date(2012, 8, 6, 5, 17, 0, 0, time.UTC)
fmt.Printf(«%.1f Curiosity has landed\n», stardate(day)) // Выводит: 1219.2 Curiosity has landed

s := sol(1422)
fmt.Printf(«%.1f Happy birthday\n», stardate(s)) // Выводит: 1086.0 Happy birthday

1
2
3
4
5

day=time.Date(2012,8,6,5,17,,,time.UTC)

fmt.Printf(«%.1f Curiosity has landed\n»,stardate(day))// Выводит: 1219.2 Curiosity has landed

s=sol(1422)

fmt.Printf(«%.1f Happy birthday\n»,stardate(s))// Выводит: 1086.0 Happy birthday

Вопрос для проверки: 

В чем преимущество неявно удовлетворяемых интерфейсов?

Вложенные структуры

Структура сама может являться полем структуры. Пример: структура Model – модель автомобиля, имеет название, номер, год выпуска и поле Make, которое в
свою очередь хранит номер марки и её название.

#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define YEAR_OFFSET 1890

typedef struct Model {
	int id;
	struct {
		int id;
		char *name;
	} make;
	char *name;
	unsigned char year; //year is an offset to 1890
} Model;

char* mallocByString(const char *str) {
	char* p = (char*) malloc(strlen(str) + 1);
	strcpy(p, str);
	return p;
}

void freeModel(Model* model) {
	free(model->make.name);
	free(model->name);
}

void xmlModel(Model *model) {
	printf(
"<model id=\"%d\">\n"
"    <make id=\"%d\">\n"
"        <name>%s</name>\n"
"    </make>\n"
"    <year>%d</year>\n"
"    <name>%s</nam>>\n"
"</model>", model->id, model->make.id, model->make.name, model->year, model->name);
}

int main() {
	
	Model cl;
	cl.id = 1;
	cl.make.id = 1;
	cl.make.name = mallocByString("Acura");
	cl.name = mallocByString("CL");
	cl.year = (2003 - YEAR_OFFSET);

	xmlModel(&cl);
	freeModel(&cl);

	getch();
}

Вложенные структуры инициализируются как многомерные массивы. В предыдущем примере можно произвести начальную инициализацию следующим образом:

#include <stdio.h>
#include <conio.h>

typedef struct Model {
    int id;
    struct {
        int id;
        char *name;
    } make;
    char *name;
    unsigned char year; //year is an offset to 1890
} Model;

void main() {
	Model m = {10, {10, "Acura"}, "CL", 112};
	printf("Model name = %s\n", m.name);
	printf("Make name = %s\n", m.make.name);
	getch();
}

P.S. подобным образом инициализировать строки не стоит, здесь так сделано только для того, чтобы упростить код.

Оператор равенства ==

Оператор равенства возвращает значение , если его операнды равны. В противном случае возвращается значение .

Равенство типов значений

Операнды равны, если равны их значения.

Примечание

У операторов , , , и , если какой-то из операндов не является числом (Double.NaN или Single.NaN), результатом операции является . Это означает, что значение не больше, не меньше и не равно любому другому значению (или ), включая . Дополнительные сведения и примеры см. в справочных статьях по Double.NaN или Single.NaN.

Два операнда одного типа enum равны, если равны соответствующие значения базового целочисленного типа.

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

Начиная с версии C# 7.3 операторы и поддерживаются кортежами C#. Дополнительные сведения см. в разделе статьи Типы кортежей.

Равенство ссылочных типов

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

Как показано в примере, определяемые пользователем ссылочные типы поддерживают оператор по умолчанию. Однако ссылочный тип может перегружать оператор . Если ссылочный тип перегружает оператор , воспользуйтесь методом Object.ReferenceEquals, чтобы проверить, что две ссылки этого типа указывают на один и тот же объект.

Равенство типов записей

Типы записей, доступные в C# 9.0 и более поздних версий, поддерживают операторы и , которые по умолчанию обеспечивают семантику равенства значений. То есть два операнда записи равны, когда оба они равны или равны соответствующие значения всех полей и автоматически реализуемых свойств.

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

Равенство строк

Два операнда равны, если они оба имеют значение или оба экземпляра строки имеют одинаковую длину и идентичные символы в каждой позиции символа.

Это порядковое сравнение, учитывающее регистр. Дополнительные сведения о том, как сравнивать строки, см. в статье Сравнение строк в C#.

Равенство делегатов

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

Подробные сведения см. в разделе (Операторы равенства делегатов) в спецификации языка C#.

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

Go nested structs

Go structs can be nested.

nested.go

package main

import "fmt"

type Address struct {
    city    string
    country string
}

type User struct {
    name    string
    age     int
    address Address
}

func main() {

    p := User{
        name: "John Doe",
        age:  34,
        address: Address{
            city:    "New York",
            country: "USA",
        },
    }

    fmt.Println("Name:", p.name)
    fmt.Println("Age:", p.age)
    fmt.Println("City:", p.address.city)
    fmt.Println("Country:", p.address.country)
}

In the example, the struct is nested inside the
struct.

fmt.Println("City:", p.address.city)
fmt.Println("Country:", p.address.country)

To access the fields of the nested struct, we access first the inner struct
with the dot operator; then we access the respective fields.

$ go run nested.go
Name: John Doe
Age: 34
City: New York
Country: USA

This is the output.

Интерфейсы

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

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

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

Мы будем вызывать эту функцию так:

Интерфейсы также могут быть использованы в качестве полей:

Мы можем даже хранить в данные , определив в ней метод
:

Теперь может содержать , и даже другие
.

Creating and initializing a Struct in GoLang

Now, we will create structs and initialize them with values. There are a few ways we could do that.

1. Using struct Literal Syntax

Struct literal syntax is just assigning values when declaring and it is really easy.

package main

import (
	"fmt"
)

type Fruit struct {
	name string
}

func main() {
	var apple = Fruit{"Apple"} // struct literal syntax
	fmt.Println(apple)      // prints {Apple}
}

2. Using the new keyword

We can use the new keyword when declaring a struct. Then we can assign values using dot notation to initialize it.

package main

import (
	"fmt"
)

type Fruit struct {
	name string
}

func main() {
	var banana = new(Fruit)
	banana.name = "Banana"
	fmt.Println(banana)      // prints &{Banana}
}

3. Using pointer address operator

The pointer address operator(&) can be used to declare a struct. Let’s see how we would do that.

package main

import (
	"fmt"
)

type Fruit struct {
	name string
}

func main() {
	var mango = &Fruit{"Mango"}
	fmt.Println(mango)      // prints &{Mango}
}

Пример структур Go

Структура Go — это определяемый пользователем тип, представляющий коллекция полей.

Его можно использовать там, где имеет смысл сгруппировать данные в один блок, а не поддерживать каждый из их как отдельные типы.

Например, у студента есть имя, фамилия и возраст. Имеет смысл сгруппировать эти три поля в единую структуру

  type Student struct {firstName string lastName string age int} 

В приведенном выше фрагменте кода объявляется тип структуры Student , у которого есть поля first_name, last_name и age. Структуру также можно сделать более компактной, объявив поля, принадлежащие к одному типу, в одной строке, за которой следует имя типа.

В приведенной выше программе структуры firstName и lastName принадлежат к одной строке типа, age как целое число, и, следовательно, структура может быть переписана следующим образом.

  type Student struct {firstName, lastName string age int} 

Массивы структур

Работа с массивами структур аналогична работе со статическими массивами других типов данных.

Пример Библиотека из 3 книг

1234567891011121314151617181920212223242526272829303132

#include <stdio.h>#include <stdlib.h>struct book{  char title;  char author;  int value;};int main(){  struct book libry;  int i;  system(«chcp 1251»);  system(«cls»);  for (i = 0; i<3; i++)  {    printf(«Введите название %d книги : «, i + 1);    gets_s(libry.title);    printf(«Введите автора %d книги : «, i + 1);    gets_s(libry.author);    printf(«Введите цену %d книги : «, i + 1);    scanf_s(«%d», &libry.value);    getchar();  }  for (i = 0; i<3; i++)  {    printf(«\n %d. %s «, i + 1, libry.author);    printf(«%s %d», libry.title, libry.value);  }  getchar();  return 0;}

Результат выполнения

Компилятор не меняет порядок полей в структуре и не может оптимизировать такие случае. А мы можем.

Смежные поля могут быть объедены, если их сумма не превышает выравнивания структуры.

type someDataV2 struct{   a int8  // 1 bytec int8  // 1 byteb int64 // 8 byte}

Переместив поля, можно уменьшить размер структуры до 16 байт.

Type someDataV2 is 16 bytes longa at offset 0, size=1, align=1c at offset 1, size=1, align=1b at offset 8, size=8, align=8someDataV2 align is 8Bytes are &uint8{0x1, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}

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

Почти всегда, гораздо важней читаемость кода, чем такие оптимизации

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

Линтеры, которые могут помочь:

  • https://gitlab.com/opennota/check
  • https://github.com/mdempsky/maligned

Код примеров play.golang или github

На сегодня все. Спасибо!

Рейтинг
( Пока оценок нет )
Понравилась статья? Поделиться с друзьями:
Мой редактор ОС
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: