Последние новости

YoungCoder теперь и на Stepikе. Записывайтесь: https://vk.cc/75rISy

Чтобы записаться на курс, необходимо зарегистрироваться на Степике: https://vk.cc/75rIC4

Это моя личная ссылка-приглашение на Stepik для вас. Регистрируясь по этой ссылке, записываясь на курсы и решая задачи, Вы помогаете автору данного сайта принять участие в конкурсе платформы Stepik! Подробности конкурса здесь: https://vk.cc/75rKuS

воскресенье, 29 сентября 2013 г.

Урок 17. Создание динамического массива. Указатели и практическое применение.


Добрый день друзья. Сегодня у нас особый урок. Во-первых, он будет более практичный и небольшой по объему, во-вторых, он посвящен ответам на вопросы, которые мне задали в нашей группе вконтакте, и в-третьих, он будет уже использовать некоторые возможности С++. Это осознанный шаг, и я думаю, во многом, он облегчит жизнь и вам и мне. Приступим.
Начну с последнего вопроса. Как создать динамический массив. Допустим, мы пишем программу, которая вычисляет среднее значение введенных чисел. Но мы же не знаем, сколько чисел собирается ввести пользователь. Естественно мы могли бы организовать цикл и просто сохранять все веденные числа в одну переменную, а потом лишь поделить на количество введенных элементов. Но что делать, если мы хотим все эти введенные числа использовать в дальнейшем. Или хотим посчитать среднее лишь по некоторым из этих чисел. Тут-то нам на помощь и придут динамические массивы. Т.е. массивы, длину которых задает не в коде программы. На самом деле это достаточно просто.
Примерно, это выглядело бы так:
Листинг 17.1
#include <stdio.h>

int main(){

      int N;

      printf("Vvedite kolichestvo dannih\N");

      scanf("%d",&N);

      int arr[N];


      return 0;

}

ВНИМАНИЕ! В современных компиляторах языка Си этот код будет работать! В Си такая возможность предполагается новым стандартом.

Но если мы сделаем так, то наша программа не скомпилируется даже. Получим ошибку, что при объявлении массива, нужно константное выражение.  Естественно, мы могли бы создать массив на 100 элементов и не париться об этом, но мы пойдем другим путём, более оптимальным.
Как я уже писал в прошлом уроке, память можно представить себе в виде последовательных ячеек с адресами. Так вот, наша программа, может работать с этой памятью почти напрямую. Т.е. мы можем выделить себе в свободной памяти некоторый кусочек и в нем работать.
Это называется выделение памяти «в куче». Для того, чтобы выделить себе некоторую область в памяти, необходимо использовать команду new(). Смотрите, как это работает.
Листинг 17.2
#include <stdio.h>



int main(){

      int *num = new (int);

      *num=4;

      printf("%d \n",*num);

      return 0;

}


Результат работы это программы, представлен на следующем рисунке.

Рис.1 Выделение памяти под переменную типа int.
Команда new() выделяет необходимое количество памяти, под тип объекта, который указан в скобках. Ну т.е. в нашем случае, мы попросили выделить для нас память под одну переменную типа int. Данная команда возвращает указатель на выделенный фрагмент памяти. Поэтому мы в принципе и сохраняем её в соответствующую переменную.
С одним числом разобрались, но мы же хотели целый массив таких чисел. Да без проблем, просто укажем, что это должен быть массив из нужного нам количества переменных.
Листинг 17.3
#include <stdio.h>



int main(){

      int N; 
      printf("Vvedite kolichestvo dannih\n");

      scanf("%d",&N);

      int *arr = new (int [N]); 
      return 0;

}


Вот в таком виде наша программа уже скомпилируется и будет делать именно то, что нам нужно. Пользователь введет 20 и она создаст массив из 20 элементов типа int. Введет пять - будет массив из пяти элементов. Удобно, не правда ли? Не надо расходовать лишнюю память, создавая массив из 100 элементов из которых только пять первых будут использоваться.
А самое классно знаете что? То, что работать с этим массивом можно прямо точно так же как если бы мы создали его самостоятельно. Ну т.е. нам не потребуется добавлять звездочки и т.д. Вот, например, в следующей программе мы присваиваем вновь созданному динамическому массиву из двух элементов некоторые значения.
Листинг 17.4
#include <stdio.h>



