Как сделать массив указателей c

Обновлено: 02.07.2024

каково общее правило для понимания более сложных деклараций?

третий такой же, как и первый.

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

использовать ключевое слово cdecl программа, как предложено K & R.

это работает и в другую сторону.

Я не знаю, есть ли у него официальное название, но я называю его правой-левой штукой(TM).

начните с переменной, затем идите вправо, влево и вправо. и так далее.

arr1-это массив из 8 указателей на целые.

arr2-указатель (скобка блок справа-слева) на массив из 8 целых чисел.

arr3-это массив из 8 указателей на целое число.

Это должно помочь вам со сложными декларациями.

ответ для последних двух также можно вычесть из золотого правила в C:

объявление следует за использованием.

что произойдет, если вы dereference arr2? Вы получаете массив из 8 целых чисел.

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

это также помогает при работе с указателями на функции. Взять sigjuice по пример:

что происходит, когда вы разыменование x? Вы получаете функцию, которую вы можете вызвать без аргументов. Что происходит, когда вы называете это? Он вернет указатель на float.

приоритет оператора всегда сложно, хотя. Однако использование круглых скобок также может привести к путанице, поскольку объявление следует за использованием. По крайней мере, для меня, интуитивно arr2 выглядит как массив из 8 указателей на целые числа, но на самом деле все наоборот. Просто взять некоторые привыкают. Достаточная причина, чтобы всегда добавлять комментарий к этим объявлениям, если вы спросите меня:)

edit: example

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

обратите внимание, что значение границы никогда не изменяется, поэтому компилятор может оптимизировать это. Это отличается от того, что вы могли бы изначально использовать: const int (*border)[3] : это объявляет границу как указатель на массив из 3 целых чисел, который не будет изменять значение, пока существует переменная. Однако этот указатель может быть указан на любой другой такой массив в любое время. Вместо этого мы хотим такого поведения для аргумента (потому что эта функция не изменяет ни одно из этих целых чисел). Декларации следует использовать.

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

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

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

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

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

указатели с++, указатели c++, new, delete

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

указатели с++, указатели c++, new, delete

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

указатели с++, указатели c++, new, delete

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

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

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

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

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

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

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

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

Теоретическая часть

Одной из наиболее распространенных конструкций с использованием указателей являются массивы [8.1]. Результатом использования указателей для массивов является меньшее количество используемой памяти и высокая производительность [8.1].

По краткому определению, указатель – это переменная , содержащая адрес другой переменной [8.2]. Так как указатель содержит адрес переменной (объекта), то это дает возможность "косвенного" доступа к этой переменной (объекту) через указатель . В качестве объектов в данной лабораторной работе будут рассмотрены числовые и символьные массивы.

\le

