Программа на языке С – это совокупность функций. Запуск любой программы начинается с запуска главной функции, содержащей в себе всю остальную часть программы. Внутри главной функции для реализации заданного алгоритма вызываются все другие необходимые функции. Часть функций создается самим программистом, другая часть – библиотечные функции – поставляется пользователю со средой программирования и используется в процессе разработки программ (например, printf(), sqrt() и др.).
Простейший метод использования нескольких функций требует их размещения в одном и том же файле. Затем выполняется компиляция этого файла, как если бы он содержал единственную функцию [17.1]. Другие подходы к решению этой проблемы существенно зависят от конкретной операционной системы (Unix-подобные системы, Windows, Macintosh). Компиляторы операционных систем Windows и Macintosh представляют собой компиляторы, ориентированные на проекты [17.1]. Проект описывает ресурсы, используемые программой. Эти ресурсы включают файлы исходного программного кода.
Если поместить главную функцию main() в один файл, а определения собственной функции программиста – во второй файл, то первому файлу нужны прототипы функций. Для этого можно хранить прототипы функций в одном из заголовочных файлов.
Хороший тон в программировании рекомендует размещать прототипы функций и объявлять их константы в заголовочном файле [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 спецификатора
Спецификаторы сообщают компилятору, как он должен разместить соответствующие переменные в памяти. Спецификатор
Приведем характеристику спецификаторов
В языке С при редактировании связей к переменной может применяться одно из трех связываний: внутреннее, внешнее или же не относящееся ни к одному из этих типов. В общем случае к именам функций и глобальных переменных применяется внешнее связывание. Это означает, что после компоновки они будут доступны во всех файлах, составляющих программу. К объектам, объявленным со спецификатором 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. Он сообщает компилятору, что имена и типы переменных, следующих далее, объявлены в другом месте. Все ссылки на
Для подключения имени файла, созданного пользователем, его заключают в двойные кавычки.
Результат выполнения программы показан на рис 17.1
(рис 17.1) Результат выполнения программы, состоящей из двух файловВ общем случае h -файл (например, second.h ) формируется редактором кода: надо создать заготовку обычным способом, очистить все поле редактора и записать в это поле необходимые данные (программный код созданной функции). Затем выполнить команду главного меню: File/Save as и выбрать для сохраняемого файла расширение .h в раскрывающемся списке типов сохраняемого файла: C++ Header Files (*.h; *.hh; *.hpp; *.hxx; *.inl; *.tlh; *.tli). Сохраненный файл с расширением .h следует подключить к проекту. Для этого потребуется в узле Solution Explorer навести курсор мыши к папке #include файл следует включить в основную программу.
Другой способ, реализуемый в Microsoft Visual Studio 2010, состоит в том, что сразу через пункт меню .
(рис 17.2) Процесс создания нового файла с расширением .hДалее в правом нижнем углу нажмем клавишу Open. Откроется пустое поле – заготовка для набора необходимого кода. По умолчанию этот файл имеет имя Header1.h. При повторном создании заголовочного файла это будет Header2.h и т.д. После написания кода можно сохранить этот заголовочный файл по желанию в любом каталоге с любым (допустимым) именем (а расширение остается .h ).
Переменные, объявленные со спецификатором static, хранятся постоянно внутри своей функции или файла. В отличие от глобальных переменных они невидимы за пределами своей функции или файла, но они сохраняют значение между вызовами [17.2]. Спецификатор static воздействует на локальные и глобальные переменные по-разному.
Коренное отличие статических локальных от глобальных переменных заключается в том, что статические локальные переменные видны только внутри блока, в котором они объявлены. Если бы не было
При инициализации статической локальной переменной следует учитывать, что значение присваивается ей только один раз – в начале работы всей программы, но не при каждом входе в блок программы, как обычной локальной переменной.
Спецификатор static в объявлении глобальной переменной заставляет компилятор создать глобальную переменную, видимую только в том файле, в котором она объявлена. В таком случае статическая глобальная переменная подвергается внутреннему связыванию.
Таким образом, имена локальных статических переменных видимы только внутри блока, в котором они объявлены; имена глобальных статических переменных видимы только внутри файла, в котором они объявлены [17.2].
Спецификатор register был разработан для того, чтобы компилятор сохранял значение переменной в регистре центрального процессора, а не в памяти, как обычно. Это означает, что операции над регистровыми переменными выполняются намного быстрее [17.2].
Спецификатор register можно применять только к локальным переменным и формальным параметрам функций. В объявлении глобальных переменных применение спецификатора register не допускается.
В языке программирования С с помощью оператора нельзя получить адрес регистровой переменной, потому что она может храниться в регистре процессора, который обычно не имеет адреса.
Ощутимый эффект от спецификатора register может быть получен только для переменных целого ( int ) и символьного ( char ) типа.
Спецификатор auto присваивает объявляемым объектам auto одновременно являются определениями и резервируют память. auto определяет переменные с автоматическим хранением явным образом.
Автоматическое хранение способствует экономии памяти, поскольку
Для переменных со спецификатором 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 ) и двух подключаемых функций, созданных программистом (например, , print() ). В файле myfile3.c поместим функцию , а функцию 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(), поэтому в теле функции можно обращаться к функциям и print(). Функция предназначена для возведения целого числа в степень. Она формирует матрицу планирования (с помощью указателя *PTR ) и вывода значений матрицы на консоль и (или) в текстовый файл. Имеет важное значение очередность подключения файлов myfile3.c и myfile2.c. Следует обратить внимание на синтаксис заключения в двойные ковычки имени текстового файла data.txt. Функция возвращает наименьшее целое (представленное в виде значения с плава
ющей точкой), которое больше своего аргумента или равно ему [17.2]. Она включена на тот
случай, если пользователь введет не целое число
Возможный результат выполнения программы при выводе искомой матрицы на консоль показан на рис 17.4.
(рис 17.4) Матрица планирования N = 24"
Задание 1
degree () примените стандартную функцию pow().Пример 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
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
../.Пример 4. Напишите программу, в которой используется функция формирования накопительной суммы для заданного одномерного целочисленного массива, размерность которого вводится с клавиатуры пользователем.
Для пояснения формирования накопительной суммы приведем пример. Пусть задан исходный одномерный массив:
1 2 3 4 5 6 7.
Тогда массив накопительной суммы (cumsum –
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
Пример 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
Пример 6. Напишите программу вычисления степенного полинома в заданной точке по схеме Горнера.
Пусть задан полином в следующем виде:
$$P(x)=a_o x^n+a_1x^{n-1}+\cdots+a_{n-1}x+a_n.$$Алгоритм
Полагая, что $$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.Программа на языке С – это совокупность функций. Запуск любой программы начинается с запуска главной функции, содержащей в себе всю остальную часть программы. Внутри главной функции для реализации заданного алгоритма вызываются все другие необходимые функции. Часть функций создается самим программистом, другая часть – библиотечные функции – поставляется пользователю со средой программирования и используется в процессе разработки программ (например, printf(), sqrt() и др.).
Простейший метод использования нескольких функций требует их размещения в одном и том же файле. Затем выполняется компиляция этого файла, как если бы он содержал единственную функцию [17.1]. Другие подходы к решению этой проблемы существенно зависят от конкретной операционной системы (Unix-подобные системы, Windows, Macintosh). Компиляторы операционных систем Windows и Macintosh представляют собой компиляторы, ориентированные на проекты [17.1]. Проект описывает ресурсы, используемые программой. Эти ресурсы включают файлы исходного программного кода.
Если поместить главную функцию main() в один файл, а определения собственной функции программиста – во второй файл, то первому файлу нужны прототипы функций. Для этого можно хранить прототипы функций в одном из заголовочных файлов.
Хороший тон в программировании рекомендует размещать прототипы функций и объявлять их константы в заголовочном файле [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 спецификатора
Спецификаторы сообщают компилятору, как он должен разместить соответствующие переменные в памяти. Спецификатор
Приведем характеристику спецификаторов
В языке С при редактировании связей к переменной может применяться одно из трех связываний: внутреннее, внешнее или же не относящееся ни к одному из этих типов. В общем случае к именам функций и глобальных переменных применяется внешнее связывание. Это означает, что после компоновки они будут доступны во всех файлах, составляющих программу. К объектам, объявленным со спецификатором 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. Он сообщает компилятору, что имена и типы переменных, следующих далее, объявлены в другом месте. Все ссылки на
Для подключения имени файла, созданного пользователем, его заключают в двойные кавычки.
Результат выполнения программы показан на рис 17.1
(рис 17.1) Результат выполнения программы, состоящей из двух файловВ общем случае h -файл (например, second.h ) формируется редактором кода: надо создать заготовку обычным способом, очистить все поле редактора и записать в это поле необходимые данные (программный код созданной функции). Затем выполнить команду главного меню: File/Save as и выбрать для сохраняемого файла расширение .h в раскрывающемся списке типов сохраняемого файла: C++ Header Files (*.h; *.hh; *.hpp; *.hxx; *.inl; *.tlh; *.tli). Сохраненный файл с расширением .h следует подключить к проекту. Для этого потребуется в узле Solution Explorer навести курсор мыши к папке #include файл следует включить в основную программу.
Другой способ, реализуемый в Microsoft Visual Studio 2010, состоит в том, что сразу через пункт меню .
(рис 17.2) Процесс создания нового файла с расширением .hДалее в правом нижнем углу нажмем клавишу Open. Откроется пустое поле – заготовка для набора необходимого кода. По умолчанию этот файл имеет имя Header1.h. При повторном создании заголовочного файла это будет Header2.h и т.д. После написания кода можно сохранить этот заголовочный файл по желанию в любом каталоге с любым (допустимым) именем (а расширение остается .h ).
Переменные, объявленные со спецификатором static, хранятся постоянно внутри своей функции или файла. В отличие от глобальных переменных они невидимы за пределами своей функции или файла, но они сохраняют значение между вызовами [17.2]. Спецификатор static воздействует на локальные и глобальные переменные по-разному.
Коренное отличие статических локальных от глобальных переменных заключается в том, что статические локальные переменные видны только внутри блока, в котором они объявлены. Если бы не было
При инициализации статической локальной переменной следует учитывать, что значение присваивается ей только один раз – в начале работы всей программы, но не при каждом входе в блок программы, как обычной локальной переменной.
Спецификатор static в объявлении глобальной переменной заставляет компилятор создать глобальную переменную, видимую только в том файле, в котором она объявлена. В таком случае статическая глобальная переменная подвергается внутреннему связыванию.
Таким образом, имена локальных статических переменных видимы только внутри блока, в котором они объявлены; имена глобальных статических переменных видимы только внутри файла, в котором они объявлены [17.2].
Спецификатор register был разработан для того, чтобы компилятор сохранял значение переменной в регистре центрального процессора, а не в памяти, как обычно. Это означает, что операции над регистровыми переменными выполняются намного быстрее [17.2].
Спецификатор register можно применять только к локальным переменным и формальным параметрам функций. В объявлении глобальных переменных применение спецификатора register не допускается.
В языке программирования С с помощью оператора нельзя получить адрес регистровой переменной, потому что она может храниться в регистре процессора, который обычно не имеет адреса.
Ощутимый эффект от спецификатора register может быть получен только для переменных целого ( int ) и символьного ( char ) типа.
Спецификатор auto присваивает объявляемым объектам auto одновременно являются определениями и резервируют память. auto определяет переменные с автоматическим хранением явным образом.
Автоматическое хранение способствует экономии памяти, поскольку
Для переменных со спецификатором 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 ) и двух подключаемых функций, созданных программистом (например, , print() ). В файле myfile3.c поместим функцию , а функцию 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(), поэтому в теле функции можно обращаться к функциям и print(). Функция предназначена для возведения целого числа в степень. Она формирует матрицу планирования (с помощью указателя *PTR ) и вывода значений матрицы на консоль и (или) в текстовый файл. Имеет важное значение очередность подключения файлов myfile3.c и myfile2.c. Следует обратить внимание на синтаксис заключения в двойные ковычки имени текстового файла data.txt. Функция возвращает наименьшее целое (представленное в виде значения с плава
ющей точкой), которое больше своего аргумента или равно ему [17.2]. Она включена на тот
случай, если пользователь введет не целое число
Возможный результат выполнения программы при выводе искомой матрицы на консоль показан на рис 17.4.
(рис 17.4) Матрица планирования N = 24"
Задание 1
degree () примените стандартную функцию pow().Пример 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
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
../.Пример 4. Напишите программу, в которой используется функция формирования накопительной суммы для заданного одномерного целочисленного массива, размерность которого вводится с клавиатуры пользователем.
Для пояснения формирования накопительной суммы приведем пример. Пусть задан исходный одномерный массив:
1 2 3 4 5 6 7.
Тогда массив накопительной суммы (cumsum –
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
Пример 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
Пример 6. Напишите программу вычисления степенного полинома в заданной точке по схеме Горнера.
Пусть задан полином в следующем виде:
$$P(x)=a_o x^n+a_1x^{n-1}+\cdots+a_{n-1}x+a_n.$$Алгоритм
Полагая, что $$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.Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.