Файл: Современные языки программирования (Теоретические основы языков программированияя).pdf

ВУЗ: Не указан

Категория: Курсовая работа

Дисциплина: Не указана

Добавлен: 25.05.2023

Просмотров: 232

Скачиваний: 3

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

Координаты вершин будут выражаться как комплексное значение — это естественно для физического мира, так как комплексные величины это и есть отображение точек 2D-плоскости. Но самое главное, что такой подход с самого начала потребует работы со структурными объектами (2-х компонентные комплексные значения). А геометрическая фигура (треугольник) естественным образом подталкивает к использованию понятий класса и объекта. Есть где разгуляться!

Но прежде, чем приступить к реализациям, нужно сделать минимальный экскурс в теорию комплексных вычислений. Каждое комплексное число представляется суммой вещественной и мнимой компонент:

z = real + i * imagine - где real — это вещественная часть числа, а imagine — мнимая его часть (real и imagine здесь конкретный числовые, вещественные значения для данного конкретного комплексного числа).

На вещественной плоскости (2D) число z отображается точкой, для котрой: real — это координата точки по горизонтали (ось X), а imagine — это координата точки по вертикали (ось Y). Также каждое комплексное число имеют другую форму представления, так называемую экспоненциальную, вида:

z = abs * exp( i * arg ) - здесь abs — это длина вектора z (от точки 0,0), а arg — фазовый угол наклона вектора относительно оси X (выраженный в радианах).

Эти две формы представления описывают одну и ту же точку плоскости, и между ними существуют взаимно однозначные соответствия. Они связаны соотношениями (все показанные математические функции присутствуют в библиотеке любого языка программирования):

real = abs * cos( arg )

imagine = abs * sin( arg )

abs = sqrt( real2 + imagine2 )

arg = atan2( imagine, real )

z = abs * exp( i * arg ) = abs * ( cos( arg ) + i * sin( arg ) )

Z1 = 0. + 0. * i

z2 = -3. + 0 * i

z3 = 2. + 0 * i

z5 = 0 - sqrt( 3. ) * i

z6 = 0 + 4 * i

z7 = 2. + 3. * i

z8 = -4 + i

z9 = -3 - 3 * i

z10 = sqrt( 2. ) - i

Математические библиотеки манипуляции с комплексными числами содержат встроенные функции преобразования из одной формы в другую. Например, для показанных на рисунке некоторых чисел (векторов) имеет место (угол arg показан в радианах, долях π и в угловых градусах для наглядности — это одно и то же значение):

z1 = ( +2.0 , + 3.0i ) <=> abs = 3.606 , arg = 0.983 = 0.31*π = 56°

z5 = ( -0.0 , -1.7i ) <=> abs = 1.732 , arg = -1.571 = -0.50*π = -90°

z8 = ( -4.0 , +1.0i ) <=> abs = 4.123 , arg = 2.897 = 0.92*π = 166°

z9 = ( -3.0 , -3.0i ) <=> abs = 4.243 , arg = -2.356 = -0.75*π = -135°

z10 = ( +1.4 , -1.0i ) <=> abs = 1.732 , arg = -0.615 = -0.20*π = -35°

Зачем нам такие сложности? А затем, что дальше всё становится очень просто:

вектор, замыкающий точки z9 и z8 будет вычисляться просто как ( z8 - z9 );


его длина (нужная нам как составляющая периметра) — как abs( z8 - z9 );

а площадь треугольника, построенного на сторонах z9 и z8 будет вычисляться как:

abs( z8 ) * abs( z9 ) * sin( arg( z8 ) - arg( z9 ) ) / 2.

Показанного вполне достаточно для всех наших последующих рассмотрений.

Язык С.

Листинг 1. Реализация C (файл triangle.c):

#include <stdio.h>

#include <stdlib.h>

#include <math.h>

#include <complex.h>

#include <string.h>

#define NODES 3 // число вершин