В языке С массивы – это упорядоченные данные (элементы) одного типа. Компилятор языка С рассматривает имя массива как адрес его первого элемента (в языке С нумерация элементов массива начинается с нуля). Например, если имя массива Arr с десятью элементами, то i -й элемент (0 i компилятор преобразует его по правилам работы с указателями с операцией разыменования : *(Arr + i) . Здесь Arr как бы указатель , а i – целочисленная переменная . Сумма (Arr + i) указывает на i -й элемент массива, а операция разыменования (оператор раскрытия ссылки * ) дает значение самого элемента.

Имя массива без индекса образует указатель на начало этого массива.

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

Пусть, например, массив Arr содержит 10 целочисленных переменных:

Тогда можно объявить указатель ptr , который будет указывать на элементы массива Arr :

Тип указателя (в примере это int ) должен соответствовать типу объявленного массива.

Для того, чтобы указатель ptr ссылался на первый элемент (с нулевым индексом) массива Arr , можно использовать утверждение:

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

Обе формы записи эквивалентны.

Аналогичные утверждения будут справедливы для других типов массивов : char, float, double и пр.

Если указатель ptr указывал на первый элемент (с нулевым индексом) массива Arr , то для обращения к следующему элементу массива допустимы следующие формы утверждений:

Соответственно, выражение *(ptr+1) будет ссылаться на значение , содержащееся в элементе Arr[1] .

заставит указатель *ptr ссылаться на элемент массива, находящийся на расстоянии n от того, на который ранее ссылался указатель , независимо от типа элементов, содержащихся в массиве [8.1]. Разумеется, значение n должно быть в допустимых пределах для данного объема массива.

При работе с указателями и массивами особенно удобны операторы инкремента "++" и декремента "––" [8.1]. Использование оператора инкремента с указателем аналогично операции суммирования с единицей, а операция декремента имеет тот же эффект, что и вычитание единицы из указателя.

В языке программирования С вполне корректной операцией является сравнение указателей. К указателям применяются операции сравнения ">" , "> texample">"! texample">"= texample">" " [3]. Сравнивать указатели допустимо только с другими указателями того же типа или с константой NULL , обозначающей значение условного нулевого адреса. Константа NULL – это особое значение переменной-указателя, присваиваемое ей в том случае, когда она не должна иметь никакого значения. Его можно присвоить переменной-указателю любого типа. Оно представляет собой целое число нуль.

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

Рассмотрим инициализацию указателей типа char :

Переменная *ptr является указателем, а не массивом. Поэтому строковая константа "hello, world" не может храниться в указателе *ptr. Тогда возникает вопрос, где хранится строковая константа. Для этого следует знать, что происходит, когда компилятор встречает строковую константу. Компилятор создает так называемую таблицу строк. В ней он сохраняет строковые константы , которые встречаются ему по ходу чтения текста программы [8.4]. Следовательно, когда встречается объявление с инициализацией, то компилятор сохраняет "hello, world" в таблице строк, а указатель *ptr записывает ее адрес .

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

Массив указателей фиксированных размеров вводится одним из следующих определений [8.4]:

В данной инструкции тип может быть как одним из базовых типов, так и производным типом ;

имя_массива – идентификатор , определяемый пользователем по правилам языка С ;

размер – константное выражение , вычисляемое в процессе трансляции программы;

инициализатор – список в фигурных скобках значений элементов заданного типа (т.е. тип ).

В приведенных примерах каждый элемент массивов pd и pi является указателем на объекты типа int .

Значением каждого элемента pd[j] и pi[k] может быть адрес объекта типа int . Все 6 элементов массива pd указателей не инициализированы.В массиве pi три элемента, и они инициализированы адресами конкретных элементов массива data .

В случае обработки строк текста они, как правило, имеют различную длину, и их нельзя сравнить или переместить одной элементарной операцией в отличие от целых чисел. В этом случае эффективным средством является массив указателей. Например, если сортируемые строки располагаются в одном длинном символьном массиве вплотную — начало одной к концу другой, то к каждой строке можно обращаться по указателю на ее первый символ [8.2]. Сами же указатели можно поместить в массив , т.е. создать массив указателей. Две строки можно сравнить, рассмотрев указатели на них.

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

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

В языке программирования С предусматриваются ситуации, когда указатели указывают на указатели. Такие ситуации называются многоуровневой адресацией. Пример объявления указателя на указатель :

В приведенном объявлении **ptr2 – это указатель на указатель на число типа int . При этом наличие двух звездочек свидетельствует о том, что имеется двухуровневая адресация . Для получения значения конкретного числа следует выполнить следующие действия:

В результате в выходной поток (на дисплей пользователя) будет выведено число 88. В приведенном фрагменте переменная *ptr объявлена как указатель на целое число , а **ptr2 – как указатель на указатель на целое. Значение , выводимое в выходной поток (число 88), осуществляется операцией разыменования указателя **ptr2 .

\times

Для многомерных массивов указатели указывают на адреса элементов массива построчно. Рассмотрим пример двухмерного целочисленного массива М размера 3 5, т.е. состоящего из 3 строк и 5 столбцов, и определим указатель :

Элементы массива ( по индексам) располагаются в ячейках памяти по строкам в следующем порядке:

Сначала запоминается первая строка, затем вторая, затем третья. В данном случае двухмерный массив – это массив трех одномерных массивов, состоящих из 5 элементов.

Указатель указывает на адреса элементов в порядке расположения их в памяти. Поэтому тождественны равенства:

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

где i – номер строки заданной матрицы, j – номер столбца, n – число столбцов в матрице.

Практическая часть

Пример 1. Напишите программу считывания строк разной длины с использованием арифметики указателей.

Программный код решения примера:

В программе использован одномерный массив указателей. Функция printf() и спецификатор преобразования %s допускают использование в качестве параметра указатель на строку. При этом на дисплей выводится не значение указателя, а содержимое адресуемой им строки. Обратный слэш "\" служит для переноса содержимого операторной строки на новую строку (для удобства восприятия). Оператор sizeof() вычисляется во время компиляции программы. Во время компиляции он обычно превращается в целую константу, значение которой равно размеру типа или объекта [8.4], в данном случае соответствует размеру массива указателей.

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

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

  • Одна из возможных ошибок новичков — забыть в запуске функции использовать круглые скобки.

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

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

Смотрим на 13-ю строчку кода.

arr является массивом из 3 указателей и при этом каждый из трёх указателей указывает на функцию с пустым списком формальных параметров (пустые скобки), и тип всей этой сущности — void соответствует типу каждой ячейки массива, а поскольку каждая ячейка содержит функцию, то этот тип обозначает тип функций, хранимых в массиве, т. е. массив хранит функции, тип у которых void .

Смотрим на 15-ю строчку кода. Там мы вытаскиваем значение из массива и использованием круглых скобок задействуем вытащенное значение как функцию. Не забываем, что индексация массивов начинается с нуля, и поэтому то, что мы пишем 2 в квадратных скобках в месте обращения к ячейке массива — будет обозначать третий индекс массива. (0,1,2) — двойка на третьей позиции. Так как в массиве расположены не самые обычные значения, а адреса функций, то и обращаемся мы к значениям не как к обычным значениям, а как к функциям, после чего и происходит вызов той функции, к которой мы обратились задействовав указатель, хранящийся в массиве.

Если вы легко разруливаете работу с параметрами функций, поймёте массив указателей, то эта тема может показаться вам очень даже лёгкой. Массив указателей на функции по сути ничем не отличается от обычного массива значений. Разве что обычные значения вызывать как функции не нужно, а массив — он и есть массив.

С массивом указателей на функции, возвращающие неуказательные типы, вроде как должно было стать что-нибудь да понятно. А как поместить в массив функции, возвращающие указатели на указатели? Такое можно проворачивать, но надо обязательно учитывать три вещи:
указатели — это не массивы, не функции, поэтому нельзя будет в ячейку запихать указатель на функцию, надо только саму функцию
следует понимать, что могут быть проблемы с очисткой памяти: очень легко потерять связь с выделенной памятью, поэтому лучше всего запоминать адреса в отдельные указатели
надо хорошо понимать, где массив [] , а где не массив,
ведь для удаления из памяти массива надо делать delete [] , а для удаления немассива delete

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