Многомерные массивы в языке Go
Многомерный массив — это массив массивов. Представьте его кактаблицу данныхилиматрицу, где каждый элемент доступен по нескольким индексам.
Многомерные массивы в Go можно использовать для обработки структурированной информации, такой как табличные данные, матричные операции или игровые доски.
Основные концепции
- Одномерный массив: как точки на прямой, чтобы найти конкретный элемент, нужна только одна координата (индекс)
- Двумерный массив: как таблица, требующая двух координат: строки и столбца
- Трёхмерный массив: как куб, требующий трёх координат: длина, ширина и высота
- Более высокие размерности: теоретически могут иметь много измерений, но на практике чаще всего используются двумерные и трёхмерные массивы
Это можно сравнить с нашими повседневными парковочными местами:
- Одномерный массив= ряд парковочных мест (нужен только номер парковочного места)
- Двумерный массив= многоэтажная парковка (нужны этаж и номер парковочного места)
- Трёхмерный массив= несколько многоэтажных парковок (нужны номер парковки, этаж и номер парковочного места)
Go поддерживает многомерные массивы. Ниже приведены распространённые способы объявления многомерных массивов:
var variable_name [SIZE1][SIZE2]...[SIZEN] variable_type
Объявление и инициализация:
// Объявление двумерного массива var arrayName [rows][cols]elementType // Объявление и инициализация var arrayName [rows][cols]elementType = [rows][cols]elementType{initialValues}В следующем примере объявляется трёхмерный целочисленный массив:
var threedim [5][10][4]int
Двумерный массив
Двумерный массив — это простейший многомерный массив. Двумерный массив по сути состоит из одномерных массивов. Способ определения двумерного массива следующий:
var arrayName [ x ][ y ] variable_type
variable_type — это тип данных в Go, arrayName — имя массива. Двумерный массив можно рассматривать как таблицу, где x — строки, а y — столбцы. На следующей диаграмме показан двумерный массив a с тремя строками и четырьмя столбцами:

