Программирование на языке C в Microsoft Visual Studio 2010

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

Разбить на страницы
Показывать лекцию целиком

Теоретическая часть

Программа на языке С – это совокупность функций. Запуск любой программы начинается с запуска главной функции, содержащей в себе всю остальную часть программы. Внутри главной функции для реализации заданного алгоритма вызываются все другие необходимые функции. Часть функций создается самим программистом, другая часть – библиотечные функции – поставляется пользователю со средой программирования и используется в процессе разработки программ (например, printf(), sqrt() и др.).

Простейший метод использования нескольких функций требует их размещения в одном и том же файле. Затем выполняется компиляция этого файла, как если бы он содержал единственную функцию [17.1]. Другие подходы к решению этой проблемы существенно зависят от конкретной операционной системы (Unix-подобные системы, Windows, Macintosh). Компиляторы операционных систем Windows и Macintosh представляют собой компиляторы, ориентированные на проекты [17.1]. Проект описывает ресурсы, используемые программой. Эти ресурсы включают файлы исходного программного кода.

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

Хороший тон в программировании рекомендует размещать прототипы функций и объявлять их константы в заголовочном файле [17.1]. Назначение отдельных задач отдельным функциям способствует улучшениям программы.

Функция может быть либо внешней (по умолчанию), либо статической. К внешней функции доступ могут осуществлять функции из других файлов, в то же время статическая функция может использоваться только в файле, в котором она определена [17.1]. Например, возможны следующие объявления функций:

double gamma(); // внешняя функция по умолчанию
static double beta();
extern double delta();

Функция gamma() и delta() могут использоваться функциями из других файлов, которые являются частью программы, тогда как beta() – нет. В силу этого применение функции beta() ограничено одним файлом, поэтому в других файлах можно использовать функции с тем же именем. Одна из причин использования класса статической памяти заключается в необходимости создания функций, приватных для конкретных модулей, благодаря чему во многих случаях удается избежать конфликта имен [17.1].

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

Одним из золотых правил для надежного программирования есть принцип "необходимости знать", или принцип минимально необходимой области видимости [17.1]. Рекомендуется держать всю внутреннюю работу каждой функции максимально закрытой по отношению к другим функциям, используя совместно только те переменные, без которых нельзя обойтись по логике программы. Другие классы памяти полезны, и ими можно воспользоваться. Однако всякий раз следует задать вопрос: а есть ли в этом необходимость?

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

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

