Имеется молекула, состоящая из n атомов. Геометрическая конфигурация этой молекулы характеризуется положением в пространстве каждого из атомов. Так как атомы — протяженные объекты, а их границы не имеют четкого определения, то на практике под положением атома понимают положение его ядра. Итак, геометрическая конфигурация молекулы задана декартовыми координатами ядер атомов xi, yi, zi (i = 1, 2, ¼, n). Помимо координат, указаны символы химических элементов. Например, данные о геометрической конфигурации молекулы ацетальдегида CH3CHO записаны в файле acetald.dat в следующем виде:
C -0.0789455 -0.4488120 0.0000000
C 1.2834071 0.1812646 0.0000000
O -1.2036536 0.2344798 0.0000000
H -0.1310309 -1.5615937 0.0000000
H 1.2042458 1.2643756 0.0000000
H 1.8441620 -0.1192523 -0.8801361
H 1.8441620 -0.1192523 0.8801361
В первой строке указано количество атомов в молекуле (7), затем для каждого атома записан химический символ и три координаты ядра (x, y, z), выраженные в ангстремах[1].
Требуется написать программу, которая читает из файла данные о молекуле и сообщает наименьшее и наибольшее межъядерные расстояния (с указанием атомов, к которым эти расстояния относятся).
Координаты точки в пространстве (в нашем случае — координаты ядра атома) удобно хранить в виде массива из трех элементов. Совокупность координат всех атомов молекулы — это массив координат точек (ядер), т.е. массив массивов (или двумерный массив).
Химические символы записываются одной или двумя буквами, т.е. в общем случае для их представления нужны строки символов (букв) максимальной длины 2. В языке C строки символов — это массивы элементов типа char. В частности, "hello!" — тоже массив (хотя и константный), причем его длина на единицу больше, чем число символов между кавычками, т.е. "hello!" имеет размер 7 элементов (7 байтов). Дело в том, что конец строки принято отмечать специальным символом с кодом 0 (обозначается '\0'). Функции стандартной библиотеки C, имеющие дело со строками, предполагают, что конец строки помечен именно таким способом. Это касается, в частности, ввода и вывода строк с помощью scanf, printf и других подобных функций. Поэтому размер массива для хранения строки длиной 2 символа должен быть не менее 3 элементов, т.е. один химический символ можно разместить в массиве
char s[3];
а для хранения символов всех атомов молекулы понадобится массив строк, т.е. опять-таки массив массивов (с элементами типа char).
Вычисление расстояния между двумя точками (ядрами атомов) имеет смысл выделить в отдельную функцию (назовем ее dist от слова distance — расстояние). Аргументами функции будут два трехэлементных массива координат. Такую функцию можно использовать не только в этой задаче, но и в любой другой, где требуется вычислять расстояния между точками 3-мерного пространства.
Текст программы приведен ниже:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define MAXAT 20 /* макс. количество атомов в молекуле */
/* Функция dist вычисляет расстояние между точками p1 и p2 */
double dist (double p1[3], double p2[3])
{
int i;
double d, s = 0.0;
for (i = 0; i < 3; i++)
s += (d = p2[i] - p1[i], d*d);
return sqrt(s);
}
/* Главная программа */
void main (int nargs, char *arg[])
{
static double at[MAXAT][3]; /* массив координат атомов */
static char sym[MAXAT][3]; /* массив хим. символов */
int n; /* число атомов в молекуле */
int imin, jmin; /* номера пары самых близких атомов */
int imax, jmax; /* номера пары наиболее удаленных атомов */
double d, dmin, dmax; /* текущее, наименьшее и наибольшее расстояния */
int i, j; /* переменные циклов для перебора пар атомов */
FILE *f = stdin; /* файл, из которого вводятся данные */
/* Если файл указан в командной строке, открыть его;
иначе использовать стандартный поток ввода (stdin) */
if (nargs > 1) {
f = fopen (arg[1], "r");
if (f == NULL) {
perror (arg[1]);
exit (1);
}
}
/* Ввод исходных данных */
fscanf (f, "%d", &n);
for (i = 0; i < n; i++)
fscanf(f,"%2s%lf%lf%lf", sym[i], &at[i][0], &at[i][1], &at[i][2]);
fclose (f);
/* Поиск минимального и максимального расстояний */
dmin = 1e38;
dmax = 0.0;
for (i = 0; i < n-1; i++)
for (j = i+1; j < n; j++) {
d = dist (at[i], at[j]);
if (d < dmin) {
dmin = d;
imin = i;
jmin = j;
}
if (d > dmax) {
dmax = d;
imax = i;
jmax = j;
}
}
/* Вывод результатов */
printf ("Dmin = %8.5f %d(%s) - %d(%s)\n",
dmin, imin+1, sym[imin], jmin+1, sym[jmin]);
printf ("Dmax = %8.5f %d(%s) - %d(%s)\n",
dmax, imax+1, sym[imax], jmax+1, sym[jmax]);
}
В случае приведенных выше данных для молекулы ацетальдегида программа выводит такой результат:
Dmin = 1.08600 2(C) - 5(H)
Dmax = 3.19201 3(O) - 6(H)
Обратите внимание на то, как используются элементы-массивы двумерных массивов at и sym при обращении к функции dist и при вводе/выводе химических символов атомов.
Название файла, где содержатся исходные данные, программа получает в виде первого параметра командной строки (функция main может получить доступ к командной строке через свои аргументы — подробнее см. книгу Кернигана и Ритчи, гл. 5, п. 5.10). Если же программу запустили без параметров, то данные будут вводиться из стандартного потока ввода (stdin), который указан в качестве начального значения переменной f. Пусть исполняемый файл, полученный в результате компиляции и компоновки приведенной программы, называется prog.exe. Тогда запустить программу можно тремя разными способами:
1. Файл с данными указывается как параметр командной строки:
prog acetald.dat
2. Командная строка без параметров:
prog
После запуска программа начнет читать данные из стандартного потока ввода, т.е. ввод с клавиатуры.
3. Стандартный поток ввода переадресуется на файл с помощью символа < в командной строке:
prog <acetald.dat
Так же, как и в предыдущем случае, данные читаются из потока stdin (программа не заметит никакой разницы между этими вариантами). Однако операционная система будет посылать данные в поток stdin не с клавиатуры, а из файла acetald.dat.
[1] Данные получены в результате квантовохимического расчета молекулы ацетальдегида.