К элементам двумерного массива можно получить доступ черезa[ i ][ j ].
Пример
import "fmt"
func main() {
// Шаг 1: Создать массив
values := [][]int{}
// Шаг 2: Использовать функцию append() для добавления двух строк одномерных массивов в пустой двумерный массив
row1 := []int{1, 2, 3}
row2 := []int{4, 5, 6}
values = append(values, row1)
values = append(values, row2)
// Шаг 3: Отобразить две строки данных
fmt.Println("Row 1")
fmt.Println(values[0])
fmt.Println("Строка 2")
fmt.Println(values[1])
// Шаг 4: Доступ к первому элементу
fmt.Println("Первый элемент:")
fmt.Println(values[0][0])
}
Вывод приведенного выше примера:
Row 1 [1 2 3] Row 2 [4 5 6] 第一个元素为: 1
Инициализация двумерного массива
Многомерные массивы можно инициализировать с помощью фигурных скобок. В следующем примере представлен двумерный массив с 3 строками и 4 столбцами:
a := [3][4]int{
{0, 1, 2, 3} , /* 第一行索引为 0 */
{4, 5, 6, 7} , /* 第二行索引为 1 */
{8, 9, 10, 11}, /* 第三行索引为 2 */
}
Примечание:В приведённом выше коде предпоследняя строка}должна иметь запятую, потому что последняя строка}Не обязательно должна быть на одной строке; её также можно записать так:a := [3][4]int{
{0, 1, 2, 3} , /* 第一行索引为 0 */
{4, 5, 6, 7} , /* 第二行索引为 1 */
{8, 9, 10, 11}} /* 第三行索引为 2 */
В следующем примере инициализируется двумерный массив с 2 строками и 2 столбцами:
Пример
import "fmt"
func main() {
// Создаём двумерный массив
sites := [2][2]string{}
// Добавляем элементы в двумерный массив
sites[0][0] = "Google"
sites[0][1] = "Example"
sites[1][0] = "Taobao"
sites[1][1] = "Weibo"
// Вывести результат
fmt.Println(sites)
}
Вывод приведенного выше примера:
[[Google Example] [Taobao Weibo]]
Доступ к двумерному массиву
Доступ к двумерному массиву осуществляется путем указания координат, таких как индекс строки и индекс столбца в массиве. Например:
val := a[2][3] или var value int = a[2][3]
В приведенном выше примере был осуществлен доступ к четвертому элементу в третьей строке двумерного массива val.
Для вывода элементов двумерного массива можно использовать вложенные циклы:
Пример
import "fmt"
func main() {
/* Массив - 5 строк 2 столбца*/
var a = [5][2]int{ {0,0}, {1,2}, {2,4}, {3,6},{4,8}}
var i, j int
/* вывести элементы массива */
for i = 0; i < 5; i++ {
for j = 0; j < 2; j++ {
fmt.Printf("a[%d][%d] = %d\n", i,j, a[i][j] )
}
}
}
Вывод приведенного выше примера:
a[0][0] = 0 a[0][1] = 0 a[1][0] = 1 a[1][1] = 2 a[2][0] = 2 a[2][1] = 4 a[3][0] = 3 a[3][1] = 6 a[4][0] = 4 a[4][1] = 8
В следующем примере создается многомерный массив, в котором количество элементов в каждом измерении неодинаково:
Пример
import "fmt"
func main() {
// Создать пустой двумерный массив
animals := [][]string{}
// Создайте три одномерных массива, каждый с разной длиной
row1 := []string{"рыба", "акула", "угорь"}
row2 := []string{"птица"}
row3 := []string{"ящерица", "саламандра"}
// Используйте функцию append() для добавления одномерного массива в двумерный массив
animals = append(animals, row1)
animals = append(animals, row2)
animals = append(animals, row3)
// Цикл для вывода
for i := range animals {
fmt.Printf("Строка: %v\n", i)
fmt.Println(animals[i])
}
}
Вывод приведенного выше примера:
Row: 0 [fish shark eel] Row: 1 [bird] Row: 2 [lizard salamander]
Доступ к элементам массива и их изменение
Доступ к элементам
Пример
import "fmt"
func main() {
// Создать матрицу 3x3
matrix := [3][3]int{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
}
// Доступ к одному элементу
fmt.Println("Строка 1, Столбец 2:", matrix[0][1]) // Вывод: 2
fmt.Println("Строка 3, Столбец 3:", matrix[2][2]) // Вывод: 9
// Доступ ко всей строке
fmt.Println("Строка 2:", matrix[1]) // Вывод: [4 5 6]
// Обход всех элементов
fmt.Println("\nОбход всех элементов:")
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
fmt.Printf("matrix[%d][%d] = %d\n", i, j, matrix[i][j])
}
}
}
изменение элементов
Пример
import "fmt"
func main() {
// Создать нулевую матрицу 2x2
var grid [2][2]int
fmt.Println("Матрица до изменения:", grid)
// Изменить элемент в определенной позиции
grid[0][0] = 10
grid[0][1] = 20
grid[1][0] = 30
grid[1][1] = 40
fmt.Println("Матрица после изменения:", grid)
// Пакетно изменить строку
grid[0] = [2]int{100, 200}
fmt.Println("После изменения первой строки:", grid)
}
Трехмерные и многомерные массивы
Пример трехмерного массива
Пример
import "fmt"
func main() {
// Объявить трехмерный массив 2x3x4
// Можно понимать как: 2 плоскости, каждая с 3 строками и 4 столбцами
var cube [2][3][4]int
// Инициализация 3D
cube = [2][3][4]int{
{ // Первая плоскость
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12},
},
{ // Вторая плоскость
{13, 14, 15, 16},
{17, 18, 19, 20},
{21, 22, 23, 24},
},
}
// Доступ к элементам 3D-массива
fmt.Println("cube[0][1][2] =", cube[0][1][2]) // Вывод: 7
fmt.Println("cube[1][2][3] =", cube[1][2][3]) // Вывод: 24
// Обход 3D-массива
fmt.Println("\nСодержимое трёхмерного массива:)
for i := 0; i < 2; i++ {
fmt.Printf("Плоскость %d:\n", i)
for j := 0; j < 3; j++ {
for k := 0; k < 4; k++ {
fmt.Printf("%3d ", cube[i][j][k])
}
fmt.Println()
}
fmt.Println()
}
}
см. рисунок ниже:

Общие операции с многомерными массивами
1. Итерация с range
Пример
import "fmt"
func main() {
// Создать двумерный массив
scores := [3][4]int{
{85, 90, 78, 92},
{88, 76, 95, 89},
{92, 85, 88, 90},
}
fmt.Println("Таблица оценок студентов:")
// Использовать range для перебора двумерного массива
for i, row := range scores {
fmt.Printf("Оценки студента %d: ", i+1)
for j, score := range row {
fmt.Printf("%d ", score)
// Если нужны и индекс, и значение
_ = j // Избежать предупреждения о неиспользуемой переменной
}
fmt.Println()
}
// Нужно только значение, а не индекс
total := 0
count := 0
for _, row := range scores {
for _, score := range row {
total += score
count++
}
}
fmt.Printf("\nСредний балл: %.2f\n", float64(total)/float64(count))
}
2. Получение длины массива
Пример
import "fmt"
func main() {
// Создание нерегулярного многомерного массива
jagged := [3][3]int{
{1, 2, 3},
{4, 5},
{6, 7, 8, 9}, // Примечание: это вызовет ошибку компиляции, потому что каждая строка должна быть одинаковой длины
}
// Правильный пример: получение размеров массива
matrix := [4][5]int{}
// Получение количества строк
rows := len(matrix)
fmt.Println("Строк:", rows) // Вывод: 4
// Получить количество столбцов в первой строке (все строки имеют одинаковую длину)
cols := len(matrix[0])
fmt.Println("Столбцов:", cols) // Вывод: 5
// Получить общее количество элементов
totalElements := rows * cols
fmt.Println("Всего элементов:", totalElements) // Вывод: 20
}
3. Сравнение массивов
Пример
import "fmt"
func main() {
// Создать два идентичных двумерных массива
a := [2][2]int{{1, 2}, {3, 4}}
b := [2][2]int{{1, 2}, {3, 4}}
c := [2][2]int{{1, 2}, {3, 5}}
// Массивы можно сравнивать напрямую (только когда размерности точно совпадают)
fmt.Println("a == b:", a == b) // Вывод: true
fmt.Println("a == c:", a == c) // Вывод: false
// Примечание: массивы разных размерностей нельзя сравнивать
// d := [2][3]int{{1, 2, 3}, {4, 5, 6}}
// fmt.Println(a == d) // Ошибка компиляции: несоответствие типов
}
Практические сценарии применения
Сценарий 1: Игровое поле (Крестики-нолики)
Пример
import "fmt"
func main() {
// Инициализировать доску 3x3 для крестиков-ноликов
var board [3][3]string
// Инициализировать как пустое
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
board[i][j] = " "
}
}
// Имитировать ходы
board[0][0] = "X"
board[1][1] = "O"
board[2][2] = "X"
// Вывести доску
fmt.Println("Доска для крестиков-ноликов:")
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
fmt.Printf(" %s ", board[i][j])
if j < 2 {
fmt.Printf("|")
}
}
fmt.Println()
if i < 2 {
fmt.Println("---+---+---")
}
}
}
Сценарий 2: Система управления оценками студентов
Пример
import "fmt"
func main() {
// Определение: 3 студента, у каждого студента по 4 курса
var grades [3][4]float64
// Ввод баллов студентов
grades = [3][4]float64{
{85.5, 90.0, 78.5, 92.0}, // Баллы студента 1
{88.0, 76.5, 95.0, 89.5}, // Баллы студента 2
{92.5, 85.0, 88.5, 90.0}, // Баллы студента 3
}
// Вычислить средний балл каждого студента
fmt.Println("Статистика баллов студентов:")
for i, studentGrades := range grades {
sum := 0.0
for _, grade := range studentGrades {
sum += grade
}
average := sum / float64(len(studentGrades))
fmt.Printf("Студент %d: средний балл = %.2f\n", i+1, average)
}
// Вычислить средний балл по каждому курсу
fmt.Println("\nСредний балл по курсу:")
for j := 0; j < 4; j++ {
sum := 0.0
for i := 0; i < 3; i++ {
sum += grades[i][j]
}
average := sum / 3.0
fmt.Printf("Курс %d: средний балл = %.2f\n", j+1, average)
}
}
Сценарий 3: Обработка пикселей изображения
Пример
import "fmt"
func main() {
// Симулировать простое изображение в оттенках серого 3x3
// Диапазон значений каждого пикселя: 0(чёрный) ~ 255(белый)
var image [3][3]int
// Инициализируем изображение (простой градиент)
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
image[i][j] = (i + j) * 50
}
}
// Отобразить исходное изображение
fmt.Println("Исходное изображение:")
displayImage(image)
// Обработка изображения: увеличение яркости
fmt.Println("\nИзображение после увеличения яркости:")
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
// Увеличить яркость, но не превышать 255
newValue := image[i][j] + 50
if newValue > 255 {
newValue = 255
}
image[i][j] = newValue
}
}
displayImage(image)
}
func displayImage(img [3][3]int) {
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
fmt.Printf("%3d ", img[i][j])
}
fmt.Println()
}
}
Заметки и лучшие практики
1. Длина массива — часть типа
Пример
import "fmt"
func main() {
// Эти два — разные типы!
var a [2][3]int
var b [3][2]int
// Следующий код вызовет ошибку компиляции
// a = b // ошибка: несоответствие типов
fmt.Printf("тип a: %T\n", a) // [2][3]int
fmt.Printf("тип b: %T\n", b) // [3][2]int
}
2. Тип-значение vs ссылочный тип
Пример
import "fmt"
func main() {
// Массивы — это типы-значения
original := [2][2]int{{1, 2}, {3, 4}}
// Присваивание создаёт копию
copy := original
// Изменение копии не влияет на исходный массив
copy[0][0] = 100
fmt.Println("Исходный массив:", original) // [[1 2] [3 4]]
fmt.Println("Копия массива:", copy) // [[100 2] [3 4]]
// Если вам нужна ссылочная семантика, можно использовать срезы (описаны в следующих статьях)
}
3. Вопросы производительности
- Непрерывная память: многомерные массивы хранятся в памяти непрерывно, обеспечивая быстрый доступ
- Фиксированный размер: длина массива определяется во время компиляции и не может изменяться динамически
- Подходящие сценарии: когда размер данных известен и фиксирован, массивы — лучший выбор
Часто задаваемые вопросы
Вопрос 1: В чём разница между многомерным массивом и вложенным срезом?
| Особенности | Многомерные массивы | Вложенные срезы |
|---|---|---|
| Size | Фиксированный, определяется во время компиляции | Динамический, изменяемый во время выполнения |
| Память | Непрерывное выделение | Возможно, несмежное |
| Производительность | Высокая скорость доступа | Немного медленнее, имеет дополнительные накладные расходы |
| Сценарий использования | Размер данных известен | Размер данных переменный |
Вопрос 2: Как создать зубчатый двумерный массив?
Массивы Go требуют, чтобы каждая строка имела одинаковую длину. Если вам нужна зубчатая структура, следует использовать срезы:
Пример
irregular := [][]int{
{1, 2, 3},
{4, 5}, // В этой строке только 2 элемента
{6, 7, 8, 9}, // Эта строка содержит 4 элемента
}
Q3: Можно ли использовать многомерный массив в качестве параметра функции?
Да, но обратите внимание, что массивы являются типами значений, и передача больших массивов приводит к затратам производительности:
Пример
// Обработка матрицы...
matrix[0][0] = 100
return matrix
}
// Лучший способ — использовать указатели или срезы
func processMatrixPtr(matrix *[3][3]int) {
matrix[0][0] = 100
}