Стандарт языка С поддерживает 4 спецификатора класса памяти [17.2]:

  • extern
  • static
  • register
  • auto
  • Спецификаторы сообщают компилятору, как он должен разместить соответствующие переменные в памяти. Спецификатор класса памяти в объявлении всегда должен стоять первым [2].

    Приведем характеристику спецификаторов классов памяти [17.2].

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

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

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

    Пример использования спецификатора extern при использовании глобальных переменных:

    #include <stdio.h>
    #include <conio.h>
    
    // Главная функция
    int main (void) {
    // объявление глобальных переменных
    	extern int a, b;
    	
    printf("\n\t a = %d; b = %d\n", a, b);
    printf("\n Press any key: ");
    _getch();
    return 0;  }
    
    // инициализация (описание) глобальных переменных
    int a = 33, b = 34;

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

    При компиляции выполняются следующие правила:

  • Если компилятор находит переменную, не объявленную внутри блока, он ищет ее объявление во внешних блоках.
  • Если не находит ее там, то ищет среди объявлений глобальных переменных.
  • Спецификатор extern играет большую роль в программах, состоящих из многих файлов [17.3]. В языке С программа может быть записана в нескольких файлах, которые компилируются раздельно, а затем компонуются в одно целое. В этом случае необходимо как-то сообщить всем файлам о глобальных переменных программы. Самый лучший (и наиболее переносимый) способ сделать это – определить (описать) все глобальные переменные в одном файле и объявить их со спецификатором extern в остальных файлах, например, как это сделано в следующей программе:

    Первый файл ( main.c ) Второй файл ( second.h )
    #include <stdio.h>
    #include <conio.h>
    #include "D:\second.h"
    
    int x = 99, y = 77;
    char ch;
    void func1(void);
    int main(void)
    {
    ch = 'Z';
    func1();
    
    printf("\n Press any key: ");
    _getch();
    return 0;
    }
    
    void func1(void)
    {
    func22();
    func23();
    	
    printf("\n\t x = %d; y = %d;\
     ch = %c\n", x, y, ch);
    }
    extern int x, y;
    extern char ch;
    
    void func22(void)
    {
    y = 100;
    }
    
    void func23(void)
    {
    x = y/10;
    ch = 'R';
    
    }

    В программе первый файл – это основная часть программного проекта. Второй файл создан как текстовый файл (с помощью блокнота) с расширением *.h. Список глобальных переменных ( x, y, ch ) копируется из первого файла во второй, а затем добавляется спецификатор extern. Он сообщает компилятору, что имена и типы переменных, следующих далее, объявлены в другом месте. Все ссылки на внешние переменные распознаются в процессе редактирования связей. Подключение второго файла выполнено с указанием имени диска (D:), на котором расположен файл second.h.

    Для подключения имени файла, созданного пользователем, его заключают в двойные кавычки.

    Результат выполнения программы показан на рис 17.1

    (рис 17.1) Результат выполнения программы, состоящей из двух файлов

    В общем случае h -файл (например, second.h ) формируется редактором кода: надо создать заготовку обычным способом, очистить все поле редактора и записать в это поле необходимые данные (программный код созданной функции). Затем выполнить команду главного меню: File/Save as и выбрать для сохраняемого файла расширение .h в раскрывающемся списке типов сохраняемого файла: C++ Header Files (*.h; *.hh; *.hpp; *.hxx; *.inl; *.tlh; *.tli). Сохраненный файл с расширением .h следует подключить к проекту. Для этого потребуется в узле Solution Explorer навести курсор мыши к папке Header Files и правой кнопкой мыши выбрать Add – Existing Item сохраненный файл second.h. Затем с помощью оператора #include файл следует включить в основную программу.

    Другой способ, реализуемый в Microsoft Visual Studio 2010, состоит в том, что сразу через пункт меню .

    (рис 17.2) Процесс создания нового файла с расширением .h

    Далее в правом нижнем углу нажмем клавишу Open. Откроется пустое поле – заготовка для набора необходимого кода. По умолчанию этот файл имеет имя Header1.h. При повторном создании заголовочного файла это будет Header2.h и т.д. После написания кода можно сохранить этот заголовочный файл по желанию в любом каталоге с любым (допустимым) именем (а расширение остается .h ).

    Спецификатор 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 нет значения по умолчанию.

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

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

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

    (рис 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) Матрица планирования N = 24"

    Задание 1

  • Проверьте работу программ при записи результата в файл. В качестве имени текстового файла примите compX.txt, где Х – номер компьютера, на котором выполняется лабораторная работа.
  • В файле hfile.h поменяйте местами подключаемые файлы. Добавьте необходимые изменения, чтобы программа работала корректно (без предупреждений).
  • Вместо разработанной функции degree() примените стандартную функцию pow().
  • В программу добавьте изменения для вывода матрицы планирования при зеркальном отображении столбцов.
  • В матрицу планирования добавьте в качестве первого столбца массив положительных единиц, т.е. +1.
  • Пример 2. Напишите программу вывода на консоль содержимого текстового файла, расположенного в заданном каталоге. Вывод на консоль содержимого файла оформить в виде отдельного файла.

    Предположим, что программный проект расположен на диске Е:\Project_C. Текстовый файл расположен на диске D:\data16.txt. Пусть содержимое текстового файла будет следующим:

    1, 2, 3, 4, 5
     Programming in C
     department: electronic engineering

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

    // Подключаемый файл fun1.c
    #include <stdlib.h> // Для функции exit()
    
    void print_file(char *st) { 
    char str[79+1];
    FILE *fid;
    printf("\n Data from \"%s\":\n", st);
    
    if ( (fopen_s(amp;fid, st, "r")) ) {
    printf("\n File could not be opened.\n");
    
    printf("\n Break. Press any key: ");
    _getch(); exit(1); }
    
    printf("\n Data from the file \"%s"\n", st);
    while (!feof(fid)) {
    fgets(str, 79, fid);
    printf("%s", str);
    }
    	
    fclose(fid);
    printf("\n");
    }
    // Основной программный модуль проекта main.c
    #include <stdio.h>
    #include <conio.h>
    #include "D:\\fun1.c"
    
    // Главная функция
    int main (void) {
    	char str[79+1];
    	printf("\n Enter the file name and path: ");
    	gets_s(str, 79);
    
    // Вызов функции распечатки файла	
    	print_file(str);
    	
    	printf("\n Press any key: ");
    	_getch();
         return 0;
     }

    Результат выполнения программы показан на рис 17.5.

    (рис 17.5) Содержимое заданного текстового файла

    Задание 2

  • На консоль выведите содержимое подключаемого файла fun1.c.
  • Измените расположение подключаемого файла, например, поместите его на диск D:\students\fun1.c.
  • В текстовый файл запишите фамилию и имя пользователя (студента), год рождения, год поступления в университет, наименование специальности.
  • Содержимое текстового файла предыдущего пункта выведите на консоль и запишите в другой текстовый файл с именем compX.txt, где Х – номер компьютера, на котором выполняется лабораторная работа. Запись в текстовый файл оформите в виде функции surname.c, где surname – фамилия пользователя.
  • В программе вместо указателя, являющегося формальным параметром функции print_file(), примените массив символов.
  • Пример 3. Напишите программу преобразования десятичной системы счисления в двоичную, восьмеричную, шестнадцатеричную на основе системы меню. Примените массив указателей на функцию, используйте заголовочные файлы.

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

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

    // Основной программный модуль main.c
    #include <stdio.h>
    #include <conio.h>
    #include "hdec.h"
    
    int main (void) {
    int index[3] = {0, 1, 2};
    long int number;
    int basen;
    
    void (*fun[3])(long int) = {dec2, dec8, dec16};
    
    printf("\n Enter a positive integer numbers: ");
    scanf_s("%ld", amp;amp;amp;amp;number);
    printf("\n Select base system notation (2, 8, 16): ");
    scanf_s("%d", amp;basen);
    if (basen == 2)
    (*fun[0])(number);
    else if (basen == 8)
    (*fun[1])(number);
    else if (basen == 16)
    (*fun[2])(number);
    else {
    printf("\n\t Error choice.");
    printf("\n Break. Press any key: ");
    _getch();
    return 0;
    }
    
    printf("\n\n Press any key: ");
    _getch();
    return 0;
    }
    // Подключаемый файл Dec_2.c
    // Перевод в двоичную систему счисления
    #include "base.h"
    void dec2 (long int x) {
    int i = 0;
    int Num[64];
    int next;
    long int xx = x;
    
    do {
    Num[i] = x % base2; 
    
    ++i;
    x /= base2;
    
    } while (x != 0);
    
    // Результат в обратном порядке
    printf("\n Decimal number \"%ld\" has a binary equivalent:\n", xx);
    for (--i; i >= 0; --i) {
    next = Num[i];
    printf("%2d", next);
    }
    }
    // Подключаемый файл Dec_8.c
    // Перевод в восьмеричную систему счисления
    #include "base.h"
    
    void dec8 (long int y) {
    int i = 0, next;
    int Num[64];
    long int yy = y;
    
    do {
    Num[i] = y % base8; 
    ++i;
    y /= base8;
    
    } while (y != 0);
    
    // Результат в обратном порядке
    printf("\n Decimal number \"%ld\" has an octal equivalent:\n", yy);
    for (--i; i >= 0; --i) {
    next = Num[i];
    printf("%2d", next);
    }
    }
    // Подключаемый файл Dec_16.c
    // Перевод в шестнадцатеричную систему счисления
    #include "base.h"
    
    void dec16 (long int z) {
    int i = 0;
    int Num[64];
    int next;
    long int zz = z;
    
    const char digits_16[16] = {
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
    'A', 'B', 'C', 'D', 'E', 'F' };
    
    do {
    Num[i] = z % base16; 
    ++i;
    z /= base16;
    
    } while (z != 0);
    
    // Результат в обратном порядке
    printf("\n Decimal number \"%ld\" has a hexadecimal equivalent:\n", zz);
    for (--i; i >= 0; --i) {
    next = Num[i];
    printf("%2c", digits_16[next]);
    }
    }
    // Подключаемый файл base.h
    // Основания систем счисления
    #define base2 2
    #define base8 8
    #define base16 16
    // Подключаемый файл hdec.h
    // Поключение файлов с функциями преобразования систем 
    // счисления
    
    #include "Dec_2.c"
    #include "Dec_8.c"
    #include "Dec_16.c"
    
    // Прототипы функций преобразования систем счисления
    void dec2 (long int a);
    void dec8 (long int b);
    void dec16 (long int c);

    В программе используется заголовочный файл base.h в целях демонстрации препроцессорных директив языка С. Другой заголовочный файл hdec.h подключает файлы с программами преобразования чисел и прототипы функций. Внимание! Особенность создания h -файлов заключается в том, что они должны заканчиваться пустой строкой (после всех операторов, прототипов функций и пр.)

    Файлы Dec_2.c, Dec_8.c, Dec_16.c, base.h, hdec.h созданы с помощью инструментальной среды Microsoft Visual Studio 2010 при создании нового файла из меню File–New–File с последующим выбором: либо h -файлы, либо cpp -файлы с последующим установлением расширения языка С, т.е. . с. Созданные файлы следует сохранить в папке с основным программным котодом типа main.c.

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

    (*fun[0])(number);

    Десятичное число number передается в функцию dec2(). Нулевой индекс соответствует выбору функции перевода десятичного числа в двоичный эквивалент.

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

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

    (рис 17.6) Перевод десятичного числа в шестнадцатеричное

    Задание 3

  • Получаемые результаты проверьте с помощью встроенного калькулятора calc операционной системы Windows.
  • Все файлы проекта, кроме main.c, расположите на один уровень выше с помощью нотации ../.
  • Дополните программу вывода преобразованного десятичного числа во всех системах счисления одновременно, т.е. по основаниям 2, 8, 16.
  • Видоизмените программу так, чтобы функции преобразования десятичного числа возвращали преобразованное число в основной модуль программного проекта и в нем происходил вывод на консоль.
  • Пример 4. Напишите программу, в которой используется функция формирования накопительной суммы для заданного одномерного целочисленного массива, размерность которого вводится с клавиатуры пользователем.

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

    1 2 3 4 5 6 7.

    Тогда массив накопительной суммы (cumsum – cumulative sum of elements):

    1 3 6 10 15 21 28.

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

    // Файл main.c
    // Основной модуль проекта
    //#include <stdio.h>
    #include <conio.h>
    //#include <stdlib.h>
    #include <locale.h>
    #include "hcum.h"
    
    int main (void) {
    	int i, n;
    	int *M;// Указатель для исходного массива
    	int *N;// Указатель для накопительного массива
    
    // Для поддержки русских шрифтов
    setlocale(LC_ALL, ".1251"); 
    	printf("\n Введите размерность одномерного массива: ");
    	scanf_s("%d", amp;amp;amp;amp;n);
    
    	M = (int *)calloc(n, sizeof(int));
    
    	// Формирование исходного массива
    	for (i = 0; i < n; ++i)
    		M[i] = (i + 1);	
    
    printf("\n Исходный массив:\n ");
    	for (i = 0; i < n; ++i)	
    		if (M[i] < 10)
    			printf("%3d", M[i]); 
    
    		else if (M[i] == 10)
    			printf(" %3d", M[i]);
    		else if (M[i] > 10 amp;amp; M[i] < 100)
    			printf("%4d", M[i]); 
    		else
    			printf("%5d", M[i]); 
    
    	printf("\n\n Массив накопительной суммы:\n ");
    	// M - фактический аргумент функции cumsum()
    	N = cumsum(M, n);
    	for (i = 0; i < n; ++i) 
    		if (N[i] < 10)
    			printf("%3d", N[i]);
    		else if (N[i] == 10 )
    			printf(" %3d", N[i]); 
    		else if (N[i] > 10 amp;amp; N[i] < 100)
    			printf("%4d", N[i]); 
    		else
    			printf("%5d", N[i]); 
    
    // Освобождение выделенной памяти
    	free(M);
    	free(N);
    
    	printf("\n\n\n Нажмите любую клавишу (Press any key): ");
    	_getch();
    	return 0;
    }
    // Подключаемый заголовочный Файл hcum.h
    #include <stdlib.h>
    #include <stdio.h>
    #include <locale.h>
    
    // Прототип функции cumsum()
    int *(cumsum)(int *arr, int n);
    // Подключаемый файл fcum.c
    // Функция cumsum()
    #include "hcum.h"
    
    int *(cumsum)(int *arr, int n)
     {
    	int i;
    	int *SUM;// Указатель для накопительного массива
    
    
    	// Выделение памяти для нового указателя
    	SUM = (int *)calloc(n, sizeof(int));
    setlocale(LC_ALL, ".1251"); // для русских шрифтов
    	// Проверка выделенной памяти
    	if (SUM == NULL) {
    		printf("\n Память не выделена.\n");
    		exit(1); 	}
    	
    // Основной код формирования накопительной суммы
    	SUM[0] = arr[0];
    	for (i = 1; i < n; ++i)
    		SUM[i] = SUM[i-1] + arr[i];
    
    	return (SUM); 
    }

    Следует обратить внимание на расположение заголовочного файла stdlib.h – он впереди подключаемого файла fcum.c, поскольку в файле fcum.c используется динамическое распределение памяти, для чего требуется библиотека stdlib.h. Формирование накопительной суммы выполнено в разработанной функции fcum.c.

    В программе закомментированы библиотечные заголовочные файлы, которые включены в файл hcum.h. Сама накопительная функция cumsum() определена через указатель на функцию. Поэтому она возвращает указатель, который указывает на нулевой элемент массива накопительной суммы. Созданные файлы hcum.h и fcum.c расположены в папке проекта вместе с файлом главной функции main.c.

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

    (рис 17.7) Пример расчета накопительной суммы

    Задание 4

  • Файлы hcum.h и fcum.c расположите за пределами папки проекта. Применить различные комбинации расположения файлов hcum.h и fcum.c в различных папках данного диска. Обеспечьте работоспособность программы.
  • Внесите изменения в программу, чтобы она была работоспособной без заголовочного файла hcum.h.
  • Напишите программу формирования накопительной суммы вещественных чисел заданного массива. Формирование исходного массива выполните по случайному равномерному закону из интервала [–X; +X], где Х – номер компьютера, на котором выполняется лабораторная работа.
  • Напишите функцию для расчета накопительной суммы столбцов прямоугольной матрицы, размерность которой задается пользователем, и заполняется, например, натуральными числами.
  • Напишите функцию для расчета накопительной суммы строк прямоугольной матрицы, размерность которой задается пользователем, и заполняется, например, натуральными числами.
  • В программе предусмотрите вывод результатов в текстовый файл с именем compX.txt, где Х – номер компьютера, на котором выполняется лабораторная работа.
  • Пример 5. Напишите программу быстрой сортировки Хоара одномерного массива целых чисел с расположением функций в разных файлов [5]. Предусмотрите формирование одномерного массива случайным образом с динамическим распределением памяти.

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

    //   1-й файл с главной функцией - файл main.c 
    
    #include <stdio.h>
    #include <conio.h>
    #include <stdlib.h>
    #include <locale.h>
    #include <time.h>
    #include "hsort.h"// Созданный заголовочный файл
    	
    int main (void) {
    int i, n;
    	int *M;// Указатель для исходного массива
    	int Limit = 100;
    	time_t t; // переменная текущего времени
    
    // Рандомизация генератора псевдослучайных чисел
    	srand( (unsigned int) time(amp;t));
    
    // Для поддержки русских шрифтов
    	setlocale(LC_ALL, ".1251"); 
    
    	printf("\n\t Быстрая сортировка Хоара\n");
         printf("\n Введите размерность одномерного массива: ");
    	scanf_s("%d", amp;n);
    
    // Выделение памяти для заданной размерности массива
    	M = (int *)malloc(n*sizeof(int));
    
    // Формирование случайного исходного массива
    	for (i = 0; i < n; ++i)
    		M[i] = -Limit/2 + rand() % Limit; 	
    
    	printf("\n Исходный одномерный массив:\n");
    	for (i = 0; i < n; ++i)	
    		if (abs(M[i]) < 10) {
    			if (M[i] < 0)
    			printf("%4d", M[i]); 
    			else
    			printf("%3d", M[i]);
    		}
    		else {
    			if (M[i] < 0)
    			printf("%5d", M[i]);
    			else
    			printf("%4d", M[i]);
    		}
    
    printf("\n\n Отсортированный одномерный массив:\n");
    QuickSort(M, n);
    for (i = 0; i < n; ++i)	
    		if (abs(M[i]) < 10) {
    			if (M[i] < 0)
    			printf("%4d", M[i]); 
    			else
    			printf("%3d", M[i]);
    		}
    		else {
    			if (M[i] < 0)
    			printf("%5d", M[i]);
    			else
    			printf("%4d", M[i]);
    		}
    
    	printf("\n\n Нажмите любую клавишу: ");
    	_getch();
    	return 0;
    }
    // 2-файл - подключаемый заголовочный файл hsort.h 
    // с прототипом функции быстрой сортировки Хоара
    void QuickSort(int *A, int n);
    // 3-й файл - подключаемый файл my_sort.c 
    // с кодом быстрой сортировки Хоара
    
    void QuickSort (int *A, int n)
     {
    	int i, j, s;
    	int L, R;
    	int k, x;
    #define D 1000
    	struct stack {
    		int L;
    		int R;
    	} st[D];// имитация стека
    
    	s = 1; st[1].L = 0; st[1].R = n - 1;
    
    	do {
    		L = st[s].L; R = st[s].R; s--;
    		do {
    			i = L; j = R;
    			x = A[(L+R)/2]; // разделяющий элемент
    			
                   do {
    				while (A[i] < x)
    					i++;
    				while (x < A[j])
    					j--;
    				if (i <= j){
    					k = A[i]; A[i] = A[j]; A[j] = k;
    					i++; j--;
    				}// end if
    
    			} while (i < j); // end 3d do
    
    			if (i < R)
    			{s++; st[s].L = i; st[s].R = R;}
    			
                R = j;
    		} while (L < R);// end 2nd do
    	} while (s != 0);// end 1st do
    
    }

    Цикл do–while применен для того, чтобы тело цикла выполнялось хотя бы один раз.

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

    (рис 17.8) Пример сортировки одномерного массива

    Задание 5

  • Произведите сборку проекта из трех предложенных файлов.
  • Напишите программу с сохранением исходного массива и полученного отсортированного массива.
  • Выведите результаты в текстовый файл с именем compX.txt, где Х – номер компьютера, на котором выполняется лабораторная работа. В текстовом файле массивы выведите в виде двух столбцов: исходный массив и отсортированный массив. Размер массивов не менее 10.
  • Измените программу: исключите заголовочный файл hsort.h.
  • Введите изменения в программу для сортировки вещественных чисел.
  • Пример 6. Напишите программу вычисления степенного полинома в заданной точке по схеме Горнера.

    Пусть задан полином в следующем виде:

    $$P(x)=a_o x^n+a_1x^{n-1}+\cdots+a_{n-1}x+a_n.$$

    Алгоритм схемы Горнера осуществляется при помощи формулы [17.6]:

    $$P(x)=a_o+x(a_1+x(a_2+\cdots+x(a_{n-1}+xa_n)\cdots)).$$

    Полагая, что $$u_n=a_n,$$ расчет полинома в заданной точке производится по следующей итерационной формуле [17.6]:

    $$u_k=x_ou_{k+1}+a_k,k=n-1,\cdots,1,10.$$

    Для программной реализации примера примем следующий полином:

    $$P(x)=x^4+2x^3+3x^2+4x+5.$$

    Рассчитаем значение полинома в точке $$х_0 = 2.$$

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

    #include <stdio.h>
    #include <conio.h>
    #include <locale.h>
    
    // Размерность массива коэффициентов полинома
    #define N 5
    // Главная функция
    int main (void)  {
    	int i;
    
    // Массив коэффициентов полинома
    double A[ ] = {1.0, 2.0, 3.0, 4.0, 5.0};
    double x0 = 2.0, y;
    
    // Для русских шрифтов
    setlocale(LC_ALL, ".1251");
    
    // Для вывода чисел с плавающей точкой
    setlocale(LC_NUMERIC, "English");
    // Консольный заголовок
      printf("\n\t Применение схемы Горнера\n");
    printf(" Вычисление полинома P(x) %d порядка в точке х = %1.4f:\n", N-1, x0);
    
    // Основной цикла расчета по схеме Горнера
    y = A[0];
    
    for ( i = N-2; i >= 0; --i )
    y = y*x0 + A[(N-1)-i];
    
    // Вывод результата
    printf("\n\t P(x) = %0.4f \n", y );
    
    printf("\n Нажмите любую клавишу (Press any key): \a");
    _getch();
    return 0;
    }

    Следует обратить внимание на индексацию массива, который находится в теле цикла. В последней функции printf() использован спецификатор "\a" для подачи звукового сигнала.

    Результат выполнения программы показан на рис 17.9.

    (рис 17.9) Результат вычисления полинома по схеме Горнера

    Задание 6

  • Примените оператор цикла do – while вместо оператора цикла for.
  • Создайте подключаемый файл, в котором реализуется схема Горнера. Предусмотрите вызов этого файла из главной функции.
  • Предусмотрите ввод коэффициентов полинома с клавиатуры. Далее предусмотрите обращение к созданному файлу с реализацией схемы Горнера.
  • Напишите программу символической записи на консоли заданного полинома по известным коэффициентам. Оформите этот фрагмент программы в виде подключаемого файла.
  • Предусмотрите запись в текстовый файл символического представления полинома с заданными коэффициентами и значения полинома в заданной точке. Имя файла примите compX.txt, где X – номер компьютера, на котором выполняется лабораторная работа.
  • Контрольные вопросы

  • Как рекомендуется организовать внутреннюю работу пользовательских функций по отношению к другим функциям в программах на языке С? Перечислите основные правила организации внутренней работы функций и достоинства этих правил.
  • В чем заключается основное назначение заголовочных файлов ( h -файлов) в проектах языка С?
  • Как следует объявить функцию, чтобы доступ к ней был невозможен за пределами файла, где она была определена?
  • Как следует объявить функцию, чтобы к ней можно было обращаться из других функций проекта?
  • Какие классификаторы классов памяти поддерживает стандарт языка С?
  • Какой классификатор памяти используется по умолчанию в программах на языке С?
  • Какие расширения можно применить к файлам, содержащим пользовательские функции?
  • Как осуществляется компиляция файлов с пользовательскими функциями в программной среде Visual Studio?
  • Как осуществляется подключение файлов с пользовательскими функциями, которые расположены на различных логических дисках компьютера?
  • Страницы:

    Теоретическая часть

    Программа на языке С – это совокупность функций. Запуск любой программы начинается с запуска главной функции, содержащей в себе всю остальную часть программы. Внутри главной функции для реализации заданного алгоритма вызываются все другие необходимые функции. Часть функций создается самим программистом, другая часть – библиотечные функции – поставляется пользователю со средой программирования и используется в процессе разработки программ (например, printf(), sqrt() и др.).

    Простейший метод использования нескольких функций требует их размещения в одном и том же файле. Затем выполняется компиляция этого файла, как если бы он содержал единственную функцию [17.1]. Другие подходы к решению этой проблемы существенно зависят от конкретной операционной системы (Unix-подобные системы, Windows, Macintosh). Компиляторы операционных систем Windows и Macintosh представляют собой компиляторы, ориентированные на проекты [17.1]. Проект описывает ресурсы, используемые программой. Эти ресурсы включают файлы исходного программного кода.

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

    Хороший тон в программировании рекомендует размещать прототипы функций и объявлять их константы в заголовочном файле [17.1]. Назначение отдельных задач отдельным функциям способствует улучшениям программы.

    Функция может быть либо внешней (по умолчанию), либо статической. К внешней функции доступ могут осуществлять функции из других файлов, в то же время статическая функция может использоваться только в файле, в котором она определена [17.1]. Например, возможны следующие объявления функций:

    double gamma(); // внешняя функция по умолчанию
    static double beta();
    extern double delta();

    Функция gamma() и delta() могут использоваться функциями из других файлов, которые являются частью программы, тогда как beta() – нет. В силу этого применение функции beta() ограничено одним файлом, поэтому в других файлах можно использовать функции с тем же именем. Одна из причин использования класса статической памяти заключается в необходимости создания функций, приватных для конкретных модулей, благодаря чему во многих случаях удается избежать конфликта имен [17.1].

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

    Одним из золотых правил для надежного программирования есть принцип "необходимости знать", или принцип минимально необходимой области видимости [17.1]. Рекомендуется держать всю внутреннюю работу каждой функции максимально закрытой по отношению к другим функциям, используя совместно только те переменные, без которых нельзя обойтись по логике программы. Другие классы памяти полезны, и ими можно воспользоваться. Однако всякий раз следует задать вопрос: а есть ли в этом необходимость?

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

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

    Стандарт языка С поддерживает 4 спецификатора класса памяти [17.2]:

  • extern
  • static
  • register
  • auto
  • Спецификаторы сообщают компилятору, как он должен разместить соответствующие переменные в памяти. Спецификатор класса памяти в объявлении всегда должен стоять первым [2].

    Приведем характеристику спецификаторов классов памяти [17.2].

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

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

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

    Пример использования спецификатора extern при использовании глобальных переменных:

    #include <stdio.h>
    #include <conio.h>
    
    // Главная функция
    int main (void) {
    // объявление глобальных переменных
    	extern int a, b;
    	
    printf("\n\t a = %d; b = %d\n", a, b);
    printf("\n Press any key: ");
    _getch();
    return 0;  }
    
    // инициализация (описание) глобальных переменных
    int a = 33, b = 34;

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

    При компиляции выполняются следующие правила:

  • Если компилятор находит переменную, не объявленную внутри блока, он ищет ее объявление во внешних блоках.
  • Если не находит ее там, то ищет среди объявлений глобальных переменных.
  • Спецификатор extern играет большую роль в программах, состоящих из многих файлов [17.3]. В языке С программа может быть записана в нескольких файлах, которые компилируются раздельно, а затем компонуются в одно целое. В этом случае необходимо как-то сообщить всем файлам о глобальных переменных программы. Самый лучший (и наиболее переносимый) способ сделать это – определить (описать) все глобальные переменные в одном файле и объявить их со спецификатором extern в остальных файлах, например, как это сделано в следующей программе:

    Первый файл ( main.c ) Второй файл ( second.h )
    #include <stdio.h>
    #include <conio.h>
    #include "D:\second.h"
    
    int x = 99, y = 77;
    char ch;
    void func1(void);
    int main(void)
    {
    ch = 'Z';
    func1();
    
    printf("\n Press any key: ");
    _getch();
    return 0;
    }
    
    void func1(void)
    {
    func22();
    func23();
    	
    printf("\n\t x = %d; y = %d;\
     ch = %c\n", x, y, ch);
    }
    extern int x, y;
    extern char ch;
    
    void func22(void)
    {
    y = 100;
    }
    
    void func23(void)
    {
    x = y/10;
    ch = 'R';
    
    }

    В программе первый файл – это основная часть программного проекта. Второй файл создан как текстовый файл (с помощью блокнота) с расширением *.h. Список глобальных переменных ( x, y, ch ) копируется из первого файла во второй, а затем добавляется спецификатор extern. Он сообщает компилятору, что имена и типы переменных, следующих далее, объявлены в другом месте. Все ссылки на внешние переменные распознаются в процессе редактирования связей. Подключение второго файла выполнено с указанием имени диска (D:), на котором расположен файл second.h.

    Для подключения имени файла, созданного пользователем, его заключают в двойные кавычки.

    Результат выполнения программы показан на рис 17.1

    (рис 17.1) Результат выполнения программы, состоящей из двух файлов

    В общем случае h -файл (например, second.h ) формируется редактором кода: надо создать заготовку обычным способом, очистить все поле редактора и записать в это поле необходимые данные (программный код созданной функции). Затем выполнить команду главного меню: File/Save as и выбрать для сохраняемого файла расширение .h в раскрывающемся списке типов сохраняемого файла: C++ Header Files (*.h; *.hh; *.hpp; *.hxx; *.inl; *.tlh; *.tli). Сохраненный файл с расширением .h следует подключить к проекту. Для этого потребуется в узле Solution Explorer навести курсор мыши к папке Header Files и правой кнопкой мыши выбрать Add – Existing Item сохраненный файл second.h. Затем с помощью оператора #include файл следует включить в основную программу.

    Другой способ, реализуемый в Microsoft Visual Studio 2010, состоит в том, что сразу через пункт меню .

    (рис 17.2) Процесс создания нового файла с расширением .h

    Далее в правом нижнем углу нажмем клавишу Open. Откроется пустое поле – заготовка для набора необходимого кода. По умолчанию этот файл имеет имя Header1.h. При повторном создании заголовочного файла это будет Header2.h и т.д. После написания кода можно сохранить этот заголовочный файл по желанию в любом каталоге с любым (допустимым) именем (а расширение остается .h ).

    Спецификатор 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 нет значения по умолчанию.

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

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

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

    (рис 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) Матрица планирования N = 24"

    Задание 1

  • Проверьте работу программ при записи результата в файл. В качестве имени текстового файла примите compX.txt, где Х – номер компьютера, на котором выполняется лабораторная работа.
  • В файле hfile.h поменяйте местами подключаемые файлы. Добавьте необходимые изменения, чтобы программа работала корректно (без предупреждений).
  • Вместо разработанной функции degree() примените стандартную функцию pow().
  • В программу добавьте изменения для вывода матрицы планирования при зеркальном отображении столбцов.
  • В матрицу планирования добавьте в качестве первого столбца массив положительных единиц, т.е. +1.
  • Пример 2. Напишите программу вывода на консоль содержимого текстового файла, расположенного в заданном каталоге. Вывод на консоль содержимого файла оформить в виде отдельного файла.

    Предположим, что программный проект расположен на диске Е:\Project_C. Текстовый файл расположен на диске D:\data16.txt. Пусть содержимое текстового файла будет следующим:

    1, 2, 3, 4, 5
     Programming in C
     department: electronic engineering

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

    // Подключаемый файл fun1.c
    #include <stdlib.h> // Для функции exit()
    
    void print_file(char *st) { 
    char str[79+1];
    FILE *fid;
    printf("\n Data from \"%s\":\n", st);
    
    if ( (fopen_s(amp;fid, st, "r")) ) {
    printf("\n File could not be opened.\n");
    
    printf("\n Break. Press any key: ");
    _getch(); exit(1); }
    
    printf("\n Data from the file \"%s"\n", st);
    while (!feof(fid)) {
    fgets(str, 79, fid);
    printf("%s", str);
    }
    	
    fclose(fid);
    printf("\n");
    }
    // Основной программный модуль проекта main.c
    #include <stdio.h>
    #include <conio.h>
    #include "D:\\fun1.c"
    
    // Главная функция
    int main (void) {
    	char str[79+1];
    	printf("\n Enter the file name and path: ");
    	gets_s(str, 79);
    
    // Вызов функции распечатки файла	
    	print_file(str);
    	
    	printf("\n Press any key: ");
    	_getch();
         return 0;
     }

    Результат выполнения программы показан на рис 17.5.

    (рис 17.5) Содержимое заданного текстового файла

    Задание 2

  • На консоль выведите содержимое подключаемого файла fun1.c.
  • Измените расположение подключаемого файла, например, поместите его на диск D:\students\fun1.c.
  • В текстовый файл запишите фамилию и имя пользователя (студента), год рождения, год поступления в университет, наименование специальности.
  • Содержимое текстового файла предыдущего пункта выведите на консоль и запишите в другой текстовый файл с именем compX.txt, где Х – номер компьютера, на котором выполняется лабораторная работа. Запись в текстовый файл оформите в виде функции surname.c, где surname – фамилия пользователя.
  • В программе вместо указателя, являющегося формальным параметром функции print_file(), примените массив символов.
  • Пример 3. Напишите программу преобразования десятичной системы счисления в двоичную, восьмеричную, шестнадцатеричную на основе системы меню. Примените массив указателей на функцию, используйте заголовочные файлы.

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

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

    // Основной программный модуль main.c
    #include <stdio.h>
    #include <conio.h>
    #include "hdec.h"
    
    int main (void) {
    int index[3] = {0, 1, 2};
    long int number;
    int basen;
    
    void (*fun[3])(long int) = {dec2, dec8, dec16};
    
    printf("\n Enter a positive integer numbers: ");
    scanf_s("%ld", amp;amp;amp;amp;number);
    printf("\n Select base system notation (2, 8, 16): ");
    scanf_s("%d", amp;basen);
    if (basen == 2)
    (*fun[0])(number);
    else if (basen == 8)
    (*fun[1])(number);
    else if (basen == 16)
    (*fun[2])(number);
    else {
    printf("\n\t Error choice.");
    printf("\n Break. Press any key: ");
    _getch();
    return 0;
    }
    
    printf("\n\n Press any key: ");
    _getch();
    return 0;
    }
    // Подключаемый файл Dec_2.c
    // Перевод в двоичную систему счисления
    #include "base.h"
    void dec2 (long int x) {
    int i = 0;
    int Num[64];
    int next;
    long int xx = x;
    
    do {
    Num[i] = x % base2; 
    
    ++i;
    x /= base2;
    
    } while (x != 0);
    
    // Результат в обратном порядке
    printf("\n Decimal number \"%ld\" has a binary equivalent:\n", xx);
    for (--i; i >= 0; --i) {
    next = Num[i];
    printf("%2d", next);
    }
    }
    // Подключаемый файл Dec_8.c
    // Перевод в восьмеричную систему счисления
    #include "base.h"
    
    void dec8 (long int y) {
    int i = 0, next;
    int Num[64];
    long int yy = y;
    
    do {
    Num[i] = y % base8; 
    ++i;
    y /= base8;
    
    } while (y != 0);
    
    // Результат в обратном порядке
    printf("\n Decimal number \"%ld\" has an octal equivalent:\n", yy);
    for (--i; i >= 0; --i) {
    next = Num[i];
    printf("%2d", next);
    }
    }
    // Подключаемый файл Dec_16.c
    // Перевод в шестнадцатеричную систему счисления
    #include "base.h"
    
    void dec16 (long int z) {
    int i = 0;
    int Num[64];
    int next;
    long int zz = z;
    
    const char digits_16[16] = {
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
    'A', 'B', 'C', 'D', 'E', 'F' };
    
    do {
    Num[i] = z % base16; 
    ++i;
    z /= base16;
    
    } while (z != 0);
    
    // Результат в обратном порядке
    printf("\n Decimal number \"%ld\" has a hexadecimal equivalent:\n", zz);
    for (--i; i >= 0; --i) {
    next = Num[i];
    printf("%2c", digits_16[next]);
    }
    }
    // Подключаемый файл base.h
    // Основания систем счисления
    #define base2 2
    #define base8 8
    #define base16 16
    // Подключаемый файл hdec.h
    // Поключение файлов с функциями преобразования систем 
    // счисления
    
    #include "Dec_2.c"
    #include "Dec_8.c"
    #include "Dec_16.c"
    
    // Прототипы функций преобразования систем счисления
    void dec2 (long int a);
    void dec8 (long int b);
    void dec16 (long int c);

    В программе используется заголовочный файл base.h в целях демонстрации препроцессорных директив языка С. Другой заголовочный файл hdec.h подключает файлы с программами преобразования чисел и прототипы функций. Внимание! Особенность создания h -файлов заключается в том, что они должны заканчиваться пустой строкой (после всех операторов, прототипов функций и пр.)

    Файлы Dec_2.c, Dec_8.c, Dec_16.c, base.h, hdec.h созданы с помощью инструментальной среды Microsoft Visual Studio 2010 при создании нового файла из меню File–New–File с последующим выбором: либо h -файлы, либо cpp -файлы с последующим установлением расширения языка С, т.е. . с. Созданные файлы следует сохранить в папке с основным программным котодом типа main.c.

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

    (*fun[0])(number);

    Десятичное число number передается в функцию dec2(). Нулевой индекс соответствует выбору функции перевода десятичного числа в двоичный эквивалент.

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

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

    (рис 17.6) Перевод десятичного числа в шестнадцатеричное

    Задание 3

  • Получаемые результаты проверьте с помощью встроенного калькулятора calc операционной системы Windows.
  • Все файлы проекта, кроме main.c, расположите на один уровень выше с помощью нотации ../.
  • Дополните программу вывода преобразованного десятичного числа во всех системах счисления одновременно, т.е. по основаниям 2, 8, 16.
  • Видоизмените программу так, чтобы функции преобразования десятичного числа возвращали преобразованное число в основной модуль программного проекта и в нем происходил вывод на консоль.
  • Пример 4. Напишите программу, в которой используется функция формирования накопительной суммы для заданного одномерного целочисленного массива, размерность которого вводится с клавиатуры пользователем.

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

    1 2 3 4 5 6 7.

    Тогда массив накопительной суммы (cumsum – cumulative sum of elements):

    1 3 6 10 15 21 28.

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

    // Файл main.c
    // Основной модуль проекта
    //#include <stdio.h>
    #include <conio.h>
    //#include <stdlib.h>
    #include <locale.h>
    #include "hcum.h"
    
    int main (void) {
    	int i, n;
    	int *M;// Указатель для исходного массива
    	int *N;// Указатель для накопительного массива
    
    // Для поддержки русских шрифтов
    setlocale(LC_ALL, ".1251"); 
    	printf("\n Введите размерность одномерного массива: ");
    	scanf_s("%d", amp;amp;amp;amp;n);
    
    	M = (int *)calloc(n, sizeof(int));
    
    	// Формирование исходного массива
    	for (i = 0; i < n; ++i)
    		M[i] = (i + 1);	
    
    printf("\n Исходный массив:\n ");
    	for (i = 0; i < n; ++i)	
    		if (M[i] < 10)
    			printf("%3d", M[i]); 
    
    		else if (M[i] == 10)
    			printf(" %3d", M[i]);
    		else if (M[i] > 10 amp;amp; M[i] < 100)
    			printf("%4d", M[i]); 
    		else
    			printf("%5d", M[i]); 
    
    	printf("\n\n Массив накопительной суммы:\n ");
    	// M - фактический аргумент функции cumsum()
    	N = cumsum(M, n);
    	for (i = 0; i < n; ++i) 
    		if (N[i] < 10)
    			printf("%3d", N[i]);
    		else if (N[i] == 10 )
    			printf(" %3d", N[i]); 
    		else if (N[i] > 10 amp;amp; N[i] < 100)
    			printf("%4d", N[i]); 
    		else
    			printf("%5d", N[i]); 
    
    // Освобождение выделенной памяти
    	free(M);
    	free(N);
    
    	printf("\n\n\n Нажмите любую клавишу (Press any key): ");
    	_getch();
    	return 0;
    }
    // Подключаемый заголовочный Файл hcum.h
    #include <stdlib.h>
    #include <stdio.h>
    #include <locale.h>
    
    // Прототип функции cumsum()
    int *(cumsum)(int *arr, int n);
    // Подключаемый файл fcum.c
    // Функция cumsum()
    #include "hcum.h"
    
    int *(cumsum)(int *arr, int n)
     {
    	int i;
    	int *SUM;// Указатель для накопительного массива
    
    
    	// Выделение памяти для нового указателя
    	SUM = (int *)calloc(n, sizeof(int));
    setlocale(LC_ALL, ".1251"); // для русских шрифтов
    	// Проверка выделенной памяти
    	if (SUM == NULL) {
    		printf("\n Память не выделена.\n");
    		exit(1); 	}
    	
    // Основной код формирования накопительной суммы
    	SUM[0] = arr[0];
    	for (i = 1; i < n; ++i)
    		SUM[i] = SUM[i-1] + arr[i];
    
    	return (SUM); 
    }

    Следует обратить внимание на расположение заголовочного файла stdlib.h – он впереди подключаемого файла fcum.c, поскольку в файле fcum.c используется динамическое распределение памяти, для чего требуется библиотека stdlib.h. Формирование накопительной суммы выполнено в разработанной функции fcum.c.

    В программе закомментированы библиотечные заголовочные файлы, которые включены в файл hcum.h. Сама накопительная функция cumsum() определена через указатель на функцию. Поэтому она возвращает указатель, который указывает на нулевой элемент массива накопительной суммы. Созданные файлы hcum.h и fcum.c расположены в папке проекта вместе с файлом главной функции main.c.

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

    (рис 17.7) Пример расчета накопительной суммы

    Задание 4

  • Файлы hcum.h и fcum.c расположите за пределами папки проекта. Применить различные комбинации расположения файлов hcum.h и fcum.c в различных папках данного диска. Обеспечьте работоспособность программы.
  • Внесите изменения в программу, чтобы она была работоспособной без заголовочного файла hcum.h.
  • Напишите программу формирования накопительной суммы вещественных чисел заданного массива. Формирование исходного массива выполните по случайному равномерному закону из интервала [–X; +X], где Х – номер компьютера, на котором выполняется лабораторная работа.
  • Напишите функцию для расчета накопительной суммы столбцов прямоугольной матрицы, размерность которой задается пользователем, и заполняется, например, натуральными числами.
  • Напишите функцию для расчета накопительной суммы строк прямоугольной матрицы, размерность которой задается пользователем, и заполняется, например, натуральными числами.
  • В программе предусмотрите вывод результатов в текстовый файл с именем compX.txt, где Х – номер компьютера, на котором выполняется лабораторная работа.
  • Пример 5. Напишите программу быстрой сортировки Хоара одномерного массива целых чисел с расположением функций в разных файлов [5]. Предусмотрите формирование одномерного массива случайным образом с динамическим распределением памяти.

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

    //   1-й файл с главной функцией - файл main.c 
    
    #include <stdio.h>
    #include <conio.h>
    #include <stdlib.h>
    #include <locale.h>
    #include <time.h>
    #include "hsort.h"// Созданный заголовочный файл
    	
    int main (void) {
    int i, n;
    	int *M;// Указатель для исходного массива
    	int Limit = 100;
    	time_t t; // переменная текущего времени
    
    // Рандомизация генератора псевдослучайных чисел
    	srand( (unsigned int) time(amp;t));
    
    // Для поддержки русских шрифтов
    	setlocale(LC_ALL, ".1251"); 
    
    	printf("\n\t Быстрая сортировка Хоара\n");
         printf("\n Введите размерность одномерного массива: ");
    	scanf_s("%d", amp;n);
    
    // Выделение памяти для заданной размерности массива
    	M = (int *)malloc(n*sizeof(int));
    
    // Формирование случайного исходного массива
    	for (i = 0; i < n; ++i)
    		M[i] = -Limit/2 + rand() % Limit; 	
    
    	printf("\n Исходный одномерный массив:\n");
    	for (i = 0; i < n; ++i)	
    		if (abs(M[i]) < 10) {
    			if (M[i] < 0)
    			printf("%4d", M[i]); 
    			else
    			printf("%3d", M[i]);
    		}
    		else {
    			if (M[i] < 0)
    			printf("%5d", M[i]);
    			else
    			printf("%4d", M[i]);
    		}
    
    printf("\n\n Отсортированный одномерный массив:\n");
    QuickSort(M, n);
    for (i = 0; i < n; ++i)	
    		if (abs(M[i]) < 10) {
    			if (M[i] < 0)
    			printf("%4d", M[i]); 
    			else
    			printf("%3d", M[i]);
    		}
    		else {
    			if (M[i] < 0)
    			printf("%5d", M[i]);
    			else
    			printf("%4d", M[i]);
    		}
    
    	printf("\n\n Нажмите любую клавишу: ");
    	_getch();
    	return 0;
    }
    // 2-файл - подключаемый заголовочный файл hsort.h 
    // с прототипом функции быстрой сортировки Хоара
    void QuickSort(int *A, int n);
    // 3-й файл - подключаемый файл my_sort.c 
    // с кодом быстрой сортировки Хоара
    
    void QuickSort (int *A, int n)
     {
    	int i, j, s;
    	int L, R;
    	int k, x;
    #define D 1000
    	struct stack {
    		int L;
    		int R;
    	} st[D];// имитация стека
    
    	s = 1; st[1].L = 0; st[1].R = n - 1;
    
    	do {
    		L = st[s].L; R = st[s].R; s--;
    		do {
    			i = L; j = R;
    			x = A[(L+R)/2]; // разделяющий элемент
    			
                   do {
    				while (A[i] < x)
    					i++;
    				while (x < A[j])
    					j--;
    				if (i <= j){
    					k = A[i]; A[i] = A[j]; A[j] = k;
    					i++; j--;
    				}// end if
    
    			} while (i < j); // end 3d do
    
    			if (i < R)
    			{s++; st[s].L = i; st[s].R = R;}
    			
                R = j;
    		} while (L < R);// end 2nd do
    	} while (s != 0);// end 1st do
    
    }

    Цикл do–while применен для того, чтобы тело цикла выполнялось хотя бы один раз.

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

    (рис 17.8) Пример сортировки одномерного массива

    Задание 5

  • Произведите сборку проекта из трех предложенных файлов.
  • Напишите программу с сохранением исходного массива и полученного отсортированного массива.
  • Выведите результаты в текстовый файл с именем compX.txt, где Х – номер компьютера, на котором выполняется лабораторная работа. В текстовом файле массивы выведите в виде двух столбцов: исходный массив и отсортированный массив. Размер массивов не менее 10.
  • Измените программу: исключите заголовочный файл hsort.h.
  • Введите изменения в программу для сортировки вещественных чисел.
  • Пример 6. Напишите программу вычисления степенного полинома в заданной точке по схеме Горнера.

    Пусть задан полином в следующем виде:

    $$P(x)=a_o x^n+a_1x^{n-1}+\cdots+a_{n-1}x+a_n.$$

    Алгоритм схемы Горнера осуществляется при помощи формулы [17.6]:

    $$P(x)=a_o+x(a_1+x(a_2+\cdots+x(a_{n-1}+xa_n)\cdots)).$$

    Полагая, что $$u_n=a_n,$$ расчет полинома в заданной точке производится по следующей итерационной формуле [17.6]:

    $$u_k=x_ou_{k+1}+a_k,k=n-1,\cdots,1,10.$$

    Для программной реализации примера примем следующий полином:

    $$P(x)=x^4+2x^3+3x^2+4x+5.$$

    Рассчитаем значение полинома в точке $$х_0 = 2.$$

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

    #include <stdio.h>
    #include <conio.h>
    #include <locale.h>
    
    // Размерность массива коэффициентов полинома
    #define N 5
    // Главная функция
    int main (void)  {
    	int i;
    
    // Массив коэффициентов полинома
    double A[ ] = {1.0, 2.0, 3.0, 4.0, 5.0};
    double x0 = 2.0, y;
    
    // Для русских шрифтов
    setlocale(LC_ALL, ".1251");
    
    // Для вывода чисел с плавающей точкой
    setlocale(LC_NUMERIC, "English");
    // Консольный заголовок
      printf("\n\t Применение схемы Горнера\n");
    printf(" Вычисление полинома P(x) %d порядка в точке х = %1.4f:\n", N-1, x0);
    
    // Основной цикла расчета по схеме Горнера
    y = A[0];
    
    for ( i = N-2; i >= 0; --i )
    y = y*x0 + A[(N-1)-i];
    
    // Вывод результата
    printf("\n\t P(x) = %0.4f \n", y );
    
    printf("\n Нажмите любую клавишу (Press any key): \a");
    _getch();
    return 0;
    }

    Следует обратить внимание на индексацию массива, который находится в теле цикла. В последней функции printf() использован спецификатор "\a" для подачи звукового сигнала.

    Результат выполнения программы показан на рис 17.9.

    (рис 17.9) Результат вычисления полинома по схеме Горнера

    Задание 6

  • Примените оператор цикла do – while вместо оператора цикла for.
  • Создайте подключаемый файл, в котором реализуется схема Горнера. Предусмотрите вызов этого файла из главной функции.
  • Предусмотрите ввод коэффициентов полинома с клавиатуры. Далее предусмотрите обращение к созданному файлу с реализацией схемы Горнера.
  • Напишите программу символической записи на консоли заданного полинома по известным коэффициентам. Оформите этот фрагмент программы в виде подключаемого файла.
  • Предусмотрите запись в текстовый файл символического представления полинома с заданными коэффициентами и значения полинома в заданной точке. Имя файла примите compX.txt, где X – номер компьютера, на котором выполняется лабораторная работа.
  • Контрольные вопросы

  • Как рекомендуется организовать внутреннюю работу пользовательских функций по отношению к другим функциям в программах на языке С? Перечислите основные правила организации внутренней работы функций и достоинства этих правил.
  • В чем заключается основное назначение заголовочных файлов ( h -файлов) в проектах языка С?
  • Как следует объявить функцию, чтобы доступ к ней был невозможен за пределами файла, где она была определена?
  • Как следует объявить функцию, чтобы к ней можно было обращаться из других функций проекта?
  • Какие классификаторы классов памяти поддерживает стандарт языка С?
  • Какой классификатор памяти используется по умолчанию в программах на языке С?
  • Какие расширения можно применить к файлам, содержащим пользовательские функции?
  • Как осуществляется компиляция файлов с пользовательскими функциями в программной среде Visual Studio?
  • Как осуществляется подключение файлов с пользовательскими функциями, которые расположены на различных логических дисках компьютера?
  • Вернуться к учебному плану