Добавить файл в файл c

Обновлено: 21.11.2024

Я хочу включить файл .c в другой. Возможно ли это? Это работает хорошо, если я включаю файл заголовка в файл .c, но не работает, если я включаю файл .c в другой файл .c.
Я использую Visual Studio и получаю следующую ошибку:

Я не знаю, что может означать эта ошибка. Пришло время спросить более продвинутых программистов на C. :)

@BasileStarynkevitch: это плохая практика и для экспертов. Хотите верьте, хотите нет, но компилировать и связывать кучу небольших единиц перевода не так сложно, как пытаться скомпилировать одну огромную единицу перевода.

8 ответов 8

Я хочу включить файл .c в другой.

Нет. Вы действительно, действительно не знаете. Не предпринимайте никаких шагов по этому пути; это заканчивается только болью и страданием. Это плохая практика даже для тривиальных программ, таких как ваш пример, а тем более для программ любой реальной сложности. Тривиальное однострочное изменение в одном файле потребует от вас пересборки как этого файла, так и всего, что его включает, что является пустой тратой времени. Вы теряете возможность контролировать доступ к данным и функциям; все во включаемом файле .c видно включаемому файлу, даже функции и переменные области видимости файла, объявленные как static . Если вы включите файл .c, который включает в себя другой файл .c, который включает другой файл .c и так далее, вы можете получить единицу перевода, слишком большую для обработки компилятором.< /p>

Отдельная компиляция и компоновка — однозначно хорошая вещь. В файлы .c следует включать только файлы заголовков, которые описывают интерфейс (определения типов, объявления прототипов функций, определения макросов, внешние объявления), а не реализацию.

"Тривиальное однострочное изменение в одном файле потребует от вас перестроить как этот файл, так и все, что его включает, что является пустой тратой времени" --> разве это не то же самое, если вы включите заголовок файл и изменить в нем строчку?

@zgulser: Да, когда вы меняете интерфейс (определение макроса, добавление функции, определение типа и т. д.), вам нужно перестроить все, что зависит от этого интерфейса. Однако, если вы изменяете только реализацию функции, не нужно перестраивать ни один файл, кроме файла, содержащего эту функцию. К сожалению, включение файлов .c в другие файлы .c приводит к перестройке мира при изменении строки, спрятанной глубоко в цикле for.

Это очень субъективное мнение. Я согласен с тем, что делать это практикой не следует в пользу библиотек. Для сложных программ это было бы неэффективным, запутанным беспорядком и, вполне возможно, привело бы к ошибкам компиляции. Но говорить «Нет, ты не хочешь. Ты действительно, действительно не хочешь» без контекста довольно высокомерно и самонадеянно. Я делал это много раз для быстрого и грязного тестирования, бенчмаркинга и отладки. простой способ разместить целевой код на простой тестовой платформе без хлопот с компиляцией и компоновкой или созданием файлов.

Это работает, но нужно быть осторожным при построении программы. Кроме того, как люди отметили в комментариях, это обычно считается плохой идеей. Это неожиданно и создает подобные проблемы. Есть несколько преимуществ, особенно для того, что кажется тривиальной программой. Вероятно, вам следует полностью переосмыслить этот подход.

После того, как вы сделаете что-то подобное, вы должны только скомпилировать main.c , а не пытаться связать его с результатом компиляции sayHello.c , что вы, кажется, делаете.

Возможно, вам потребуется указать Visual Studio исключить последний файл из сборки.

Да, любой файл '.c' можно включить в другую программу. Включите в программу файл '.h', например 'stdio.h'. После этого мы можем вызывать те функции, которые записаны в этот внешний файл.

Вывод:: Привет, мир!

Это ошибка компоновщика. После компиляции всех ваших файлов .c в файлы .obj (выполненной компилятором) компоновщик «объединяет» их вместе, чтобы создать вашу dll/exe. Компоновщик обнаружил, что два объекта объявляют одну и ту же функцию (что явно недопустимо). В этом случае вам нужно, чтобы компоновщик обрабатывал только main.obj, а не sayhello.obj (поскольку его код уже включен в main.obj).

Это потому, что в main.obj у вас ТАКЖЕ будет функция sayHello() из-за включения!

Возможно, вы определили эту функцию дважды

один в вашем текущем файле и один в sayhello.c

Вы должны удалить одно определение.

скажиHello.h

main.c

