Массив при объявлении должен быть полностью инициализирован иначе компилятор выдаст ошибку

Синтаксис статической инициализации массива использует фигурные скобки, например: int array[10] = {... Вопрос по теме: c++, arrays, compiler-errors.

3 ответа

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

int array[10] = { 0 };

Это будет инициализировать нулевой массив.

Для многомерных массивов вам нужны вложенные фигурные скобки, например:

int cipher[Array_size][Array_size]= { { 0 } };

Обратите внимание, что Array_size должна быть константой времени компиляции, чтобы это работало. Если Array_size не известно во время компиляции, вы должны использовать динамическую инициализацию. (Предпочтительно, std::vector).

Charles Salvia
01 дек. 2010, в 21:58

Поделиться

Вы не можете инициализировать массив до ‘0’, как это

int cipher[Array_size][Array_size]=0;

Вы можете либо инициализировать все значения в массиве, как вы его объявляете так:

// When using different values
int a[3] = {10,20,30};

// When using the same value for all members
int a[3] = {0};

// When using same value for all members in a 2D array
int a[Array_size][Array_size] = { { 0 } };

Или вам нужно инициализировать значения после объявления. Если вы хотите, например, инициализировать все значения до 0, вы можете сделать что-то вроде:

for (int i = 0; i < Array_size; i++ ) {
    a[i] = 0;
}

MahlerFive
01 дек. 2010, в 21:41

Поделиться

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

int cipher[Array_size][Array_size]=0;

Синтаксис для 2D-массивов:

int cipher[Array_size][Array_size]={{0}};

Обратите внимание на фигурные скобки в правой части инструкции инициализации.

для 1D массивов:

int tomultiply[Array_size]={0};

Nathan Fellman
01 дек. 2010, в 21:42

Поделиться

Ещё вопросы

  • 0Выравнивание столбцов в таблице DIV
  • 0Вызов неопределенного метода в топливе PHP
  • 1TCP слушатель получает только первое сообщение
  • 1React Native — измерять компонент при получении реквизита
  • 1В pandas / numpy как создать сводную таблицу с количеством строковых элементов?
  • 0передача данных td на скрытый ввод и последующее размещение с помощью PHP
  • 0Невозможно распаковать результат запроса pymysql в переменные
  • 0Использование GetElementsById для поиска веб-сайта
  • 1Удалить строку Модель объекта ASP.NET MVC ADO.NET
  • 1скрипт Google Apps подсчитывает символы в ячейке
  • 0Реальное применение HTML5-тега <ins />
  • 0angularjs data-ui-sref перезагрузка не работает?
  • 0JQuery AJAX пост текущая дата и время ASP.NET веб-API
  • 1Python Добавить список диктов
  • 0Перезапись объектов в C ++
  • 0MySQL: запись таблицы в CSV-файл
  • 1PyCharm на MacOS не может обрабатывать файлы
  • 0Как pg_put_line знает, в какую таблицу должны быть вставлены данные?
  • 0Как добавить Zend REST API на существующий сайт Concrete5?
  • 1Ошибка при чтении размеров изображения PNG
  • 0Задание полное с элементом?
  • 1Модальные диалоги «Загрузка…» не являются хорошим решением для интерфейса?
  • 1Доставка тестовых параметров с помощью команды adb shell am start
  • 0JQuery load () внешнего PHP, изображения не работают
  • 0SVG элемент из массива
  • 1пользовательское http-сообщение о статусе hapijs
  • 0Ссылка Fancybox на все изображения в div
  • 0Настройте mysimpleads с помощью CakePHP
  • 0Правильное использование видимого свойства привязки данных
  • 0Использование JS для циклического перемещения списка в соответствии с текстом
  • 1Внедрение различных реализаций интерфейса в разные контроллеры с помощью Autofac
  • 1Сохраните Android MapView и перезагрузите
  • 1Калибровочная игольчатая точка
  • 1Обновлять словарь за итерацию, а не за весь
  • 0RegEx-Tel Number Позволяет вводить спам-тире
  • 0bxslider с адаптивным фоновым изображением
  • 1Hibernate Search Ассоциация со свойством IndexEmbedded с составным идентификатором
  • 0C ++ размещение «const» проблемы.
  • 1Как отладить код JavaScript, когда он загружен document.write?
  • 0C ++ Auto Typecast
  • 0jQueryUI, перетаскиваемый на фиксированный элемент
  • 1Выборочные тесты пружинных блоков данных выборочно проходят
  • 0Получить две строки разных значений одного и того же столбца из базы данных (MySQL)
  • 0Модульное тестирование углового контроллера с жасмином
  • 1Добавить текстовое поле в нижний колонтитул gridview
  • 0Как получить доступ к константам без статического пути
  • 1сдвиг питона данных в фреймах данных
  • 1Поведение сравнения переключений JavaScript в случае строки [closed]
  • 1Реализация алгоритма SAD (сумма абсолютных разностей)
  • 1Встроенные циклы, содержащие соединения SQL

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

Иллюстрация


Содержание

Часть материала взята из статьи C++ Arrays (programiz.com)

Объявление массива в C/C++

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

// массив из 100 целых чисел
int ages[100];

// массив из 100 целых неотрицательных чисел
unsigned ages[100];

// массив из 20 чисел с плавающей точкой
// (вы можете использовать константу, известную при компиляции)
constexpr unsigned ARRAY_SIZE = 20;
float rotations[ARRAY_SIZE];

// общий синтаксис
DataType variableName[ARRAY_SIZE];

В C++ массивы статичны: вы не сможете изменить размер или тип элементов после объявления.

Доступ к элементам массива

Вы можете получать доступ к элементам массива, используя индексы и оператор []. Допустим, вы объявили массив marks, как показано ниже. К первому элементу можно обратиться выражением marks[0], ко второму — выражением marks[1], и так далее. Доступ всегда начинается с единицы, а индекс последнего элемента на единицу меньше размера массива.

void example()
{
    // Объявляем массив оценок
    int marks[5];
    // Заполняем массив по элементам.
    marks[0] = 19;
    marks[1] = 10;
    marks[2] = 8;
    marks[3] = 17;
    marks[4] = 9;
}

Иллюстрация

Инициализация массива при объявлении

Можно инициализировать массив при объявлении. Для этого надо указать в списке столько значений, сколько вмещает массив, либо одно значение 0, чтобы заполнить массив нулями:

// Объявляем массив размера 5 и инициализируем.
int marks[5] = { 19, 10, 8, 17, 9 };

// Объявляем массив без указания размера,
//  размер будет определён из списка инициализациии.
int marks[] = { 19, 10, 8, 17, 9 };

// Объявляем массив размера 10 и заполняем нулями.
int ages[10] = { 0 };

Обход элементов массива в цикле

Узнать число элементов в массиве можно функцией std::size. Обойти можно, используя цикл по индексам либо range-based for:

#include <iostream>

int main()
{
    int ages[] = { 17, 18, 29, 30, 16, 27, 22 };

    // цикл по индексам массива,
    //  специальный тип size_t - это беззнаковое целое,
    //  разрядность которого совпадает с разрядностью платформы
    //  (4 байта на 32-битных машинах и 8 байт на 64 битных)
    for (size_t i = 0; i < std::size(ages); ++i)
    {
        const int age = ages[i];
        std::cout << "age #" << i << " is " << age << std::endl;
    }

    // цикл по всем элементам массива,
    //  эта конструкция известна как range-based for.
    int agesSum = 0;
    for (int age : ages)
    {
        agesSum += age;
    }
    std::cout << "ages sum is " << agesSum << std::endl;

    // ещё одна фишка: цикл в стиле итераторов
    auto end = std::end(ages);
    int minAge = ages[0];
    for (auto it = std::begin(ages); it != end; ++it)
    {
        // если этот элемент меньше минимального, обновляем минимальный возраст.
        const int age = *it;
        if (age < minAge)
        {
            minAge = age;
        }
    }
    std::cout << "smallest age is " << minAge << std::endl;
}

Неопределённое поведение: выход за границы (out of bounds)

Выход за пределы массива является неопределённым поведением (англ. undefined behavior). Нет гарантий, как поведёт себя программа в этом случае. Высока вероятность, что вы испортите память других переменных, но эффект может различаться в разных режимах компиляции:

#include <iostream>

int main()
{
    // Индексы элементов: 0, 1, 2
    int ages[] = {1, 2, 3};

    // Неопределённое поведение! Запрос элемента с индексом 3 в массиве,
    //  где такого индекса нет.
    std::cout << ages[3] << std::cout;
}

Передача массива как параметра функции

Массив в стиле языка C хранит только указатель на начало и не хранит свой размер, что и создаёт сложность в передаче в функцию. Размер массива известен во время компиляции, но не известен во время выполнения. Поэтому передать размер можно несколькими не очень очевидными путями:

#include <iostream>

// Передаём указатель на начало массива и размер массива
// Тип size_t - это целочисленный тип, число байтов которого равно числу байт в указателях,
//  то есть 4 байта на 32-битных платформах и 8 байт на 64-битных.
void printArrayV1(int* values, size_t size)
{
    for (size_t i = 0; i < size; ++i)
    {
        std::cout << values[i] << std::endl;
    }
}

// Передаём ссылку на массив известного размера
constexpr size_t AGES_COUNT = 3;

void printArrayV2(int (&values)[AGES_COUNT])
{
    for (size_t i = 0; i < std::size(values); ++i)
    {
        std::cout << values[i] << std::endl;
    }
}

// Третий способ - использовать gsl::span,
//  но ввиду сложности этого пути мы не станем его описывать.

int main()
{
    // Индексы элементов: 0, 1, 2
    int ages[] = {1, 2, 3};

    printArrayV1(ages, std::size(ages));
    printArrayV2(ages);
}

Динамически изменяемый массив

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

Так мог бы выглядеть имитация динамического массива:

#include <cassert>
#include <cstdlib>
#include <iostream>

// Псевдо-динамический массив, вмещает не больше 100 элементов
struct DynamicArray
{
    static constexpr size_t MAX_SIZE = 100;

    size_t size = 0;
    int data[MAX_SIZE] = { 0 };
};

// Добавляет элемент в конец массива
void array_push(DynamicArray& array, int value)
{
    // Если условие не соблюдатся, assert вызовет аварийное
    //  завершение программы (но только в отладочной сборке).
    assert(array.size < DynamicArray::MAX_SIZE);

    // Поместим значение по индексу [size], после последнего элемента
    array.data[array.size] = value;
    // Увеличим размер на единицу
    ++array.size;
}

void array_print(const DynamicArray& array)
{
    std::cout << "{";
    for (size_t i = 0; i < array.size; ++i)
    {
        std::cout << array.data[i];
        // Если следующий элемент существует, выводим запятую
        if (i + 1 < array.size)
        {
            std::cout << ", ";
        }
    }
    std::cout << "}" << std::endl;
}

// Программа создаст массив, заполнит его и выведет содержимое: [10, 3, 7]
int main()
{
    DynamicArray ages;
    array_push(ages, 10);
    array_push(ages, 3);
    array_push(ages, 7);
    array_print(ages);
}

Класс std::vector<T>

Стандартная библиотека C++ содержит шаблонный класс vector, который работает как динамический массив произвольного размера. Размер может расти до тех пор, пока у операционной системы есть область памяти подходящего размера (вплоть до нескольких гигабайт).

Класс является шаблонным, то есть при объявлении переменной потребуется параметризовать шаблон класса vector типом элемента:

#include <vector>

int main()
{
    std::vector<int> ages = { 10, 3, 7 };
}

Использование вектора похоже на использование массива:

  • работает запрос элемента ages[index], причём индексация так же начинается с нуля
  • при выходе за границы динамического массива так же возникает неопределённое поведение (англ. undefined behavior)
  • работает перебор элементов с помощью индексов, range-based for или итераторов
  • есть метод size для получения размера: ages.size()
#include <vector>
#include <iostream>

// Печатает содержимое динамического массива чисел
// В отличии от статичного массива, объект класса vector легко передать как параметр.
void print(const std::vector<int>& values)
{
    std::cout << "{";
    for (size_t i = 0; i < values.size(); ++i)
    {
        std::cout << values[i];
        // Если следующий элемент существует, выводим запятую
        if (i + 1 < values.size())
        {
            std::cout << ", ";
        }
    }
    std::cout << "}" << std::endl;
}

int main()
{
    std::vector<int> ages = { 10, 3, 7 };
    print(ages);
}

Добавление элементов в конец массива

Для добавления существует два метода: push_back и emplace_back

  • push_back получает значение элемента и добавляет в конец
  • emplace_back работает сложнее: он получает параметры, необходимые конструктору элемента, и конструирует его прямо в конце массива

Вы можете практически всегда использовать push_back. Метод pop_back можно использовать для удаления элемента:

#include <vector>
#include <iostream>

int main()
{
    // эквивалентно инициализации ages = { 10, 3, 7 }.
    std::vector<int> ages;
    ages.push_back(10);
    ages.push_back(3);
    ages.push_back(7);

    // убираем последний элемент
    ages.pop_back();
}

В документации std::vector можно прочитать о других методах.

Перемещение элементов в памяти при изменении массива

Динамический массив использует для хранения элементов динамическую память (так же известную как “куча”, англ. heap). При добавлении большого числа элементов динамический массив несколько раз перераспределяет память, поскольку выделенной ранее линейной области памяти уже не хватает для хранения всех элементов. Обычно при нехватке памяти под очередной элемент vector запрашивает новую область памяти в 1,5-2 раза больше предыдущей, перемещает в неё уже существующие элементы и добавляет в конец новый, а затем освобождает старую область памяти.

Если не сообразили, как это происходит, взгляните на картинку:

Иллюстрация

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

Метод erase для удаления элементов из середины

Метод erase класса vector получает итератор и уничтожает элемент, на который итератор указывает:

#include <vector>
#include <iostream>

// Печатает содержимое динамического массива чисел
void print(const std::vector<int>& values)
{
    std::cout << "{";
    for (size_t i = 0; i < values.size(); ++i)
    {
        std::cout << values[i];
        // Если следующий элемент существует, выводим запятую
        if (i + 1 < values.size())
        {
            std::cout << ", ";
        }
    }
    std::cout << "}" << std::endl;
}

int main()
{
    // эквивалентно инициализации ages = { 10, 3, 7 }.
    std::vector<int> ages = { 10, 3, 7 };

    // удаляем элемент с индексом 0, остаётся {3, 7}
    ages.erase(ages.begin());

    // удаляем элемент с индексом 1, остаётся {3}
    ages.erase(ages.begin() + 1);

    print(ages);
}

Последствия перемещения элементов: ошибка в простом цикле с erase

Использование итератора, ссылки или указателя на элемент после перераспределения памяти в массиве является неопределённым поведением: скорее всего произойдёт падение программы либо будет пропущено несколько элементов коллекции. Это показано в примере ниже:

#include <vector>
#include <iostream>

// ! КОД С НЕОПРЕДЕЛЁННЫМ ПОВЕДЕНИЕМ !
// После вызова erase итератор it невалиден и не должен использоваться.
void eraseNegativeAndPrint(std::vector<int> &ages)
{
    for (auto it = ages.begin(); it != ages.end(); ++it)
    {
        if (*it < 0)
        {
            ages.erase(it);
        }
        else
        {
            std::cout << *it << " ";
        }
    }
    std::cout << std::endl;
}

int main()
{
    // эквивалентно инициализации ages = { 10, 3, 7 }.
    std::vector<int> ages = { 10, -33, 23, -18, 7, 38, 99 };
    eraseNegativeAndPrint(ages);
}

Если вы запустите этот код, вы можете увидеть что угодно. Скорее всего программа выведет 10 38 99, хотя должна вывести 10 23 7 38 99 по замыслу автора.

Для решения этой проблемы метод erase возвращает новый, валидный итератор на элемент, следующий после удалённого. Если элемент был последним, erase вернёт итератор end. Учитывая это, мы можем исправить код, чтобы новое значение it либо получалось из erase, либо получалось путём инкремента:

#include <vector>
#include <iostream>

void eraseNegativeAndPrint(std::vector<int> &ages)
{
    for (auto it = ages.begin(); it != ages.end();)
    {
        if (*it < 0)
        {
            it = ages.erase(it);
        }
        else
        {
            std::cout << *it << " ";
            ++it;
        }
    }
    std::cout << std::endl;
}

int main()
{
    // эквивалентно инициализации ages = { 10, 3, 7 }.
    std::vector<int> ages = { 10, -33, 23, -18, 7, 38, 99 };
    eraseNegativeAndPrint(ages);
}

Программа корректно напечатает 10 23 7 38 99.

Идиома remove_if + erase

В C++ есть замечательная библиотека алгоритмов <algorithm>. В данном случае алгоритмом называют шаблон функции, способный заменить цикл в какой-то одной узкой задаче. Например, remove_if перемещает элементы, соответствующие условию, в конец массива (в “удалённую” зону), и возвращает итератор на начала “удалённой” зоны. Затем вызовом erase можно уничтожить элементы из этой зоны.

#include <vector>
#include <iostream>
#include <algorithm>

bool isNegative(int value)
{
    return (value < 0);
}

void eraseNegativeAndPrint(std::vector<int> &ages)
{
    // Алгоритм remove_if принимает два итератора и функцию, которая решает,
    //  надо ли удалять элемент.
    auto newEnd = std::remove_if(ages.begin(), ages.end(), isNegative);

    // У метода erase есть версия, принимающая два итератора:
    //  она удаляет все элементы от первого до второго,
    //  включая первый и не включая второй.
    ages.erase(newEnd, ages.end());

    for (const int age : ages)
    {
        std::cout << age << " ";
    }
}

int main()
{
    // эквивалентно инициализации ages = { 10, 3, 7 }.
    std::vector<int> ages = { 10, -33, 23, -18, 7, 38, 99 };
    eraseNegativeAndPrint(ages);
}

Функция корректно напечатает 10 23 7 38 99.

Для дополнительного чтения

  • О выборе структур данных для начинающих
  • Алгоритмы STL

Модификаторы для параметров методов

Программист может поручить компилятору контроль за параметрами методов, обозначив одни как входные, другие — как выходные. Для таких целей в C# предусмотрен ряд модификаторов, перечисленных в таблице

Таблица
20.8 .
Модификаторы для параметров методов C#

Модификатор Назначение
(нет) — по умолчанию in ) Если модификатор никак не помечен, то по умолчанию считается, что это входящий параметр in для передачи переменной как значения. Вместо пропуска можно поставить in — результат будет таким же.
out Маркирует возвращаемый методом параметр.
ref Маркирует входной параметр, передаваемый по ссылке. Изменение параметра внутри метода означает изменение самих данных, хранящихся вне метода и адресуемых ссылкой.
params Этот модификатор позволяет передавать целый набор параметров как единое целое. В любом методе может быть только один модификатор params и параметр с этим модификатором должен стоять последним в списке параметров метода.