typedef double complex triangle_t [ NODES ]; // тип треугольника

static double perimeter( triangle_t pts ) {

double summa = 0.0;

int i, j;

for( i = 0; i < NODES; i++ ) {

j = NODES - 1 == i ? 0 : i + 1;

summa += cabs( pts[ i ] - pts[ j ] );

}

return summa;

}

static double square( triangle_t pts ) {

double complex side1 = pts[ 1 ] - pts[ 0 ],

side2 = pts[ 2 ] - pts[ 0 ];

return cabs( side1 ) * cabs( side2 ) *

fabs( sin( carg( side1 ) - carg( side2 ) ) ) / 2.;

}

#define INLEN 40

int main( int argc, char **argv, char **envp ) {

while( 1 ) {

int i = 0;

triangle_t polygon;

printf( "координаты вершин в формате: X Y\n" );

while( i < NODES ) {

float x, y;

printf( "вершина № %d: ", i + 1 );

fflush( stdout );

char s[ INLEN ], e[ INLEN ];

if( !fgets( s, sizeof( s ) - 1, stdin ) ) // строка ввода

printf( "завершение\n" ), exit( EXIT_SUCCESS );

s[ strlen( s ) - 1 ] = '\0'; // удалить EOL

while( ' ' == s[ strlen( s ) - 1 ] ) // удалить хвостовые пробелы

s[ strlen( s ) - 1 ] = '\0';

if( sscanf( s, "%f%*c%f%s", &x, &y, (char*)&e ) != 2 ) {

printf( "ошибка ввода!\n" );

continue;

}

polygon[ i++ ] = x + I * y;

}

printf( "вершин %d : ", NODES );

for( i = 0; i < NODES; i++ )

printf( "[%.2f,%.2f] ",

creal( polygon[ i ] ), cimag( polygon[ i ] ) );

printf( "\nпериметр = %.2f\nплощадь = %.2f\n"

"---------------------------------\n",

perimeter( polygon ), square( polygon ) );

}

}

Код выдержан в духе структурности, никакой объектной модели язык C не предлагает. Для описания геометрических фигур «треугольник» определяется новый тип triangle_t, но это не более чем синтаксический трюк, позволяющий сократить и упростить запись кода.

Непереносимость языка C между операционными системами обусловлена, главным образом, несовместимостью используемых библиотек (API), а не различиями в толкованиях синтаксиса и семантики языка различными компиляторами. Для ликвидации такой несовместимости было реализовано несколько проектов API программных обёрток, независимых от платформы. Одним из таких проектов является, например, Apache Portable Runtime (APR) — эти библиотеки использованы в таких крупных проектах Voice-IP программных коммутаторах как Asterisk и FreeSWITH.

А вот как выполняется только что собранное нами приложение:

$ ./triangle_c

координаты вершин в формате: X Y

вершина № 1: 1. 1.

вершина № 2: 2. 1.

вершина № 3: 1. 2.

вершин 3 : [1.00,1.00] [2.00,1.00] [1.00,2.00]

периметр = 3.41

площадь = 0.50

---------------------------------


координаты вершин в формате: X Y

вершина № 1: завершение работы

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

Язык C++ намного шире своего прародителя, включает различные новые независимые парадигмы, такие как: строгая статическая именная типизация; классы, объекты и наследования; переопределения функций и операций, шаблоны (template), пространства имён... (легче перечислить то что осталось, чем то, что добавилось). Но там, где появляется широта возможностей, возникают и сложность и громоздкость.

Реализация описываемой задачи на C++ может выглядеть так:

Листинг 2. Реализация C++ (файл triangle.cc):

#include <stdlib.h>

#include <complex>

#include <iostream>

using namespace std;

