Указатели С . Частина 2





указатели с  , указатели c

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

Сейчас мы рассмотрим пример где будем передавать в функцию указатели на строки (покажчики типу char). При этом сама функция возвращает указатель. Задача следующая: Есть два указателя на строки, під які виділені необхідні їм ділянки пам'яті. Необходимо объединить эти две строки. Тобто треба виділити нову ділянку пам'яті для першого рядка, щоб стало можливим дописати в неї другий рядок.

Перед тем, как набирать код, щоб вас не “засыпало” помилками – установите свежую версию среды Microsoft Visual Studio. Наприклад Microsoft Visual Studio 2013 Express. Якщо ви використовуєте більш ранню версію, то вместо функций strcat_s() застосовуйте strcat() і strcpy() замість strcpy_s().

Идем по порядку. В рядку 4 находится прототип функции. Её задача – виділити нову ділянку пам'яті для рядка, в конец которой запишется другая строка. Про це ми поговоримо нижче, когда дойдем до определения этой функции. Переходим к строкам 11 – 12. В них определены переменные, які будуть зберігати значення довжини рядків "рядок 1 " і "+ рядок 2". Довжина підраховується за допомогою вбудованої функції strlen(). Она вернет значение длины строки без учета символа \0 . Тому при ініціалізації змінних strSize1 і strSize2 ми додаємо до того що поверне strlen(…) единицу.

В рядку 14 определён указатель pStr1 на первую строку. Чтобы записать строку, сначала необходимо выделить под нее память: char* pStr1 = new char[strSize1]; Нижче копіюємо рядок в виділену пам'ять: strcpy_s(pStr1, strSize1, "рядок 1 "); . Первый параметр, який приймає функція strcpy_s() – указатель на адрес куда надо скопировать строку; второй – размер строки; третий – сама строка. другий покажчик pStr2 визначаємо точно так же.

В стр. 20 – 21, просимо показати на екран записані рядки. Для этого достаточно обратиться к ним по имени указателей. Указатель хранит адрес нулевой ячейки. Когда мы просим показать его на экран – будут поочередно отображаться символы массива (участка памяти, который был выделен ранее), поки не зустрінеться символ кінця рядка – \0 .

Стр. 23-24 – смотрим сколько байт памяти занимает каждая строка. Строка 26 исключена комментарием. В ней осуществлена попытка дописать вторую строку в первую. Попробуйте удалить // і відкомпілювати програму. Виконання програми перерветься помилкою. Ви побачите на екрані наступне:

покажчики на рядки з ++, покажчики на рядки c ++, new, deleteКонечно – ведь выделенный участок памяти под первую строку слишком мал, для того чтобы дописать в него еще какие-либо данные. В рядку 28 записуємо в змінну requiredSize реально необхідний розмір пам'яті для запису двох рядків: int requiredSize = (strSize1 + strSize2) - 1; Одиницю віднімаємо тому, що в змінних strSize1 і strSize2 уже включены два \0 , а нам необходим только один.

Перемістимося до визначення функції giveNewMem() – стр. 42 – 51. Вона прийме перший рядок (указатель на неё) и целое число (достаточный размер памяти). Дивіться – нам надо в этой функции выделить новый участок памяти для записи в него символов. Значит функция должна вернуть указатель на этот новый участок памяти. Так мы сможем записать в указатель pStr1 Нова адреса, по которому расположена память для записи двух строк. Тому в заголовку функції пишемо так char* giveNewMem(char *pstr1, int reqSize)

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

Створюємо новий покажчик і відразу виділяємо під нього пам'ять, достаточную для размещения символов обеих строк (стр. 44). Далее копируем в выделенную память символы первой строки: strcpy_s(strInFunc, reqSize, pstr1); рядок skopirovana – потрібно звільнити пам'ять, которую она занимала, чтобы не произошло утечки памяти (стр. 48). Повертаємо з функції покажчик на нову ділянку пам'яті: return strInFunc;

Получается, когда мы вызываем эту функцию из main() (стр. 31) pStr1 = giveNewMem(pStr1, requiredSize); – в указатель pStr1 запишется адрес нового участка памяти, который способен вместить две строки. Осталось только дописать в эту память вторую строку (стр. 33) и показать её на экран. Перед выходом из программы освобождаем память. Сначала ту что была выделена в функции, а потом ту где располагается вторая строка.

Откомпилируем программу:

покажчики на рядки з ++, покажчики на рядки c ++, new, delete

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

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

Зачем нужны указатели в C ?

  • с помощью указателей, возможно выделение динамической памяти. Так пам'ять під дані виділяється в процесі роботи програми, а не на этапе компиляции. Це дуже вигідно, коли ми не знаємо до початку роботи програми скільки реально буде використано даних (переменных). Чтобы выделять память и освобождать ее, используются операторы new і delete.
  • покажчики часто використовуються для доступу до об'ємним ділянкам даних з функцій. К данным символьных и числовых массивов например, которые определены вне функции. Такой подход пришел из программирования на языке C. У C ++ щодо числових змінних і масивів зручніше використовувати передачу по ссылке. Вскоре мы с вами рассмотрим эту тему. Относительно строк в стиле Си лучше применять передачу по указателю.
  • используя указатели – мы работаем с памятью по адресам напрямую. Это быстрее, чем обращение по имени переменных.

Объявление указателей, взятие адреса, разыменование

Рассмотрим подробно на примере:

В рядку 8 определена обычная целочисленная переменная. В рядку 9 закоментувавши оголошення і ініціалізація покажчика. Попробуйте удалить знаки комментирования // и откомпилировать. Нам сообщат об ошибке. Все правильно. Ведь указатель должен хранить адрес (шестнадцатеричное число), а не значение (десятеричное число или символ). Для того, щоб отримати адресу змінної використовується операція взяття адреси & (амперсанд: Shift + 7). В рядку 10 создаем указатель и записываем в него адрес переменной:

инициализация указателя

Чтобы определить указатель, надо объявить его тип, за типом поставити зірочку * и дать ему имя. Инициализировать указатель можно только адресом. Если вы не присваиваете адрес указателю при его объявлении, обязательно инициализируйте его нулем. Тогда такой указатель не сможет привести к сложным ошибкам, которые тяжело найти. Он просто ни на что не будет указывать.

Обычно нам не особо интересно, какой адрес хранит указатель. Нас интересуют данные, расположенные по этому адресу. Чтобы посмотреть эти данные (или внести в них изменения) к имени указателя надо применить операцию разыменования. Это такая же звездочка *, как и при объявлении.

розіменування покажчика

Только в потоке cout она трактуется иначе – не так как при объявлении указателя. Она помогает обратиться к данным, хранящимся по адресу. Это продемонстрировано в строке 15 исходного кода.

В рядку 18 определен целочисленный массив на пять элементов. Ниже определен указатель. Зверніть увагу – щоб ініціалізувати покажчик адресою масиву, операция & не застосовується. Это потому, що ім'я масиву посилається на адресу нульовий осередки. Получается что запись

инициализация указателя

вполне нормально компилируется. У змінну-покажчик pFirstArr будет записан адрес нулевой ячейки массива firstArr. Эта запись аналогична следующей

инициализация указателя

В рядку 25 показан пример обращения к данным элементов массива через указатель. Используется нотация массивов. То есть нам не надо применять операцию разыменования, чтобы обратиться к данным массива:

cout << "pfirstArr[0] = " << pFirstArr[0] << endl << endl;

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

Строки 27 – 28 исходного кода – определение Си-строки і визначення покажчика на цей рядок. Указатели замечательно справляются с работой со строками. Коли ми в потоці cout звертаємося по імені до покажчика на символьний масив, він нам покаже всю рядок. Так же как и в случае с массивами, компилятор будет выводить символы на экран, поки не виявить в масиві символ кінця рядка \0

Подивіться на підсумок роботи програми і на вихідний код ще раз. Постарайтесь понять как он работает.

инициализация указателя, розіменування покажчика, & взятие адреса

Еще одно отступление от темы, щоб підбадьорити тих, кому тяжело дается тема указателей :) Вы не одни. Всё приходит с практикой! И к вам дойдет! Не паникуйте, якщо це виглядає занадто заплутаним для вас. Решайте как можно больше задач по программированию. Навіть якщо ви щось будете робити не так в процесі написання коду, наши замечательные среды разработки дадут об этом знать.

Обязательно посмотрите видео об указателях (с 12-й минуты), если вы не смотрели его в первой части статьи:

Покажчики, как параметры (аргументи) функций:




Підписатися на повідомлення про нові статті на нашому сайті:


Згоден отримувати повідомлення від purecodecpp.com на мій e-mail

дата
сторінка
Покажчики на рядки в C ++
рейтинг
5

7 думки про "Указатели С . Частина 2

  1. Не можу зрозуміти , в 15 рядку коду – strcpy_s(pStr1, strSize1, “рядок 1 “);
    в функції strcpy_s використовується 3 аргументу, раніше при описі цієї функції в ній вказувалося тільки 2 аргументу (куди скопіювати і що скопіювати).
    власне питання : чому тепер в ній 3 аргументу? Тому що ми зараз використовуємо покажчик як перший аргумент? Чи тому що це нова версія цієї функції (strcpy_s зЬгсру замість)? Або цієї особливість visual studio? т.к. в код:: blocks функція strcpy приймає 2 аргументу <>.

    1. У прикладі коду використана функція strcpy_s(), а не strcpy(), яка дійсно має 2 аргументу. Дивіться імена уважніше.

      P.S. Це зовсім не означає, що так слід робити: strcpy_s() – функція, яка не входить до стандартів ні POSIX, ні C, ні Linux і т.д. і т.п. … і використовується тільки в операційних системах Windows (ви не знайдете навіть її опису в літературі). Але конкретний цей приклад – коректний.

      1. Зрозумів тільки те що опису strcpy_s я не знайду, а чому цієї функції потрібно знати розмір рядка не зрозуміло.
        Розмір рядка їй треба знати тому що першим аргументом покажчик? Або це особливість оновленої функції? І скільки аргументів ця функція взагалі приймає (може приймати) ?

      2. У меня на code::blocks тоже не берет.Может это из-за того,что среду разработки нужно другую?более новую?Здесь написано про нее, пишут что более безопасная функция,так как предупреждает переполнение буфера! https://msdn.microsoft.com/ru-ru/library/8ef0s5kh.aspx

    2. Вказувати розмір копируемой рядки дуже корисно: якщо рядок джерело, випадково, довше приймача, то ви отримаєте помилку доступу до пам'яті з крахом всього програми.

      Подивіться опис бібліотечної та стандартної функції strncpy(), яка безпечніше і частіше застосовується в професійному коді ніж strcpy().

  2. Середовище розробки DEV CPP 5.11.
    Перший приклад працює і без функції перевиделенія пам'яті :)

залишити коментар

Код розміщуйте в тегах: <pre class="lang:C ++ декодуванням:true ">ВАШ КОД</заздалегідь>