Проиллюстрируем прменение модификаторов на примере

using System;
  
namespace Test
{
class Test
{
//***************************************
public int Add(int x, int y)
{
  return x + y;
}
//***************************************
public void Add(int x, int y, out int sum)
{
  sum = x + y;
}
//***************************************
public void UpperCaseThisString(ref string str)
{
  // Возвращает символьную строку с верхнем регистре
  str = str.ToUpper();
}
//***************************************
public void DisplayArrayOfInt(string msg, params int[] list)
{
  Console.Write(msg);
  for(int i = 0; i < list.Length; i++)
    Console.Write(" {0}", list[i]);// Вывод в строку
}
  
//***************************************
//***************************************
static void Main()
{
  Test test = new Test();
  Console.WriteLine("5 + 10 = {0}", test.Add(5, 10));

  int sum; // Инициализировать необязательно - выходной 
  test.Add(50, 100, out sum);
  Console.WriteLine("50 + 100 = {0}", sum);
  
  string message = "nПривет студентам от Снеткова!";
  Console.WriteLine("Перед обработкой:{0}", message);
  test.UpperCaseThisString(ref message);
  Console.WriteLine("После обработки:{0}", message);
  
  int[] arrayInt = new int[3]{1, 2, 3};
  test.DisplayArrayOfInt("Вывод из массива целых:", 
    arrayInt);
  test.DisplayArrayOfInt("nВывод списка из трех целых:",
     11, 22, 33);
  test.DisplayArrayOfInt("nВывод списка из пяти целых:", 
      55, 66, 77, 88, 99);
        
  while(true);
}
}
}


