Опубликован: 14.12.2010 | Уровень: для всех | Доступ: свободно
Лекция 18:

Программы на языке С, состоящие из нескольких файлов

< Лекция 17 || Лекция 18: 12345 || Лекция 19 >

Спецификатор static

Переменные, объявленные со спецификатором static, хранятся постоянно внутри своей функции или файла. В отличие от глобальных переменных они невидимы за пределами своей функции или файла, но они сохраняют значение между вызовами [17.2]. Спецификатор static воздействует на локальные и глобальные переменные по-разному.

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

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

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

Таким образом, имена локальных статических переменных видимы только внутри блока, в котором они объявлены; имена глобальных статических переменных видимы только внутри файла, в котором они объявлены [17.2].

Спецификатор register

Спецификатор register был разработан для того, чтобы компилятор сохранял значение переменной в регистре центрального процессора, а не в памяти, как обычно. Это означает, что операции над регистровыми переменными выполняются намного быстрее [17.2].

Спецификатор register можно применять только к локальным переменным и формальным параметрам функций. В объявлении глобальных переменных применение спецификатора register не допускается.

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

Ощутимый эффект от спецификатора register может быть получен только для переменных целого ( int ) и символьного ( char ) типа.

Спецификатор auto

Спецификатор auto присваивает объявляемым объектам автоматический класс памяти, его можно применять только внутри функции [17.4]. Объявления со спецификатором auto одновременно являются определениями и резервируют память. Автоматический класс памяти определяет собой автоматический период хранения, который могут иметь только переменные. Локальные переменные функции (определенные в списке параметров или в теле функции) обычно имеют автоматический период хранения. Ключевое слово auto определяет переменные с автоматическим хранением явным образом.

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

Для переменных со спецификатором auto нет значения по умолчанию.

Обычно создаваемые программистом разработки на языке С принято оформлять в виде файлов с расширением , хотя расширение может быть любым, например, .txt, .doc и т.д. Соответственно, разработки для С++ имеют расширение .срр. Для создания собственного файла можно использовать инструментарий Microsoft Visual Studio 2010. Для этого следует из пункта меню "File" выбрать подпункт "New" и далее в соответствии с рис. 17.2 выбрать С++File(.cpp). После нажатия клавиши "Open" откроется файл Source1.cpp (при повторном обращении будет Source2.cpp и т.д.). Будет открыто окно редактирования для набора программного кода. После созданный файл можно сохранить с расширением .c. Теперь следует грамотно объявить переменные, используемые в проекте, и функции в файлах типа *.h, *.c. Создаваемые в среде Visual Studio файлы можно раздельно компилировать, т.е. проверять ошибки, которые отслеживаются при обычной компиляции.

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

Приведем возможные действия для создания файлов типа *.с и *.h в программной среде MS Visual Studio 2010. Для этого сначала следует открыть стартовую страницу MS Visual Studio 2010. Далее открыть пункт меню File–New –File. Откроется страница, показанная на рис. 17.2. Далее следует выбрать либо С++File(.cpp) либо Header File(.h). Завершить нажатием кнопки Open. Если выбрать С++File(.cpp), то откроется окно для редактирования, которое по умолчанию имеет имя Source1.cpp. Написав необходимый код, надо сохранить файл с именем по усмотрению пользователя, но с расширением .с. Сохранение проведем по цепочке из пункта меню File: File–Save – Source1.cpp As... В пункте меню "Тип файла" выбрать C Source File (*.c). В итоге откроется окно (в котором могут быть ранее созданные проекты), показанное на рис. 17.3.

Окно для сохранения С-файла

Рис. 17.3. Окно для сохранения С-файла

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

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

#include "..\..\some.h"

Такое подключение означает, что файл some.h находится на два уровня выше, чем главный файл main.c.

Можно указывать полный путь расположения заданного h -файла. Если разрабатывается проект, который переносится с диска на диск, то всякий раз придется прописывать полный путь к h -файлу. Поэтому обычно договариваются переносить проект на новый диск со всеми файлами. Тогда каждый программист просто рассчитывает число уровней до заданного h -файла. При этом структура объявлений функций сохранится неизменной, т.е. без указания полного пути.