Похоже, проблема заключается в том, что sayHello.c был скомпилирован в объектный файл с именем sayHello.obj , а main.c (включая весь исходный текст из sayHello.c ) был скомпилирован в main.obj , так что оба Файлы .obj получили копии всех внешних символов (например, определений нестатических функций) из sayHello.c .

Затем все объектные файлы должны были быть "связаны" друг с другом для получения окончательного исполняемого файла программы, но связывание разрывается, когда какие-либо внешние символы появляются более одного раза.

Если вы окажетесь в такой ситуации, вам нужно прекратить связывание sayHello.obj с main.obj (и тогда вы, возможно, вообще не захотите компилировать sayHello.c в отдельный объектный файл).

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

Поскольку вы используете Visual Studio, он, вероятно, делает для вас кучу предположений, например, "каждый файл .c должен быть скомпилирован в свой собственный объектный файл, и все эти объектные файлы должны быть связаны вместе", и вы должны найти способ обойти или отключить это предположение для sayHello.c .

В этом руководстве вы узнаете об обработке файлов в C. Вы научитесь обрабатывать стандартный ввод-вывод в C с помощью функций fprintf(), fscanf(), fread(), fwrite(), fseek() и т. д. помощь примеров.

Файл — это контейнер на компьютерных запоминающих устройствах, используемый для хранения данных.

Зачем нужны файлы?

  • При завершении программы все данные теряются. Сохранение в файле сохранит ваши данные, даже если программа завершит работу.
  • Если вам нужно ввести большое количество данных, ввод их всех займет много времени.
    Однако, если у вас есть файл, содержащий все данные, вы можете легко получить доступ к содержимому файла с помощью нескольких команд на языке C.
  • Вы можете легко перенести свои данные с одного компьютера на другой без каких-либо изменений.

Типы файлов

При работе с файлами необходимо знать два типа файлов:

1. Текстовые файлы

Текстовые файлы — это обычные файлы .txt. Вы можете легко создавать текстовые файлы с помощью любых простых текстовых редакторов, таких как Блокнот.

Когда вы откроете эти файлы, вы увидите все содержимое файла в виде обычного текста. Вы можете легко редактировать или удалять содержимое.

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

2. Бинарные файлы

Двоичные файлы — это в основном файлы .bin на вашем компьютере.

Вместо того, чтобы хранить данные в виде обычного текста, они хранят их в двоичной форме (0 и 1).

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

Операции с файлами

В C вы можете выполнять четыре основные операции с файлами, текстовыми или двоичными:

  1. Создание нового файла
  2. Открытие существующего файла
  3. Закрытие файла
  4. Чтение и запись информации в файл

Работа с файлами

При работе с файлами необходимо объявить указатель типа файл. Это объявление необходимо для связи между файлом и программой.

Открытие файла — для создания и редактирования

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

Синтаксис открытия файла в стандартном вводе-выводе:

  • Предположим, что файл newprogram.txt не существует в папке E:\cprogram. Первая функция создает новый файл с именем newprogram.txt и открывает его для записи в соответствии с режимом 'w'.
    Режим записи позволяет создавать и редактировать (перезаписывать) содержимое файла.
  • Теперь предположим, что второй двоичный файл oldprogram.bin существует в папке E:\cprogram. Вторая функция открывает существующий файл для чтения в бинарном режиме 'rb'.
    Режим чтения позволяет только читать файл, вы не можете записывать в файл.

Закрытие файла

Файл (как текстовый, так и двоичный) должен быть закрыт после чтения/записи.

Закрытие файла выполняется с помощью функции fclose().

Здесь fptr — это указатель файла, связанный с файлом, который нужно закрыть.

Чтение и запись в текстовый файл

Для чтения и записи в текстовый файл мы используем функции fprintf() и fscanf().

Это просто версии файлов printf() и scanf() . Единственное отличие состоит в том, что fprintf() и fscanf() ожидают указатель на структуру FILE.

Пример 1. Запись в текстовый файл

Эта программа получает номер от пользователя и сохраняет его в файле program.txt .

После того, как вы скомпилируете и запустите эту программу, вы увидите текстовый файл program.txt, созданный на диске C вашего компьютера. Когда вы откроете файл, вы увидите введенное целое число.

Пример 2. Чтение из текстового файла

Эта программа считывает целое число из файла program.txt и выводит его на экран.

Если вы успешно создали файл из примера 1, запустив эту программу, вы получите введенное целое число.

Другие функции, такие как fgetchar() , fputc() и т. д., можно использовать аналогичным образом.

Чтение и запись в двоичный файл

Функции fread() и fwrite() используются для чтения и записи в файл на диске соответственно в случае двоичных файлов.