Листинг
20.38 .
Применение модификаторов параметров методов

Результат примера

5 + 10 = 15
50 + 100 = 150
Перед обработкой:
Привет студентам от Снеткова!
После обработки:
ПРИВЕТ СТУДЕНТАМ ОТ СНЕТКОВА!
Вывод из массива целых: 1 2 3
Вывод списка из трех целых: 11 22 33
Вывод списка из пяти целых: 55 66 77 88 99

Отметьте для себя, что модификаторы out и ref должны применяться и в определении метода и в его вызове, т.е. как при описании формальных параметров, так и фактических.

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

using System;
  
namespace Test
{
class Person
  {
  private string Name;
  private int Age;
  
  //***************************************
  // Конструктор
  public Person(string name, int age)
  {
  Name = name;
  Age = age;
  }
  //***************************************
  // Конструктор по умолчанию
  public Person(){}
  //***************************************
  public void PrintInfo()
  {
  Console.WriteLine("Имя={0}; Возраст={1}", Name, Age);
  }
  }
//****************************************************************
class Test
  {
  //***************************************
  // Что же мне пришлют на этот раз?
  public void DisplayArrayOfObject(params object[] list)
  {
  for(int i = 0; i < list.Length; i++)
  {
  // Относится ли текущий объект из переданного
  // массива объектов к классу Person?
  if(list[i] is Person)
    ((Person)list[i]).PrintInfo();
  else
    Console.WriteLine(list[i]);
  }
  }
//***************************************
static void Main()
  {
  // Создали объект только
  // для того, чтобы вызвать его метод
  Test test = new Test();
  
  // Создаем сотрудника
  Person person = new Person("Студент 
    Вильгельм И.А.", 21);
  // Передаем сотрудника и другие разнотипные объекты в нужном 
  // количестве методу, объявленному с модификатором params
  test.DisplayArrayOfObject(
    "Переменное число параметров любого типа",
    "T 001 TT",
    12345,
    person,
    Math.PI,
    true,
    "Привет студентам от Снеткова",
    "..................... и т.д.");
  
  while(true);
  }
}
}


Листинг
20.39 .
Передача методу произвольных параметров

Результат примера

Переменное число параметров любого типа 
T 001 TT
12345
Имя=Студент Вильгельм И.А.; Возраст=21 
3,14159265358979
True
Привет студентам от Снеткова 
..................... и т.д.

В языке C# нет операций адресации и разадресации & и *, как это имеет место быть в C и C++. Вся передача обеспечивается использованием ссылок с явным или неявным использованием модификатора ref.

Массивы в C#

Массивы в C# — это набор однотипных элементов, доступ к которым производится по их числовому индексу. Наряду с массивами элементов простого встроенного типа (имеющим псевдонимы системных типов) существуют массивы объектов, структур, интерфейсов. Массивы могут быть одномерными (простыми) и многомерными. Массивы объявляются путем помещения квадратных скобок ( операторов индекса ) между типом массива и его именем. После объявления переменной массива при запросе под него памяти указывается количество элементов для хранения.

Следует помнить, что размер массива задается при его создании, но не при объявлении. Если мы создаем массив фиксированного размера, то использование ключевого слова new обязательно. Если при объявлении мы массив сразу инициализируем, то new необязательно — размерность массива определит компилятор. Индексация элементов массива начинается с нуля.

В C++ список инициализации может по количеству элементов не соответствовать заказанной размерности массива. В этом случае компилятор игнорирует лишние элемемнты списка или часть элементов оставляет неинициализированными. В C# заказанная размерность массива должна строго соответствовать количеству элементов в списке инициализации, иначе компилятор выдаст ошибку.

Одномерные массивы

using System;
  
namespace Test
{
class Test
{
  //***************************************
  private void DisplayString(string subTitle, string[] list)
  {
  Console.WriteLine(subTitle);
  for(int i = 0; i < list.Length; i++)
    {
    Console.WriteLine("{0}) {1}", i, list[i]);
    }
  }
  //***************************************
  private void DisplayInt(string subTitle, int[] list)
  {
  Console.WriteLine(subTitle);
  for(int i = 0; i < list.Length; i++)
  {
  Console.WriteLine("{0}) {1}", i, list[i]);
  }
  }
  //***************************************
static void Main()
{
  string title =  "************************
    ********************n"
    + "tОдномерные массивы:n"
    + "*****************************
    ***************n";
  Console.WriteLine(title);
  
  // Создадим объект для применения методов
  // DisplayString() и DisplayInt()
  Test test = new Test();
  
  // Создадим ссылочную переменную типа массива с адресом null
  string[] str1; 
  // Запросим память у системы для 10 элементов и запомним адрес.
  str1 = new string[10]; 
  test.DisplayString( "Строковый массив без 
    инициализации элементов.n"
    + "Строковые элементы по умолчаниюn"
    + "инициализируются пустыми строками."
    , str1);
  
  // Поэлементная инициализация с использованием индексов
  // и той же ссылки с новым адресом.
  // Адрес старого массива потерян в куче. Утерянную 
  // память системе вернет сборщик мусора Garbare Collector
  str1 = new string[7];
  str1[0] = "Колян";
  str1[1] = "Вован";
  str1[2] = "Иван";
  str1[3] = "Болван";
  str1[4] = "Чурбан";
  test.DisplayString("nПоэлементная 
    инициализация с использованием индекса.n"
    + "Старая адресуемая память утеряна 
    безвозвратно:", str1);
  
  // Создадим ссылочную переменную и сразу инциализируем ее адресом.
  int[] int1 = new int[5]; 
  test.DisplayInt("n" 
    + "Числовой массив фиксированного размера.n"
    + "Элементы по умолчанию инициализируются нулями."
    , int1);
  
  // Создаем ссылку на массив. Инициализируем ее адресом, запросив
  // память с фиксированным количеством элементов,
  // и сразу инициализируем элементы массива.
  // Размер списка инициализации должен строго соответствовать
  // запрошенной размерности массива.
  int[] int2 = new int[5] {11, 22, 33, 44, 55};
  test.DisplayInt(
    "n" 
    + "Запрошенный размер массива долженn"
    + "строго соответствовать списку инициализации."
    , int2);
  
  // Создаем ссылку на массив. Инициализируем ее адресом, запросив
  // память без указания количества элементов. Количество элементов
  // определит компилятор при инициализации списком.
  // Используем оператор new.
  int[] int3 = new int[] {11, 22, 33, 44, 55, 66, 77};
  test.DisplayInt(
    "n" 
    + "Размер по списку инициализации, nпамять 
    запрашивается по new"
    + ":"
    , int3);
  
  // Создаем ссылку на массив. Инициализируем ее адресом, запросив
  // память без указания количества элементов. Количество элементов
  // определит компилятор при инициализации списком.
  // Оператор new не используем.
  int[] int4 = {11, 22, 33, 44, 55, 66, 77, 88, 99};
  test.DisplayInt(
    "n" 
    + "Размер по списку инициализации, nпамять 
    запрашивается без new"
    + ":"
    , int4);
      
  while(true);
  }
  }
}


Листинг
20.40 .
Одномерные массивы

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

********************************************
  Одномерные массивы:
******************************************** 
  
Строковый массив без инициализации элементов.
Строковые элементы по умолчанию
инициализируются пустыми строками.
0)
1)
2)
3)
4)
5)
6)
7)
8)
9)
  