int main(){

      int N;

      printf("Vvedite kolichestvo dannih\n");

      scanf("%d",&N);

      int *arr = new (int [N]);

      arr[0]=1;

      scanf("%d", &arr[1]);

      printf("%d %d\n", arr[0],arr[1]);

      return 0;

}

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

Как видите, все как и прежде. Это действительно удобно. Теперь немножко отвлечемся. Команда new() это команда языка С++, в чистом Си её нет. Там есть некоторый её аналог команда malloc. Но она менее удобная, чем new. Поэтому пользуйтесь.
Я думаю, всех учили убирать за собой, когда навели беспорядок. В этом плане память, как мама. Не любит, когда не убрано. Поэтому есть и еще одна команда, которая позволяет убрать за собой. Ну т.е. освободить память, которую мы заняли. Она называется delete(). Логично, да?
Передаете ей в качестве аргумента, то что вы выделили и она самостоятельно наведет уборку. Это нужно делать всегда, когда вы выделяли память. Конечно, если вы не сделаете так, ничего особенно страшного не случится. Но представьте ситуацию, что каждая программа, которую вы запускаете, сохраняет в оперативной памяти вашего компьютера какие-то данные и потом не удаляет их. Не порядок, не так ли? Или пришел к вам гость, сходил в туалет и не смыл за собой.  Ну вот и вы не сорите за собой. Я вот, кстати намусорил сейчас в вашем компьютере немного, если вы уже запускали программы из примеров. =))
Покажу на примере первой программы, как это делается.
Листинг 17.5
#include <stdio.h>



int main(){

      int *num = new (int);

      *num=4;

      printf("%d \n",*num);

      delete num;

      return 0;

}

Если высвобождаете память, в которой хранился массив, то необходимо указать это.
Листинг 17.6
delete []arr;

С первым вопросом разобрались и на этом закончим сегодняшний мини-урок. 
Я надеюсь он был полезен вам.  Не пожалейте лайков, как говорится. )
До скорой, я надеюсь, встречи.
 

