Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Visual_Studio_2010

.pdf
Скачиваний:
71
Добавлен:
03.03.2016
Размер:
5.94 Mб
Скачать

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

3.Введите число дня рождения пользователя.

4.Проверьте программу при вводе отрицательных целых чисел.

5.Измените программу для поразрядного сдвига вправо.

Контрольные вопросы

1.Как осуществляется нумерация разрядов байта?

2.Для каких систем счисления в языке С имеются классификаторы форматируемых данных?

3.Какие логические поразрядные операции существуют в языке С?

4.Какие логические операции сдвига существуют в языке С? Какими операторами они реализуются?

5.Что такое битовое поле в языке С? Где оно может быть определено?

6.В чем различие поразрядных и логических операторов НЕ, И и ИЛИ?

7.Как можно обменять значения двух целочисленных переменных без использования третьей переменной?

8.Чем отличается операция сдвига вправо для типов int и unsigned?

БИБЛИОГРАФИЧЕСКИЙ СПИСОК

1.Прата С. Язык программирования С. Лекции и упражнения : пер. с англ. / С. Прата. – 5-е изд. – М. : Вильямс, 2006. – 960 с.

2.Кочан С. Программирование на языке С : пер. с англ./С. Кочан. –

3-е изд. – М. : Вильямс, 2007. – 496 с.

3.Подбельский В. В. Программирование на языке Си : учеб. пособие/ В. В. Подбельский, С. С. Фомин. – 2-е изд., доп. – М. : Финансы и стати-

стика, 2007. – 600 с.

291

Тема 17

ПРОГРАММЫ НА ЯЗЫКЕ С, СОСТОЯЩИЕ ИЗ НЕСКОЛЬКИХ ФАЙЛОВ

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

ТЕОРЕТИЧЕСКАЯ ЧАСТЬ

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

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

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

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

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

extern double delta();

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

292

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

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

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

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

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

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

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

static register auto

Спецификаторы сообщают компилятору, как он должен разместить соответствующие переменные в памяти. Спецификатор класса памяти в объявлении

293

всегда должен стоять первым [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 не было бы необходимости.

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

1)если компилятор находит переменную, не объявленную внутри блока, он ищет ее объявление во внешних блоках;

2)если не находит ее там, то ищет среди объявлений глобальных переменных.

Спецификатор extern играет большую роль в программах, состоящих из многих файлов [3]. В языке С программа может быть записана в нескольких

294

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

Первый файл (main.c)

#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);

}

Второй файл (second.h)

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. Результат выполнения программы, состоящей из двух файлов

295

В общем случае 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, состоит в том, чтобы сразу через пункт меню File выбрать New File и

далее в списке Installed Templates – Visual C++ Header File (.h). Откроется окно, представленное на рис.17.2.

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

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

296

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

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

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

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

Таким образом, имена локальных статических переменных видимы толь-

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

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

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

только к локальным переменным и формальным параметрам функций. В объ-

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

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

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

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

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

297

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

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

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

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

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

298

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

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

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

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

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

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

299

ПРАКТИЧЕСКАЯ ЧАСТЬ

Пример 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 2k строк. Число столбцов равно k. Cтолбцы матрицы заполняются +1 или –1 по степеням двойки. То есть первый столбец заполняется +1, –1, +1, –1, +1, –1 и т. д., второй – +1, +1, –1, –1, +1, +1,

–1, –1 и т. д., третий – +1, +1, +1, +1, –1, –1, –1, –1 и т. д. Эту матрицу называют

матрицей планирования эксперимента типа 2k . Ее расчет должен выполняться в одном файле, а печать результата – в другом, при этом результат можно записать в текстовый файл. В файле 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", &k);

if (k < 1 || (ceil(k) != k) ) { printf("\n Error! Press any key: "); _getch(); return -1;

}

//Обращение к функции формирования матрицы планирования print((int)k);

printf("\n\n Press any key: "); _getch();

return 0; }

300

Тут вы можете оставить комментарий к выбранному абзацу или сообщить об ошибке.

Оставленные комментарии видны всем.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]