Поэлементная инициализация с использованием индекса.
Старая адресуемая память утеряна безвозвратно:
0) Колян
1) Вован
2) Иван
3) Болван
4) Чурбан
5)
6)
  
Числовой массив фиксированного размера.
Элементы по умолчанию инициализируются нулями.
0) 0
1) 0
2) 0
3) 0
4) 0
  
Запрошенный размер массива должен
строго соответствовать списку инициализации.
0) 11
1) 22
2) 33
3) 44
4) 55
  
Размер по списку инициализации, 
память запрашивается по new:
0) 11
1) 22
2) 33
3) 44
4) 55
5) 66
6) 77
  
Размер по списку инициализации, 
память запрашивается без new:
0) 11
1) 22
2) 33
3) 44
4) 55
5) 66
6) 77
7) 88
8) 99

Добавлено 4 июня 2021 в 22:48

Данный урок продолжает обсуждение массивов, начатое в уроке «10.1 – Массивы (часть 1)».

Инициализация фиксированных массивов

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

Один из способов «инициализировать» массив – делать это поэлементно:

int prime[5]; // храним первые 5 простых чисел
prime[0] = 2;
prime[1] = 3;
prime[2] = 5;
prime[3] = 7;
prime[4] = 11;

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

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

// используем список инициализаторов для инициализации фиксированного массива
int prime[5]{ 2, 3, 5, 7, 11 };

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