Обычно в h -файлах дается описание прототипов разработанных файлов, постоянных, общих для проекта и препроцессорных директив. В случае использования препроцессорных директив (#define...) следует после них оставить одну пустую строку.

Практическая часть

Пример 1. Рассмотрим пример создания проекта в Microsoft Visual Studio 2010, состоящего из одного заголовочного файла (например, hfile.h ) и двух подключаемых функций, созданных программистом (например, degree(), print() ). В файле myfile3.c поместим функцию degree(), а функцию print() поместим в файл myfile2.c. Файл с главной функцией создаваемого проекта озаглавим как main.c. При этом файлы hfile.h, myfile3.c и myfile2.c разместим на другом диске, например, на диске D.

В качестве примера запрограммируем решение следующей задачи. Сформируйте матрицу, состоящую из N=2^k строк. Число столбцов равно k. При этом столбцы такой матрицы заполняются +1 или –1 по степеням двойки. То есть, первый столбец заполняется +1, –1, +1, –1, +1, –1 и т.д. Второй столбец: +1, +1, –1, –1, +1, +1, –1, –1 и т.д. Третий столбец: +1, +1, +1, +1, –1, –1, –1, –1 и т.д. Эту матрицу называют матрицей планирования эксперимента типа 2^k. Расчет такой матрицы должен выполняться в одном файле, а печать результата – в другом файле, при этом результат можно записать в текстовый файл. В файле main.c происходит обращение к созданным функциям.

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

Программный код решения примера:

#include <stdio.h>
#include <conio.h>
#include <math.h>
#include "D:\\hfile.h"

int main(void) {
double k;

printf("\n\tConstruction of a matrix of planning experiment N = 2^k\n");
printf("\n\tEnter a natural number: ");
scanf_s("%lf", &amp;k);

if (k < 1 || (ceil(k) != k) ) {
printf("\n Error! Press any key: ");
_getch();
 return 0; }
// Обращение к функции формирования матрицы планирования
print((int)k);

printf("\n\n Press any key: ");
_getch();
return 0; 
}
// Файл myfile3.c
int degree(int x, int k)  {
 int i, a = 1;
for (i = 1; i <= k; ++i)
a *= x; return a; }
// Файл myfie2.c
#include <math.h>
#include <stdlib.h>

void print(int k) {
int i, j, N, *PTR;
FILE *fid;

N = degree(2,k); // N = 2^k

// Динамически выделяемая память
PTR = (int *)malloc(N*k*sizeof(int));
for (i = 0; i < N; ++i)
   for (j = 0; j < k; ++j)
PTR[i*k + j] = 0; // заполнение нулями

for (i = 0; i < N; ++i)
   for (j = 0; j < k; ++j)
PTR[i*k + j] = degree(-1,(int)floor(i/degree(2,j)));
printf("\n Matrix is planning experiment, N = 2^k, N = %d, k = %d\n", N, k);
if (k < 5) {
   for (i = 0; i < N; ++i)  {
printf("\n%4d) ", i+1);
     for (j = 0; j < k; ++j)
printf(" %3d", PTR[i*k+j]);
}
}
else {
    fopen_s(&amp;amp;amp;amp;fid, "D:\\data.txt", "w");
fprintf(fid, "\r\n Matrix is planning experiment, N = 2^k, N = %d, k = %d\r\n", N, k);
for (i = 0; i < N; ++i) {
fprintf(fid, "\r\n%4d) ", i+1);
    for (j = 0; j < k; ++j)
fprintf(fid, "%3d", PTR[i*k+j]);
}
fclose(fid);
printf("\n See the result in the file \"D:\\data.txt\" \n");
}
}
// Заголовочный файл hfile.h
#include "D:\myfile3.c"
#include "D:\myfile2.c"

Подключение файлов сделано в двух местах: в главной функции main() и в заголовочном файле hfile.h. При этом прототипы функций не прописаны. Заголовочный файл hfile.h "выгружает" содержимое файлов myfile3.c и myfile2.c перед главной функцией main(), поэтому в теле функции можно обращаться к функциям degree() и print(). Функция degree() предназначена для возведения целого числа в степень. Она формирует матрицу планирования (с помощью указателя *PTR ) и вывода значений матрицы на консоль и (или) в текстовый файл. Имеет важное значение очередность подключения файлов myfile3.c и myfile2.c. Следует обратить внимание на синтаксис заключения в двойные ковычки имени текстового файла data.txt. Функция ceil() возвращает наименьшее целое (представленное в виде значения с плава ющей точкой), которое больше своего аргумента или равно ему [17.2]. Она включена на тот случай, если пользователь введет не целое число

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


Рис. 17.4.
< Лекция 17 || Лекция 18: 12345 || Лекция 19 >
Мухаммадюсуф Курбонов
Мухаммадюсуф Курбонов