Запись в двоичный файл

Для записи в двоичный файл необходимо использовать функцию fwrite(). Функции принимают четыре аргумента:

  1. адрес данных для записи на диск
  2. размер данных для записи на диск
  3. количество таких данных
  4. указатель на файл, в который вы хотите записать.

Пример 3. Запись в двоичный файл с помощью fwrite()

В этой программе мы создаем новый файл program.bin на диске C.

Мы объявляем структуру threeNum с тремя числами — n1, n2 и n3 и определяем ее в основной функции как num.

Теперь внутри цикла for мы сохраняем значение в файле с помощью fwrite() .

Первый параметр принимает адрес num, а второй параметр принимает размер структуры threeNum .

Поскольку мы вставляем только один экземпляр num , третий параметр равен 1 . И последний параметр *fptr указывает на файл, в котором мы сохраняем данные.

Наконец, мы закрываем файл.

Чтение из двоичного файла

Функция fread() также принимает 4 аргумента, как и функция fwrite(), описанная выше.

Пример 4. Чтение из двоичного файла с помощью функции fread()

В этой программе вы читаете один и тот же файл program.bin и перебираете записи одну за другой.

Проще говоря, вы читаете одну запись threeNum размера threeNum из файла, на который указывает *fptr, в структуру num .

Вы получите те же записи, что и в примере 3.

Получение данных с помощью fseek()

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

Это приведет к потере большого количества памяти и рабочего времени. Более простой способ получить необходимые данные можно с помощью fseek() .

Как следует из названия, fseek() ищет курсор для данной записи в файле.

Синтаксис fseek()

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

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

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

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

Обратите внимание, что fopen может дать сбой, даже если ваша программа совершенно корректна: вы можете попытаться открыть файл, указанный пользователем, а этот файл может не существовать (или он может быть защищен от записи). В этих случаях fopen создаст файл, если вы укажете режим файла как "w", "w+", "a" или "a+", иначе он вернет 0, указатель NULL.

Вот простой пример использования fopen:

Этот код откроет файл test.txt для чтения в текстовом режиме. Чтобы открыть файл в двоичном режиме, вы должны добавить b в конец строки режима; например, "rb" (для режимов чтения и записи букву b можно добавить либо после знака "плюс" - "r+b" - либо перед - "rb+")

Чтобы закрыть функцию, вы можете использовать функцию:

fclose возвращает ноль, если файл успешно закрыт.

Пример fclose:

Для работы с текстовым вводом и выводом вы используете fprintf и fscanf, оба похожи на своих друзей printf и scanf, за исключением того, что вы должны передать указатель FILE в качестве первого аргумента.

Попробуйте следующий пример:

Это создаст файл test.txt в каталоге /tmp и напишет Это тестирование в этом файле.

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

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

fgetc возвращает целое число. На самом деле это означает, что когда он читает обычный символ в файле, он возвращает значение, подходящее для хранения в беззнаковом символе (в основном, число в диапазоне от 0 до 255). С другой стороны, когда вы находитесь в самом конце файла, вы не можете получить символьное значение — в этом случае fgetc вернет «EOF», что является константой, указывающей, что вы достигли конца файла. конец файла.

Функция fputc позволяет вам писать посимвольно — это может оказаться полезным, если вы хотите копировать файл посимвольно.Это выглядит так:

Обратите внимание, что первый аргумент должен находиться в диапазоне беззнакового символа, чтобы он был допустимым символом. Второй аргумент — это файл, в который нужно записать. В случае успеха fputc вернет значение c, а в случае неудачи вернет EOF.

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

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

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

Для создания файла в программе на языке C используется следующий синтаксис,

В приведенном выше синтаксисе файл представляет собой структуру данных, определенную в стандартной библиотеке.

fopen — это стандартная функция, которая используется для открытия файла.

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

fp — это указатель файла, указывающий на тип файла.

Каждый раз, когда вы открываете или создаете файл, вы должны указать, что вы собираетесь делать с файлом. Файл в программировании на языке C может быть создан или открыт для чтения/записи. Режим используется, чтобы указать, хотите ли вы открыть файл для какой-либо из указанных ниже целей. Ниже приведены различные типы режимов программирования на языке C, которые можно использовать при работе с файлом.

