Int main()
{
struct emp prgmr, raise(struct emp, double);
...
printf("Old salary: %.2f\n", prgmr.salary);
prgmr = raise(prgmr, .12);
printf("New salary: %.2f\n", prgmr.salary);
}
struct emp raise(struct emp person, double increase)
{
person.salary *= (1+ increase);
return person;
}
Данная программа выполнялась бы более эффективно, если в функцию raise() передавался указатель на структуру. В этом случае при передаче управления в функцию передается лишь значение указателя, что делает доступными в функции элементы структуры. Этот вариант программы показан в прим. 2.
Пример 2
#include <stdio.h>
#include "emp.h" /* содержит шаблон структуры emp */
int main()
{
struct emp prgmr;
void raise(struct emp *, double);
...
printf("Old salary: %.2f\n", prgmr.salary);
raise(&prgmr, .12);
printf("New salary: %.2f\n", prgmr.salary);
}
void raise(struct emp *person, double increase)
{
person->salary *= (1+ increase);
}
-
Объединения. Сравнение объединения и структуры.
Объединение — объект, который в каждый момент времени содержит один из нескольких элементов различных типов.
Все компоненты объявления структур, такие как шаблоны, имена типа, имена элементов и т.д. применимы и при объявлении объединений. Единственное отличие состоит в том, что при объявлении объединения вместо ключевого слова struct используется union.
Примечание 1
Объединение не может иметь битовые поля.
Операции доступа к элементу структуры (операция . и операция ->) могут применяться и к объединениям. Допустимы массивы объединений и указатели на объединения. Объединения могут передаваться функции как параметры и возвращаться функцией.
В отличие от структуры, объединение может в любой момент времени содержать только один из своих элементов. Объединение позволяет использовать одну область памяти для хранения различных видов данных в разные моменты времени. Фактически, объединение — это структура, в которой все поля начинаются со смещением 0, таким образом, поля накладываются друг на друга.
-
Шаги компиляции Си.
На первом шаге программа "препроцессируется". Специальные директивы обрабатываются препроцессором. Результат этой обработки - расширенный исходный текст программы на языке Си.
На следующем шаге программа действительно компилируется и переводится на низкоуровневый язык ассемблера.
Затем ассемблер транслирует ассемблерную программу в объектный код. Хотя объектный код - это машинный код, он еще невыполним.
Исполняемый код строит программа - компоновщик, называемая в ОС UNIX редактором связей. Одна из задач редактора связей состоит в согласовании неразрешенных ссылок.
Неразрешенная ссылка - ссылка на объект, который в данной части программы неопределен. Редактор связей связывает объявления внешних переменных с соответствующими внешними определениями. Другим типом неразрешенной ссылки является вызов функции, не определенной в программе, например, вызов функции printf(). Редактор связей ищет объектный код функции в одной или более библиотеках и включает этот код в исполнимый модуль. Результатом работы редактора связей при отсутствии ошибок является исполнимая программа.
В системе ОС UNIX процесс компиляции может быть остановлен после любого шага путем использования соответствующего флага команды cc, который определяет режим работы команды. Создается файл с тем же именем, что и исходный файл, и окончанием .i, .s или .o, указывающем на тип файла. По умолчанию имя исполнимой программы - a.out.
-
Препроцессор Си. Поименованные константы. Макросы.
Препроцессор языка Си – это составная часть компилятора, реализующая первую стадию компиляции.
Препроцессор - это мощный инструмент, часто используемый для повышения удобочитаемости, надежности и переносимости программ.
Обработка препроцессором выполняется на первом этапе компиляции исходного текста программы.
Препроцессор читает исходный текст, отыскивая и обрабатывая директивы препроцессора, которые представляют собой строки текста, начинающиеся с символа #.
Пример 1
#include <stdio.h>
#define SIXE 100
Для переносимости программы следует указывать символ # в позиции 1, а непосредственно после него – управляющее слово (include, define , и т.п.), поскольку некоторые препроцессоры языка K&R Си не допускают, чтобы символы промежутков предшествовали символу # или следовали непосредственно после него.
Препроцессор позволяет определять константы и макросы, включать файлы, предоставляет возможности условной компиляции.
Поименованные константы определяются с помощью директивы препроцессора define:
#define идентификатор строка-шаблон
Поименованные константы используются для того, чтобы облегчить чтение и изменение программ.
До и после идентификатора в директиве define должен быть один или более пробелов. Строка-шаблон начинается с первого отличного от пробела символа и заканчивается символом перевода строки. Строку-шаблон можно продолжить более чем на одну строку, начиная новые строки символом \.
После определения поименованной константы препроцессор заменяет в исходном тексте программы идентификатор значением строки-шаблона. Внутри комментариев и строковых констант такая замена не производится.
Макрос — это короткая процедура, у которой могут быть аргументы.
Макрос определяется с помощью директивы препроцессора #define.
Определение макроса похоже на определение поименованной константы.
#define идентификатор(аргумент[,аргумент]...) строка-шаблон
В скобках непосредственно после идентификатора могут быть указаны имена аргументов. Между идентификатором и символом ( не должно быть символов промежутка. При расширении макроса препроцессор осуществляет вставку строк.
-
Файлы заголовков. Организация сложных программ.
Директива препроцессора include влечет вставку в текущую позицию текста программы на языке Си копии указанного файла.
#include <file.h>
#include "file.h"
Подобные файлы, называемые файлами заголовков или файлами вставок, обычно содержат директивы define для поименованных констант и макросов, объявления функций, внешних переменных и т.п., необходимые для нескольких исходных файлов. Преимущество подобного выделения в отдельный файл в том, что такие определения локализуются в общем месте. Несколько программ могут включать этот файл и быть уверенными в использовании одинаковой информации. Если необходимо изменение, его можно сделать в одном месте.
По соглашению, имена файлов-вставок имеют расширение .h , что означает файл заголовков (header), названных так потому, что директивы include обычно указываются в начале исходных файлов языка Си. Если имя файла заключено в угловые скобки < и >, препроцессор ищет файл в "стандартном месте". В операционных системах UNIX таким стандартным местом является каталог /usr/include. Если имя файла заключено в двойные кавычки ", используется стандартное соглашение операционной системы для доступа к файлу. В операционных системах UNIX может использоваться полное или относительное путевое имя.
Пример 1
#include "proj.h"
#include "/local/include/x33.h"
#include "../include/defs.h"
Файлы вставки могут быть вложены; файл заголовков может содержать директивы include.
В файле заголовков не должно быть ни определений функций (программ этих функций), ни определений внешних переменных, по которым выделяется память (например, int x). Иначе, если в нескольких файлах используется один и тот же файл заголовков, возникнут неприятности. При связывании этих файлов редактор связей может сообщить о "многократном определении идентификаторов".
В файле заголовков могут содержаться и другие директивы препроцессора и языка Си, например, операторы typedef, шаблоны структур и объединений (struct, union) и операторы enum.
-
Условная компиляция.
Условная компиляция — это средство препроцессора, которое в зависимости от условия включает в программу операторы языка Си или препроцессора.
Условная компиляция позволяет строить гибкие программы на языке Си. Это средство особенно часто используется при разработке программ, которые должны переноситься на различные процессоры, в различные операционные системы и в прочие операционные среды.
Условная компиляция реализуется с помощью следующих директив препроцессора:
#if
#else
#elif (аналогично else if)
#endif
#ifdef
#ifndef
В основном, условная компиляция выполняется следующим образом:
если это-и-это истинно
вставить указанные здесь операторы в исполнимую программу
Можно задать и ветвь иначе.
Директивы #if используются для вставки строк в файл, в случае, если истинно некоторое константное выражение.
Директивы #ifdef используются для вставки строк, если определена указанная поименованная константа.
Для вставки строк при условии, что константа не имеет определения, вместо директивы #ifdef можно использовать директиву #ifndef.
-
Буферизованный ввод-вывод.
Буферизация - способ организации ввода-вывода в программе, позволяющий минимизировать число обращений к устройству.
При чтении из дискового файла, блок данных копируется с диска в пользовательский буфер. Многими операционными системами используется также промежуточный "системный буфер". Этот блок данных, называемый также физическим блоком или блоком ввода-вывода, имеет объем, заданный поименованной константой BUFSIZ, определенной в файле заголовков stdio.h. Операции чтения из файла фактически читают данные из этого буфера. При исчерпании буфера вновь выполняется чтение с диска, и буфер пополняется. Обращение к диску лишь при необходимости обеспечивает существенную экономию времени. Программисты могут читать из файла множеством способов (посимвольно, построчно и т.д.) и не задумываться о минимизации количества обращений к диску. Стандартный пакет ввода-вывода гарантирует решение этой проблемы.
-
Файл. Открытие и закрытие файла.
Структура FILE — это структура, в которой хранится информация о том, как открыт файл (например, для чтения), и где позиция следующего чтения или записи. Структура FILE инициализируется вызовом функции fopen(). Она используется операционной системой и доступна различным функциям для чтения-записи. Программисту не нужно обращаться прямо к полям этой структуры и даже знать, что это за поля. Программист должен лишь объявить указатель на FILE перед открытием файла. Этот указатель получает значение во время открытия файла вызовом функции fopen() и должен впоследствии передаваться как параметр другим функциям, работающим с данным файлом. Программисту никогда нельзя изменять значение этого указателя. После закрытия файла соответствующая ему переменная файлового указателя на FILE может использоваться повторно.
Функция fopen() — функция открытия файла.
FILE *fopen(const char *имя_файла, const char *тип)
Первым аргументом функции fopen() является адрес строки, содержащей путевое имя файла. Второй аргумент—это адрес строки, определяющей тип работы с открываемым файлом.
Заданный именем файл открывается в соответствии с указанным типом. Тип может принимать следующие значения:
"r" — текстовый файл открывается для чтения (read);
"w"- текстовый файл создается для записи; старое содержимое, если оно было, выбрасывается (write);
"a" — текстовый файл открывается или создается для записи в конец файла (add);
"r+" — текстовый файл открывается для исправления, т.е. для чтения и записи;
"w+"- текстовый файл создается для исправления, старое содержимое, если оно было, выбрасывается;
"a+" — текстовый файл открывается или создается для исправления уже существующей информации и добавления новой в конец файла.
Функция fopen() возвращает указатель на структуру FILE, который затем присваивается переменной. Этот указатель называют внутренним. После открытия файла к нему обращаются с использованием указателя на этот файл, имя файла больше не потребуется..
Функция fclose() - стандартная функция ввода-вывода языка Си, с помощью которой осуществляется закрытие файла.
