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

Обновлено: 05.07.2024

Передача через указатель

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

Если передавать параметры через указатель (так называемый С-стиль), код функции выглядит так:

mul5(&var); // Здесь символ & обозначает операцию взятия адреса переменной

Такой синтаксис неудобен. Внутри тела функции, перед переменной, которая является указателем, приходится ставить звездочку * для работы со значением, на который указывает указатель. Кроме того, при вызове функции приходится ставить амперсанд & перед именем переменной для того, чтобы передать адрес переменной (который будет принят в функцию как указатель).

Передача через ссылку

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

А вызов выглядит так:

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

Однако, следует помнить , что такая передача по ссылке возможна только для единичных типов C/C++ . И не работает для передачи массивов!

Поэтому, к примеру, если функция принимает:

  • одномерный массив block из элементов char , элементы которого надо изменить внутри функции (обозначим это как сложный/составной тип)
  • целочислительную переменную shift , которую тоже надо изменить внутри функции (это простой базовый тип)

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

void clear(unsigned char *block, int &shift)

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

unsigned char* anyBlock="Long string";

for(int i=0; i<len; ++i)

clear(anyBlock, anyShift); // Вызов функции

То есть, в вызове нет никаких спецсимволов указателей/адресов ни для первого ни для второго параметра. Для первого параметра нет спецсимвола взятия адреса (хоть и идет работа с указателем) из-за того, что имя массива имеет тип указателя. Для второго параметра нет спецсимвола взятия адреса из-за того, что передача идет по ссылке.

При таком вызове будут изменяться как ячейки массива anyBlock , так и переменная anyShift .

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

Имя структуры FILE говорит программе о том, что определяемая переменная является указателем именно на файловую структуру. Звездочка предписывает создать указатель с соответствующим именем переменной.

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

FILE *infile, *outfile;

Как открыть файл

Связь между программой и файлом устанавливается при помощи функции fopen(), синтаксис которой показан на рис.12.3.

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


Рис. 12.3. Синтаксис функции fopen()

определенными правилами. Например, в операционной системе MS-DOS имя файла может состоять максимум из восьми символов, плюс расширение имени, состоящее не более чем из трех символов (расширение не является обязательным элементом). Если вы хотите вывести информацию на печатающее устройство, а не в дисковый файл, в качестве имени файла в кавычках указывается "PRN". При этом автоматически осуществляется вывод данных на принтер.

r - Указывает на то, что будет выполняться чтение информации из файла в память компьютера. Если файл к этому моменту не существует на диске, программа сообщит об ошибке выполнения.

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

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


Компиляторы Си++ и многие компиляторы Си стандарта ANSI позволяют открывать файл одновременно и для чтения, и для записи. Для этого в аргументе функции указываются режимы доступа "r+", "w+" или "a+".

Например, если вы хотите создать файл с именем CD.DAT для хранения картотеки коллекции компакт-дисков, вы должны использовать следующие инструкции:

cdfile = fopen("CD.DAT", "w");

Если в программе требуется осуществить чтение из файла, а не запись в него, используйте следующую запись:

cdfile = fopen("CD.DAT", "r");

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

Если вы хотите распечатать информацию о вашей коллекции на принтере, используйте следующую последовательность инструкций:

cdfile = fopen("PRN", "w");

Учтите, что вывод информации на принтер возможен только с режимом доступа "w".

Как Си/Си++ работает с файлами

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

При чтении информации из файла, указатель определяет следующие данные, которые должны быть считаны с диска. Когда файл открывается впервые с использованием режима доступа "r", указатель помещается на первый символ файла. При выполнении очередной операции чтения, указатель перемещается к следующей порции данных, которые должны быть прочитаны. Величина шага перемещения при этом зависит от количества информации, которая считывается за один прием (рис.12.4). Если за один раз считывается только один символ, указатель передвинется на следующий символ, если читается целая структура, указатель перейдет на следующую структуру. Как только вся информация прочитана из файла, указатель попадает на специальный код, называемый символом конца файла*. Попытка продолжения чтения после достижения конца файла приведет к ошибке выполнения.

* Наличие символа конца файла на самом деле вовсе не является обязательным.(Прим.ред.)