Режим файла Описание
r Открыть файл для чтения. Если файл находится в режиме чтения, то никакие данные не удаляются, если файл уже присутствует в системе.
w Открыть файл для записи . Если файл находится в режиме записи, то создается новый файл, если файл вообще не существует. Если файл уже присутствует в системе, то все данные внутри файла усекаются, и он открывается для записи.
a Откройте файл в
режиме добавления. Если файл находится в режиме добавления, то файл открывается. Содержимое файла не меняется.
r+ открыть для чтения и записи с начала
w+ открыть для чтения и записи, перезаписать файл
a+ открыть для чтения и записи, дополнить файл

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

Файл создается в той же папке, в которой вы сохранили свой код.

Вы можете указать путь, по которому вы хотите создать файл

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

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

«C» предоставляет функцию fclose для выполнения операции закрытия файла. Синтаксис fclose следующий:

Функция fclose принимает в качестве аргумента указатель файла. Затем файл, связанный с указателем файла, закрывается с помощью функции fclose. Возвращает 0, если закрытие прошло успешно, и EOF (конец файла), если при закрытии файла произошла ошибка.

После закрытия файла тот же указатель файла можно использовать и с другими файлами.

В программировании на языке C файлы автоматически закрываются при завершении программы. Закрытие файла вручную путем написания функции fclose является хорошей практикой программирования.

Запись в файл

В C при записи в файл символы новой строки "\n" должны добавляться явно.

Библиотека stdio предлагает необходимые функции для записи в файл:

  • fputc(char, file_pointer): записывает символ в файл, на который указывает file_pointer.
  • fputs(str, file_pointer): записывает строку в файл, на который указывает file_pointer.
  • fprintf(file_pointer, str, variable_lists): печатает строку в файл, на который указывает file_pointer. Строка может дополнительно включать спецификаторы формата и список переменных variable_lists.

Приведенная ниже программа показывает, как выполнить запись в файл:

Функция fputc():

Приведенная выше программа записывает один символ в файл fputc_test.txt, пока не дойдет до символа следующей строки «\n», который указывает на то, что предложение было успешно записано. Процесс заключается в том, чтобы взять каждый символ массива и записать его в файл.

  1. В приведенной выше программе мы создали и открыли файл с именем fputc_test.txt в режиме записи и объявили нашу строку, которая будет записана в файл.
  2. Мы выполняем посимвольную операцию записи, используя цикл for, и помещаем каждый символ в наш файл до тех пор, пока не встретится символ «\n», после чего файл закрывается с помощью функции fclose.

fputs() Функция:

  1. В приведенной выше программе мы создали и открыли файл с именем fputs_test.txt в режиме записи.
  2. После того, как мы выполним операцию записи с помощью функции fputs(), записав три разные строки
  3. Затем файл закрывается с помощью функции fclose.

Функция fprintf():

  1. В приведенной выше программе мы создали и открыли файл с именем fprintf_test.txt в режиме записи.
  2. После выполнения операции записи с помощью функции fprintf() путем записи строки файл закрывается с помощью функции fclose.

Чтение данных из файла

Есть три разные функции, предназначенные для чтения данных из файла

  • fgetc(file_pointer): возвращает следующий символ из файла, на который указывает указатель файла. Когда конец файла достигнут, EOF отправляется обратно.
  • fgets(buffer, n, file_pointer): считывает n-1 символов из файла и сохраняет строку в буфере, к которому в качестве последнего добавляется символ NULL «\0».
  • fscanf(указатель_файла, спецификаторы_переменных, адреса_переменных): используется для разбора и анализа данных. Он считывает символы из файла и присваивает ввод списку указателей переменных variable_addresses, используя спецификаторы преобразования. Имейте в виду, что, как и в случае с scanf, fscanf прекращает чтение строки при обнаружении пробела или новой строки.

Следующая программа демонстрирует чтение из файла fputs_test.txt с использованием функций fgets(),fscanf() и fgetc() соответственно:

Результат:

  1. В приведенной выше программе мы открыли файл с именем «fprintf_test.txt», который был ранее написан с использованием функции fprintf() и содержит строку «Изучение C с Guru99». Мы читаем его с помощью функции fgets(), которая считывает строку за строкой, при этом размер буфера должен быть достаточным для обработки всей строки.
  2. Мы снова открываем файл, чтобы сбросить файл указателя на начало файла. Создайте различные строковые переменные для обработки каждого слова отдельно. Распечатайте переменные, чтобы увидеть их содержимое. Функция fscanf() в основном используется для извлечения и анализа данных из файла.
  3. Повторно откройте файл, чтобы сбросить файл указателя на начало файла. Считайте данные и распечатайте их из файла символ за символом, используя функцию getc(), пока не встретится оператор EOF
  4. После выполнения операции чтения файла с использованием разных вариантов мы снова закрыли файл с помощью функции fclose.