13 комментариев :

  1. Прекрасные уроки!! СПАСИБО ВСЕ ОЧЕНЬ ПОНЯТНО С НЕТЕРПЕНИЕМ ЖДЁМ НОВЫХ.

    ОтветитьУдалить
  2. Мой мозг по прежнему отказывается понимать, указатели :)
    https://www.dropbox.com/s/z2t2m72qziq5b88/si..JPG
    Правильно я все понял?

    ОтветитьУдалить
  3. Да, только не просто под N элементов, а под N элементов определенного типа. В вашем случае int.

    ОтветитьУдалить
    Ответы
    1. И кроме того, адрес начала этого блока памяти, который мы застолбили, мы сохранили в переменную-указатель arr. )) Это важно, а то получается выделить мы выделили, а как с ней работать не знаем. )))

      Удалить
  4. Долгое время не мог(не хотел) понять указатели,но после втыка на лабе освоил за час)))
    Для начала хочу узнать:
    Функция new замена (int*)calloc(n,sizeof(int)) и (int*)malloc(n*sizeof(int))???
    И в каких случаях используется free()?Для malloc или calloc? И является ли free() аналогом delete()??

    ОтветитьУдалить
  5. ВАжный вопрос!
    а что если в листинге 17.4 ввести колличество данных "1" ну или "0" ???
    это по сути массив из одного или из нуля элементов, хотя при этом выполняется реализация нескольких элементов :
    код:
    #include

    int main(){
    int N;
    printf("Vvedite kolichestvo dannih\n");
    scanf("%d",&N);
    int *arr = new (int [N]);
    arr[0]=1;
    scanf("%d", &arr[1]);
    printf("%d %d\n", arr[0],arr[1]);
    return 0;
    }

    реализация:

    Vvedite kolichestvo dannih
    0 - это мы ввели
    34 - это мы ввели
    1 34 - вывела программа, вывела два элемента, хотя задавали мы "псевдомассив" из нуля элементов

    ОтветитьУдалить
    Ответы
    1. Ну во-первых, когда мы передаем int [0] в new она выделяет память под один элемент типа int. Но для этого было бы достаточно просто написать new (int); Т.е под один элемент передаваемого типа мы всегда этой функцией резервируем память. Теперь о том, откуда берется второе значение. Си как я уже упоминал ранее, дает программисту полный контроль над памятью. В этом самое главное преимущество, и в то же время вред указателей. Когда мы создали массив arr мы просто выделили кусочек памяти. А потом мы записали в следующую ячейку еще одно число. Почему это так. Я уже отмечал, в одном из уроков, что данные в массиве располагаются последовательно. И когда мы обращаемся к элементу массива, например, arr[1] на самом деле мы берем адрес нулевого элемента массива и прибавляем к нему единицу. Это называется адресная арифметика, можете погуглить про это. Можете проверить и вместо arr[1] записать *(arr+1). Увидите, что результат один и тот же.

      Получается мы просто записали по некоторому адресу число и его потом оттуда вывели. Вот и всё. )

      Удалить
  6. Три раза прочитал и все время понимал одно и тоже.

    То есть я сам в программе задаю число, количество мной в дальнейшем введеных чисел?!

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

    Если это так, то что делать, если я ввел 3, то есть выделил три места, но тут раз и мне понадобилось еще одно, не перезапускать же программу?

    Листинг 17.2 совершенно не могу понять. Что именно там выделяет функция new()? Мы просто указателю присвоили 4 и вывели это на экран. А что именно делает строчка :
    int*num=new(int);
    остается загадкой...

    Публика требует разъяснений:)

    Спасибо)

    ОтветитьУдалить
    Ответы
    1. Перезапускать. Либо написать её так, чтобы можно было перевыделять память во время её работы. =)

      Теперь про листинг 17.2
      int*num=new(int); рассмотрим эту строчку.
      сначала выполняется команда new(int), которая выделяет где-то в памяти место, необходимое для записи одного значения типа int. Т.е. где-то она выделяет 4 байта (если тип int в вашей системе занимает 2 байта, то будет выделено 2 байта). Результатом работы этой функции будет адрес, где она выделила память. Пусть, например, это адрес 0x000011. Этот адрес сохраняется в указателе с именем num.
      Теперь в num записано 0x000011. А вот в памяти по адресу 0х000011 записано неизвестно что, мусор какой-то.

      *num=4;
      Звездочка у указателя обозначает обратиться к памяти по адресу хранящемуся в указателе.
      Буквально мы этой строкой говорим, сохрани по адресу 0х000011 число 4.

      Теперь в num хранится 0x000011, а по адресу 0x000011 в памяти хранится число 4.

      Удалить
  7. мне этого так не хватало ещё в первом уроке о массивах. Я прям сразу попробовал прописать эти строки
    int n;
    scanf("%d", &n);
    int Massiv [n];
    думал какая гениальная идея я тогда....

    ОтветитьУдалить
    Ответы
    1. удалить тоже в компилятор пробовал вводить.... delite.... но почему-то не работало
      теперь заработало)

      Удалить
  8. всё что я понял
    int *s - обьявление указателя
    *s=8 -привязать к адресу указателя значение
    *s -использование значения указателя
    s -использование адреса указателя ...... вообщем то всё что я понял про указатели.....
    надеюсь оно так и есть.....

    ОтветитьУдалить
  9. Такой вопрос: как исправить то, что 2 динамических массива ссылаются на один адрес при добавлении новых значений через push_back() реализованным мной?
    Сейчас стало снова номрально, но что делать чтобы такого не было?

    ОтветитьУдалить

Примечание. Отправлять комментарии могут только участники этого блога.