Слайд 1Вещественный тип данных, указатели, функции
Слайд 2Вещественные типы данных
Стандарт C++ определяет три типа данных для хранения вещественных
значений: float, double и long double. Все эти типы предназначены для представления отрицательных и положительных значений (спецификатор unsigned к ним не применим) в разных диапазонах:
тип float занимает в памяти 4 байта с диапазоном абсолютных значений от 3.4е-38 до 3.4е+38;
тип double занимает в памяти 8 байт с диапазоном абсолютных значений от 1.7е-308 до 1.7е+308;
тип long double занимает в памяти 10 байт с диапазоном абсолютных значений от 3.4e-4932 до 3.4e+4932.
Замечание. В консольных приложениях Windows тип данных long double занимает в памяти 8 байт, то есть ничем не отличается от типа double.
Слайд 3Представление констант
Константы вещественных типов задаются двумя способами:
нормальный формат: 123.456 или -3.14;
экспоненциальный
формат: 1.23456e2 (1.23456е+2). Привести другие примеры.
Дробная часть отделяется от целой части точкой, а не запятой.
Слайд 4Представление вещественных чисел в компьютере
Внутреннее представление вещественного числа состоит из двух
частей — мантиссы и порядка:
-1.2345e+2
| |
мантисса порядок
Тип float занимает 4 байта, из которых один двоичный разряд отводится под знак мантиссы, 8 разрядов под порядок и 23 под мантиссу.
Для величин типа double, занимающих 8 байт, под порядок и мантиссу отводится 11 и 52 разряда соответственно.
Длина мантиссы определяет точность числа, а длина порядка — его диапазон.
Слайд 5Точность вычислений и «ловушка» для программиста
Все вычисления с вещественными значениями осуществляются
приближенно, при этом, ошибки вычислений могут достигать весьма существенных значений. Это объясняется дискретностью внутреннего (машинного) представления непрерывного диапазона вещественных значений. Точность представления значений вещественных типов зависит от размера мантиссы. Относительная точность представления вещественных значений остается постоянной при различных значениях порядка. Однако, абсолютная точность существенно зависит от значения порядка (с уменьшением порядка абсолютная точность возрастает).
Слайд 6
Преобразования типов данных
При выполнении различных операций над разнотипными данными необходимы преобразования
одних типов данных к другим.
В языке C++ различают неявное (автоматическое) и явное преобразование типов данных.
Слайд 7Правила неявного (арифметического) преобразования
Все данные типов char и short int преобразуются
к типу int.
Если хотя бы один из операндов имеет тип double, то и другой операнд преобразуется к типу double (если он другого типа); результат вычисления имеет тип double.
Если хотя бы один из операндов имеет тип float, то и другой операнд преобразуется к типу float (если он другого типа); результат вычисления имеет тип float.
Если хотя бы один операнд имеет тип long, то и другой операнд преобразуется к типу long (если он другого типа); результат имеет тип long.
Если хотя бы один из операндов имеет тип unsigned, то и другой операнд преобразуется к типу unsigned (если его тип не unsigned); результат имеет тип unsigned.
Если ни один из случаев 1-5 не имеет места, то оба операнда должны иметь тип int; такой же тип будет и у результата.
Слайд 8Преобразование типов данных при операции присваивания
При выполнении операции присваивания тип значения
выражения автоматически преобразуется к типу левого операнда (к типу данных переменной в левой части). При этом возможны потери данных или точности.
Слайд 9Явное преобразование типов данных
Явное преобразование типов данных осуществляется с помощью соответствующей
операции преобразования типов данных, которая имеет один из двух следующих форматов:
(<тип данных>) <выражение> или <тип данных> (<выражение>)
Например:
(int) 3.14 int (3.14)
(double) a или double (a)
(long) (a + 1e5f) long (a + 1e5f)
Слайд 10Исходы явного преобразования типов данных
Явные преобразования типов данных имеют своим исходом
три ситуации:
преобразование без потерь;
с потерей точности;
с потерей данных.
При явном преобразовании типов значения преобразуемых величин на самом деле не изменяются – изменяется только представление этих значений при выполнении действий над ними.
Слайд 11Исходы явного преобразования типов данных
Преобразование происходит без потерь, если преобразуемое значение
принадлежит множеству значений типа, к которому осуществляется преобразование.
Преобразование любого вещественного типа к целому осуществляется путем отбрасывания дробной части вещественного значения, поэтому практически всегда такие преобразования приводят к потере точности (осуществляются приближенно)
Попытки преобразования значений выходящих за пределы диапазона типа данных, к которому осуществляется преобразование, приводят к полному искажению данных.
Слайд 12Тип данных «Указатель»
Указатели – это тоже обычные переменные, но они служат
для хранения адресов памяти.
Указатели определяются в программе следующим образом:
<тип данных> *<имя переменной>
Здесь <тип данных> определяет так называемый базовый тип указателя.
<Имя переменной> является идентификатором переменной-указателя.
Признаком того, что это переменная указатель, является символ *, располагающийся между базовым типом указателя и именем переменной-указателя.
Например:
int *p1;
double *p2;
Слайд 13
Работа с указателями
Присвоить указателю адрес некоторой переменной можно инструкцией присваивания и
операцией &, например,
int A = 2351, *p1;
double B = 3.14, *p2;
p1 = &A; // Указателю p1 присваивается адрес переменной А
p2 = &B; // Указателю p2 присваивается адрес переменной В
Слайд 14Работа с указателями
Получить значение объекта, на который ссылается некоторый указатель можно
с помощью операции * (эту операцию обычно называют разыменованием указателя):
int A = 2351, *p1;
double B = 3.14, *p2;
p1 = &A; // Указателю p1 присваивается адрес переменной А
p2 = &B; // Указателю p2 присваивается адрес переменной В
cout << “Значение переменной А: ” << *p1 << endl;
cout << “Адрес переменной А: ” << p1 << endl;
cout << “Значение переменной В: ” << *p2 << endl;
cout << “Адрес переменной В: ” << p2 << endl;
Слайд 15Внимание!
При использовании указателей в выражениях важно помнить, что операция * имеет
наивысший приоритет по отношению к другим операциям (за исключением операции унарный – (минус)).
Слайд 16
Арифметика указателей
К указателям можно применять некоторые арифметические операции. К таким операциям
относятся: +, -, ++, --. Результаты выполнения этих операций по отношению к указателям существенно отличаются от результатов соответствующих арифметических операций, выполняющихся с обычными числовыми данными.
Добавлять к указателям или вычитать из указателей можно только целые значения.
Слайд 17Преимущества использования функций
Использование функций позволяет:
значительно упростить разработку сложных программ;
сократить объем текста
программы и генерируемого результирующего кода программы;
значительно упростить отладку и модификацию программ;
распределить работу над одной программой между различными исполнителями программистами.
Фактически разработка более-менее сложных программ практически невозможна без использования функций.
Слайд 18
Описание функций в программе
Любая функция состоит из двух основных элементов: заголовка
и тела функции.
Заголовок функции имеет следующий формат:
<Тип возвращаемого значения> <Идентификатор – имя функции> (<Параметры>)
Тело функции представляет собой блок инструкций языка программирования, разделенных символами “точка с запятой”:
{
<Инструкция 1>;
<Инструкция 2>;
………………….
<Инструкция N>;
}
Слайд 19Пример описания функции
double Example (double d, int k)
{
double r;
r =
d * k;
return r;
}
Не все функции должны возвращать значения. В этом случае <Тип возвращаемого значения> задается ключевым словом void, которое означает – “пусто”
Слайд 20
Завершение работы функции (инструкция return)
Если функция не возвращает через свое имя
никакого значения, то она завершается после выполнения последней инструкции тела функции. При необходимости досрочного завершения работы функции необходимо использовать инструкцию return, которая приводит к немедленному завершению работы функции.
В одной и той же функции могут быть использованы несколько инструкций return.
Если функция возвращает через свое имя некоторое значение, то выход из функции обязательно должен осуществляться с помощью инструкции return. В этом случае эта инструкция не только вызывает окончание работы функции, но и осуществляет передачу возвращаемого функцией значения.
Значение, которое возвращает инструкция return, по типу должно соответствовать типу возвращаемого функцией значения.
Слайд 21
Список параметров функции
Параметры функций служат для обеспечения взаимодействия между функцией
и вызвавшей ее программой.
Параметры функций перечисляются в заголовке через символ ‘,’ (запятая). Каждый параметр должен соответствовать определенному типу данных. Параметры могут быть любых типов данных. Тип данных для каждого параметра (даже если все они имеют один и тот же тип данных) задается отдельно в соответствии со следующим форматом:
<Тип данных параметра> <Идентификатор – имя параметра>
Слайд 22Примеры
void Example1 (int a, int b)
или
double Example2 (int A, double
B)
Слайд 23Вызов функции
При вызове функции на места параметров подставляются некоторые конкретные значения,
которые обычно называют аргументами функции
Количество, типы данных и порядок следования аргументов должны соответствовать списку параметров функции.
Функции, возвращающие значения, могут использоваться в качестве элементов различных выражений.
Слайд 24Передача данных по значению
При вызове функции в определенной области памяти (в
стеке программы) для каждого параметра функции создается переменная соответствующая типу данных параметра.
В эти переменные копируются значения аргументов, использовавшихся при вызове функции. При выполнении кода функции эти копии значений аргументов могут использоваться для обработки, могут изменять свои значения, но эти изменения никак не затрагивают значений самих аргументов.
Такой способ служит для передачи данных только внутрь функции, но не из функции обратно в программу.
Слайд 25Передача данных с помощью указателей
Для использования передачи данных с помощью
указателей необходимо обязательно выполнить три следующих пункта:
Соответствующий параметр в заголовке функции необходимо определить как указатель на тип данных аргумента.
При вызове функции на месте параметров-указателей необходимо использовать адрес аргумента, а не сам аргумент.
При обращении внутри функции к значению аргумента через параметр-указатель необходимо осуществить разыменование этого указателя.
Слайд 26Пример
Необходимо разработать функцию, возвращающую результат деления и остаток от деления двух
целых чисел.
int Div (int N1, int N2, int *Ost) // int *Ost – параметр-указатель
{
*Ost = N1 % N2; // *Ost – разыменование параметра-указателя
return N1 / N2;
}
Слайд 27Передача массивов в качестве аргумента
Несколько проще обстоит дело с передачей массивов,
так как переменные типа массив сами являются указателями на первый элемент массива. В связи с этим отпадает необходимость в выполнении пунктов 2 и 3 из перечисленных выше.
Слайд 28Пример
void WriteArr ( int Arr[], int n)
{
for (int I
= 0; I < n; ++I)
cout << Arr[I] << “ “;
cout << endl;
}
Слайд 29Передача данных по ссылке
В языке C++ имеется более простой способ
передачи данных по адресу, а именно – передача данных по ссылке.
int Div (int N1, int N2, int &Ost) // int &Ost – параметр-ссылка
{
Ost = N1 % N2; // Разыменования параметра-ссылки Ost не требуется
return N1 / N2;
}
Слайд 30Аргументы - константы
Недостатком передачи данных по адресу является скрытый побочный эффект,
связанный с возможным непредвиденным изменением внутри функции значения аргумента переданного по адресу. Однако этого эффекта можно избежать, если определить соответствующий параметр функции как константу:
void Proc(const double *D)
{
……
*D = 3.14; // Ошибка в процессе компиляции
……
}
Слайд 31Перегружаемые функции
Перегруженными функциями называются функции, имеющие одинаковые имена, но различающиеся
количеством, типами данных или порядком следования разнотипных параметров. Например:
void f (char c)
void f (int c)
void f (char c, int i)
void f (int c, char i)
void f (char c, char i)
Нельзя перегружать функции, различающиеся только типами данных возвращаемых значений.
int f (char c, char i)
Слайд 32Параметры по умолчанию
void F (int I, double D, char C =
’a’, int J = 10)
{
cout << C << “ “ << J << endl;
}
int main ()
{
F (0, 3.14); // Результат: а 10
F (0, 3.14, ’G’); // Результат: G 10
F (0, 3.14, ’G’, 1000); // Результат: G 1000
Слайд 33Параметры по умолчанию
Количество параметров по умолчанию может быть любым. При использовании
параметров по умолчанию необходимо помнить:
все параметры по умолчанию должны находиться в конце списка параметров функции;
если при вызове функции не указывается аргумент для некоторого параметра по умолчанию, то и все следующие аргументы должны быть пропущены.
Слайд 34Рекурсивное использование функций
Функции внутри своего тела могут вызывать сами себя. Такой
вызов называется рекурсией.
Рассмотрим функцию Pow(D,P)возведения вещественного значения D в целую положительную степень P.
Очевидно, что:
Pow(D,P)=1 при P=0;
Pow=Pow(D,P-1)*D при P>0;
Слайд 35Реализация функции
double Pow (double D, unsigned P)
{
if (P)
return Pow ( D, P – 1 ) * D;
else
return 1;
}
Слайд 36Передача функций в качестве параметров
Используя тот факт, что имена функций
являются обычными указателями, можно передавать функции в качестве аргументов других функций.
Для этого необходимо с помощью оператора typedef определить тип данных указателя на функции, передаваемые в качестве аргумента, который должен соответствовать характеристикам передаваемых функций (тип возвращаемого значения, параметры функции).
Слайд 37Пример
double add (double a, double b)
{ return a + b;}
double mul
(double a, double b)
{return a * b;}
typedef double (*f_Ptr) (double, double);
double oper (f_Ptr F, double a, double b)
{return F (a, b);}
int main ()
{ cout << oper (add, 20, 30) << endl; // 50
cout << oper (mul, 20, 30) << endl; // 600
return 0;}
Слайд 38Встраиваемые функции (inline - функции)
Встраиваемые функции задаются ключевым словом inline:
inline int
ReadInt(char *S)
Некоторые компиляторы накладывают определенные ограничения на содержание встраиваемых функций. К таким ограничениям обычно относятся использование внутри встраиваемых функций:
рекурсии;
циклов, переключателей, инструкций goto;
статических (static) переменных.
Слайд 39Прототипы функций
Прототипом функции называется заголовок функции (со списком параметров), заканчивающийся символом
;.
Например:
double F (int P1, double P2 ); // Это прототип функции F
double F (int P1, double P2 ) // А это сама функция F
{ return P1 * P2;}