• Двумерный динамический массив си. Одномерные динамические массивы

    // объявление двумерного динамического массива на 10 элементов:

    float **ptrarray = new float* ; // две строки в массиве

    for (int count = 0; count < 2; count++)

    ptrarray = new float ; // и пять столбцов

    // где ptrarray – массив указателей на выделенный участок памяти под массив вещественных чисел типа float

    Сначала объявляется указатель второго порядка float **ptrarray, который ссылается на массив указателей float* ,где размер массива равен двум. После чего в циклеforкаждой строке массива объявленного встроке 2 выделяется память под пять элементов. В результате получается двумерный динамический массив ptrarray.Рассмотрим пример высвобождения памяти отводимой под двумерный динамический массив.

    // высвобождение памяти отводимой под двумерный динамический массив:

    for (int count = 0; count < 2; count++)

    delete ptrarray;

    // где 2 – количество строк в массиве

    #include
    #include
    #include
    void main()
    {

    int *a; // указатель на массив

    system("chcp 1251");

    scanf("%d", &n);

    scanf("%d", &m);

    // Выделение памяти

    a = (int*) malloc(n*m*sizeof(int));

    // Ввод элементов массива

    for(i=0; i

    for(j=0; j

    printf("a[%d][%d] = ", i, j);

    scanf("%d", (a+i*m+j));

    // Вывод элементов массива

    for(i=0; i

    for(j=0; j

    printf("%5d ", *(a+i*m+j)); // 5 знакомест под элемент массива

    getchar(); getchar();
    }

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

    Введите количество строк: 3

    Введите количество столбцов: 4

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

    Функция malloc() – возвращает указатель на первый байт области памяти размером size, которая была выделена из динамически распределяемой области памяти. Если в динамической области памяти не хватает памяти, то возвращается нулевой указатель.

    #include
    #include
    #include
    void main()
    {

    int **a; // указатель на указатель на строку

    system("chcp 1251");

    printf("Введите количество строк: ");

    scanf("%d", &n);

    printf("Введите количество столбцов: ");

    scanf("%d", &m);

    // Выделение памяти под указатели на строки

    a = (int**)malloc(n*sizeof(int*));

    // Ввод элементов массива

    for(i=0; i

    // Выделение памяти под хранение строк

    a[i] = (int*)malloc(m*sizeof(int));

    for(j=0; j

    printf("a[%d][%d] = ", i, j);

    scanf("%d", &a[i][j]);

    // Вывод элементов массива

    for(i=0; i

    for(j=0; j

    printf("%5d ", a[i][j]); // 5 знакомест под элемент массива

    free(a[i]); // освобождение памяти под строку

    getchar(); getchar();
    }

    Результат выполнения программы аналогичен предыдущему случаю.

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

    Указатели.

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

    По типу данных в СИ указатели делятся на:

    Типизированный указатель – указатель, содержащий адрес данных определенного типа (системного или пользовательского).

    Не типизированный указатель – указатель, содержащий адрес данных неопределенного типа (просто адрес).

    Объявление указателя;

    Установка указателя;

    обращение к значению, расположенному по указателю. Объявление (описание) указателя в языке СИ имеет следующий вид:

    Тип *имя [=значение];

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

    Нулевое значение (идентификатор NULL);

    Другой указатель;

    Адрес переменной (через операцию взятия адреса);

    Выражение, представляющее собой арифметику указателей;

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

    #include

    int var; // обычная целочисленная переменная

    int *ptrVar; // целочисленный указатель (ptrVar должен быть типа int, так как он будет ссылаться на переменную типа int)

    ptrVar = &var; // присвоили указателю адрес ячейки в памяти, где лежит значение переменной var

    scanf("%d", &var); // в переменную var положили значение, введенное с клавиатуры

    printf("%d\n", *ptrVar); // вывод значения через указатель

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

    Лекция №3.

    Функции.

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

    Описание функции на языке СИ осуществляется в любом месте программы вне описания других функций и состоит из трех элементов:

    1. прототип функции;

    2. заголовок функции;

    3. тело функции.

    Прототип функции – необязательная часть описания функции, предназначенная для объявления некоторой функции, интерфейс которой соответствует данному прототипу.Объявление прототипа имеет следующий вид:

    Тип имя(список типов формальных параметров);

    Параметры функции – значения, передаваемые в функцию при ее вызове.

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

    Тип имя(список формальных параметров)

    Примеры заголовков функций:

    Int func(int i, double x, double y)

    Void func(int ind, char *string)

    Double func(void)

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

    Реализация функции на СИ для вычисления факториала числа.

    Double factorial(unsigned);

    Double factorial(unsigned num)

    Double fact = 1.0;

    For(unsigned i=1;i<=num;i++)

    Fact *= (double)i;

    Return fact;

    Структуры.

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

    Объявление в СИ структуры имеет вид:

    Struct [имя типа]

    Поле_1;

    Поле_2;

    Поле_N;

      } [список переменных];

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

    Файлы.

    Файл – это именованная область данных на каком-либо носителе информации. Типы файлов (относительно языка «СИ»):
      текстовые;
      бинарные.
    Основные операции производимые над файлами:
    1.Открытие файлов.
    2.Чтение и запись данных.
    3.Закрытие файлов.

    Дополнительные операции:
    1.Навигация по файлу.
    2.Обработка ошибок работы с файлами.
    3.Удаление и переименование файлов.
    4.Описание переменной

    Режимы открытия файлов с СИ

    Перенаправление потоков
     FILE * freopen(const char *filename, const char *mode, FILE *stream);

    Функция возвращает:
     Указатель на файл – все нормально,
     NULL – ошибка переопределения.

    Закрытие файла
     int fclose(FILE *stream);

    Функция возвращает:
     0 – файл успешно закрыт.
     1 – произошла ошибка закрытия файла.

    Проверка на достижение конца файла
     int feof(FILE *stream);
     stream - указатель на открытый файл.

    Функция возвращает:
     0 – если конец файла еще не достигнут.
     !0 – достигнут конец файла.

    Открытие текстовых файлов
    Во втором параметре дополнительно указывается символ t (необязательно):
     rt, wt, at, rt+, wt+, at+

    Чтение из текстового файла

    Форматированное чтение
     int fscanf(FILE *stream, const char * format, ...);

    Функция возвращает:
     >0 – число успешно прочитанных переменных,
     0 – ни одна из переменных не была успешно прочитана,
     EOF – ошибка или достигнут конец файла.
    Чтение строки

    Функция возвращает:
     buffer – все нормально,
    Чтение строки
     char * fgets(char * buffer, int maxlen, FILE *stream);

    Функция возвращает:
     buffer – все нормально,
     NULL – ошибка или достигнут конец файла.
    Чтение символа
     int fgetc(FILE *stream);
    Функция возвращает:
     код символа – если все нормально,
     EOF – если ошибка или достигнут конец файла.
    Помещение символа обратно в поток
     int ungetc(int c, FILE *stream);
    Функция возвращает:
     код символа – если все успешно,
     EOF – произошла ошибка.

    Запись в текстовый файл в СИ

    Форматированный вывод
     int fprintf(FILE *stream, const char *format, ...);
    Функция возвращает:
     число записанных символов – если все нормально,
     отрицательное значение – если ошибка.
    Запись строки
     int fputs(const char *string, FILE *stream);
    Функция возвращает:
     число записанных символов – все нормально,
     EOF – произошла ошибка.
    Запись символа
     int fputc(int c, FILE *stream);
    Функция возвращает:
     код записанного символа – все нормально,
     EOF – произошла ошибка.
    Открытие бинарных файлов
     Во втором параметре дополнительно указывается символ b (обязательно):rb, wb, ab, rb+, wb+, ab+
    Чтение из бинарных файлов
     size_t fread(void *buffer, size_t size, size_t num,FILE *stream);
    Функция возвращает количество прочитанных блоков. Если оно меньше num, то произошла ошибка или достигнут
    конец файла.

    Запись в бинарный файл
     size_t fwrite(const void *buffer, size_t size, size_t num, FILE *stream);
    Функция возвращает количество записанных блоков. Если оно меньше num, то произошла ошибка.

    Навигация по файлу

    Чтение текущего смещения в файле:
     long int ftell(FILE *stream);
    Изменение текущего смещения в файле:
     int fseek(FILE *stream, long int offset, int origin);

    SEEK_SET (0) – от начала файла.
     SEEK_CUR (1) – от текущей позиции.
     SEEK_END (2) – от конца файла.
    Функция возвращает:
     0 – все нормально,
     !0 – произошла ошибка.
    Перемещение к началу файла:
     void rewind(FILE *stream);
    Чтение текущей позиции в файле:
     int fgetpos(FILE *stream, fpos_t *pos);
    Установка текущей позиции в файле:
     int fsetpos(FILE *stream, const fpos_t *pos);
    Функции возвращают:
     0 – все успешно,
     !0 – произошла ошибка.
    Структура fpos_t:
     typedef struct fpos_t {
      long off;
      mbstate_t wstate;
     } fpos_t;

    Получение признака ошибки:
     int ferror(FILE *stream);
    Функция возвращает ненулевое значение, если возникла ошибка.
    Функция сброса ошибки:
     void clearerr(FILE *stream);
    Функция вывода сообщения об ошибке:
     void perror(const char *string);

    Буферизация

    Функция очистки буфера:
     int fflush(FILE *stream);
    Функция возвращает:
     0 – все нормально.
     EOF – произошла ошибка.
    Функция управления буфером:
     void setbuf(FILE *stream, char * buffer);

    Создает буфер размером BUFSIZ. Используется до ввода или вывода в поток.

    Временные файлы

    Функция создания временного файла:
     FILE * tmpfile(void);
    Создает временный файл в режиме wb+. После закрытия файла, последний автоматически удаляется.
    Функция генерации имени временного файла:
     char * tmpnam(char *buffer);

    Удаление и переименование

    Функция удаления файла:
     int remove(const char *filename);
    Функция переименования файла:
     int rename(const char *fname, const char *nname);
    Функции возвращают:
     0 – в случае успеха,
     !0 – в противном случае.

    Лекция №4.

    Стек.

    Стек (stack) является как бы противоположностью очереди, поскольку он работает по принципу "последним пришел - первым вышел" (last-in, first-out, LIFO). Чтобы наглядно представить себе стек, вспомните стопку тарелок. Первая тарелка, стоящая на столе, будет использована последней, а последняя тарелка, положенная наверх - первой. Стеки часто применяются в системном программном обеспечении, включая компиляторы и интерпретаторы.

    При работе со стеками операции занесения и извлечения элемента являются основными. Данные операции традиционно называются "затолкать в стек" (push) и "вытолкнуть из стека" (pop). Поэтому для реализации стека необходимо написать две функции: push(), которая "заталкивает" значение в стек, и pop(), которая "выталкивает" значение из стека. Также необходимо выделить область памяти, которая будет использоваться в качестве стека. Для этой цели можно отвести массив или динамически выделить фрагмент памяти с помощью функций языка С, предусмотренных для динамического распределения памяти. Как и в случае очереди, функция извлечения получает из списка элемент и удаляет его, если он не хранится где-либо еше. Ниже приведена общая форма функций push() и pop(), работающих с целочисленным массивом. Стеки данных другого типа можно организовывать, изменив базовый тип данных массива.

    int tos=0; /* вершина стека */

    /* Затолкать элемент в стек. */

    void push(int i)

    if(tos >= MAX) {

    printf("Стак полон\n");

    /* Получить верхний элемент стека. */

    if(tos < 0) {

    printf("Стек пуст\n");

    return stack;

    Переменная tos ("top of stack" - "вершина стека") содержит индекс вершины стека. При реализации данных функций необходимо учитывать случаи, когда стек заполнен или пуст. В нашем случае признаком пустого стека является равенство tos нулю, а признаком переполнения стека - такое увеличение tos, что его значение указывает куда-нибудь за пределы последней ячейки массива.

    Пример работы со стеком.

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

    /* Простой калькулятор с четырмя действиями. */

    #include

    #include

    int *p; /* указатель на область свободной памяти */

    int *tos; /* указатель на вершину стека */

    int *bos; /* указатель на дно стека */

    void push(int i);

    p = (int *) malloc(MAX*sizeof(int)); /* получить память для стека */

    printf("Ошибка при выделении памяти\n");

    bos = p + MAX-1;

    printf("Калькулятор с четырьмя действиями\n");

    printf("Нажмите "q" для выхода\n");

    printf("%d\n", a+b);

    printf("%d\n", b-a);

    printf("%d\n", b*a);

    printf("Деление на 0.\n");

    printf("%d\n", b/a);

    case ".": /* показать содержимое вершины стека */

    printf("Текущее значение на вершине стека: %d\n", a);

    } while(*s != "q");

    /* Занесение элемента в стек. */

    void push(int i)

    if(p > bos) {

    printf("Стек полон\n");

    /* Получение верхнего элемента из стека. */

    if(p < tos) {

    printf("Стек пуст\n");

    Очередь.

    Очередь - это линейный список информации, работа с которой происходит по принципу "первым пришел - первым вышел" (first-in, first-out); этот принцип (и очередь как структура данных) иногда еще называется FIFO. Это значит, что первый помещенный в очередь элемент будет получен из нее первым, второй помещенный элемент будет извлечен вторым и т.д. Это единственный способ работы с очередью; произвольный доступ к отдельным элементам не разрешается.

    Чтобы представить себе работу очереди, давайте введем две функции: qstore() и qretrieve() (от "store"- "сохранять", "retrieve" - "получать"). Функция qstore() помещает элемент в конец очереди, а функция qretrieve() удаляет элемент из начала очереди и возвращает его значение. В таблице показано действие последовательности таких операций.

    Действие Содержимое очереди
    qstore(A) A
    qstore(B) А В
    qstore(C) A B C
    qretrieve() возвращает А В С
    qstore(D) B C D
    qretrieve() возвращает В C D
    qretrieve() возвращает С D

    Следует иметь в виду, что операция извлечения удаляет элемент из очереди и уничтожает его, если он не хранится где-нибудь в другом месте. Поэтому после извлечения всех элементов очередь будет пуста.

    В программировании очереди применяются при решении многих задач. Один из наиболее популярных видов таких задач - симуляция. Очереди также применяются в планировщиках задач операционных систем и при буферизации ввода/вывода.

    /* Мини-планировщик событий */

    #include

    #include

    #include

    #include

    char *p, *qretrieve(void);

    void enter(void), qstore(char *q), review(void), delete_ap(void);

    for(t=0; t < MAX; ++t) p[t] = NULL; /* иницилизировать массив

    пустыми указателями */

    printf("Ввести (E), Список (L), Удалить (R), Выход (Q): ");

    *s = toupper(*s);

    /* Вставка в очередь новой встречи. */

    void enter(void)

    printf("Введите встречу %d: ", spos+1);

    if(*s==0) break; /* запись не была произведена */

    p = (char *) malloc(strlen(s)+1);

    printf("Не хватает памяти.\n");

    if(*s) qstore(p);

    /* Просмотр содержимого очереди. */

    void review(void)

    for(t=rpos; t < spos; ++t)

    printf("%d. %s\n", t+1, p[t]);

    /* Удаление встречи из очереди. */

    void delete_ap(void)

    if((p=qretrieve())==NULL) return;

    printf("%s\n", p);

    /* Вставка встречи. */

    void qstore(char *q)

    printf("List Full\n");

    /* Извлечение встречи. */

    char *qretrieve(void)

    if(rpos==spos) {

    printf("Встречь больше нет.\n");

    return p;

    Список.

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

    int data;//поле данных

    s *next;//следующий элемент

    } *first,*curr;//первый и текущий элемент

    Инициализация:

    first->next=curr;

    чтобы получить первый элемент используй first->data

    чтобы добавить новый элемент: curr->next=new s;

    curr=curr->next;//переходишь к последнему

    и чтобы получить например 50 элемент через цикл перебирай список:

    curr=first;//переход к первому

    for(int i=0;i<50;i++)

    if(curr->next!=NULL)

    curr=curr->next;


    Похожая информация.


    Обычно, объем памяти, необходимый для той или иной переменной, задается еще до процесса компиляции посредством объявления этой переменной. Если же возникает необходимость в создание переменной, размер которой неизвестен заранее, то используют динамическую память. Резервирование и освобождение памяти в программах на C++ может происходить в любой момент времени. Осуществляются операции распределения памяти двумя способами:

    • с помощью функции malloc , calloc , realloc и free;
    • посредством оператора new и delete .

    Функция malloc резервирует непрерывный блок ячеек памяти для хранения указанного объекта и возвращает указатель на первую ячейку этого блока. Обращение к функции имеет вид:

    void *malloc(size);

    Здесь size - целое беззнаковое значение, определяющее размер выделяемого участка памяти в байтах. Если резервирование памяти прошло успешно, то функция возвращает переменную типа void * , которую можно привести к любому необходимому типу указателя.

    Функция - calloc также предназначена для выделения памяти. Запись ниже означает, что будет выделено num элементов по size байт.

    void *calloc (nime, size);

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

    Функция realloc изменяет размер выделенной ранее памяти. Обращаются к ней так:

    char *realloc (void *p, size);

    Здесь p - указатель на область памяти, размер которой нужно изменить на size . Если в результате работы функции меняется адрес области памяти, то новый адрес вернется в качестве результата. Если фактическое значение первого параметра NULL , то функция realloc работает также, как и функция malloc , то есть выделяет участок памяти размером size байт.

    Для освобождения выделенной памяти используется функция free . Обращаются к ней так:

    void free (void *p size);

    Здесь p - указатель на участок памяти, ранее выделенный функциями malloc , calloc или realloc .

    Операторы new и delete аналогичны функциям malloc и free . New выделяет память, а его единственный аргумент - это выражение, определяющее количество байтов, которые будут зарезервированы. Возвращает оператор указатель на начало выделенного блока памяти. Оператор delete освобождает память, его аргумент - адрес первой ячейки блока, который необходимо освободить.

    Динамический массив - массив переменной длины, память под который выделяется в процессе выполнения программы. Выделение памяти осуществляется функциями calloc, malloc или оператором new . Адрес первого элемента выделенного участка памяти хранится в переменной, объявленной как указатель. Например, следующий оператор означает, что описан указатель mas и ему присвоен адрес начала непрерывной области динамической памяти, выделенной с помощью оператора new :

    int *mas=new int;

    Выделено столько памяти, сколько необходимо для хранения 10 величин типа int.

    Фактически, в переменной mas хранится адрес нулевого элемента динамического массива. Следовательно, адрес следующего, первого элемента, в выделенном участке памяти - mas +1, а mas +i является адресом i-го элемента. Обращение к i-му элементу динамического массива можно выполнить, как обычно mas[i], или другим способом *(mas +i ) . Важно следить за тем, чтобы не выйти за границы выделенного участка памяти.

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

    Предлагаю рассмотреть несколько задач, закрепляющих данный урок:

    Задача 1

    Найти сумму вещественных элементов динамического массива.

    //Пример использования функции malloc и free #include "stdafx.h" #include using namespace std; int main() { setlocale(LC_ALL,"Rus"); int i, n; float *a; //указатель на float float s; cout<<"\n"; cin>>n; //ввод размерности массива //выделение памяти под массив из n вещественных элементов a=(float *)malloc(n*sizeof(float)); cout<<"Введите массив A \n"; //ввод элементов массива for (i=0; i>*(a+i); } //накапливание суммы элементов массива for (s=0, i=0; i

    //Пример использования функции malloc и free

    #include "stdafx.h"

    #include

    using namespace std ;

    int main ()

    int i , n ;

    float * a ; //указатель на float

    float s ;

    cout << "\n" ; cin >> n ; //ввод размерности массива

    //выделение памяти под массив из n вещественных элементов

    a = (float * ) malloc (n * sizeof (float ) ) ;

    cout << "Введите массив A \n" ;

    //ввод элементов массива

    for (i = 0 ; i < n ; i ++ )

    cin >> * (a + i ) ;

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

    for (s = 0 , i = 0 ; i < n ; i ++ )

    s += * (a + i ) ;

    //вывод значения суммы

    cout << "S=" << s << "\n" ;

    //освобождение памяти

    free (a ) ;

    system ("pause" ) ;

    return 0 ;

    Задача 2

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

    //Пример использования операторов new и delete #include "stdafx.h" #include using namespace std; int main() { setlocale(LC_ALL,"Rus"); int i, n; //ввод количества элементов массива cout<<"n="; cin>>n; //выделение памяти int *a=new int[n]; cout<<"Введите элементы массива:\n"; //ввод массива for (i=0; i>a[i]; //вывод заданного массива for (i=0; i

    //Пример использования операторов new и delete

    #include "stdafx.h"

    #include

    using namespace std ;

    int main ()

    setlocale (LC_ALL , "Rus" ) ;

    int i , n ;

    //ввод количества элементов массива

    cout << "n=" ; cin >> n ;

    //выделение памяти

    int * a = new int [ n ] ;

    cout << "Введите элементы массива:\n" ;

    //ввод массива

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

    Указатель в С++ – переменная, которая в себе хранит адрес данных (значения) в памяти, а не сами данные.

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

    Допустим, в программе нам необходимо создать целочисленный массив, точный размер которого нам не известен до начала работы программы. То есть мы не знаем какое количество чисел понадобится пользователю внести в этот массив. Конечно, мы можем подстраховаться и объявить массив на несколько тысяч элементов (к примеру на 5 000). Этого (по нашему субъективному мнению) должно хватить пользователю для работы. Да – действительно – этого может быть достаточно. Но не будем забывать, что этот массив займет в оперативной памяти много места (5 000 * 4 (тип int) = 20 000 байт). Мы то подстраховались, а пользователь будет заполнять только 10 элементов нашего массива. Получается, что реально 40 байт в работе, а 19 960 байт напрасно занимают память.

    неразумное использование оперативной памяти

    #include using namespace std; int main() { setlocale(LC_ALL, "rus"); const int SizeOfArray = 5000; int arrWithDigits = {}; cout << "Массив занял в памяти " << sizeof(arrWithDigits) << " байт" << endl; int amount = 0; cout << "Сколько чисел вы введёте в массив? "; cin >> amount; cout << "Реально необходимо " << amount * sizeof(int) << " байт" << endl; for (int i = 0; i < amount; i++) { cout << i + 1 << "-е число: "; cin >> arrWithDigits[i]; } cout << endl; for (int i = 0; i < amount; i++) { cout << arrWithDigits[i] << " "; } cout << endl; return 0; }

    #include

    using namespace std ;

    int main ()

    const int SizeOfArray = 5000 ;

    int arrWithDigits [ SizeOfArray ] = { } ;

    cout << "Массив занял в памяти " << sizeof (arrWithDigits ) << " байт" << endl ;

    int amount = 0 ;

    cout << "Сколько чисел вы введёте в массив? " ;

    cin >> amount ;

    cout << "Реально необходимо " << amount * sizeof (int ) << " байт" << endl ;

    for (int i = 0 ; i < amount ; i ++ )

    cout << i + 1 << "-е число: " ;

    cin >> arrWithDigits [ i ] ;

    cout << endl ;

    for (int i = 0 ; i < amount ; i ++ )

    cout << arrWithDigits [ i ] << " " ;

    cout << endl ;

    return 0 ;

    В стандартную библиотечную функцию sizeof() передаем объявленный массив arrWithDigits строка 10. Она вернёт на место вызова размер в байтах, который занимает этот массив в памяти. На вопрос “Сколько чисел вы введете в массив?” ответим – 10. В строке 15, выражение amount * sizeof(int) станет равнозначным 10 * 4, так как функция sizeof(int) вернет 4 (размер в байтах типа int). Далее введем числа с клавиатуры и программа покажет их на экран. Получается, что остальные 4990 элементов будут хранить нули. Так что нет смысла их показывать.

    Главная информация на экране: массив занял 20 000 байт, а реально для него необходимо 40 байт. Как выйти из этой ситуации? Возможно, кому-то захочется переписать программу так, чтобы пользователь с клавиатуры вводил размер массива и уже после ввода значения объявить массив с необходимым количеством элементов. Но это невозможно реализовать без указателей. Как вы помните – размер массива должен быть константой . То есть целочисленная константа должна быть инициализирована до объявления массива и мы не можем запросить её ввод с клавиатуры. Поэкспериментируйте – проверьте.


    Тут нам подсвечивает красным оператор >> так как изменять константное значение нельзя.

    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


    Тут нас предупреждают о том, что размером массива НЕ может быть значение обычной переменной. Необходимо константное значение!

    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    В следующем коде мы будем использовать указатель и новые для вас операторы new (выделяет память) и delete (освобождает память).

    разумное использование оперативной памяти, применяя указатели

    #include #include using namespace std; int main() { setlocale(LC_ALL, "rus"); int sizeOfArray = 0; // размер массива (введет пользователь) cout << "Чтобы создать массив чисел, введите его размер: "; cin >> sizeOfArray; // ВНИМАНИЕ! int* arrWithDigits - объявление указателя // на участок памяти, которую выделит new int* arrWithDigits = new int ; for (int i = 0; i < sizeOfArray; i++) { arrWithDigits[i] = i + 1; cout << arrWithDigits[i] << " "; } cout << endl; delete arrWithDigits; // освобождение памяти return 0; }

    #include

    #include

    using namespace std ;

    int main ()

    setlocale (LC_ALL , "rus" ) ;

    int sizeOfArray = 0 ; // размер массива (введет пользователь)

    cout << "Чтобы создать массив чисел, введите его размер: " ;

    cin >> sizeOfArray ;

    // ВНИМАНИЕ! int* arrWithDigits - объявление указателя

    // на участок памяти, которую выделит new

    int * arrWithDigits = new int [ sizeOfArray ] ;

    for (int i = 0 ; i < sizeOfArray ; i ++ )

    arrWithDigits [ i ] = i + 1 ;

    cout << arrWithDigits [ i ] << " " ;

    cout << endl ;

    delete arrWithDigits ; // освобождение памяти

    return 0 ;

    Пользователь вводит значение с клавиатуры – строка 12. Ниже определён указатель: int * arrWithDigits Эта запись означает, что arrWithDigits – это указатель. Он создан для хранения адреса ячейки, в которой будет находиться целое число. В нашем случае arrWithDigits будет указывать на ячейку массива с индексом 0. Знак * – тот же что применяется при умножении. По контексту компилятор “поймет”, что это объявление указателя, а не умножение. Далее следует знак = и оператор new , который выделяет участок памяти. Мы помним, что у нас память должна быть выделена под массив, а не под одно число. Запись new int [ sizeOfArray ] можно расшифровать так: new (выдели память) int (для хранения целых чисел) (в количестве sizeOfArray ).

    Таким образом в строке 16 был определён динамический массив . Это значит, что память под него выделится (или не выделится) во время работы программы, а не во время компиляции, как это происходит с обычными массивами. То есть выделение памяти зависит от развития программы и решений, которые принимаются непосредственно в её работе. В нашем случае – зависит от того, что введёт пользователь в переменную sizeOfArray

    В строке 25 применяется оператор delete . Он освобождает выделенную оператором new память. Так как new выделил память под размещение массива, то и при её освобождении надо дать понять компилятору, что необходимо освободить память массива, а не только его нулевой ячейки, на которую указывает arrWithDigits . Поэтому между delete и именем указателя ставятся квадратные скобки delete arrWithDigits ; Следует запомнить, что каждый раз, когда выделяется память с помощью new , необходимо эту память освободить используя delete . Конечно, по завершении программы память, занимаемая ей, будет автоматически освобождена. Но пусть для вас станет хорошей привычкой использование операторов new и delete в паре. Ведь в программе могут располагаться 5-6 массивов например. И если вы будете освобождать память, каждый раз, когда она уже не потребуется в дальнейшем в запущенной программе – память будет расходоваться более разумно.

    Допустим в нашей программе мы заполнили массив десятью значениями. Далее посчитали их сумму и записали в какую-то переменную. И всё – больше мы с этим массивом работать уже не будем. Программа же продолжает работу и в ней создаются новые динамические массивы для каких-то целей. В этом случае целесообразно освободить память, которую занимает первый массив. Тогда при выделении памяти под остальные массивы эта память может быть использована в программе повторно.

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

    попытка изменить переменные, переданные в функцию

    #include #include using namespace std; void changeData(int varForCh1, int varForCh2); int main() { setlocale(LC_ALL, "rus"); int variableForChange_1 = 0; int variableForChange_2 = 0; cout << "variableForChange_1 = " << variableForChange_1 << endl; cout << "variableForChange_2 = " << variableForChange_2 << endl; cout << endl; changeData(variableForChange_1, variableForChange_2); cout << endl; cout << "variableForChange_1 = " << variableForChange_1 << endl; cout << "variableForChange_2 = " << variableForChange_2 << endl; return 0; } void changeData(int varForCh1, int varForCh2) { cout << "Введите новое значение первой переменной: "; cin >> varForCh1; cout << "Введите новое значение второй переменной: "; cin >> varForCh2; }

    #include

    #include

    using namespace std ;

    void changeData (int varForCh1 , int varForCh2 ) ;

    int main ()

    setlocale (LC_ALL , "rus" ) ;

    int variableForChange_1 = 0 ;

    int variableForChange_2 = 0 ;

    cout << "variableForChange_1 = " << variableForChange_1 << endl ;

    cout << "variableForChange_2 = " << variableForChange_2 << endl ;

    cout << endl ;

    changeData (variableForChange_1 , variableForChange_2 ) ;

    cout << endl ;

    cout << "variableForChange_1 = " << variableForChange_1 << endl ;

    cout << "variableForChange_2 = " << variableForChange_2 << endl ;

    return 0 ;

    void changeData (int varForCh1 , int varForCh2 )

    cout << "Введите новое значение первой переменной: " ;

    cin >> varForCh1 ;

    cout << "Введите новое значение второй переменной: " ;

    cin >> varForCh2 ;

    Запустите программу и введите новые значения переменных. Вы увидите в итоге, что по завершении работы функции, переменные не изменились и равны 0.

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

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

    изменение значений переменных, используя указатели

    #include #include using namespace std; void changeData(int* varForCh1, int* varForCh2); int main() { setlocale(LC_ALL, "rus"); int variableForChange_1 = 0; int variableForChange_2 = 0; cout << "variableForChange_1 = " << variableForChange_1 << endl; cout << "variableForChange_2 = " << variableForChange_2 << endl; cout << endl; changeData(&variableForChange_1, &variableForChange_2); cout << endl; cout << "variableForChange_1 = " << variableForChange_1 << endl; cout << "variableForChange_2 = " << variableForChange_2 << endl; return 0; } void changeData(int* varForCh1, int* varForCh2) { cout << "Введите новое значение первой переменной: "; cin >> *varForCh1; cout << "Введите новое значение второй переменной: "; cin >> *varForCh2; }

    Первый таймер на этом веб-сайте, поэтому здесь идет.

    Я новичок на С++, и сейчас я работаю над книгой "Структуры данных, использующие С++ 2nd ed, D.S. Malik".

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

    Int *board;

    И затем использовать for-loop для создания "столбцов" при использовании массива указателей как "строк".

    Второй метод, вы используете указатель на указатель.

    Int **board; board = new int* ;

    Мой вопрос таков: какой метод лучше? Метод ** мне легче визуализировать, но первый метод можно использовать почти так же. Оба способа можно использовать для создания динамических 2-мерных массивов.

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

    Int row, col; cout << "Enter row size:"; cin >> row; cout << "\ncol:"; cin >> col; int *p_board; for (int i=0; i < row; i++) p_board[i] = new int; for (int i=0; i < row; i++) { for (int j=0; j < col; j++) { p_board[i][j] = j; cout << p_board[i][j] << " "; } cout << endl; } cout << endl << endl; int **p_p_board; p_p_board = new int* ; for (int i=0; i < row; i++) p_p_board[i] = new int; for (int i=0; i < row; i++) { for (int j=0; j < col; j++) { p_p_board[i][j] = j; cout << p_p_board[i][j] << " "; } cout << endl; }

    4 ответов

    Первый метод нельзя использовать для создания динамических 2D-массивов, потому что:

    Int *board;

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

    For (int i = 0; i < 4; ++i) { board[i] = new int; }

    то, что вы заканчиваете, представляет собой 2D-массив с статическим числом строк (в данном случае 4) и динамическим числом столбцов (в данном случае 10). Таким образом, динамика не полностью , так как при распределении массива в стеке вы должны указывать постоянный размер , т.е. Известный в время . Динамический массив называется динамическим , потому что его размер не обязательно должен быть известен в время компиляции , но скорее может быть определен некоторой переменной в во время выполнения .

    Еще раз, когда вы выполните:

    Int *board;

    Const int x = 4; // <--- `const` qualifier is absolutely needed in this case! int *board[x];

    вы предоставляете константу, известную в время компиляции (в данном случае 4 или x), чтобы компилятор теперь мог предварительно выделить эту память для вашего массива, и когда ваша программа будет загружена в память, у нее уже будет этот объем памяти для массива board , поэтому он называется static , т.е. потому что размер жестко закодирован и не могут динамически меняться (во время выполнения).

    С другой стороны, когда вы делаете:

    Int **board; board = new int*;

    Int x = 10; // <--- Notice that it does not have to be `const` anymore! int **board; board = new int*[x];

    компилятор не знает, сколько потребуется массиву памяти board , и поэтому он не заранее выделяет все. Но когда вы запускаете свою программу, размер массива будет определяться значением переменной x (во время выполнения), а соответствующее пространство для массива board будет выделено на так называемую кучу - область памяти, в которой все программы, запущенные на вашем компьютере, могут выделять неизвестно заранее (во время компиляции) суммирует память для личного использования.

    В результате, чтобы действительно создать динамический 2D-массив, вам нужно пойти со вторым методом:

    Int **board; board = new int*; // dynamic array (size 10) of pointers to int for (int i = 0; i < 10; ++i) { board[i] = new int; // each i-th pointer is now pointing to dynamic array (size 10) of actual int values }

    Мы только что создали квадратный 2D-массив размером 10 на 10. Чтобы пройти его и заполнить его фактическими значениями, например 1, мы могли бы использовать вложенные циклы:

    For (int i = 0; i < 10; ++i) { // for each row for (int j = 0; j < 10; ++j) { // for each column board[i][j] = 1; } }

    То, что вы описываете для второго метода, дает только 1D-массив:

    Int *board = new int;

    Это просто выделяет массив с 10 элементами. Возможно, вы имели в виду что-то вроде этого:

    Int **board = new int*; for (int i = 0; i < 4; i++) { board[i] = new int; }

    В этом случае мы выделяем 4 int* , а затем каждый из них укажем на динамически выделенный массив из 10 int s.

    Итак, теперь мы сравниваем это с int* board; . Основное различие заключается в том, что при использовании такого массива количество "строк" ​​должно быть известно во время компиляции. Это потому, что массивы должны иметь фиксированные размеры времени компиляции. У вас может также возникнуть проблема, если вы хотите, возможно, вернуть этот массив из int* s, поскольку массив будет уничтожен в конце его области.

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

    For (int i = 0; i < 4; i++) { delete board[i]; } delete board;

    Я должен рекомендовать вместо этого использовать стандартный контейнер. Вы можете использовать std::array 4> или, возможно, std::vector> , который вы инициализируете соответствующим размером.

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

    Этот вопрос в основном эквивалентен следующему:

    Является int* x = new int; "лучше", чем int x ?

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

    Этот код хорошо работает с очень небольшим количеством требований к внешним библиотекам и показывает базовое использование int **array .

    Этот ответ показывает, что массив each имеет динамический размер, а также как назначить линейный массив динамически размера в массив ветвей динамического размера.

    Эта программа принимает аргументы из STDIN в следующем формате:

    2 2 3 1 5 4 5 1 2 8 9 3 0 1 1 3

    Код для программы ниже...

    #include int main() { int **array_of_arrays; int num_arrays, num_queries; num_arrays = num_queries = 0; std::cin >> num_arrays >> num_queries; //std::cout << num_arrays << " " << num_queries; //Process the Arrays array_of_arrays = new int*; int size_current_array = 0; for (int i = 0; i < num_arrays; i++) { std::cin >> size_current_array; int *tmp_array = new int; for (int j = 0; j < size_current_array; j++) { int tmp = 0; std::cin >> tmp; tmp_array[j] = tmp; } array_of_arrays[i] = tmp_array; } //Process the Queries int x, y; x = y = 0; for (int q = 0; q < num_queries; q++) { std::cin >> x >> y; //std::cout << "Current x & y: " << x << ", " << y << "\n"; std::cout << array_of_arrays[x][y] << "\n"; } return 0; }

    Это очень простая реализация int main и зависит только от std::cin и std::cout . Barebones, но достаточно хорошо, чтобы показать, как работать с простыми многомерными массивами.

    Цель лекции : изучить объявления, выделения и освобождения памяти для одномерных динамических массивов, обращения к элементам, научиться решать задачи с использованием одномерных динамических массивов в языке C++.

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

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

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

    Объявление одномерных динамических массивов

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

    Синтаксис :

    Тип * ИмяМассива;

    Тип – тип элементов объявляемого динамического массива . Элементами динамического массива не могут быть функции и элементы типа void .

    Например:

    int *a; double *d;

    В данных примерах a и d являются указателями на начало выделяемого участка памяти. Указатели принимают значение адреса выделяемой области памяти для значений типа int и типа double соответственно.

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

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

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

    1) при помощи операции new , которая выделяет для размещения массива участок динамической памяти соответствующего размера и не позволяет инициализировать элементы массива.

    Синтаксис :

    ИмяМассива = new Тип [ВыражениеТипаКонстанты];

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

    ВыражениеТипаКонстанты – задает количество элементов ( размерность) массива . Выражение константного типа вычисляется на этапе компиляции.

    Например:

    int *mas; mas = new int ; /*выделение динамической памяти размером 100*sizeof(int) байтов*/ double *m = new double [n]; /*выделение динамической памяти размером n*sizeof(double) байтов*/ long (*lm); lm = new long ; /*выделение динамической памяти размером 2*4*sizeof(long) байтов*/

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

    2) при помощи библиотечной функции malloc (calloc) , которая служит для выделения динамической памяти.

    Синтаксис :

    ИмяМассива = (Тип *) malloc(N*sizeof(Тип));

    ИмяМассива = (Тип *) calloc(N, sizeof(Тип));

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

    Тип – тип указателя на массив .

    N – количество элементов массива.

    Например:

    float *a; a=(float *)malloc(10*sizeof(float)); // или a=(float *)calloc(10,sizeof(float)); /*выделение динамической памяти размером 10*sizeof(float) байтов*/

    Так как функция malloc (calloc) возвращает нетипизированный указатель void * , то необходимо выполнять преобразование полученного