Интерактивное чтение и запись файлов с помощью getc и putc

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

Следующая программа демонстрирует функции обработки файлов в программировании на языке C:

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

Часто говорят, что искусство компьютерного программирования частично заключается в управлении сложностью и частичном присвоении имен вещам. Я утверждаю, что это в значительной степени верно с добавлением «иногда это требует рисования прямоугольников».

В этой статье я назову некоторые вещи и устраню некоторые сложности при написании небольшой программы на C, которая в общих чертах основана на структуре программы, которую я обсуждал в разделе "Как написать хорошую основную функцию C", но отличается. Этот что-нибудь сделает. Возьмите свой любимый напиток, редактор и компилятор, включите несколько мелодий, и давайте вместе напишем немного интересную программу на C.

Философия хорошей программы Unix

Первое, что нужно знать об этой программе C, это то, что это инструмент командной строки Unix.Это означает, что он работает (или может быть перенесен) на операционные системы, которые обеспечивают среду выполнения Unix C. Когда Unix была изобретена в Bell Labs, она с самого начала была пропитана философией разработки. Моими собственными словами: программы делают одно, делают это хорошо и воздействуют на файлы. Хотя имеет смысл делать что-то одно и делать это хорошо, часть о «действиях с файлами» кажется немного неуместной.

Оказывается, абстракция "файла" в Unix очень эффективна. Файл Unix представляет собой поток байтов, который заканчивается маркером конца файла (EOF). Вот и все. Любая другая структура файла определяется приложением, а не операционной системой. Операционная система предоставляет системные вызовы, которые позволяют программе выполнять набор стандартных операций с файлами: открывать, читать, записывать, искать и закрывать (есть и другие, но эти важные). Стандартизация доступа к файлам позволяет различным программам использовать общую абстракцию и работать вместе, даже если разные люди реализуют их на разных языках программирования.

Наличие общего файлового интерфейса позволяет создавать компонуемые программы. Вывод одной программы может быть вводом другой программы. Семейство операционных систем Unix по умолчанию предоставляет три файла при выполнении программы: стандартный ввод (stdin), стандартный вывод (stdout) и стандартный файл ошибок (stderr). Два из этих файлов открыты только для записи: stdout и stderr, а stdin открыт только для чтения. Мы видим это в действии всякий раз, когда используем перенаправление файлов в командной оболочке, такой как Bash:

Эту конструкцию можно кратко описать так: вывод ls записывается в стандартный вывод, который перенаправляется на стандартный ввод grep, чей стандартный вывод перенаправляется на sed, чей стандартный вывод перенаправляется для записи в файл с именем ack в текущем каталог.

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

MeowMeow: концепция потокового кодировщика/декодера

Когда я был росистым ребенком, изучавшим информатику в s, существовало множество схем кодирования. Некоторые из них предназначались для сжатия файлов, некоторые для упаковки файлов вместе, а у других не было никакой цели, кроме как быть мучительно глупыми. Примером последней является схема кодирования MooMoo.

Чтобы дать нашей программе цель, я обновлю эту концепцию для 2000-х годов и реализую концепцию, называемую кодированием MeowMeow (поскольку Интернет любит кошек). Основная идея здесь состоит в том, чтобы взять файлы и закодировать каждую часть (половину байта) текстом «мяу». Строчная буква обозначает ноль, а прописная — единицу. Да, это увеличит размер файла, поскольку мы обмениваем 4 бита на 32 бита. Да, это бессмысленно. Но представьте удивление на чьем-то лице, когда это происходит:

Это будет потрясающе.

Реализация, наконец

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

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

Короче говоря, я создал каталог, полный пустых файлов, и передал их в git.

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

Обычно программа запускается как один простой файл main.c, содержащий только две или три функции, решающие проблему. А потом программист опрометчиво показывает эту программу подруге или ее начальнику, и вдруг количество функций в файле резко увеличивается для поддержки всех всплывающих новых «функций» и «требований». Первое правило «Клуба программ» — не говорить о «Клубе программ». Второе правило — минимизировать количество функций в одном файле.

Честно говоря, компилятору C совершенно все равно, если каждая функция в вашей программе находится в одном файле. Но мы не пишем программы для компьютеров или компиляторов; мы пишем их для других людей (которые иногда являются нами). Я знаю, что это, вероятно, сюрприз, но это правда. Программа воплощает в себе набор алгоритмов, которые решают задачу с помощью компьютера, и важно, чтобы люди понимали это, когда параметры задачи меняются непредвиденным образом. Людям придется изменить программу, и они будут проклинать ваше имя, если у вас есть все 2049 функций в одном файле.

