Как передать указатель в функцию c
Передача через указатель
С точки зрения удобства программирования, передавать значения, которые могут изменяться внутри функции лучше всего через ссылку (а не через указатель). Чтобы в этом разобраться, ниже описан возможный, но не самый лучший способ передачи параметров — по указателю.
Если передавать параметры через указатель (так называемый С-стиль), код функции выглядит так:
mul5(&var); // Здесь символ & обозначает операцию взятия адреса переменной
Такой синтаксис неудобен. Внутри тела функции, перед переменной, которая является указателем, приходится ставить звездочку * для работы со значением, на который указывает указатель. Кроме того, при вызове функции приходится ставить амперсанд & перед именем переменной для того, чтобы передать адрес переменной (который будет принят в функцию как указатель).
Передача через ссылку
Если передавать параметры через ссылку , код функции выглядит так:
А вызов выглядит так:
Видно, что нет никаких разыменовываний в теле функции функции. Так же при таком вызове не требуется передача адреса переменной.
Однако, следует помнить , что такая передача по ссылке возможна только для единичных типов C/C++ . И не работает для передачи массивов!
Поэтому, к примеру, если функция принимает:
- одномерный массив block из элементов char , элементы которого надо изменить внутри функции (обозначим это как сложный/составной тип)
- целочислительную переменную shift , которую тоже надо изменить внутри функции (это простой базовый тип)
то реализация функции будет выглядеть так:
void clear(unsigned char *block, int &shift)
А вызов функции будет выглядеть так:
unsigned char* anyBlock=»Long string»;
clear(anyBlock, anyShift); // Вызов функции
То есть, в вызове нет никаких спецсимволов указателей/адресов ни для первого ни для второго параметра. Для первого параметра нет спецсимвола взятия адреса (хоть и идет работа с указателем) из-за того, что имя массива имеет тип указателя. Для второго параметра нет спецсимвола взятия адреса из-за того, что передача идет по ссылке.
При таком вызове будут изменяться как ячейки массива anyBlock , так и переменная anyShift .
- Стандарт языка программирования Си
- Передача параметра в функцию по указателю в C стиле
- Передача параметров в функцию по указателю (C стиль) и по ссылке (C++ стиль)
- Передача структуры в функцию и изменение значений элементов структуры
- В чем разница объявления строки как массива и как указателя
- Использование очень больших чисел
- getch() в Linux
- Аргументы функции main()
- Как побайтно считать файл
- Реализация циклического сдвига ROR и ROL
- Динамическая загрузка библиотек в Linux
- Функция fmemopen() — открытие набора байт как файла в памяти с получением файлового дескриптора
- Подмена дефайнов (#define)
- Успешной отладки, шутка
- Как описывать функции с аргументами по-умолчанию в C/C++
- Форматированный вывод через функцию printf
- Создание и удаления двухмерного и трехмерного динамического массива
- Как передать в функцию двумерный массив, размер которого известен
- Как передать в функцию динамический двумерный массив
- Как правильно читать объявления в Си
- Вычисление pbkdf2 на языке C
- Определение разрядности платформы 32 или 64 бит
- Функции getuid() и geteuid()
- Указатели и символьные строки в языке C
- Практическое применение LD_PRELOAD или замещение функций в Linux
- Проблемы C-подобных языков. Где находиться типу: справа или слева? (Теория)
- Откуда в языке Си появился синтаксис указателей, и для чего он предназначался изначально
- Как в языке Си вызвать функцию, для которой известен адрес вызова в виде числа
- Вызов функции по известному адресу в языке Си — абстрактный тип данных указателя на функцию
- Структуры в языке Си. Определения структур в сравнении с языком C++
- Структуры в языке Си. Указатели на структуры
- Структуры в языке Си. Массивы структур
- Операция запятая «,» в языке Си и Си++
- Самый быстрый и оптимальный способ копирования строк в Cи и C++
- Стоит запомнить: цикл for в языке Си/Си++ — это цикл с предусловием
- Как происходит компиляция C/C++ кода. Единица трансляции
Указатели на функции
Чаще всего к ошибкам приводит использование указателей па функцию. Хотя функция — это не переменная, она по-прежнему имеет физическое положение в памяти, которое может быть присвоено указателю. Адрес, присвоенный указателю, является входной точкой в функцию. Указатель может использоваться вместо имени функции. Он также позволяет передавать функции как обычные аргументы в другие функции.
Чтобы понять, как работают указатели на функции, необходимо понять, как компилируются и вызываются функции в С. По мере компиляции функций исходный код преобразуется в объектный и устанавливается точка входа. При вызове функции происходит обращение к данной точке входа. Поэтому указатель на функцию может использоваться для вызова функции.
Адрес функции получается при использовании имени функции без каких-либо скобок или аргументов. (Очень похоже на массивы, где адрес получается с использованием имени массива без индексов.) Рассмотрим следующую программу, предназначенную для демонстрации нюансов объявления:
#include
#include
void check(char *a, char *b, int (*cmp) (const char *, const char *));
int main(void)
char s1 [80], s2[80];
int (*p) (const char*, const char*);
p = strcmp; /* получение адреса strcmp() */
gets(s1);
gets (s2);
check(s1, s2, p);
return 0;
>
void check (char *a, char *b, int (*cmp) (const char *, const char *))
printf(«Testing for equality.\n»);
if(!(*cmp) (a, b)) printf(«Equal»);
else printf(«Not equal»);
>
Когда вызывается check(), ей передаются два указателя на символы и один указатель на функцию. В функции check() аргументы объявляются как указатели на символы и указатель на функцию. Надо обратить внимание на способ объявления указателя на функцию. Следует использовать аналогичный метод при объявлении других указателей на функцию, за исключением тех случаев, когда отличается возвращаемый тип или передаваемые параметры. Скобки вокруг *cmp необходимы для правильной интерпретации компилятором данного выражения.
При объявлении указателя на функцию можно по-прежнему использовать прототип, как показано в предыдущей программе. Тем не менее в большинстве случаев имена настоящих параметров неизвестны. Поэтому можно оставить имена пустыми или можно использовать любые понравившиеся имена.
Рассмотрим работу функции strcmp() в функции check(). Оператор
if (!(*cmp) (a, b) ) printf(«Equal»);
осуществляет вызов функции, в данном случае strcmp(), с помощью cmp, который указывает на данную функцию. Вызов происходит с аргументами a и b. Данный оператор демонстрирует общий вид использования указателя на функцию для вызова функции, на которую он указывает. Круглые скобки вокруг *cmp необходимы вследствие наличия приоритетов. На самом деле можно напрямую использовать cmp, как показано ниже:
if (!cmp(a, b)) printf(«Equal»);
Данная версия также вызывает функцию, на которую указывает cmp, но она использует нормальный синтаксис. Использование ( cmp) помогает всем, читающим программу, понять, что указатель на функцию используется для вызова функции вместо вызова функции cmp. Возможно вызвать напрямую check(), используя strcmp, как показано ниже:
check(s1, s2, strcmp);
Данный оператор устраняет необходимость наличия дополнительной переменной-указателя.
Можно задаться вопросом, почему кто-то хочет написать программу таким способом. В данном примере ничего не достигается, но появляются большие проблемы. Тем не менее иногда бывают моменты, когда выгодно передавать функции в процедуры или хранить массивы функций. Следующий пример демонстрирует использование указателей на функции. При написании интерпретатора наиболее типично использование вызовов функций для различных подпрограмм поддержки, типа синус, косинус и тангенс. Вместо использования большого списка для оператора switch, можно использовать массив указателей на функции, в котором доступ к необходимой функции осуществляется с помощью индекса. В данной программе check() может использоваться для проверки как алфавитного, так и численного равенства простым вызовом различных функций сравнения.
#include
#include
#include
#include
void check(char *a, char *b, int (*cmp) (const char *, const char *));
int numcmp (const char *a, const char *b) ;
int main(void)
char s1[80], s2 [80];
gets (s1);
gets (s2);
if(isalpha(*s1))
check (s1, s2, strcmp);
else
check(s1, s2, numcmp);
return 0;
>
void check(char *a, char *b, int (*cmp) (const char *,const char *))
printf(«Testing for equality.\n»);
if(!(*cmp) (a, b)) printf («Equal»);
else printf(«Hot equal»);
>
int numcmp (const char *a, const char *b)
If(atoi(a)==atoi(b)) return 0;
else return 1;
>
Урок №99. Передача по адресу
Есть еще один способ передачи переменных в функцию в языке C++ — по адресу.
Оглавление:
- Передача по адресу
- Передача по константному адресу
- Передача адресов по ссылке
- Существует только передача по значению
- Плюсы и минусы передачи по адресу
Передача по адресу
Передача аргументов по адресу — это передача адреса переменной-аргумента (а не значения исходной переменной). Поскольку аргумент является адресом, то параметром функции должен быть указатель. Затем функция сможет разыменовать этот указатель для доступа или изменения исходного значения. Вот пример функции, которая принимает параметр, передаваемый по адресу:
void printArray ( int * array , int length )
for ( int index = 0 ; index < length ; ++ index ) std :: cout << array [ index ] << ' ' ;
Вот пример программы, которая вызывает эту функцию:
int array [ 7 ] = < 9 , 8 , 6 , 4 , 3 , 2 , 1 >; // помните, что массивы распадаются в указатели при передаче
printArray ( array , 7 ) ; // поэтому здесь array — это указатель на первый элемент массива (в использовании оператора & нет необходимости)
Помните, что фиксированные массивы распадаются в указатели при передаче в функцию, поэтому их длину нужно передавать в виде отдельного параметра. Перед разыменованием параметров, передаваемых по адресу, не лишним будет проверить — не являются ли они нулевыми указателями. Разыменование нулевого указателя приведет к сбою в программе. Вот функция printArray() с проверкой (обнаружением) нулевых указателей:
void printArray ( int * array , int length )
// Если пользователь передал нулевой указатель в качестве array
if ( ! array )
for ( int index = 0 ; index < length ; ++ index ) std :: cout << array [ index ] << ' ' ; int array [ 7 ] = < 9 , 8 , 6 , 4 , 3 , 2 , 1 >;
printArray ( array , 7 ) ;
Передача по константному адресу
Поскольку printArray() все равно не изменяет значения получаемых аргументов, то хорошей идеей будет сделать параметр array константным:
void printArray ( const int * array , int length )
// Если пользователь передал нулевой указатель в качестве array
if ( ! array )
for ( int index = 0 ; index < length ; ++ index ) std :: cout << array [ index ] << ' ' ; int array [ 7 ] = < 9 , 8 , 6 , 4 , 3 , 2 , 1 >;
printArray ( array , 7 ) ;
Так мы видим сразу, что printArray() не изменит переданный аргумент array . Когда вы передаете указатель в функцию по адресу, то значение этого указателя (адрес, на который он указывает) копируется из аргумента в параметр функции. Другими словами, он передается по значению! Если изменить значение параметра функции, то изменится только копия, исходный указатель-аргумент не будет изменен. Например:
void setToNull ( int * tempPtr )
// Мы присваиваем tempPtr другое значение (мы не изменяем значение, на которое указывает tempPtr)
tempPtr = nullptr ; // используйте 0, если не поддерживается C++11
// Сначала мы присваиваем ptr адрес six, т.е. *ptr = 6
int six = 6 ;
// Здесь выведется 6
std :: cout << * ptr << "\n" ; // tempPtr получит копию ptr setToNull ( ptr ) ; // ptr до сих пор указывает на переменную six! // Здесь выведется 6 std :: cout << * ptr << "\n" ; std :: cout << " ptr is null" ;
В tempPtr копируется адрес указателя ptr . Несмотря на то, что мы изменили tempPtr на нулевой указатель (присвоили ему nullptr ), это никак не повлияло на значение, на которое указывает ptr . Следовательно, результат выполнения программы:
Обратите внимание, хотя сам адрес передается по значению, вы все равно можете разыменовать его для изменения значения исходного аргумента. Запутано? Давайте проясним:
При передаче аргумента по адресу в переменную-параметр функции копируется адрес из аргумента. В этот момент параметр функции и аргумент указывают на одно и то же значение.
Если параметр функции затем разыменовать для изменения исходного значения, то это приведет к изменению значения, на которое указывает аргумент, поскольку параметр функции и аргумент указывают на одно и то же значение!
Если параметру функции присвоить другой адрес, то это никак не повлияет на аргумент, поскольку параметр функции является копией, а изменение копии не приводит к изменению оригинала. После изменения адреса параметра функции, параметр функции и аргумент будут указывать на разные значения, поэтому разыменование параметра и дальнейшее его изменение никак не повлияют на значение, на которое указывает аргумент.
В следующей программе это всё хорошо проиллюстрировано:
Указатели на функции
Уже было отмечено, что указатели могут указывать на самые разнообразные типы объектов в программе на языке C++. Точнее, на все и на любые виды объектов, являющиеся данными в программе.
Но такими же объектами, как и традиционные объекты данных, являются функции в программе. Поэтому напрашивается желание попытаться определить и использовать указатель на функцию. Создадим вот такую простейшую программу (ex1.cc):
Указатель на функцию. Листинг 1
double ( * pfunc ) ( double ) ;
В момент этого объявления указатель pfunc представляет собой ничего более, как некоторый адрес во внутреннем представлении компьютера (4 байта в 32-бит операционной системе, 8 байт в 64-бит операционной системе). Это в точности тот же внутренний вид, который имеет, скажем, указатель на целочисленную переменную int* .
Но этот указатель имеет свой строго определённый тип: указатель на функцию, принимающую один параметр типа double , и возвращающую значение типа double . Но вот на какую конкретно функцию указывает указатель, в точке его определения — не важно: значение указателя не определено.
А вот следующим оператором присвоения мы привязываем указатель на функцию к конкретной функции area() . Абсолютно правильным было бы записать присвоение указателю адреса функции: pfunc = &area . Но компилятор C++ настолько умён, что и упоминание имени функции в операторе присвоения интерпретирует как её адрес.
Таким образом запись pfunc = area также совершенно корректная. С этого момента мы можем использовать указатель pfunc для вызова функции на которую он указывает. Для этого мы записываем значение на которое указывает указатель *pfunc (операция * в таком контексте называется разыменованием указателя).
Скобки вокруг разыменованного значения pfunc в записи (*pfunc)( r ) нужны из соображений приоритетов раскрытия операций в выражении. Выполнение этого примера:
Пока использование указателей на функции не принесло ничего принципиально нового в рассматриваемом примере, кроме некоторых замысловатостей синтаксиса. Но в этом примере мы пока только узнали каким образом можно определить и использовать указатели на функции. А теперь мы можем перейти к вопросу зачем это нужно и как мы можем это использовать.
Предположим, для некоторого крупного проекта мы готовим последовательность тестов, выполняемых в процесс развития и роста самого тестируемого проекта (это так называемая технология разработки тестирования, и очень продуктивная). Число пошаговых тестов в такой ситуации будет постоянно нарастать по мере продвижения готовности базового проекта.
В этой ситуации мы можем поступить так (ex2.cc):