Рис. 12.4. Указатель сохраняет сведения о текущей позиции в файле

Если файл открывается с режимом доступа "w", указатель также помещается в начало файла, так что первые введенные данные будут помещены в начало файла. При закрытии файла после введенного массива данных будет добавлен символ конца файла. Если файл к моменту его открытия с использованием режима доступа "w" уже существует, все содержащиеся в нем данные затираются и «поверх» них записывается новая информация, введенная с помощью процедуры записи. Любые данные, которые могут остаться не уничтоженными, располагаются после нового символа конца файла, так что к ним уже нельзя будет обратиться при следующем чтении данных из файла. Таким образом, любая попытка записи данных в существующий файл с использованием режима доступа "w" приведет к уничтожению хранящейся в нем на данный момент информации. Это произойдет даже в том случае, если файл будет просто открыт и закрыт, без записи каких-либо данных.

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

Я застрял на проблеме, которую не могу понять. Для моей задачи я должен сделать некоторую работу с файлами .txt в C, и, в частности, я должен передать имена файлов, используя параметры командной строки. Допустим, у нас есть эта часть кода:

Меня смущает то, как вы работаете с этими аргументами? Где вы размещаете свой .txt файл? Я прочитал сто тем, но все еще не могу обернуть голову вокруг этого. Я использую XCode и работаю на Mac. Нужно ли каким-либо образом использовать Терминал для работы с этим кодом? Любой пролить свет на эту тему будет принята с благодарностью.

3 ответа

В XCode вы можете указать аргументы для передачи вашей программе в схеме. Перейдите в Product-> Scheme-> Edit Scheme и перейдите на вкладку «Аргументы» (см. Скриншот).

Затем любой файл загружается из того места, где XCode размещает исполняемый файл. Самый простой способ узнать, где это находится, - щелкнуть правой кнопкой мыши продукт и «показать в поиске» (см. Скриншот).

Вне XCode, например в окне терминала просто укажите параметры после имени команды, например, ./testCommandLine test.txt . Любой открытый файл - если не указан абсолютный путь - запускает поиск из текущего рабочего каталога.

Надеюсь, это поможет.

Edit current scheme

enter image description here

Если задан относительный путь к файлу, fopen откроет файлы относительно текущего рабочего каталога. Если вы передадите абсолютный путь к вашей программе, то fopen будет использовать его. Например, если ваш file.txt находится в /home/User/file.txt , вы можете передать всю эту строку в вашу программу, и fopen найдет ее.

Шаг 1, чтобы проверить, что у вас есть параметр.

После этого вы можете использовать параметр (ы) напрямую. Обратите внимание, что argv[0] будет путем / именем исполняемого файла, argv[1] будет первым параметром (если он был передан) и так далее. При открытии файлов они должны быть либо помещены в рабочий каталог, либо вы должны указать fopen путь в дополнение к имени файла. Рабочий каталог может быть или не быть каталогом, содержащим ваш исполняемый файл, в зависимости от вашей среды сборки.

Ч ем дальше мы изучаем си, тем больше становятся программы. Мы собираем все действия в одну функцию main и по несколько раз копируем одни и те же действия, создаём десятки переменных с уникальными именами. Наши программы распухают и становятся всё менее и менее понятными, ветвления становятся всё длиннее и ветвистее.

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

Функция – это именованная часть программы, которая может быть многократно вызвана из другого участка программы (в котором эта функция видна). Функция может принимать фиксированное либо переменное число аргументов, а может не иметь аргументов. Функция может как возвращать значение, так и быть пустой (void) и ничего не возвращать.

Мы уже знакомы с многими функциями и знаем, как их вызывать – это функции библиотек stdio, stdlib, string, conio и пр. Более того, main – это тоже функция. Она отличается от остальных только тем, что является точкой входа при запуске приложения.
Функция в си определяется в глобальном контексте. Синтаксис функции:

Самый простой пример – функция, которая принимает число типа float и возвращает квадрат этого числа

Внутри функции sqr мы создали локальную переменную, которой присвоили значение аргумента. В качестве аргумента функции передали число 9,3. Служебное слово return возвращает значение переменной tmp. Можно переписать функцию следующим образом:

В данном случае сначала будет выполнено умножение, а после этого возврат значения. В том случае, если функция ничего не возвращает, типом возвращаемого значения будет void. Например, функция, которая печатает квадрат числа:

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

Если функция не принимает аргументов, то скобки оставляют пустыми. Можно также написать слово void:

Формальные и фактические параметры

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

Например, пусть есть функция, которая возвращает квадрат числа и функция, которая суммирует два числа.

Обращаю внимание, что приведение типов просиходит неявно и только тогда, когда это возможно. Если функция получает число в качестве аргумента, то нельзя ей передать переменную строку, например "20" и т.д. Вообще, лучше всегда использовать верный тип или явно приводить тип к нужному.
Если функция возвращает значение, то оно не обязательно должно быть сохранено. Например, мы пользуемся функцией getch, которая считывает символ и возвращает его.

Передача аргументов

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

Программы выведет
200
100
200
Понятно почему. Внутри функции мы работаем с переменной x, которая является копией переменной d. Мы изменяем локальную копию, но сама переменная d при этом не меняется. После выхода из функции локальная переменная будет уничтожена. Переменная d при этом никак не изменится.
Каким образом тогда можно изменить переменную? Для этого нужно передать адрес этой переменной. Перепишем функцию, чтобы она принимала указатель типа int

Вот теперь программа выводит
200
100
100
Здесь также была создана локальная переменная, но так как передан был адрес, то мы изменили значение переменной d, используя её адрес в оперативной памяти.

В программировании первый способ передачи параметров называют передачей по значению, второй – передачей по указателю. Запомните простое правило: если вы хотите изменить переменную, необходимо передавать функции указатель на эту переменную. Следовательно, чтобы изменить указатель, необходимо передавать указатель на указатель и т.д. Например, напишем функцию, которая будет принимать размер массива типа int и создавать его. С первого взгляда, функция должна выглядеть как-то так:

Но эта функция выведет ERROR. Мы передали адрес переменной. Внутри функции init была создана локальная переменная a, которая хранит адрес массива. После выхода из функции эта локальная переменная была уничтожена. Кроме того, что мы не смогли добиться нужного результата, у нас обнаружилась утечка памяти: была выделена память на куче, но уже не существует переменной, которая бы хранила адрес этого участка.

Для изменения объекта необходимо передавать указатель на него, в данном случае – указатель на указатель.

Вот теперь всё работает как надо.
Ещё подобный пример. Напишем функцию, которая принимает в качестве аргумента строку и возвращает указатель на область памяти, в которую скопирована эта строка.

В этом примере утечки памяти не происходит. Мы выделили память с помощью функции malloc, скопировали туда строку, а после этого вернули указатель. Локальные переменные были удалены, но переменная test хранит адрес участка памяти на куче, поэтому можно его удалить с помощью функции free.

Объявление функции и определение функции. Создание собственной библиотеки

В си можно объявить функцию до её определения. Объявление функции, её прототип, состоит из возвращаемого значения, имени функции и типа аргументов. Имена аргументов можно не писать. Например

Это смешанная рекурсия – функция odd возвращает 1, если число нечётное и 0, если чётное.

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

Давайте создадим простую библиотеку. Для этого нужно будет создать два файла – один с расширением .h и поместить туда прототипы функций, а другой с расширением .c и поместить туда определения этих функций. Если вы работаете с IDE, то .h файл необходимо создавать в папке Заголовочные файлы, а файлы кода в папке Файлы исходного кода. Пусть файлы называются File1.h и File1.c
Перепишем предыдущий код. Вот так будет выглядеть заголовочный файл File1.h

Содержимое файла исходного кода File1.c

Наша функция main

Рассмотрим особенности каждого файла. Наш файл, который содержит функцию main, подключает необходимые ему библиотеки а также заголовочный файл File1.h. Теперь компилятору известны прототипы функций, то есть он знает возвращаемый тип, количество и тип аргументов и имена функций.

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

Передача массива в качестве аргумента

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

В этом примере функция может иметь следующий вид

Также напомню, что правило подмены массива на указатель не рекурсивное. Это значит, что необходимо указывать размерность двумерного массива при передаче

Либо, можно писать

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

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

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

email

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

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