class point : public complex<double> { // класс вершины наследуемый от complex

private:

bool bGood;

protected:

point( void ) : bGood( true ) {

*(complex<double>*)this = complex<double>( 0.0, 0.0 );

}

point( double re, double im ) : bGood( true ) {

*(complex<double>*)this = complex<double>( re, im );

}

point( const complex<double>& c ) : bGood( true ) {

*(complex<double>*)this = c;

}

public:

friend class triangle;

inline bool bOK( void ) { return bGood; };

friend ostream& operator << ( ostream& stream, point& obj );

friend istream& operator >> ( istream& stream, point& obj );

};

inline ostream& operator << ( ostream& stream, point& obj ) {

stream << "[" << obj.real() << "," << obj.imag() << "]";

return stream;

};

inline istream& operator >> ( istream& stream, point& obj ) {

double x, y;

string s;

obj.bGood = false; // ошибка при неправильном вводе

if( ( cin >> x ).eof() ) return stream; // ввод real

if( cin.rdstate() & ios::failbit ) {

cerr << "ошибка ввода!" << endl;

cin.clear();

getline( cin, s );

return stream;

}

if( ( cin >> y ).eof() ) return stream; // ввод image

if( cin.rdstate() & ios::failbit ) {

cerr << "ошибка ввода!" << endl;

cin.clear();

getline( cin, s );

return stream;

}

getline( cin, s );

if( !s.empty() ) { // если введено больше 2-значений

basic_string<char>::iterator i;

for( i = s.begin(); i != s.end() && *i == ' '; i++ );

if( i != s.end() ) { // если там непробельные символы

cerr << "ошибка ввода: " << s << endl;

return stream;

}

}

obj = point( complex<double>( x, y ) );

return stream;

};

class triangle { // класс треугольник производный от point

public:

static const int NODES = 3; // число вершин

protected:

point pt[ NODES ]; // координаты вершин

public:

double perimeter( void ) {

double summa = 0.0;

int i, j;

for( i = 0; i < NODES; i++ ) {

j = NODES - 1 == i ? 0 : i + 1;

summa += abs( pt[ i ] - pt[ j ] );

}

return summa;

}

double square( void ) {

complex<double> side1 = pt[ 1 ] - pt[ 0 ],

side2 = pt[ 2 ] - pt[ 0 ];

return abs( side1 ) * abs( side2 ) *

fabs( sin( arg( side1 ) - arg( side2 ) ) ) / 2.;


}

inline point& operator [] ( int i ) { return pt[ i ]; }

friend istream& operator >> ( istream& stream, triangle& obj ) {

int i = 0;

while( i < NODES ) {

cout << "вершина № " << i + 1 << " : " << flush;

stream >> obj.pt[ i ];

if( stream.eof() ) return stream;

if( !obj.pt[ i ].bOK() ) continue;

i++;

}

return stream;

}

};

int main( int argc, char **argv, char **envp ) {

int i = 0;

cout.precision( 3 );

while( 1 ) {

triangle polygon;

cout << "координаты вершин в формате: X Y" << endl;

if( ( cin >> polygon ).eof() )

cout << "завершение" << endl, exit( EXIT_SUCCESS );

cout << "вершин " << triangle::NODES << " : ";

for( i = 0; i < triangle::NODES; i++ )

cout << polygon[ i ] << " ";

cout << endl << "периметр = " << polygon.perimeter() << endl

<< "площадь = " << polygon.square() << endl

<< "---------------------------------" << endl;

}

}

Код этой реализации умышленно несколько усложнён (например, при обработке ошибок ввода), но в таком варианте он позволяет увидеть (хотя бы по написанию) основные нововведения C++ относительно классического C: классы и объекты, шаблонные (template) классы, использование итераторов шаблонных классов, потоковые операции ввода и вывода (cin, cout).

Сборка такого приложения:

$ g++ -Wall -lm triangle.c -o triangle_c

Теперь, после сборки C++ приложения, мы можем кратко коснуться того вопроса, почему C код будет всегда компилироваться и собираться в среде C++. Дело в том, что C++ приложение всегда будет компоноваться со стандартной разделяемой библиотекой C (libc.so), в дополнение к своей собственной стандартной библиотеке (libstdc++.so):