Однако, если в списке инициализаторов меньше, чем может содержать массив, оставшиеся элементы инициализируются значением 0 (или любым другим значением, в которое 0 преобразуется для нецелочисленного базового типа – например, 0.0 для double). Это называется нулевой инициализацией.

Следующий пример показывает это в действии:

#include <iostream>
 
int main()
{
    int array[5]{ 7, 4, 5 }; // инициализируем только первые 3 элемента
 
    std::cout << array[0] << 'n';
    std::cout << array[1] << 'n';
    std::cout << array[2] << 'n';
    std::cout << array[3] << 'n';
    std::cout << array[4] << 'n';
 
    return 0;
}

Эта программа печатает:

7
4
5
0
0

Следовательно, чтобы инициализировать все элементы массива нулями, вы можете сделать так:

// Инициализируем все элементы значением 0
int array[5]{ };
 
// Инициализируем все элементы значением to 0.0
double array[5]{ };
 
// Инициализируем все элементы пустой строкой
std::string array[5]{ };

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

// неинициализированный
int array[5];
 
// неинициализированный
double array[5];
 
// инициализируем все элементы пустой строкой
std::string array[5];

Лучшая практика


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

Опущенное значение длины

Если вы инициализируете фиксированный массив элементов с помощью списка инициализаторов, компилятор может определить длину массива за вас, и вы можете не указывать длину массива явно.