Поэтому мы, хорошие и настоящие программисты, разбиваем функции, группируя похожие функции в отдельные файлы. Здесь у меня есть файлы main.c, mmencode.c и mmdecode.c. Для таких небольших программ это может показаться излишним. Но небольшие программы редко остаются маленькими, поэтому планировать расширение — это «хорошая идея».

А как насчет файлов .h?Я объясню их в общих чертах позже, но вкратце они называются файлами header и могут содержать определения типов языка C и директивы препроцессора C. Заголовочные файлы не должны не содержать никаких функций. Заголовки можно рассматривать как определение интерфейса прикладного программирования (API), предлагаемого файлом с расширением .c, который используется другими файлами .c.

Но что, черт возьми, такое Makefile?

Я знаю, что все вы, крутые ребята, используете интегрированную среду разработки "Ultra CodeShredder 3000" для написания следующего блокбастера, и создание вашего проекта состоит из смешивания Ctrl-Meta-Shift-Alt-Super-B. Но в мое время (и сегодня) много полезной работы выполнялось программами на C, созданными с помощью Makefiles. Makefile — это текстовый файл, содержащий рецепты работы с файлами, и программисты используют его для автоматизации сборки двоичных файлов своих программ из исходного кода (и других вещей тоже!).

Возьмем, к примеру, эту маленькую жемчужину:

Текст после октоторпа/фунта/решётки является комментарием, например, в строке 00.

Строка 01 — это присвоение переменной, где переменная TARGET принимает строковое значение my_sweet_program. По соглашению, хорошо, я предпочитаю, все переменные Makefile пишутся с заглавной буквы и используют символы подчеркивания для разделения слов.

Строка 02 состоит из имени файла, который создает рецепт, и файлов, от которых он зависит. В данном случае цель — my_sweet_program, а зависимость — main.c.

Последняя строка, 03, имеет отступ с табуляцией, а не с четырьмя пробелами. Это команда, которая будет выполняться для создания цели. В этом случае мы вызываем cc внешний интерфейс компилятора C для компиляции и компоновки my_sweet_program.

Использовать Makefile очень просто:

Файл Makefile, который создаст наш кодировщик/декодер MeowMeow, значительно сложнее, чем в этом примере, но основная структура такая же. Я расскажу об этом в стиле Барни в другой статье.

Форма следует за функцией

Моя идея состоит в том, чтобы написать программу, которая читает файл, преобразует его и записывает преобразованные данные в другой файл. Следующее сфабрикованное взаимодействие с командной строкой — это то, как я представляю себе использование программы:

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

Незначительное отступление: argv[0] и команда ln

Если вы помните, сигнатура функции C main такова:

где argc — это количество аргументов командной строки, а argv — это список указателей на символы (строк). Значение argv[0] — это путь к файлу, содержащему исполняемую программу. Многие служебные программы Unix с дополнительными функциями (например, сжатие и распаковка) выглядят как две программы, но на самом деле это одна программа с двумя именами в файловой системе. Трюк с двумя именами достигается путем создания «ссылки» файловой системы с помощью команды ln.

Пример из /usr/bin на моем ноутбуке:

Здесь git и git-receive-pack — это один и тот же файл с разными именами. Мы можем сказать, что это один и тот же файл, потому что у них одинаковый номер инода (первый столбец). Индексный дескриптор — это функция файловой системы Unix, которая выходит за рамки этой статьи.

Хорошие и/или ленивые программисты могут использовать эту особенность файловой системы Unix, чтобы писать меньше кода, но вдвое больше программ, которые они создают. Во-первых, мы пишем программу, которая меняет свое поведение в зависимости от значения argv[0], затем обязательно создаем ссылки с именами, вызывающими такое поведение.

В нашем Makefile ссылка unmeow создается по следующему рецепту:

Я склонен параметризовать все в своих файлах Makefile, редко используя "голую" строку. Я группирую все определения в верхней части Makefile, что облегчает их поиск и изменение. Это имеет большое значение, когда вы пытаетесь перенести программное обеспечение на новую платформу и вам нужно изменить все свои правила, чтобы использовать xcc вместо cc.

Рецепт должен казаться относительно простым, за исключением двух встроенных переменных $@ и $

Как написать хорошую основную функцию C

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

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