23.3. Передача в функции символьных строк
В языке С строка – это одномерный массив символов. Передача символьной строки в функцию подобна передаче любого массива в качестве параметра. Поэтому, как и для любого одномерного массива, реально в функцию передается адрес строки. Но, в отличие от обычного одномерного массива, для строк нет необходимости передавать в функцию их реальный размер, так как все строки оканчиваются нулевым символом, и их размер в функции легко вычислить.
Задача. Разработать функцию для вычисления длины строки.
printf(“Длина строки = %d\n”, lenStr(str));
Задача. Разработать функцию, которая возвращает указатель на первое вхождение символа в строку.
char *func(char c, char *s) <
if (c == *s) return s;
printf(«Символ найден: %s\n», p);
printf(«Символ не найден\n»);
Если заданного символа в строке нет, то функция возвращается указатель NULL, что позволяет проверить, было ли вхождение нужного символа или нет.
23.4. Возвращение указателей из функций
Функции, которые возвращают указатели, – это обычные функции. И работают с ними тоже обычным способом. Но есть несколько тонкостей при разработке таких функций.
Во-первых, указатели являются адресами в памяти. Поэтому в объявлении функции, которая возвращает указатель, тип возвращаемого указателя должен декларироваться явно. Например, нельзя объявлять возвращаемый тип как int *, если возвращается указатель типа char *. Иногда (правда, крайне редко) требуется, чтобы функция возвращала «универсальный» указатель, т.е. указатель, который может указывать на данные любого типа. Тогда тип результата функции следует определять как void *.
Во-вторых, такие функции возвращают адрес в памяти. Поэтому надо очень внимательно следить за тем, чтобы объект, адрес которого возвращает функция, существовал не только во время работы функции, но и после выхода из нее. Чаще всего это касается функций, которые возвращают char *.
Есть три подхода к решению проблемы существования объекта, адрес которого возвращает функция:
делать такие объекты глобальными. Это не очень хорошее решение, т.к. требуется помнить об описании нужных глобальных переменных;
описывать такой объект в вызывающей функции и передавать его в вызываемую функцию. Обычное решение для функций, которые возвращают char *. ;
описывать такие объекты в функции как static. Нормальное решение, ведь статические переменные существуют в течение всего времени выполнения программы и, значит, их адрес можно без проблем возвращать из функций.
Ниже приведены два примера правильных функций, которые возвращают указатели на строки.
Задача. Разработать функцию для удаления в строке заданного символа.
Как передать строку в функцию си
Функция может принимать параметры. Параметры позволяет передать извне в функцию некоторые данные, настроить ее выполнение. Параметры перечисляются в скобках после имени функции имеют следующее определение:
Для каждого параметра сначала указывается тип, а затем название параметра. Через запятую определяется следующий параметр.
Например, мы хотим, чтобы функция выводила разные сообщения на консоль. Для этого определим функцию, которая в качестве параметра принимает массив символов:
Здесь функция print_message() принимает один параметр, который называется message и который имеет тип char[] . Параметр message еще называется формальным параметром
В функции main три раза вызываем функцию print_message, передавая ей разные значения — строки «Hello METANIT.COM!», «Hello World!» и «Hello work. «. Эти значения еще называют аргументами . Таким образом, на консоль будут выведены три строки:
Прототип функции print_message() выглядел бы следующим образом:
Полный пример с прототипом:
Если необходимо несколько параметров, то они перечисляются через запятую. Например, напишем функцию, которая вычисляет сумму двух чисел:
Функция sum() принимает два параметра типа int .
В методе main эта функция вызывается три раза, и каждый раз ей передается два разных аргумента для параметров x и y. Соответствие между аргументами и параметрами устанавливается по их расположению: первый аргумент передает значение для первого параметра, второй аргумент — для второго параметра и так далее. Консольный вывод программы:
Прототип функции sum выглядел бы следующим образом:
Между передаваемыми в функцию аргументами и ее параметрами должно быть соответствие по типу. Если же типы не совпадают, то компилятор пытается автоматически выполнить преобразование типов.
Например, выше использовалась функция void sum(int, int) . Если мы передадим в sum значения другого типа, то компилятор будет пытаться привести их к типу int:
В обоих случаях фактически функция sum получить числа 1 и 2.
Процесс передачи параметров
При передаче параметров следует учитывать, то они передаются в функцию по значению . То есть функция не изменяет значения передаваемых аргументов. Рассмотрим следующий пример:
Функция increase через параметр получает число и увеличивает его в два раза.
В функции main вызываем функцию increase, передавая ей значение переменной a . И после того, как функция increase отработала и изменила значение своего параметра, проверяем значение переменной a .
И при выполнении мы увидим, что изменения аргументов в функции increase действуют только в рамках этой функции. Вне ее значение переменной a остается неизменным:
Почему так происходит? При компиляции функции для ее параметров выделяются отдельные участки памяти. При этом для параметров типа float формируются в памяти объекты типа double , а для параметров типов char и short int — объекты типа int .
При вызове функции вычисляются значения аргументов, которые передаются на место параметров. И затем значения аргументов заносятся в участки памяти, отведенные для параметров. При этом значения типа float преобразуются в объекты типа double , а объекты типов char и short int — в объекты типа int .
Если в вызов функции передается переменная, то функция получает копию ее значения. Поэтому все манипуляции со значением параметра внутри функции никак не затронут оригинальную переменную.
После завершения работы функции память, выделенная для параметров, освобождается.
Константные параметры
Мы можем в функции изменить значение параметра:
Однако подобное поведение не всегда желательно. Нередко бывают ситуации, когда надо гарантировать, что параметр сохнит свое значение на протяжении всей работы функции. В этом случае можно объявить параметр как константный с помощью ключевого слова const :
Если мы попробуем изменить значение константного параметра, то при компиляции мы столкнемся с ошибкой. Если же надо выполнять какие-то вычисления, то можно использовать дополнительную переменную.
Лекция 5
В языке С строки определяются как одномерный массив типа char .
Это строка из 14 символов, однако размер массива будет равен 15, так как строки в C заканчиваются символом \0 — нуль терминатор (нулевой байт: байт, все биты которого равны 0). Иногда такие строки называют нуль-терминированными.
Недостаток такого подхода состоит в том, что для того чтобы узнать длину строки необходимо просканировать всю строку до конца, в поисках \0 — это может занять много времени.
При попытке стандартным образом ввести с консоли значение "Hello world!" в str , будет введено только "Hello" (до первого пробела). Чтобы этого не происходило можно использовать следующую запись:
Строки в стиле C++
В 1980 году появился класс string :
Несмотря на то, что скорость работы C-строк немного выше чем у класса string , на прикладном уровне лучше использовать string . Однако системным программистам чаще приходится пользоваться char* .
Ввод-вывод
Если мы вводим "Hello world" в s будет храниться только "Hello", поэтому надо использовать getline(cin, s); .
Как передавать в функции C- и C++-строки
С-строки
С++-строки
Двумерные массивы
Двумерные массивы определяются как массив массивов. Вот как будет выглядеть двумерный массив int a[3][4] .
| … | a[ ][0] | a[ ][1] | a[ ][2] | a[ ][3] |
| a[0][ ] | a[0][0] | a[0][1] | a[0][2] | a[0][3] |
| a[1][ ] | a[1][0] | a[1][1] | a[1][2] | a[1][3] |
| a[2][ ] | a[2][0] | a[2][1] | a[2][2] | a[2][3] |
Передача в функции двумерных массивов
При передаче массива в функцию сохранится размер массива a[][4] : размер внешнего массива потеряется, а размер внутренних массивов сохранится.
Определение типов в C++
Синонимы (псевдонимы) типов определяются с помощью typedef .
По сути объявление переменной Matr a будет заменено на int a[3][4] .
Указатели и адреса
C++ унаследовал от C возможность работы на низком уровне.
Пусть мы имеем ячейку памяти int i = 5 . Объявление int *p; вводит указатель, то есть переменную, которая может хранить адрес ячейки памяти с любым int , например, i .
Можно использовать нулевой указатель, чтобы показать, что переменная-указатель пока не хранит никакого адреса. Для этого есть несколько способов.
Если * пишется перед именем переменной, то эта переменная — указатель, а * — операция разыменования. Если * после типа переменной, то это объявление указателя (как во всех примерах выше).
Указатели и ссылки
Если & пишется после названия типа, то это ссылка. В противном случае, если он пишется перед именем переменной, то это адрес этой переменной. Ссылку можно трактовать, как указатель, который постоянно находится в разыменованном состоянии.
Передача параметров в функции
Производительность в обоих случаях одинаковая.
Указатель void*
т.е. p может хранить адрес любого объекта.
Но можно с помощью явного приведения типов (в стиле C или в стиле C++, но не static_cast , а reinterpret_cast ).
Указатели на структуры
Указатели и константность
Это используется при передаче аргументов в функции. Объявление const int *p заведомо не позволяет написать функцию, которая изменяет переменную, переданную через указатель.
Передача строки в функцию
Как можно передать строку в функцию так, чтобы манипуляции с этой строкой внутри функции не меняли ее значения при выходе из функции?
В языке С нет типа "строка". Я не знаю, что имели в виду Вы, но при работе со строками, в качестве параметра функции передаётся указатель тип char *. В соответствии с общей идеологией языка С этот указатель не меняется. Но Вы легко можете, находясь внутри функции, изменить (испортить?) ту память, на которую указывает этот указатель.
Если Вы не собираесь менять содержимое памяти, переданной в функцию по указателю, то проще всего гарантировать себя от проблем так:
![]()
Дизайн сайта / логотип © 2023 Stack Exchange Inc; пользовательские материалы лицензированы в соответствии с CC BY-SA . rev 2023.3.11.43304
Нажимая «Принять все файлы cookie» вы соглашаетесь, что Stack Exchange может хранить файлы cookie на вашем устройстве и раскрывать информацию в соответствии с нашей Политикой в отношении файлов cookie.