Следующие две строки эквивалентны:

int array[5]{ 0, 1, 2, 3, 4 }; // определяем длину массива явно
int array[]{ 0, 1, 2, 3, 4 };  // позволяем списку инициализаторов установить длину массива

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

Массивы и перечисления

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

constexpr int numberOfStudents{5};
int testScores[numberOfStudents]{};
testScores[2] = 76;

Кого представляет testScores[2]? Не ясно.

Это можно решить, создав перечисление, в котором перечислители сопоставляются с каждым из возможных индексов массива:

enum StudentNames
{
    kenny,       // 0
    kyle,        // 1
    stan,        // 2
    butters,     // 3
    cartman,     // 4
    max_students // 5
};
 
int main()
{
    int testScores[max_students]{}; // размещаем 5 значений int
    testScores[stan] = 76;
 
    return 0;
}

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

enum StudentNames
{
    kenny,       // 0
    kyle,        // 1
    stan,        // 2
    butters,     // 3
    cartman,     // 4
    wendy,       // 5
    max_students // 6
};
 
int main()
{
    int testScores[max_students]{}; // размещаем 6 значений int
    testScores[stan] = 76; // всё еще работает
 
    return 0;
}

Обратите внимание, что этот «трюк» работает только в том случае, если вы не изменяете значения перечислителей вручную!