$ ldd triangle_c

linux-gate.so.1 => (0xb76eb000)

libm.so.6 => /lib/i386-linux-gnu/libm.so.6 (0xb76a8000)

libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb755a000)

/lib/ld-linux.so.2 (0xb76ec000)

$ ldd triangle_cc

linux-gate.so.1 => (0xb7797000)

libstdc++.so.6 => /usr/lib/i386-linux-gnu/libstdc++.so.6 (0xb768e000)

libm.so.6 => /lib/i386-linux-gnu/libm.so.6 (0xb7668000)

libgcc_s.so.1 => /lib/i386-linux-gnu/libgcc_s.so.1 (0xb764a000)

libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb74fc000)

/lib/ld-linux.so.2 (0xb7798000)

Стандартная библиотека C (libc.so) является интерфейсом к системным вызовам, предоставляемым операционной системой. А стандартная библиотека C++ является только интерфейсом к библиотечным вызовам, предоставляемым библиотекой C. В повседневной практике, зачастую, непонимание этих связей не влечёт последствий. Но в малых и встраиваемых системах, когда библиотеки могут компоноваться к приложениям статически, эквивалентное приложение, скомпилирванное в среде C++ может оказаться значительно объёмнее.

И выполнение собранного приложения:

$ ./triangle_cc

координаты вершин в формате: X Y

вершина № 1 : 1 1

вершина № 2 : 2 1

вершина № 3 : 1 2

вершин 3 : [1,1] [2,1] [1,2]

периметр = 3.41


площадь = 0.5

---------------------------------

координаты вершин в формате: X Y

вершина № 1 : завершение работы

Неизменно тот же код приложения может быть собран и новым компилятором Clang (из проектаLLVM):

$ clang++ triangle.cc -o triangle_cc

Показанные выше сборки приложений (как для языка C, так и для C++) предполагают 2 последовательных фазы: компиляции с последующим связыванием с другими объектными файлами или библиотеками. Важнейшей, в контексте нашего текущего рассмотрения, фазой являтся компиляция. Во всех рассмотренных выше случаях компиляция производится в «нативный» код используемого процессора и с учётом особенностей (форматов) операционной системы. Это означает, что для переноса такого приложения в другую среду (отличный процессор, операционная система) исходный программный код такого приложения должен быть перекомпилирован.

Если говорить в два слова о целевом предназначении, то язык C++ больше подходит для крупных целевых (прикладных) проектов: графика, визуализация, финансы, системы автоматизированного управления и автоматизированного проектирования... Для задач собственно системного программирования (утилиты, библиотеки, протоколы, …) более подходящим представляется классический C.

Java — объектно-ориентированный язык программирования, разработанный компанией Sun Microsystems (в настоящее время купленной компанией Oracle). Дата официального выпуска — 23 мая 1995 года.

Приложения Java компилируется не в бинарную форму, а в специальный стандартизованный байт-код (файлы .class и их архивы .jar). Поэтому такие программы могут работать на любой виртуальной Java-машине (JVM) вне зависимости от процессорной платформы, операционной системы, локального или удалённого хоста. Примерами самых известных на сегодня JVM, из числа используемых на разных платформах, могут служить:

    • Оригинальный JDK (Sun Java Development Kit, на сегодня Oracle JDK) — класическая первоначальная реализация Java, используется в Windows, Solaris, Linux и других системах.
    • OpenJDK, входящий в стандартный комплект Linux, и немногим уступающий JDK.
    • Dalvik Virtual Machine — Java виртуальная машина для системы Android, байт-код которой отличается от стандартного для JDK, но существуют программы трансформеры для их взаимного преобразования.

Для нашего приложения, в Java, в её стандартных библиотеках, нет комплексной математики. В таких случаях мы можем (здесь и для других языков тоже) поступать на выбор двумя способами:

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