Файл: Современные языки программирования (Теоретические основы языков программированияя).pdf
Добавлен: 25.05.2023
Просмотров: 232
Скачиваний: 3
СОДЕРЖАНИЕ
Глава 1. Теоретические основы языков программирования
1.1. Содержание понятия и классификация языков программирования
1.2. История создания языков программирования
Глава 2. Современные языки программирования
2.1. Анализ современных языков программирования
Координаты вершин будут выражаться как комплексное значение — это естественно для физического мира, так как комплексные величины это и есть отображение точек 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, в её стандартных библиотеках, нет комплексной математики. В таких случаях мы можем (здесь и для других языков тоже) поступать на выбор двумя способами:
- Написать достаточно простой набор необходимых приложению комплексных функций. Это очень несложная работа.
- Разыскать (в Интернете) модуль, реализующий интересующую нас функциональность (а таких представлено достаточно много).