Как передать указатель в функцию c
Перейти к содержимому

Как передать указатель в функцию c

  • автор:

Передача параметров по указателю

Некоторые разработчики предпочитают наличие амперсанда в f(&obj) как подсказки о том, что значение obj может измениться внутри вызова. Правда, если obj уже и так указатель, то амперсанд не будет нужен.

Плюсы передачи по указателю

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

Это экономия одновременно и по памяти, и по скорости!

Как быть, если мы хотим этой экономии и решили передавать объект по адресу, но не хотим дать функции потенциальную возможность его (случайного) изменения?

Можно использовать константный указатель: const T*

Минусы передачи по указателю

В Си существует неявное преобразование типа целого числа к типу указателя, о котором компилятор выдает лишь предупреждение, но не ошибку:

warning: passing argument 1 of ‘foo’ makes pointer from integer without a cast

Представьте себе, что foo довольно длинна, и везде, где употребляется x , нужна звёздочка, а вызывается foo 48 раз в разных местах программы — при этом иногда нужен амперсанд, иногда нет.

Итак, легко забыть звёздочку в теле функции foo или амперсанд — в её вызове, значит переданное число может случайно истолковаться как адрес числа. Обращение по непредсказуемому адресу приводит к ошибке Segmentation fault и завершению программы.

Поэтому передача аргументов по адресу опасна!

Как передать указатель в функцию c

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

Например, пусть у нас будет простейшая функция, которая увеличивает число на единицу:

#include void increment(int x) < x = x + 1; printf("increment function: %d \n", x); >int main(void)

Здесь переменная n передается в качестве аргумента для параметра x. Передача происходит по значению, поэтому любое изменение параметра x в функции increment никак не скажется на значении переменной n. Что мы можем увидеть, запустим программу:

increment function: 11 main function: 10

Теперь изменим функцию increment, использовав в качестве параметра указатель:

#include void increment(int *x) < *x = *x + 1; printf("increment function: %d \n", *x); >int main(void)

Теперь в функции increment разыменовываем указатель, получаем его значение и увеличиваем его на единицу.

*x = *x + 1;

Это изменяет значение, которое находится по адресу, хранимому в указателе x.

Поскольку теперь функция в качестве параметра принимает указатель, то при ее вызове необходимо передать адрес переменной: increment(&n); .

В итоге изменение параметра x также повлияет на переменную n:

increment function: 11 main function: 11

Еще один показательный пример применения указателей в параметрах — функция обмена значений:

#include void swap(int *a, int *b) < int temp = *a; *a = *b; *b=temp; >int main(void)

Функция swap() в качестве параметров принимает два указателя. Посредством переменной temp происходит обмен значениями.

При вызове функции swap в нее передаются адреса переменных x и y, и в итоге их значения будут изменены.

Константые параметры

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

#include // константный параметр int twice(const int *x) < //*x = *x * *x; // так нельзя, так как x - константный параметр int y = *x + *x; return y; >int main(void) < int n = 10; int m = twice(&n); printf("n = %d \n", n); // n = 10 printf("m = %d \n", m); // m = 20 return 0; >

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

Массивы в параметрах

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

Например, определим функцию для увеличения элементов массива в два раза:

#include void twice(size_t n, int p[]) < for(size_t i = 0; i < n; i++) < p[i]= p[i] * 2; >> int main(void) < int nums[] = ; // получаем количество элементов массива size_t length = sizeof(nums)/sizeof(nums[0]); twice(length, nums); for(size_t i=0; i return 0; >

Функция twice в качестве параметров принимает массив и число его элементов и в цикле увеличивает их в два раза.

В функции main передаем массив в функцию twice и затем выводим его на консоль. В результате мы увидим, что массив nums был изменен:

2 4 6 8 10

Так как передача массива в функцию фактически представляет передачу адреса первого элемента, то массивы в параметрах мы можем заменить указателями:

#include void twice(size_t n, int *p) < for(size_t i=0; i> int main(void) < int nums[] = ; size_t length = sizeof(nums)/sizeof(nums[0]); twice(length, nums); for(size_t i=0; i return 0; >

