Как вернуть указатель из функции c
В прошлой теме рассматривались такие функции, как calloc, malloc, realloc, которые возвращали в качестве результата указатель. То есть функция может не принимать указатели как параметры, но и возвращать указатель. Определение подобной функции выглядит следующим образом:
Преимуществом использования указателя как возвращаемого типа является то, что это позволяет получать из функции набор значений, в частности, массив. Например, пусть у нас в программе имеются два массива, и мы хотим их сложить с помощью специальной функции:
Функция addArrays() для хранения результатов сложения двух массивов выделяет динамическую память, на которую указывает указатель ptr. Затем эта динамическая память наполняется результатами сложения соответствующих элементов двух массивов. А возвращаемым значением служит указатель ptr.
В функции main мы можем получить возвращаемый указатель и перебрать все значения из динамической памяти:
Когда указатель становится не нужен, ранее выделенная динамическая память освобождается с помощью функции free() .
Результатом работы программы будет следующий вывод:
Стоит отметить, что указатель, возвращаемый из функции, не должен представлять адрес локальной переменной. Потому что локальная переменная существует только во время работы этой функции. Например, как ранее было сказано, через указатель мы можем ссылать на массив. Изменим функцию addArrays:
Однако мы даже не сможем скомпилировать данный пример, так как массив ptr здесь задан как локальная переменная.
Возврат данных из функции
Проверяем в Atollic True Studio , gcc для STM32F205 :
Возврат данных из функции
Сразу для понимания пример :
То есть тот тип данных , что возвращается из функции создается в основной программе просто как переменная.
Поэтому возвращая из функции массивы и другие объемные данные надо понимать , что вы расходуете память.
Возврат указателя из функции через return
uint8_t str[] (внутри функции) — размещение в стеке
uint8_t *str размещение во флеше (это где константные данные и код хранится)
static uint8_t str[](вне / в функции) , uint8_t str[] (вне функции) — в статической памяти SRAM , она же куча, (эта та , которая очищается после выключения)
Выводы
Возврат указателя из функции возможен , но главный вопрос : указатель на кого ? . То есть где находится указуемый ? : если в стеке то нельзя, если в куче и флэше , то можно.
Возврат указателя из функции через параметр передаваемый в функцию
Это возможно только если передавать двойной указатель ** !
Если надо вернуть несколько указателей из функции
Тогда можно тупо возвращать по return структуру с нужным количеством параметров.
Возврат указателей
Хотя функции, возвращающие указатели, обрабатываются точно так же, как и функции с другим типом, следует рассмотреть несколько важных понятий.
Указатели на переменные — это не целые числа и не беззнаковые целые. Это адрес памяти некоторого типа данных. Причина такого разделения состоит в том, что когда выполняются арифметические действия с указателем, изменения происходят с учетом базового типа, то есть, если указатель на целое увеличивается, он будет содержать значение на 2 больше по сравнению с предыдущим (предполагается использование 2-байтных целых). Каждый раз при увеличении указателя он указывает на следующий элемент базового типа. Поскольку каждый тип данных может иметь различную длину, компилятор должен знать, на какой тип данных указывает указатель, чтобы правильно осуществить переход к следующему элементу данных.
Ниже показана функция, возвращающая указатель на строку в месте, где найдено соответствие символов:
char *match(char с, char *s)
register int count;
count = 0;
while(c!=s[count] && s[count]) count++;
return(&s[count]);
>
Функция match() пытается вернуть указатель на позицию в строке, где первый раз найден символ с. Если не найдено соответствие, возвращается указатель, содержащий NULL. Ниже показана небольшая программа, использующая match():
#include <stdio.h>
#include <conio.h>
char *match(char c*, char *s);
int main(void)
char s[80], *p, ch;
gets (s) ;
ch = getche();
p = match(ch, s);
if (p) /* совпадение */
printf(«%s «, p);
else
printf(«No match found.»);
return 0;
>
Данная программа осуществляет чтение строки, а затем символа. Если символ содержится в строке, то выводится строка, начиная с момента совпадения. Иначе выводится «No match found».
11.5 – Возвращение значений по значению, по ссылке и по адресу
В трех предыдущих уроках вы узнали о передаче аргументов функциям по значению, по ссылке и по адресу. В этом разделе мы рассмотрим задачу возврата значений вызывающему всеми тремя способами.
Как оказалось, возврат вызывающему значений из функции по значению, по адресу или по ссылке работает почти так же, как передача аргументов в функцию. У каждого метода присутствуют все те же плюсы и минусы. Основное различие между ними состоит в том, что направление потока данных меняется на противоположное. Однако есть еще одна дополнительная сложность – поскольку локальные переменные в функции выходят за пределы области видимости и уничтожаются при возврате из функции, нам необходимо учитывать влияние этого на каждый тип возврата.
Возврат по значению
Возврат по значению – это самый простой и безопасный тип возврата значения. Когда значение возвращается по значению, вызывающему возвращается копия этого значения. Как и в случае передачи по значению, вы можете возвращать значения литералов (например, 5), переменных (например, x ) или выражений (например, x + 1 ), что делает возврат по значению очень гибким.
Еще одно преимущество возврата по значению заключается в том, что вы можете возвращать переменные (или выражения), которые включают в себя локальные переменные, объявленные внутри функции, не беспокоясь о проблемах с областью видимости. Поскольку переменные вычисляются до возврата из функции, и вызывающему возвращается копия значения, не возникает проблем, когда переменная функции выходит за пределы области видимости в конце функции.
Возврат по значению является наиболее подходящим при возврате переменных, которые были объявлены внутри функции, или для возврата аргументов функции, переданных по значению. Однако, как и передача по значению, возврат по значению для структур и больших классов выполняется медленно.
Когда использовать возврат по значению:
- При возврате переменных, которые были объявлены внутри функции
- При возврате аргументов функции, переданных по значению
Когда не использовать возврат по значению:
- При возврате встроенного массива или указателя (используйте возврат по адресу)
- При возврате большой структуры или класса (используйте возврат по ссылке)
Возврат по адресу
Возврат по адресу включает в себя возврат вызывающему адреса переменной. Подобно передаче по адресу, возврат по адресу может возвращать адрес только переменной, но не литерала или выражения (у которых нет адресов). Поскольку возврат по адресу просто копирует адрес из функции вызывающему, он выполняется быстро.
Однако возврат по адресу имеет один дополнительный недостаток, которого нет при возврате по значению – если вы попытаетесь вернуть адрес переменной, локальной для функции, ваша программа будет демонстрировать неопределенное поведение. Рассмотрим следующий пример:
Как вы можете видеть здесь, value уничтожается сразу после того, как его адрес возвращается вызывающему. Конечным результатом является то, что вызывающий получает адрес невыделенной памяти (висячий указатель), что вызовет проблемы при использовании. Это частая ошибка, которую допускают начинающие программисты. Многие более новые компиляторы выдают предупреждение (а не ошибку), если программист пытается вернуть локальную переменную по адресу – однако есть довольно много способов обмануть компилятор, чтобы он позволил вам сделать что-то нелегальное без генерации предупреждения, поэтому эта ответственность лежит на программисте, чтобы гарантировать, что указатель, который он возвращает, после возврата из функции будет указывать на допустимую переменную.
Возврат по адресу часто использовался для возврата вызывающему динамически выделенной памяти:
Это работает, потому что динамически выделяемая память не выходит за пределы области действия в конце блока, в котором она объявлена, поэтому эта память всё еще будет существовать, когда ее адрес возвращается обратно вызывающему. Отслеживание ручного распределения памяти может быть трудным. Разделение выделения и освобождения на разные функции еще больше затрудняет понимание того, кто несет ответственность за удаление ресурса и нужно ли вообще удалять ресурс. Вместо ручного управления памятью следует использовать умные указатели (рассматриваются позже) и типы, которые сами выполняют очистку после себя.
Когда использовать возврат по адресу:
- При возврате динамически выделенной памяти, когда вы не можете использовать тип, который обрабатывает выделение памяти за вас.
- При возврате аргументов функции, переданных по адресу.
Когда не использовать возврат по адресу:
- При возврате переменных, которые были объявлены внутри функции, или параметров, переданных по значению (используйте возврат по значению).
- При возврате большой структуры или класса, переданного по ссылке (используйте возврат по ссылке)
Возврат по ссылке
Подобно возврату по адресу, значения, возвращаемые по ссылке, должны быть переменными (вы не должны возвращать ссылку на литерал или выражение, которое вычисляется во временное значение, так как они выйдут из области видимости в конце функции, и вы в конечном итоге вернете висячую ссылку). Когда переменная возвращается по ссылке, обратно вызывающему передается ссылка на эту переменную. Затем вызывающий может использовать эту ссылку для продолжения изменения переменной, что иногда может быть полезно. Возврат по ссылке также выполняется быстро, что может быть полезно при возврате структур и классов.
Однако, как и при возврате по адресу, вы не должны возвращать по ссылке локальные переменные. Рассмотрим следующий пример:
В приведенном выше фрагменте функция возвращает ссылку на значение, которое будет уничтожено при возврате из функции. Это будет означать, что вызывающий получит ссылку на мусор. К счастью, ваш компилятор, вероятно, выдаст вам предупреждение или ошибку, если вы попытаетесь это сделать.
Возврат по ссылке обычно используется для возврата обратно вызывающему аргументов, переданных в функцию по ссылке. В следующем примере мы возвращаем (по ссылке) элемент массива, который был передан нашей функции по ссылке:
Когда мы вызываем getElement(array, 10) , getElement() возвращает ссылку на элемент массива с индексом 10. Затем main() использует эту ссылку для присвоения этому элементу значения 5.
Хотя это в некоторой степени надуманный пример (потому что вы можете получить доступ к array[10] напрямую), как только вы узнаете о классах, вы найдете гораздо больше применений для возврата значений по ссылке.
Когда использовать возврат по ссылке:
- При возврате параметра-ссылки.
- При возврате члена объекта, который был передан в функцию по ссылке или адресу.
- При возврате большой структуры или класса, который не будет уничтожен в конце функции (например, тот, который был передан по ссылке).
Когда не использовать возврат по ссылке:
- При возврате переменных, которые были объявлены внутри функции, или параметров, переданных по значению (используйте возврат по значению).
- При возврате значения встроенного массива или указателя (используйте возврат по адресу).
Смешивание возвращаемых ссылок и значений
Хотя функция может возвращать значение или ссылку, вызывающий может или не может присвоить этот результат переменной или ссылке соответственно. Давайте посмотрим, что происходит, когда мы смешиваем значения и ссылки при возврате.
В случае A мы присваиваем возвращаемое значение-ссылку переменной-нессылке. Поскольку giana не является ссылкой, возвращаемое значение копируется в giana , как если бы returnByReference() выполняла возврат по значению.
В случае B мы пытаемся инициализировать ссылку ref копией значения, возвращаемого функцией returnByValue() . Однако, поскольку возвращаемое значение не имеет адреса (это r-значение), это вызовет ошибку компиляции.
В случае C мы пытаемся инициализировать константную ссылку cref копией значения, возвращаемого returnByValue() . Поскольку константные ссылки могут связываться с r-значениями, здесь нет проблем. Обычно срок действия r-значения истекает в конце выражения, в котором оно создано, однако, когда оно привязывается к константной ссылке, время жизни r-значения (в данном случае возвращаемого значения функции) увеличивается до времени жизни ссылки (в данном случае cref )
Продление времени жизни не сохраняет висячие ссылки
Рассмотрим следующую программу:
В приведенной выше программе returnByReference() возвращает константную ссылку на значение, которое выйдет за пределы области видимости при завершении функции. Обычно это недопустимо, так как это приведет к висячей ссылке. Однако мы также знаем, что присвоение значения константной ссылке может продлить время жизни этого значения. Итак, что здесь имеет приоритет? Что раньше, 5 выходит из области видимости, или ref продлевает срок жизни 5?
Ответ заключается в том, что сначала 5 выходит за пределы области видимости, затем ссылка на 5 копируется обратно вызывающему, а затем ref продлевает время жизни теперь уже висячей ссылки.
Однако следующий код работает должным образом:
В этом случае литеральное значение 5 сначала копируется обратно в область видимости вызывающего ( main ), а затем ref продлевает время жизни этой копии.
Возврат нескольких значений
C++ не содержит прямого метода для передачи нескольких значений обратно вызывающему. Хотя иногда вы можете реструктурировать свой код таким образом, чтобы вы могли передавать каждый элемент данных отдельно (например, вместо того, чтобы одна функция возвращала два значения, использовать две функции, возвращающие по одному из этих значений), но это может быть громоздко и неинтуитивно.
К счастью, есть несколько косвенных методов, которые можно использовать.
Как описано в уроке «11.3 – Передача аргументов по ссылке», выходные параметры предоставляют первый метод для передачи нескольких значений данных обратно вызывающему. Мы не рекомендуем этот метод.
Второй метод предполагает использование структуры только для данных:
Третий способ (представленный в C++11) — использовать std::tuple . Кортеж (tuple) – это последовательность элементов, которые могут быть разных типов, где тип каждого элемента должен быть указан явно.
Вот пример, который возвращает кортеж и использует std::get для получения n-го элемента кортежа:
Это работает идентично предыдущему примеру.
Вы также можете использовать std::tie для распаковки кортежа в предопределенные переменные, например:
Начиная с C++17, для упрощения разделения нескольких возвращаемых значений на отдельные переменные может использоваться объявление структурированной привязки:
Использование структуры – лучший вариант, чем кортеж, если вы используете эту структуру в нескольких местах. Однако для случаев, когда вы просто упаковываете эти значения для возврата, и не будет повторного использования при определении новой структуры, кортеж немного чище, поскольку он не вводит новый пользовательский тип данных.
Заключение
В большинстве случаев возврата по значению будет достаточно. Это также самый гибкий и безопасный способ вернуть информацию вызывающему. Однако возврат по ссылке или адресу также может быть полезен, особенно при работе с динамически размещаемыми классами или структурами. При использовании возврата по ссылке или адресу убедитесь, что вы не возвращаете ссылку или адрес переменной, которая при возврате из функции выйдет за пределы области действия!
Небольшой тест
Напишите прототипы для каждой из следующих функций. Используйте наиболее подходящие параметры и возвращаемые типы (по значению, по адресу или по ссылке), включая использование const , где это необходимо.
1) Функция с именем sumTo() , которая принимает параметр int и возвращает сумму всех чисел между предыдущими значениями и переданным входным числом.
2) Функция с именем printEmployeeName() , которая принимает в качестве входных данных структуру Employee .
3) Функция с именем minmax() , которая принимает на вход два числа int и возвращает вызывающему меньшее и большее числа в std::pair . std::pair работает так же, как std::tuple , но хранит ровно два элемента.
4) Функция с именем getIndexOfLargestValue() , которая принимает массив значений int (как std::vector ) и возвращает индекс самого большого элемента в массиве.
5) Функция с именем getElement() , которая принимает массив из std::string (как std::vector ) и индекс и возвращает элемент массива по этому индексу (не копию). Предположим, что индекс действителен, а возвращаемое значение – const .