Лекция 7 1. Модули Иногда программный код оказывается слишком большим и сложным, чтобы хранить его в одном файле. В таком коде сложно ориентироваться и при возникновении ошибок их будет трудно искать и исправлять. К тому же, после исправления отдельных ошибок будет необходимо перекомпилировать заново всю программу, что при больших объемах может занять достаточно много времени. В таких случаях удобнее разбить программу на несколько исходных файлов, которые обычно называют модулями. Каждый из этих модулей можно компилировать отдельно, а затем объединить их в процессе построения конечной программы. Процесс объединения отдельно скомпилированных модулей называется связыванием (компоновкой). Чтобы создать отдельные модули программы, необходимо в окне Solution Explorer с помощью контекстного меню для пункта Source File создать отдельные файлы проекта. Все они будут иметь расширение .cpp и будут компилироваться отдельно (пункт меню Build, Compile или Ctrl-F7). Основная программа при этом может содержать только главную функцию main и прототипы всех остальных функций, которые содержатся в других модулях. После успешной компиляции каждого отдельного модуля необходимо выполнить компоновку проекта с помощью пункта Build, Build Solution или клавиши F7. Далее можно запускать программу на выполнение обычным способом. Кроме этого, в одном модуле можно использовать функции других модулей. Для этого в каждом модуле необходимо описать описывать их прототипы. Удобнее создать заголовочный файл (пункт «Заголовочные файлы в «Обозревателе решений»), в котором будут описаны все прототипы функций, а затем включить его с помощью директивы #include. Заголовочные файлы обычно имеют расширение .h. Рассмотрим создание модулей на примере. Пример. Разработать программу, которая определяет тип треугольника по его сторонам (равносторонний, равнобедренный, прямоугольный, обычный или не существует), а также вычисляет его площадь и периметр. Программа должна запрашивать стороны треугольника и выдавать на экран меню в следующем виде: Выберите номер пункта: 1.Периметр 2.Площадь 3.Тип 4.Выход Заголовочный файл: treug.h void tip(float a, float b, float c); float perimetr(float a, float b, float c); float square(float a, float b, float c); Реализация: Основной модуль – index.cpp #include <iostream> #include <iomanip> #include “treug.h” using namespace std; void main() { float x,y,z; setlocale (LC_ALL,"rus"); cout<<"Введите стороны треугольника"<<endl; cin>>x>>y>>z; int punkt=0; while (punkt!=4) { cout<<"Выберите номер пункта:"<<endl; cout<<"1.Периметр"<<endl; cout<<"2.Площадь"<<endl; cout<<"3.Тип"<<endl; cout<<"4.Выход"<<endl; cin>>punkt; switch (punkt) { case 1: cout<<"Периметр="<<setprecision(2)<<fixed<<perimetr(x,y,z)<<endl; break; case 2: cout<<"Площадь="<<setprecision(2)<<fixed<<square(x,y,z)<<endl; break; case 3: tip(x,y,z); break; case 4: break; default: cout<<"Неверный ввод"<<endl; break; } } } Дополнительный модуль – module.cpp #include <iostream> #include <iomanip> #include <cmath> using namespace std; void tip(float a, float b, float c) { int t; t=0; if ((a==b)&&(a==c)&&(c==b)) { cout<<"Равносторонний"<<endl; t=1; } if ((a==b)&&(a==c)&&(c!=b)||(a==b)&&(a!=c)&&(c==b)||(a!=b)&&(a==c)&&(c==b)) { cout<<"Равнобедренный"<<endl; t=1; } if ((a*a+b*b==c*c)||(a*a+c*c==b*b)||(c*c+b*b==a*a)) { cout<<"Прямоугольный"<<endl; t=1; } if ((a+b<=c)||(a+c<=b)||(b+c<=a)) { cout<<"Не существует"<<endl; t=1; } if (t==0) { cout<<"Обычный"<<endl; } } float square(float a, float b, float c) { float p; p=(a+b+c)/2; return (sqrt(p*(p-a)*(p-b)*(p-c))); } float perimetr(float a, float b, float c) { return (a+b+c); } 2. Указатели Указатели широко используются в C++. Именно их наличие сделало этот язык более удобным для системного программирования. Идея работы с указателями состоит в том, что пользователь работает с адресом ячейки памяти и имеет возможность динамически создавать и уничтожать переменные. Как правило, при обработке оператора объявления переменной тип имя_переменной; компилятор автоматически выделяет память под переменную в соответствии с указанным типом: Доступ к объявленной переменной осуществляется по ее имени. При этом все обращения к переменной меняются на адрес ячейки памяти, в которой хранится ее значение. При завершении программы или функции, в которой была описана переменная, память автоматически освобождается. Доступ к значению переменной можно получить иным способом — определить собственные переменные для хранения адресов ячеек памяти. Такие переменные называют указателями. С помощью указателей можно обрабатывать массивы, строки и структуры, динамически создавать новые переменные в процессе выполнения программы (а не на этапе компиляции), передавать адреса фактических параметров. Указатель — это переменная, значением которой является адрес памяти, по которому хранится объект определенного типа (другая переменная). Например, если C это переменная типа char, а P - указатель на C, то, значит, в P находится адрес, по которому в памяти компьютера хранится значение переменной C. Как и любая переменная, указатель должен быть объявлен. При объявлении указателей всегда указывается тип объекта, который будет храниться по данному адресу: Например: int *p Звездочка в описании указателя относится непосредственно к имени, поэтому, чтобы объявить несколько указателей, ее ставят перед именем каждого из них: float *x, y, *z; Операция получения адреса обозначается знаком &. Она возвращает адрес своего операнда. Например: float a; //объявлена вещественная переменная a float *adr_a; //объявлен указатель на тип float adr_a = &a; //оператор записывает в переменную adr_a //адрес переменной a Операция разадресации * возвращает значение переменной, хранящееся в по заданному адресу, то есть выполняет действие, обратное операции &: float a,b; //объявленs вещественная переменная a и b float *adr_a; //объявлен указатель на тип float adr_a=&a; b = *adr_a; //оператор записывает в переменную b //вещественное значение, хранящееся по адресу adr_a Значение одного указателя можно присвоить другому. Если указатели одного типа, то для этого применяют обычную операцию присваивания . Рассмотрим ее на примере: #include <iostream> using namespace std; int main() { float PI=3.14159, *p1, *p2; p1=p2=&PI; cout<<"По адресу p1="<<p1<<" хранится *p1="<<*p1<<"\n"; cout<<"По адресу p2="<<p2<<" хранится *p2="<<*p2<<"\n"; system ("pause"); return 0; } Если указатели ссылаются на различные типы, то при присваивании значения одного указателя другому, необходимо использовать преобразование типов. Пример #include <iostream> using namespace std; int main() { float PI=3.14159; //объявлена вещественная переменная PI float *p1; //объявлен указатель на float - p1 double *p2; //объявлен указатель на double - p2 p1=&PI; //переменной p1 присваивается значние адреса PI p2=(double *)p1; //указателю на double присваивается значение, //которое ссылается на тип float cout<<"По адресу p1="<<p1<<" хранится *p1="<<*p1<<"\n"; cout<<"По адресу p2="<<p2<<" хранится *p2="<<*p2<<"\n"; system ("pause"); return 0; } В указателях p1 и p2 хранится один и тот же адрес, но значения, на которые они ссылаются, оказываются разными. Это связано с тем, что указатель типа *float адресует 4 байта, а указатель *double - 8 байт. После присваивания p2=(double *)p1; при обращении к *p2 происходит следующее: к переменной, хранящийся по адресу p1, дописывается еще 4 следующих байт из памяти. В результате значение *p2 не совпадает со значением *p1. Указатели можно использовать также для передачи параметров в функцию. Этот способ аналогичен способу передачи параметров по ссылке. Для передачи данных по адресу требуется после типа переменной указать символ «*» (операция разадресации). Чтобы передать в функцию фактический параметр по адресу, нужно использовать операцию взятия адреса «&». Если требуется запретить изменение параметра внутри функции, используют модификатор const (при попытке изменения значения будет выдаваться ошибка компилятора). Заголовок функции в общем виде будет выглядеть так: тип имя_функции (const тип_переменной* имя_переменной, …) #include <iostream> using namespace std; int f1(int i) //данные передаются по значению { i++; return (i); } int f2 (int* j) //данные передаются по адресу { return((*j)++); } int f3 (const int* k) //изменение параметра не предусмотрено { return (*k); } int main () { int a; cin>>a; f1(a); cout<<"f(a)="<<f1(a)<<"\n"<<"a="<<a<<"\n"; //f(a)=6 a=5 f2(&a); cout<<"a="<<a<<"\n"; //a=6 f3(&a); cout<<"a="<<a<<"\n"; //a=6 system ("pause"); return 0; }