В итоге в данном случае не будет большой разницы, какой тип имеет параметр — массив или указатель. Компилятор в любом случае будет рассматривать параметр типа int array[] как указатель int* array

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

#include // функция ожидает получить массив как минимум из 4 элементов void twice(int numbers[4]) < for(size_t i = 0; i < 4; i++) < numbers[i]= numbers[i] * 2; printf("%d \t", numbers[i] * 2); >> int main(void) < int nums[5] = ; twice(nums); return 0; >

Тем не менее если в функцию будет передан массив меньшей длины, то конкретное поведение зависит от компилятора. Например, GCC успешно скомпилирует приложение, хотя при компиляции выдаст предупреждение.

Начиная со стандарта C99 Си позволяет установить минимальную длину массива с помощью оператора static :

// ожидаем массив как минимум с 4 элементами void twice(int numbers[static 4])
Константный массив

Массив также можно передавать как константный — в этом случае значения его элементов нельзя изменить:

#include // массив p - константный void twice(size_t n, const int p[]) < for(size_t i = 0; i < n; i++) < // p[i]= p[i] * 2; // Так нельзя - массив константный printf("%d \t", p[i] * 2); >> int main(void) < int nums[] = ; // получаем количество элементов массива size_t length = sizeof(nums)/sizeof(nums[0]); twice(length, nums); return 0; >

Передача указателей в функцию C++

Вопрос по C++. При передаче обычной переменной в функцию создается, как я понимаю, её копия. А что происходит, когда мы передаем указатель? Создается ли копия указателя или нет?

Отслеживать
44.8k 3 3 золотых знака 39 39 серебряных знаков 90 90 бронзовых знаков
задан 24 дек 2016 в 23:45
113 1 1 золотой знак 1 1 серебряный знак 5 5 бронзовых знаков

2 ответа 2

Сортировка: Сброс на вариант по умолчанию

Если у вас объявлена функция с параметром, как, например,

void f( T t ); 

где T — это некоторый тип, и эта функция вызывается с некоторым выражением, переданным ей в качестве аргумента, как

f( exp ); 

то инициализацию параметра можно представить следующим образом

void f( /* T t */ ) < T t = exp; //. 

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

Сравните две функции

void f( int *p ) < p = new int( 10 ); >void g( int * &p )
int main()

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

У функции g параметр имеет ссылочный тип, то есть эта ссылка на переданный функции аргумент. Поэтому функция имеет дело с исходным аргументом и меняет его в своем теле, присваивая ему адрес выделенной памяти.

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

void h( int **p )

Вызов функции будет выглядеть как

Указатели на функции

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

#include #include int sum(int a, int b) < return a + b; >void main () < //Объявляем указатель на функцию int (*fptr)(int, int) = NULL; int result; //Присваиваем указателю значение - адрес функции //Это похоже на работу с массивом: операцию взятия адреса использовать не нужно fptr = sum; //Вызов осуществляется также, как и обычной функции result = fptr(10, 40); printf("%d", result); getch(); >
#include #include int dble(int a) < return 2*a; >int deleteEven(int a) < if (a % 2 == 0) < return 0; >else < return a; >> //Функция принимает массив, его размер и указатель на функцию, //которая далее применяется ко всем элементам массива void map(int *arr, unsigned size, int (*fun)(int)) < unsigned i; for (i = 0; i < size; i++) < arr[i] = fun(arr[i]); >> void main () < int a[] = ; unsigned i; map(a, 10, deleteEven); for (i = 0; i < 10; i++) < printf("%d ", a[i]); >map(a, 10, dble); printf("\n"); for (i = 0; i < 10; i++) < printf("%d ", a[i]); >getch(); >

В этом примере мы создали функцию отображения, которая применяет ко всем элементам массива функцию, которая передаётся ей в качестве аргумента. Когда мы вызываем функцию map, достаточно просто передавать имена функций (они подменяются указателями). Запишем теперь функцию map, которая получает в качестве аргумента массив типа void:

#include #include void dbleInt(void *a) < *((int*) a) *= 2; >void deleteEvenInt(void* a) < int tmp = *((int*) a); if (tmp % 2 == 0) < *((int*) a) = 0; >> void dbleDouble(void *a) < *((double*) a) *= 2.0; >void deleteEvenDouble(void* a) < int tmp = *((double*) a); if (tmp % 2 == 0) < *((double*) a) = 0; >> //Функция принимает массив, его размер, размер одного элемента и указатель на функцию, //которая далее применяется ко всем элементам массива void map(void *arr, unsigned num, size_t size, void (*fun)(void *)) < unsigned i; char *ptr = (char*) arr; for (i = 0; i < num; i++) < fun((void*) (ptr + i*size)); >> void main () < int a[] = ; double b[] = ; unsigned i; //Работаем с массивом типа int map(a, 10, sizeof(int), deleteEvenInt); for (i = 0; i < 10; i++) < printf("%d ", a[i]); >map(a, 10, sizeof(int), dbleInt); printf("\n"); for (i = 0; i < 10; i++) < printf("%d ", a[i]); >printf("\n"); //Работаем с массивом типа double map(b, 10, sizeof(double), deleteEvenDouble); for (i = 0; i < 10; i++) < printf("%.3f ", b[i]); >map(b, 10, sizeof(double), dbleDouble); printf("\n"); for (i = 0; i < 10; i++) < printf("%.3f ", b[i]); >getch(); >

Вот где нам понадобились указатели типа void. Так как map получает указатель на функцию, то все функции должны иметь одинаковые аргументы и возвращать один и тот же тип. Но аргументы функций должны быть разного типа, поэтому мы делаем их типа void. Функция map получает указатель типа void (*)(void*), поэтому ей теперь можно передавать любую из четырёх функций.
Пример другой функции: функция filter получает указатель на массив и возвращает размер нового массива, оставляя в нём только те элементы, для которых переданный предикат возвращает логическую истину (предикат – функция, которая возвращает истину или ложь). Сначала напишем для массива типа int:

#include #include #include int isOdd(int a) < return (a % 2 != 0); >int isGtThree(int a) < return a >3; > unsigned int filter(int *arr, unsigned size, int (*pred)(int), int** out) < unsigned i; unsigned j; //размер возвращаемого масива *out = (int*) malloc(sizeof(int)*size); for (i = 0, j = 0; i < size; i++) < if (pred(arr[i])) < (*out)[j] = arr[i]; j++; >> *out = (int*) realloc(*out, j*sizeof(int)); return j; > void main () < int a[] = ; int *aOdd = NULL; int *aGtThree = NULL; unsigned i; unsigned size; size = filter(a, 10, isOdd, &aOdd); for (i = 0; i < size; i++) < printf("%d ", aOdd[i]); >printf("\n"); size = filter(a, 10, isGtThree, &aGtThree); for (i = 0; i < size; i++) < printf("%d ", aGtThree[i]); >free(aOdd); free(aGtThree); getch(); >

Теперь для массива типа void

#include #include #include #include #include int isOddInt(void *a) < return (*((int*) a) % 2 != 0); >int isGtThreeInt(void* a) < return *((int*) a) >3; > int isOddDouble(void* a) < return (int)*((double*) a) / 2 != 0; >int isGtThreeDouble(void* a) < return *((double*) a) >3.0; > unsigned int filter(void *arr, unsigned num, size_t size, int (*pred)(void*), void** out) < unsigned i; unsigned j; //размер возвращаемого масива char* ptrIn = (char*) arr; char* ptrOut = NULL; *out = (void*) malloc(num*size); ptrOut = (char*) (*out); for (i = 0, j = 0; i < num; i++) < if (pred(ptrIn + i*size)) < memcpy(ptrOut + j*size, ptrIn + i*size, size); j++; >> *out = (void*) realloc(*out, j*size); return j; > void main () < int a[] = ; double b[] = ; int *aOdd = NULL; int *aGtThree = NULL; double *bOdd = NULL; double *bGtThree = NULL; unsigned i; unsigned size; size = filter(a, 10, sizeof(int), isOddInt, (void**)&aOdd); for (i = 0; i 7lt; size; i++) < printf("%d ", aOdd[i]); >printf("\n"); size = filter(a, 10, sizeof(int), isGtThreeInt, (void**)&aGtThree); for (i = 0; i < size; i++) < printf("%d ", aGtThree[i]); >printf("\n"); size = filter(b, 10, sizeof(double), isOddDouble, (void**)&bOdd); for (i = 0; i < size; i++) < printf("%.3f ", bOdd[i]); >printf("\n"); size = filter(b, 10, sizeof(double), isGtThreeDouble, (void**)&bGtThree); for (i = 0; i < size; i++) < printf("%.3f ", bGtThree[i]); >free(aOdd); free(bOdd); free(aGtThree); free(bGtThree); getch(); >

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

#include #include int sum(int a, int b) < return a + b; >int maxx(int a, int b) < return (a >b)? a: b; > int mul(int a, int b) < return a*b; >void fold(int *arr, unsigned size, int (*fun)(int, int), int *acc) < unsigned i; *acc = fun(arr[0], arr[1]); for (i = 2; i < size; i++) < *acc = fun(*acc, arr[i]); >> void main () < int a[] = ; int result; fold(a, 10, sum, &result); printf("%d\n", result); fold(a, 10, maxx, &result); printf("%d\n", result); fold(a, 10, mul, &result); printf("%d\n", result); getch(); >

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

#include #include #include #include int cmpIntDesc(void *a, void* b) < return *((int*) a) < *((int*) b); >int cmpIntAsc(void *a, void* b) < return *((int*) a) >*((int*) b); > int cmpDoubleAsc(void *a, void* b) < return *((double*) a) < *((double*) b); >int cmpDoubleDesc(void *a, void* b) < return *((double*) a) >*((double*) b); > void insertionSort(void* arr, unsigned num, size_t size, int (*cmp)(void *a, void *b)) < unsigned i, j; char *ptr = (char*) arr; char *tmp = (char*) malloc(size); for (i = 1; i < num; i++) < if (cmp(ptr + i*size, ptr + (i-1)*size)) < j = i; while (cmp(ptr + j*size, ptr + (j-1)*size) && j >0) < memcpy(tmp, ptr + j*size, size); memcpy(ptr + j*size, ptr + (j-1)*size, size); memcpy(ptr + (j-1)*size, tmp, size); j--; >> > > void main () < int a[] = ; double b[] = ; int i; insertionSort(a, 10, sizeof(int), cmpIntAsc); for (i = 0; i < 10; i++) < printf("%d ", a[i]); >printf("\n"); insertionSort(a, 10, sizeof(int), cmpIntDesc); for (i = 0; i < 10; i++) < printf("%d ", a[i]); >printf("\n"); insertionSort(b, 10, sizeof(double), cmpDoubleAsc); for (i = 0; i < 10; i++) < printf("%.3f ", b[i]); >printf("\n"); insertionSort(b, 10, sizeof(double), cmpDoubleDesc); for (i = 0; i < 10; i++) < printf("%.3f ", b[i]); >getch(); >

Массив указателей на функции

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

#include #include #include #include #define ERROR_DIV_BY_ZERO -2 #define EPSILON 0.000001f float doSum(float a, float b) < return a + b; >float doSub(float a, float b) < return a - b; >float doMul(float a, float b) < return a * b; >float doDiv(float a, float b) < if (fabs(b) return a / b; > void main() < float (*menu[4])(float, float); int op; float a, b; menu[0] = doSum; menu[1] = doSub; menu[2] = doMul; menu[3] = doDiv; printf("enter a: "); scanf("%f", &a); printf("enter b: "); scanf("%f", &b); printf("enter operation [0 - add, 1 - sub, 2 - mul, 3 - div]"); scanf("%d", &op); if (op >= 0 && op < 4) < printf("%.6f", menu[op](a, b)); >getch(); >

Точно также можно было создать массив динамически

void main() < float (**menu)(float, float) = NULL; int op; float a, b; menu = (float(**)(float, float)) malloc(4*sizeof(float(*)(float, float))); menu[0] = doSum; menu[1] = doSub; menu[2] = doMul; menu[3] = doDiv; printf("enter a: "); scanf("%f", &a); printf("enter b: "); scanf("%f", &b); printf("enter operation [0 - add, 1 - sub, 2 - mul, 3 - div]"); scanf("%d", &op); if (op >= 0 && op < 4) < printf("%.6f", menu[op](a, b)); >free(menu); getch(); >

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

#include #include #include #include #define ERROR_DIV_BY_ZERO -2 #define EPSILON 0.000001f typedef float (*operation)(float, float); float doSum(float a, float b) < return a + b; >float doSub(float a, float b) < return a - b; >float doMul(float a, float b) < return a * b; >float doDiv(float a, float b) < if (fabs(b) return a / b; > void main() < operation *menu = NULL; int op; float a, b; menu = (operation*) malloc(4*sizeof(operation)); menu[0] = doSum; menu[1] = doSub; menu[2] = doMul; menu[3] = doDiv; printf("enter a: "); scanf("%f", &a); printf("enter b: "); scanf("%f", &b); printf("enter operation [0 - add, 1 - sub, 2 - mul, 3 - div]"); scanf("%d", &op); if (op >= 0 && op < 4) < printf("%.6f", menu[op](a, b)); >free(menu); getch(); >

Ещё один пример: функция any возвращает 1, если в переданном массиве содержится хотя бы один элемент, удовлетворяющий условию pred и 0 в противном случае.

#include #include typedef int (*Predicat)(void*); int isBetweenInt(void* a) < return *((int*) a) >10 && *((int*) a) < 12; >int isBetweenDouble(void* a) < return *((double*) a) >10.0 && *((double*) a) < 12.0; >int any(void* arr, unsigned num, size_t size, Predicat pred) < unsigned i; char* ptr = (char*) arr; for (i = 0; i < num; i++) < if (pred(ptr + i*size)) < return 1; >> return 0; > void main() < int a[10] = ; double b[10] = ; printf("has 'a' any value > 10 and < 12? %d\n", any(a, 10, sizeof(int), isBetweenInt)); printf("has 'b' any value >10 and

qsort и bsearch

В библиотеке stdlib си имеется несколько функций, которые получают в качестве аргументов указатели на функции. Функция qsort получает такие же аргументы, как и написанная нами функция insertionSort: массив типа void, размер массива, размер элемента и указатель на функцию сравнения. Давайте посмотрим простой пример - сортировка массива строк:

#include #include #include #include #define SIZE 10 //qsort передаёт функции сравнения указатели на элементы. //но наш массив - это массив указателей, так что qsort будет //передавать указатели на указатели int cmpString(const void* a, const void* b) < return strcmp(*(const char**) a, *(const char**) b); >void main() < char *words[SIZE]; char buffer[128]; int i, length; printf("Enter %d words:", SIZE); for (i = 0; i < SIZE; i++) < scanf("%127s", buffer); length = strlen(buffer); words[i] = (char*) malloc(length+1); strcpy(words[i], buffer); >printf("\n"); qsort(words, SIZE, sizeof(char*), cmpString); for (i = 0; i < SIZE; i++) < printf("%s\n", words[i]); free(words[i]); >getch(); >

Функция bsearch проводит бинарный поиск в отсортированном массиве и получает указатель на функцию сравнения, такую же, как и функция qsort. В случае, если элемент найден, то она возвращает указатель на этот элемент, если элемент не найден, то NULL.

#include #include int cmpInt(const void* a, const void* b) < return *(int*) a - *(int*) b; >void main() < int a[10] = ; int elm; int *index; printf("Enter number to search: "); scanf("%d", &elm); index = (int*) bsearch(&elm, a, 10, sizeof(int), cmpInt); if (index == NULL) < printf("element not found"); >else < printf("index = %d", (index - a)); >getch(); >

ru-Cyrl 18- tutorial Sypachev S.S. 1989-04-14 sypachev_s_s@mail.ru Stepan Sypachev students

email

Всё ещё не понятно? – пиши вопросы на ящик

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *