Проект или совместная работа над задачей
Если Вы решаете небольшую задачу, то ее, конечно, удобнее решать одному.
НО, если:
- в задаче приходится выполнять множество действий
- задача требует особых подходов к реализации функций и эти подходы знают разные люди
- у Вас очень мало времени
- наработки для этой задачи Вы хотели бы использовать в будущем
,
то разумно представить реализацию задачи как программный проект.
Проект требует более подробную проработку следующих вопросов: какие типы данных, какие инструменты нужны или желательны для решения задачи.
Когда эти вопросы будут решены, необходимо описать типы данных и все функции-инструменты. Все участники проекта должны СТРОГО следовать этому описанию.
Задача о времени
Измерения сделаны в некоторый момент времени. Время может быть представлено в полном виде:
год-месяц-день час:минуты:секунды.наносекунды
, или же в формате
дни часы:минуты:секунды.наносекунды
. В последнем случае предполагаем, что сокращенная форма, например, только секунды и наносекунды не рассматривается.
Необходимо реализовать следующие возможности:
Для решения нужно:
- преобразовывать дату из текстового представления в числовое, желательно совместимое с системным, чтобы можно было пользоваться системнными функциями:
-
time
получение текущей даты (с точки зрения системы). Время представляется в секундах с 1970 года (time_t
).
-
localtime
заполняется структура struct tm
с преобразованием секунд в год, месяц, день и т.д.
-
difftime
вычисляется разница времен
-
strftime
записывает дату в текстовую строку в удобном формате.
-
mktime
преобразует дату из структуры struct tm
в секунды time_t
- сравнивать два времени (
- вычислять
разницу времен
в днях, часах и т.д. с точностью до наносекунд.
- вычислять новую дату исходя из имеющейся и указанной
разницы времен
.
- умножать
разницу времен
на число.
- делить
разницу времен
на число.
- вычислять частное от двух
разницу времен
- печатать результат
Эти инструменты можно будет использовать в других программах.
Для этого нужно создать заголовочные файлы, в которых будет описано: все структуры данных и
интерфейс
всех получившихся функций.
Кроме того, нужно реализовать все функции так, чтобы можно было ими пользоваться без дополнительной компиляции.
Для решения этой проблемы нужно создать проект
.
В проекте все создаваемые типы данных и интерфейсы функций описываются в заголовочных файлах.
Простейший вариант проекта выглядит так:
Заголовочный файл (prim.h)
|
Файл реализации функций (prim.c)
|
Программа, которая использует функции (test.c)
|
// Новый :) тип данных
typedef int Coord;
// Интерфейс функции
int add(Coord,Coord);
|
//включить заголовочный файл
#include "prim.h"
// Реализация функции add
int add(int a, int b){
return a + b;
};
|
// Использование функции add
#include <stdio.h>
//включаем НАШ заголовочный файл
#include "prim.h"
int main(){
int a, b, c;
scanf("%d%d", &a, &b);
// Используем функцию add
с = add(a, b);
printf("a + b = %d\n",c);
}
|
Как собрать проект*
Конечно, все написанное должно быть откомпилировано и собрано.
Можно сразу скомпилировать все нужные файлы и собрать в работающий файл, а можно это сделать по частям.
Перый способ (все вместе)
gсс test.c prim.c -o test
Заголовочный файл НИКОГДА в строку компиляции не включается. Компилятор ищет его самостоятельно.
Второй способ (по-отдельности)
>gсс -c prim.c
>gcc -c test.c
>gcc prim.o test.o -o test
В этом случае получаются два объектных файла (.o). Если с файл не изменялся, то его не обязательно компилировать.
Вернемся к задаче про о времени. Напишем все интерфейсы на языке С++. Будем использовать два представление времени: полная дата DataTime
и разница времен CTime
.
Рассмотрим сначала реализацию CTime
:
Описание разницы времен
Заголовочный файл (timedat.h)
|
Файл реализации функций (ctime.cpp)
|
// заголовочные файлы - нужны функции,
// объявленные в них
// ввод/вывод в С++ на консоль
#include <iostream>
// стандартная библиотека
#include <cstdlib>
// работа с файлами в С++
#include <fstream>
// функции времени (системные)
#include <ctime>
// ввод/вывод в С. Иногда удобнее
#include <stdio.h>
// переменная errno для ловли системных
// неудач
#include <errno.h>
// работа со строками в С
#include <string.h>
#define TLimit 315360000
//для сокращенного вызова функций из std
using namespace std;
// название класса
class CTime{
// атрибуты (поля) класса
time_t ctm; // для секунд даты
int nanos; // наносекунды
//используем функцию clock
double start; // начало отсчета
double end; // конец отсчета
public:
// конструктор объекта класса
// всегда называется как класс
// это функция. По умолчанию просто выделяется
// память под объект, но конструктор может после этого
// выполнить еще дополнительные действия
CTime();
// в С++ разные по содержанию функции могут иметь
// одно имя в одном классе или одном пространстве имен
// но они должны отличаться своими параметрами
CTime( const CTime&);
//получение разницы времен в виде строки
void getTime(const char*);
// получение разницы времен как секунды
void getTime(time_t,int);
// в С++ можно ПЕРЕГРУЗИТЬ существующие операторы
// То есть такой оператор теперь будет работать с объектами
// класса CTime (обычный не может) и так как нам нужно
// это оператор сравнения двух объектов CTime
// & - ссылка передает адрес так же как и указатель,
// но работать с объектом, который передан по ссылке
// можно как с обычным (синтаксически)
int operator==(const CTime&);
//это для самостоятельной реализации
int operator>(CTime);
int operator>=(CTime);
int operator<(CTime);
int operator<=(CTime);
// оператор сложения двух разниц времен
CTime operator+(const CTime&);
// разница
CTime operator-(const CTime&);
// умножение на число
CTime operator*(float);
// частное двух объектов CTime
float operator/(const CTime&);
void print();
};
|
// включаем НАШ заголовок
#include "timedat.h"
// описание конструктора
CTime::CTime(){
// инициализируем все атрибуты
// для ВСЕХ функций класса CTime
// его атрибуты - ГЛОБАЛЬНЫЕ
ctm = 0;
nanos = 0;
};
// иногда нужно получить объект
// копированием, например
// CTime a(b);
// вот для этого такой конструктор
CTime::CTime(const CTime& a){
ctm = a.ctm;
nanos = a.nanos;
};
// получение времени через строку
void CTime::getTime(const char* s){
int day,h,min,sec;
// здесь удобно использовть С-ишный sscanf
sscanf(s,"%d%d:%d:%d.%d",&day,&h,&min,&sec,&nanos);
// nanos уже получил значения, вычисляем ctm
// nanos и ctm - ГЛОБАЛЬНЫЕ переменные для всех функций
// CTime::функция
ctm = (((day * 24) + h) * 60 + min) * 60 + sec;
};
// печать даты
void CTime::print(){
int day = ctm / 86400;
int h = (ctm / 3600) % 24;
int min = (ctm /60) % 60;
int sec = ctm % 60;
printf("%d %d:%d:%d.%d ", day, h, min, sec, nanos);
};
CTime CTime::operator+(const CTime& a){
// объект, который будет возвращен
CTime tmp;
//tmp и а - объекты класса CTime, значит
// функции этого класса имеют досуп
// кол ВСЕМ атрибутам и функциям CTime
tmp.nanos = (nanos + a.nanos) % 1000;
tmp.ctm = ctm + a.ctm + (nanos + a.nanos) / 1000;
// возвращаем заполненный объект
// будет возвращена КОПИЯ этого объекта
return tmp;
};
|
= Файл тестирования (использования функций)=
#include "timedat.h"
int main(int argc, char** argv){
char ss[100];
FILE *fin;
if(argc > 1){
fin = fopen(argv[1],"r");
if( errno ){
perror("file open: ");
exit(1);
}
}
// объявляем три объекта (переменных) CTime
CTime a,b,c;
// получаем время как строки
// Если бы код был написан на С,
// то функция выглядела бы так:
// getTime(&a,ss); - два аргумента
// или так: a = getTime(ss);
a.getTime(ss);
// печатаем для проверки
a.print();
fgets(ss,99,fin);
b.getTime(ss);
b.print();
// проверяем оператор сложения
// это сокращенная записть оператора
// его ПОЛНАЯ запись: c = a.operator+(b)
c = a + b;
// печать результат
c.print();
return 0;
}
Статическая библиотека.
Допустим, Вы написали и отладили все функции, и они прекрасно работают. Кроме этой задачи есть еще множество других задач,
в которых эти функции будут полезны.
Писать их заново или переносить файлы в новый прект хлопотно и неразумно.
Чтобы избежать этого скомпилированные функции обычно включают в библиотеки. Рассмотрим использование статической библиотеки.
Функции статической библиотеки при линковке включаются в исполняемый файл (все функции из библиотеки). Это влияет на размер
исполняемого файла. Именно поэтому при создании
библиотеки нужно придерживаться принципа "ничего лишнего".
В библиотеку добавляются ТОЛЬКО ОТЛАЖЕННЫЕ ФУНКЦИИ!!!!
При создании библиотеки используется архиватор ar.
Как собрать проект на С++ и создать статическую библиотеку
При создании библиотеки используется архиватор ar.
Создание библиотеки
- создание объектных файлов реализаций функций
- создание архива с названием libназвание_библиотеки.a
- включение объектных файлов в архив
- ранжирование архива для быстрого поиска функций
- помещение библиотечного файла в специальный каталог
Что делаем
|
Комментарий
|
|
получаем объектный код функций класса
|
>ar -rc libctime.a ctime.o
|
содаем архив libctime.a и добавляем в него
содержимое ctime.o. Это уже архив, но еще не библиотека
|
|
ранжируем (индексируем) функции в архиве. Теперь это уже статическая библиотека.
|
Как правило, заголовочные файлы проекта собираются в каталог include, а библиотечные - в lib.
При компиляции и линковке должны использоваться ключи: -Iкаталог_с_заголовками, -Lкаталог_с_библиотечными_файлами и
-lназвание_библиотеки
Далее создаем каталоги для заголовков и библиотек, помещаем их туда. После этого можно уже компилировать программу, которая
использует функции из библиотеки.
>mkdir lib
>mkdir include
>mv libctime.a lib/
>mv timedat.h include/
Компиляция
>g++ ctest.cpp -o ctest -I./include -L./lib -lctime -lm
Одна используемая библиотека - наша библиотека ctime, а вторая - системная математическая m, но это только для примера, функции из нее не нужны сейчас.
Описание даты (полный формат)
Заголовочный файл (timedat.h)-- обавлено описание еще одного класса
|
Файл реализации функций (dtime.cpp)
|
#include <iostream>
#include <cstdlib>
#include <fstream>
#include <ctime>
#include <sstream>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define TLimit 315360000
using namespace std;
class CTime{
time_t ctm;
int nanos;
public:
CTime();
CTime( const CTime&);
// для cамостоятельного программирования
// для определения длительности процесса
void start();
void stop();
void getTime(const char*);
void getTime(time_t,int);
int operator==(const CTime&);
// для самостоятельного программирования
int operator>(CTime);
int operator<(CTime);
CTime operator+(const CTime&);
// для cамостоятельного программирования
CTime operator-(const CTime&);
CTime operator*(float);
float operator/(const CTime&);
void print();
};
// Добавили описание класса DataTime
class DataTime{
// структура для времени (системная)
struct tm tmdat;
// секунды с 1970 г.
time_t ctm;
//наносекунды
int nanos;
public:
// конструктор
DataTime();
// инициализирующий конструктор
DataTime(time_t, int);
DataTime(char *);
// получить дату из строки
void getData(char*);
// получить текущую дату с заполнением структуры
void now();
// сравнение двух дат
int operator==(const DataTime&);
// сложение даты с CTime
DataTime operator+(const CTime&);
// для cамостоятельного программирования
//вычитание CTime
DataTime operator-(const CTime&);
// для cамостоятельного программирования
// получить разницу времен
CTime operator-(const DataTime&);
// печать
void print();
// для cамостоятельного программирования
// печать с указанием дня недели
// как принято в России
void print(int);
};
|
// также включается НАШ
// заголовочный файл
#include "timedat.h"
// конструктор для класса DataTime
DataTime::DataTime(){
// все атрибуты ставим в 0
ctm = 0;
nanos = 0;
bzero(&tmdat,sizeof(struct tm));
};
// получение даты из строки
void DataTime::getData(char* s){
int y,mon,day,h,min,sec;
//здесь удобнее разобрать строку sscanfом
sscanf(s,"%d-%d-%d %d:%d:%d.%d",&y,&mon,&day,&h,&min,&sec,&nanos);
// год в структуре хранится начиная с 1900, поэтому его нужно вычесть
tmdat.tm_year = y - 1900;
// месяцы начинаюс 0
tmdat.tm_mon = mon - 1;
tmdat.tm_mday = day;
// наше время отличается на 3 часа
// (есть проблемы с летним европейским временем)
tmdat.tm_hour = h + 3;
tmdat.tm_min = min;
tmdat.tm_sec = sec;
// преобрзование заполненной структуры в
// секунду. Заодно вычисляются и заполняются
// оставленные нами поля tmdat (см. man)
ctm = mktime(&tmdat);
};
// получение даты секунды и наносекунды
DataTime::DataTime(time_t a, int nn){
ctm = a;
nanos = nn;
};
// Вычитание двух дат. Сложение двух дат в таком формате
// бессмысленно.
// Результатом сложения явлется объект CTime - разница времен
CTime DataTime::operator-(const DataTime& a){
CTime tmp; // для возврата
// вычисляем разницу в секундах
time_t sec = ctm - a.ctm - 1;
int nn = 1000 + nanos - a.nanos;
// задаем время для объекта CTime секундами и наносекундами
// к полям ctm и nanos класса CTime
// функции класса DataTime доступа не имеют!!
tmp.getTime(sec + nn / 1000, nn % 1000);
// возвращаем объект
return tmp;
};
// Печать даты в удобном виде
void DataTime::print(){
// временная структура
struct tm tmp;
// строка ддля представления даты
char buffer[100];
// заполнение временной структуры
// обычно localtime создает динамическую
// переменную и возвращает указатель на нее.
// Таким образом все функции времени работатют с
// этой переменной (статическая).
// Для этой задачи нужна локальная переменная,
// поэтому сразу получаем ЗНАЧЕНИЕ
tmp = *localtime(&ctm);
// получение текстовой строки с датой в нужном формате
strftime (buffer,80,"%y-%m-%d %X",&tmp);
// печать результата
printf("%s.%d\n",buffer,nanos);
};
|
Проверка работы функций. Файл ==dtest.cpp==
#include "timedat.h"
int main(int argc, char** argv){
char ss[100];//="13 7:25:10.9876";
FILE *fin;
if(argc > 1){
fin = fopen(argv[1],"r");
if( errno ){
perror("file open: ");
exit(1);
}
}
// в файле две строки с описанием даты
// строго по фомату
fgets(ss,99,fin);
DataTime dt;
dt.getData(ss);
dt.print();
fgets(ss,99,fin);
DataTime dt1(ss);
dt1.print();
CTime res;
// получение результата вычитания
// здесь предполагается, что первое время больше
res = dt - dt1;
res.print();
return 0;
}
Как добавить функции в библиотеку.
Вот реализованы новые функции. Можно создать для них другую библиотеку. Но сейчас нужно добавить их в библиотеку ctime
.
Что делаем
|
Комментарий
|
|
получаем объектный код функций класса
|
>ar -r libctime.a dtime.o
|
В существующий архив libctime.a и добавляем содержимое dtime.o с ключом -r , но без -c .
-r добавляет новые функции и перезаписывает старые с такими же именами. -c ключ создания архива (см. man).
|
|
Снова ранжируем (индексируем) функции в архиве. Теперь это уже статическая библиотека.
|
Заголовочный файл в папке include
, библиотечный переписываем в папку lib
Компилируем программу:
>g++ dtest.cpp -I./include -o dtest -L./lib -lctime
Make-file для создания библиотеки.
Если вы написали задачу со множеством файлов-источников, и этот проект приходится собирать в разных местах, то разумнее автоматизировать этот процесс.
Для этого используется команда make
. Правда для того, чтобы она выполнила все что нужно, необходимо написать файл с правилами
Понятно, что компиляция и сборка проекта из множества файлов будет сложной. При наличии исходных текстов некоторые из них
должны быть помещены в библиотеки статические или динамические, некоторые должны быть использованы для создания запускаемых приложений и т.д.
Прописать все это словами для человека который не участвовал в разработке проекта будет почти невозможным. Вероятность того, что что-нибудь будет
забыто, а что-то перепутано очень велика. Таким образом приложение не соберется.
Для решения этой проблемы существует утилита ==make==(она существует и для WINDOWS, и для Linux). Эта утилита работает с makefile, в котором
специальном образом прописаны правила сборки проекта, зависимости и т.д.
Если просто запустить \textit{make}, то эта программа постарается найти файл makefile
и выполнить правила
, которые записаны в нем.
Если же нужно указать другой файл с инструкциями, то тогда
В предыдущих разделах мы уже рассматривали как получается готовое приложение: создавали объектные файлы, собирали библиотечные файлы, и потом из всего этого
получали готовое приложение.
Желательно, чтобы все это выполнялось автоматически.
Для начала заметим, что в каталоге src
лежит запакованный файл myprog.tar
.
Этот файл получен с помощью архиватора tar
.
>tar -zcvf prim.tar ctime.cpp dtime.cpp timedat.h dtest.cpp
Чтобы распаковать его, нужно сделать следующее:
>cd src
>tar -zxf prim.tar
Вот теперь имеются все необходимые файлы и каталоги для создания проекта. Осталось их собрать.
При отладке приходится часто компилировать и собирать одно и то же, поэтому удобно иметь makefile. К тому же он нужен для vim
.
Напишем инструкции для makefile
, чтобы сборка прошла автоматически.
В предыдущих разделах мы уже рассматривали как получается готовое приложение: создавали объектные файлы, собирали библиотечные файлы, и потом из всего этого
получали готовое приложение.
Желательно, чтобы все это выполнялось автоматически.
Основные части makefile
:
[цель]:зависимости
[tab]команды
....
[tab]команды
Цель, это то, что мы хотим, в конечном счете, получить после работы make
,
а зависимости - это то, что необходимо для получения результата.
Пример простого makefile
# Наша главная цель -
# получить исполняемый
# файл test
all: test
# от наличия каких файлов зависит test
test: ctime.o dtime.o
# что нужно выполнить, чтобы поучить test, если они есть
# для команд [tab] вначале строки обязательно
# НЕ ПРОБЕЛЫ!!
g++ -o test ctime.o dtime.o dtest.cpp
# из чего получаем ctime.o
ctime.o: ctime.cpp
# как получаем
g++ -c ctime.cpp
# из чего получаем dtime.o
dtime.o: dtime.cpp
# как получаем
g++ -c dtime.cpp
# файлы .o нужно потом убрать
clean:
rm -rf *.o
Вызов:
Допустим теперь нужно создать и автоматически поместить заголовочные и библиотечные файлы на место
Пишем makefile.lib
.
# так назвали проект
# это основная цель работы
all: myprog
# от чего зависит myprog
myprog: libdtime.a
# от чего зависит libdtime.a
libdtime.a: ctime.o dtime.o
# как получить
ar -cr libdtime.a ctime.o dtime.o
ranlib libdtime.a
ctime.o: ctime.cpp
g++ -c ctime.cpp
dtime.o: dtime.cpp
g++ -c dtime.cpp
# инсталяция - помещение в нужные каталоги
install: myprog
mkdir -p include
mkdir -p lib
mv timedat.h ./include
mv libdtime.a ./lib
# очистка
clean:
rm -rf *.o
Вызов:
> make -f makefile.lib
>male clean
Задачи
- Написать
makefile
для отладки функций классов
- Дописать и отладить оставшиеся функции классов.
- системная функция
clock
считает такты процессора. Добавить две функции в класс CTime чтобы можно было получть разницу времен между началом и концом работы программы. Проверить работу.
- Написать
makefile.lib
для получения библиотеки из исходников и инсталяции. Создать библиотеку реализованных функций.
- Поменяться с соседом библиотеками и оттестировать ее. За найденные ошибки плюсы.
- Написать программу на С по возведению числа в указанную степень разными способами. Использовать написанные библиотеки для определения времени запуска и завершение заботы программ. Эти времена записать в файл. Напечатать самую долгую и самую быструю программы
Примеры использования библиотек и классов для работы с системными файлами в файле prim.tar.
-- TatyanaOvsyannikova2011 - 28 Sep 2017