Массивы и классы перечислений

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

enum class StudentNames
{
    kenny,       // 0
    kyle,        // 1
    stan,        // 2
    butters,     // 3
    cartman,     // 4
    wendy,       // 5
    max_students // 6
};
 
int main()
{
    int testScores[StudentNames::max_students]{}; // размещаем 6 значений int
    testScores[StudentNames::stan] = 76;
 
    return 0;
}

Вы получите ошибку компиляции. Это можно решить, используя static_cast для преобразования перечислителя в целочисленный тип:

int main()
{
    int testScores[static_cast<int>(StudentNames::max_students)]{}; // разместить 6 значений int
    testScores[static_cast<int>(StudentNames::stan)] = 76;
 
    return 0;
}

Однако делать так довольно утомительно, поэтому может быть лучше использовать обычное перечисление внутри пространства имен:

namespace StudentNames
{
    enum StudentNames
    {
        kenny,       // 0
        kyle,        // 1
        stan,        // 2
        butters,     // 3
        cartman,     // 4
        wendy,       // 5
        max_students // 6
    };
}
 
int main()
{
    int testScores[StudentNames::max_students]{}; // разместить 6 значений int
    testScores[StudentNames::stan] = 76;
 
    return 0;
}

Передача массивов в функцию

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

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

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

Следующий пример иллюстрирует эту концепцию:

#include <iostream>
 
void passValue(int value) // value является копией аргумента
{
    value = 99; // поэтому его изменение здесь не изменит значение аргумента
}
 
void passArray(int prime[5]) // prime - это исходный массив
{
    prime[0] = 11; // поэтому его изменение здесь изменит исходный аргумент!
    prime[1] = 7;
    prime[2] = 5;
    prime[3] = 3;
    prime[4] = 2;
}
 
int main()
{
    int value{ 1 };
    std::cout << "before passValue: " << value << 'n';
    passValue(value);
    std::cout << "after passValue: " << value << 'n';
 
    int prime[5]{ 2, 3, 5, 7, 11 };
    std::cout << "before passArray: " << prime[0] << " " << prime[1] << " " << prime[2] << " " << prime[3] << " " << prime[4] << 'n';
    passArray(prime);
    std::cout << "after passArray: " << prime[0] << " " << prime[1] << " " << prime[2] << " " << prime[3] << " " << prime[4] << 'n';
 
    return 0;
}
before passValue: 1
after passValue: 1
before passArray: 2 3 5 7 11
after passArray: 11 7 5 3 2

В приведенном выше примере value в main() не изменяется, потому что значение параметра в функции passValue() было копией значения переменной в функции main(), а не исходной переменной. Однако, поскольку параметр массива в функции passArray() является исходным массивом, passArray() может напрямую изменять значение элементов!

Почему это происходит, связано с тем, как массивы реализованы в C++, и мы еще вернемся к этой теме после того, как рассмотрим указатели. На данный момент вы можете рассматривать это как причуду языка.

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

// несмотря на то, что на самом деле массив является исходным,
// в этой функции он должен рассматриваться как константа
void passArray(const int prime[5])
{
    // поэтому каждая из этих строк вызовет ошибку компиляции!
    prime[0] = 11;
    prime[1] = 7;
    prime[2] = 5;
    prime[3] = 3;
    prime[4] = 2;
}

Определение длины массива

Для определения длины массивов может использоваться функция std::size() из заголовка <iterator>.

Например:

#include <iostream>
#include <iterator> // for std::size
 
int main()
{
    int array[]{ 1, 1, 2, 3, 5, 8, 13, 21 };
    std::cout << "The array has: " << std::size(array) << " elementsn";
 
    return 0;
}

Эта программа напечатает:

The array has: 8 elements

Обратите внимание, что из-за того, как C++ передает массивы функциям, это не будет работать для массивов, которые были переданы функции!

#include <iostream>
#include <iterator>
 
void printSize(int array[])
{
    std::cout << std::size(array) << 'n'; // ошибка
}
 
int main()
{
    int array[]{ 1, 1, 2, 3, 5, 8, 13, 21 };
    std::cout << std::size(array) << 'n'; // напечатает размер массива
    printSize(array);
 
    return 0;
}

std::size() будет работать и с другими типами объектов (такими как std::array и std::vector), и она вызовет ошибку компиляции, если вы попытаетесь использовать ее с фиксированным массивом, который был передан в функцию! Обратите внимание, что std::size() возвращает значение без знака. Если вам нужно значение со знаком, вы можете либо выполнить приведение типа результата, либо, начиная с C++20, использовать std::ssize() (означает «signed size», размер со знаком).

std::size() была добавлена в C++17. Если вы всё еще используете старый компилятор, вам придется использовать вместо нее оператор sizeof. sizeof не так прост в использовании, как std::size(), и есть несколько вещей, на которые следует обратить внимание. Если вы используете компилятор с поддержкой C++17, можете перейти к разделу «Индексирование массива вне допустимого диапазона».

Оператор sizeof может использоваться с массивами, в этом случае он вернет общий размер массива (длина массива, умноженная на размер элемента).

#include <iostream>
 
int main()
{
    int array[]{ 1, 1, 2, 3, 5, 8, 13, 21 };
    std::cout << sizeof(array) << 'n'; // напечатает размер массива, умноженный на размер int
    std::cout << sizeof(int) << 'n';
 
    return 0;
}

На машине с 4-байтовыми int и 8-байтовыми указателями эта программа напечатала:

32
4

Если размеры типов у вас отличаются, вы можете получить другой результат.

Небольшой трюк: мы можем определить длину фиксированного массива, разделив размер всего массива на размер элемента массива:

#include <iostream>
 
int main()
{
    int array[]{ 1, 1, 2, 3, 5, 8, 13, 21 };
    std::cout << "The array has: " << sizeof(array) / sizeof(array[0]) << " elementsn";
 
    return 0;
}

Это напечатает:

The array has: 8 elements

Как это работает? Во-первых, обратите внимание, что размер всего массива равен длине массива, умноженной на размер элемента. Проще говоря: размер массива = длина массива * размер элемента.

Используя математику, мы можем изменить это уравнение: длина массива = размер массива / размер элемента. sizeof(array) – это размер массива, а sizeof(array[0]) – это размер элемента, поэтому наше уравнение принимает вид длина массива = sizeof(array) / sizeof(array[0]). Обычно в качестве элемента массива мы используем нулевой элемент, поскольку это единственный гарантированно существующий элемент независимо от длины массива.

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

Когда sizeof используется с массивом, который был передан функции, он не вызывает ошибок, как std::size(). Вместо этого он возвращает размер указателя.

#include <iostream>
 
void printSize(int array[])
{
    std::cout << sizeof(array) / sizeof(array[0]) << 'n';
}
 
int main()
{
    int array[]{ 1, 1, 2, 3, 5, 8, 13, 21 };
    std::cout << sizeof(array) / sizeof(array[0]) << 'n';
    printSize(array);
 
    return 0;
}

Снова предполагая размер указателя 8 байт и размер int 4 байта, этот код напечатает:

8
2

Примечание автора


Правильно настроенный компилятор должен вывести предупреждение, если вы попытаетесь использовать sizeof() для массива, переданного функции.

Расчет в main() был правильным, но sizeof() в printSize() вернул 8 (это размер указателя), а 8, деленное на 4, равно 2.

По этой причине будьте осторожны при использовании sizeof() для массивов!

Примечание. В общем случае термины «размер массива» и «длина массива» чаще всего используются для обозначения длины массива (размер массива в большинстве случаев бесполезен, за исключением показанного выше трюка).

Индексирование массива вне допустимого диапазона

Помните, что массив длины N имеет элементы от 0 до N-1. Итак, что произойдет, если вы попытаетесь получить доступ к элементу массива с индексом за пределами этого диапазона?

Рассмотрим следующую программу:

int main()
{
    int prime[5]{}; // храним 5 простых чисел
    prime[5] = 13;
 
    return 0;
}

В этой программе наш массив имеет длину 5, но мы пытаемся записать простое число в шестой элемент (индекс 5).

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

Хотя это случается реже, но C++ также позволяет использовать отрицательный индекс с такими же нежелательными результатами.

Правило


При использовании массивов убедитесь, что ваши индексы допустимы для диапазона вашего массива!

Небольшой тест

Вопрос 1

Объявите массив для хранения максимальной температуры (с точностью до десятых долей градуса) для каждого дня в году (предположим, в году 365 дней). Инициализируйте массив значением 0.0 для каждого дня.

Ответ

double temperature[365] { };

Вопрос 2

Создайте перечисление с именами следующих животных: курица (chicken), собака (dog), кошка (cat), слон (elephant), утка (duck) и змея (snake). Поместите перечисление в пространство имен. Определите массив с элементом для каждого из этих животных и используйте список инициализаторов, чтобы инициализировать каждый элемент для хранения количества ног, которое есть у животного.

Напишите функцию main, которая печатает количество ног слона, используя перечислитель.

Ответ

#include <iostream>
 
namespace Animals
{
    enum Animals // Имя этого перечисления можно не указывать, так как оно нигде не используется
    {
        chicken,
        dog,
        cat,
        elephant,
        duck,
        snake,
        max_animals
    };
}
 
int main()
{
    int legs[Animals::max_animals]{ 2, 4, 4, 4, 2, 0 };
 
    std::cout << "An elephant has " << legs[Animals::elephant] << " legs.n";
 
    return 0;
}

Теги

C++ / CppLearnCppДля начинающихМассивОбучениеПрограммирование

Понравилась статья? Поделить с друзьями:

Читайте также:

  • Ман ошибка edc 00652 04
  • Массаж лица ошибки
  • Ман ошибка 3850
  • Ман командор f2000 коды ошибок
  • Масса к error arm

  • 0 0 голоса
    Рейтинг статьи
    Подписаться
    Уведомить о
    guest

    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии