Программа 24. Расчет треугольника
Пусть требуется вычислить периметр и площадь треугольника по трем его сторонам a, b, c. Напишем для этого функцию triangle. Так как треугольник существует не для любых значений длин сторон, функция должна как-то информировать об этом. Пусть она будет возвращать 1, если для заданных длин сторон треугольник существует, и 0, если не существует. Две остальные величины – периметр и площадь – будем возвращать из функции через аргументы, другого способа нет, но чтобы сформировать значения аргументов внутри функции, туда будем передавать их адреса.
Вычисления можно проводить по формулам:
– периметр,
– полупериметр,
– площадь.
В main вводятся исходные данные, и вызывается функция triangle.
// Файл Triangle.cpp
#include <iostream.h>
#include <math.h>
// triangle: вычисление периметра и площади треугольника
// возвращает 1, если треугольник существует и 0 если не существует
int triangle(double a, double b, double c, double *p_perim, double *p_area)
// a, b, c - стороны треугольника
// p_perim - указатель на переменную для периметра
// p_area - указатель на переменную для площади
{
double p; // Полупериметр
// Проверка существования треугольника
if(a > b + c || b > a + c || c > a + b)
return 0; // Треугольник не существует, выход из функции
p = (a + b + c) / 2;
*p_perim = 2 * p; // Периметр
*p_area = sqrt(p * (p - a)*(p - b)*(p - c)); // Площадь
return 1;
}
int main()
{
double r, s, t; // Стороны треугольника
double P, A; // Периметр и площадь
cout << "Введите три стороны треугольника: ";
cin >> r >> s >> t;
if(triangle(r, s, t, &P, &A) == 0)
cout << "Такого треугольника не существует\n";
else
cout << "Периметр: " << P << ", площадь: " << A <<"\n";
return 0;
}
Так как в функцию triangle передается указатель p_perim на переменную для периметра, сама эта переменная получается внутри функции с помощью выражения *p_perim и ей присваивается вычисленное значение. Аналогично используется указатель p_area на переменную для площади.
При вызове triangle ей в качестве аргументов передаются &P и &A – адреса переменных P и A. Возвращаемое triangle значение сравнивается с 0. Если результат сравнения положительный, то печатается сообщение, что треугольник не существует, иначе печатаются значения P и A, вычисленные внутри функции triangle. Ниже приведены результаты, выдаваемые программой.
Введите три стороны треугольника: 3 4 5
Периметр: 12, площадь: 6
Взаимодействие формальных параметров и фактических аргументов функции triangle иллюстрируется рис.22. При вызове функции формальные параметры a, b, c получают значения фактических аргументов r, s, t; формальные параметры p_perim, p_area получают значения адресов внешних переменных P и A.
Рис.22. Формальные параметры a, b, c получают значения внешних переменных r, s, t; p_perim и p_area получают значения адресов P и A
Указатели и массивы
Определение:
int a[10];
создает массив из 10 элементов, то есть блок из 10 расположенных последовательно переменных целого типа с именами a[0], a[1], …, a[9].
Пусть определен указатель:
int *p;
После присваивания
p = &a[0];
указатель p будет содержать адрес нулевого элемента массива a. На рис.23 это показано стрелкой, причем для p нарисован прямоугольник, чтобы подчеркнуть, что этот указатель размещен где-то в памяти.
Рис.23. Массив в памяти и указатели на элементы массива
По определению, p + 1 указывает на следующий элемент массива, p + i указывает на i-й элемент после p, p - i указывает на i-й элемент перед p. Для выражений p + 1, p + 2 на рисунке прямоугольники не нарисованы, так как под них память не выделяется. Имея указатель на начало массива, можно получить доступ к любому его элементу, например, *p есть нулевой элемент массива, *(p + 1) – первый и т.д. Присваивание
*(p + 1) = 0;
обнуляет первый элемент массива.
По определению, имя массива имеет значение адреса начального элемента массива, поэтому имя массива имеет тип указателя на элемент массива, например, a имеет тип int*.
Доступ к i - му элементу массива можно получить, используя индексацию a[i] или выражение *(a + i).
Указатель – это переменная, которой можно присваивать различные значения, например,
p = a + 1;
Теперь p указывает на первый элемент массива a.
Имя массива не является переменной, так как содержит адрес конкретного участка памяти, и записи типа a = p, a++ недопустимы. Значение имени массива изменить нельзя, во всем остальном имя массива подобно указателю.
Теперь можно разобраться, почему, когда массив является аргументом функции, он не копируется внутрь функции. Пусть объявлена функция с аргументом – массивом:
void f(char s[]);
Так как имя массива – это указатель на первый элемент массива, то данное объявление эквивалентно такому:
void f(char* s);
Отсюда видно, что внутри функции создается копия указателя на нулевой элемент массива и принцип передачи аргументов по значению остается в силе.
Адресная арифметика
Как уже говорилось, указатели можно складывать и вычитать с целыми. Если p – указатель на некоторый элемент массива, то выражение p++ изменяет p так, чтобы он указывал на следующий элемент массива. Выражение p += i изменяет p так, чтобы он указывал на i - й элемент, после того, на который он указывал ранее.
Если p и q указывают на элементы одного и того же массива, то к ним можно применять операторы сравнения: ==, !=, <, <=, >, >=. Выражение p < q истинно, если p указывает на более ранний элемент массива, чем q.
Любой указатель можно сравнивать на равенство и неравенство с нулем. Если значение указателя равно нулю, это трактуется так, что указатель никуда не указывает.
Допускается вычитание указателей. Если p и q указывают на элементы одного и того же массива и p < q, то q - p + 1 есть число элементов от p до q включительно.
Символьные указатели
Для работы со строками символов часто используются указатели на char. Их определение имеет вид:
char *pc;
Строковая константа, написанная в виде ”Я строка”, есть массив символов. Во внутреннем представлении этот массив заканчивается нулевым символом ’\0’, по которому программа может найти конец строки. Адрес начала массива, в котором расположена строковая константа, можно присвоить указателю:
pc = ”Я строка”;
Здесь копируется только адрес начала строки, сами символы строки не копируются.
Указатель на строку можно использовать там, где требуются строки. Например, при выполнении следующей инструкции:
cout << pc << ” длиной ” << strlen(pc) << ”символов”;
будет напечатано:
Я строка длиной 8 символов
При подсчете символов строки учитываются и пробелы, а завершающий символ ’\0’ не учитывается.
Массивы указателей
Указатели, как и любые другие переменные, можно группировать в массивы. Удобно, например, использовать массив символьных указателей при работе с несколькими строками, которые могут иметь различную длину.