Правила использования оператора цикла с параметром
1. Параметр цикла i, а также его значения ml и m2 могут быть любого типа.
2. Параметр i, а также значения ml и m2 не должны переопределяться (менять значения) в теле цикла.
3. При завершении работы оператора for параметр i становится неопределенным и переменную i можно использовать в других целях.
4. Тело цикла может не выполниться ни разу, если m1>m2 для цикла for... с положительным шагом, или m1<m2 для отрицательного шага.
Пример 3.Решить предыдущую задачу табулирования функции с использованием оператора цикла for.
Схема алгоритма Программа
начало |
a,b |
Заголовок таблицы |
x=-0.5; 2.5; 0.1 |
y= |
x, y |
конец |
#include "stdafx.h"
#include<math.h>
int main()
{
float a, b, x, y;
printf("введите а и b ");
scanf("%f%f",&a,&b);
printf(" x y(x)\n");
for( х=-0.5; x<=2.5; x=x+0.1)
{
y=log(fabs(x))/(a*a+b*b);
printf("%8.1f %8.1f\n",x,y);
}
return 0;
}
Замечание 2.Оператор цикла while, как указывалось выше, наиболее универсальный из трех операторов цикла, используемых в языке С++. Однако конструкция оператора цикла for является наиболее простой. Поэтому рекомендуется там, где возможно, использовать оператор for.
Базовые алгоритмы
Для реализации циклических вычислительных процессов часто используются следующие базовые алгоритмы:
• табулирование функций;
• организация счетчика;
• накопление суммы или произведения;
• поиск минимального или максимального члена последовательности.
Ниже приводятся примеры программирования задач на основе базовых алгоритмов.
Задача 1. Алгоритм организации счетчика
Дана последовательность:
cos 1, cos 3, cos 5, ..., cos 99.
Определить количество положительных членов последовательности.
Решение
Представим последовательность в общем виде:
а = cos(2n -1), где п = .
Для организации счетчика в памяти компьютера выделяется ячейка, содержимое которой должно увеличиваться на 1 каждый раз, когда встречается положительный член последовательности. В программе ячейке (счетчику) соответствует переменная целого типа, например переменная L. Работа счетчика реализуется с помощью оператора присваивания L=L+1. В начальный момент содержимое ячейки должно быть равно нулю. С этой целью предварительно осуществляется очистка ячейки оператором присваивания L=0.
#include "stdafx.h"
#include<math.h>
int main()
{
float a;
int n,L; // описание переменных
L=0; // очистка счетчика
for(n=1;n<=50;n++) //запуск цикла
{
a = cos(2*n – 1.0); // тело цикла
if(a>0) L = L + 1; /*переменная-счетчик увеличивается на единицу, если а>0 */
}
printf("L=%d", L); // вывод значения счетчика
return 0;
}
Задача 2. Алгоритм накопления суммы
Дана последовательность:
sin 2x, sin 4x, sin 6x, ..., sin l6x
x - заданное вещественное число.
Вычислить сумму членов последовательности, которые по модулю больше 0.3.
Решение
Общий член последовательности имеет вид:
а = sin(2nx), где n = .
Для вычисления суммы в памяти компьютера выделяется ячейка S, к содержимому которой прибавляется член последовательности а каждый раз, когда выполняется условие > 0.3. Накопление суммы реализуется оператором присваивания S=S+a;. В начальный момент ячейка для суммирования должна быть очищена оператором S=0;.
#include "stdafx.h"
#include<math.h>
int main()
{
float a, x, S; //описание переменных задачи
int n;
printf("Введите значение х= ");
scanf("%f",&x);
S=0;//очистка суммы
for(n=1;n<=8;n++)// запуск цикла
{
a=sin(2*n*x);
if ( abs(a)>0.3) S = S + a;/* добавление числа а в сумму, если |a|>0.3 */
}
printf("S=%6.2f",S); // вывод значения суммы на экран
return 0;
}
Задача 3. Алгоритм накопления произведения
Дана последовательность:
cos 0.1, cos 0.2, cos 0.3, ..., cos 10.
Вычислить значение: Р где РО - произведение отрицательных членов последовательности.
Решение
Общий член последовательности имеет вид:
y = cos x, где 0.1 10; Δх = 0.1.
Для реализации алгоритма накопления произведения выделяется ячейка памяти РО, в которой осуществляется последовательное перемножение отрицательных членов последовательности с помощью оператора присваивания РО=РО*у; . В начальный момент в ячейку должна быть занесена единица оператором РО=1;.
#include "stdafx.h"
#include<math.h>
int main()
{
float х, у, Р, РО;
РО = 1;// установка нач. значения произведения
for (x=0.1; x<=10; x=x+0.1)//запуск цикла
{
у = cos(x);
if ( y<0) РО = РО*у;
}
Р = fabs(PO);
printf("P=%6.2f",P); //вывод на экран значения P
return 0;
}
Задача 4. Алгоритм поиска минимального члена последовательности
Дана последовательность:
ak=ektg(2k + l); к= .
Найти минимальный член последовательности.
Решение
Для реализации алгоритма выделяется ячейка памяти min, в которую сначала заносится первый член последовательности. Затем, начиная со второго, производится сравнение очередного вычисленного члена последовательности с содержимым ячейки min. Если текущий член последовательности меньше содержимого ячейки min, то oн переписывается в эту ячейку. В противном случае содержимое ячейки min сохраняет прежнее значение. При завершении сравнения всех членов последовательности в ячейке min остается минимальное значение.
Замечание 1. Алгоритм поиска максимального члена последовательности отличается от поиска минимального члена лишь тем, что в ячейке (ей можно дать, например, имя max) запоминается больший из сравниваемых членов последовательности.
Замечание 2. В начальный момент в ячейку min можно занести не первый член последовательности, а достаточно большое число, которое превышало бы область определения сравниваемых чисел (например, min=+1E6;). Тогда при сравнении с содержимым ячейки min первый член последовательности обязательно окажется меньше и перепишется в ячейку min. При поиске максимального члена последовательности в ячейку max в начальный момент заносится, наоборот, достаточно малое число, которое должно быть меньше всех сравниваемых членов последовательности (например, mах= -1Е6;). В этом случае первый из сравниваемых членов последовательности окажется больше содержимого ячейки max и запишется в эту ячейку.
Программа поиска min:
#include "stdafx.h"
#include<math.h>
int main()
{
float a, min;
int k;
min = +1E6; // нач. значение переменной min
for( k=l; k<=10;k++)
{
a = exp(1.0*k)*tan(2*k + 1.0);
if (a<min) min = a; // условие для поиска min
}
printf("min=%6.2f", min);
return 0;
}
Задача 5. Табулирование функции (или кратные циклы)
Тело цикла может содержать любой оператор, в том числе и другой оператор цикла. Структура цикла, содержащая вложенный цикл, называется кратным циклом. Число вложений может быть произвольным. Если цикл содержит один вложенный цикл, то он называется двойным, если содержит два вложенных цикла, то является тройным и т.д. Цикл, который содержит вложенный цикл, называется внешним. Вложенный цикл называется внутренним.
Переменная внутреннего цикла всегда меняется быстрее, чем внешнего. Это означает, что для каждого значения внешней переменной цикла меняются все значения внутренней переменной.
Внешний и внутренний циклы могут использовать любой вид операторов цикла (while, do-while, for).
Пример.Алгоритм табулирования функции с двумя переменными
Вычислить значение функции:
z(x, у) = sin x + cos y
при всех х, изменяющихся на интервале [-1, 1] с шагом Δх = 0.2, и у, изменяющихся на интервале [0, 1] с шагом Δу = 0.1.
Данный алгоритм реализуется с использованием двойного цикла, в котором х примем за внешнюю переменную цикла, у - за внутреннюю переменную цикла.
#include "stdafx.h"
#include<math.h>
int main()
{
float х, у, z; // описание переменных
printf("x y z(x,y)\n"); // вывод заголовка
x= -1; // начальное значение параметра внешнего цикла
while (х<=1) // запуск внешнего цикла, если х≤ 1
{
for( y=0; y<=1; y=y+0.1) //запуск внутреннего цикла
{
z=sin(x) + cos(y); // вычисление функции
printf("%6.1f %6.1f z=%6.1f",x, y, z); // вывод
}
x=x + 0.2; // изменение параметра х на шаг
}
return 0;
}
В результате выполнения программы вид таблицы на экране будет следующим:
x | y | z(x,y) |
-1.0 | 0.0 | z=… |
-1.0 | 0.1 | z=… |
… | … | … |
-1.0 | 1.0 | z=… |
-0.8 | 0.0 | z=… |
-0.8 | 1.0 | z=… |
Задача 6. Вычисление сумм последовательностей
Последовательности с заданным числом элементов
Пример 1.Найти сумму первых 20 элементов последовательности
S=1/2 – 2/4 + 3/8 – 4/16+…
Чтобы решить эту задачу, надо определить закономерность в изменении элементов. В данном случае можно заметить:
· Каждый элемент представляет собой дробь.
· Числитель дроби при переходе к следующему элементу возрастает на единицу.
· Знаменатель дроби с каждым шагов увеличивается в 2 раза.
· Знаки перед дробями чередуются (плюс, минус и т.д.).
Любой элемент последовательности можно представить в виде
S=z*c/d
У переменной z меняется знак (эту операцию можно записать в виде z=-z), значение переменной c увеличивается на единицу (c++), а переменная d умножается на 2 (d=d*2).
Алгоритм решения задачи можно записать в виде следующих шагов:
· Записать в переменную S значение 0. В этой ячейке будет накапливаться сумма;
· Записать в переменные z, c и d начальные значения (для первого элемента): z=1, c=1,d=2;
· Сделать 20 раз следующие две операции:
v добавить к сумме значение очередного элемента;
v изменить значения переменных z, c и d для вычисления следующего элемента.
#include "stdafx.h"
int main()
Начальные значения |
float S, z, c, d;
int i;
S = 0; z = 1; c = 1; d = 2;
добавить элемент к сумме |
{
S = S + z*c/d;
изменить переменные |
c++;
d = d * 2;
}
printf("Сумма S = %f", S);
return 0;
}
Суммы с ограничивающим условием
Рассмотрим более сложную задачу, когда количество элементов заранее неизвестно.
Пример 2.Найти сумму всех элементов последовательности
S=1/2 – 2/4 + 3/8 – 4/16+…
которые по модулю меньше, чем 0.001.
Эта задача имеет решение только тогда, когда элементы последовательности убывают по модулю и стремятся к нулю. Поскольку мы не знаем, сколько элементов войдет в сумму, надо использовать цикл while (или do - while). Один из вариантов решения показан ниже.
#include<math.h>
#include "stdafx.h"
начальные значения |
{
Запустить цикл, если а ≥0.001 |
S = 0; z = 1; c = 1; d = 2;
a = 1;
while ( a >= 0.001 )
добавить элемент к сумме |
a =fabs( c / d);
изменить переменные |
z = - z;
c ++;
d = d * 2;
}
printf("Сумма S = %f", S);
return 0;
}
Цикл закончится тогда, когда переменная a (она обозначает модуль очередного элемента последовательности) станет меньше 0.001. Чтобы программа вошла в цикл на первом шаге, в эту переменную надо записать любое значение, большее, чем 0.001.
Очевидно, что если переменная a не будет уменьшаться, то условие в заголовке цикла всегда будет истинно и программа «зациклится».
Указатели и массивы
Указатели
Указатель - это переменная, содержащая адрес области памяти. Указатели широко применяются в языке С++. В некоторых случаях без них просто не обойтись, а в некоторых программы с использованием указателей становится короче и эффективнее.
Начнем с того, что поговорим о структуре памяти любого компьютера. Как известно, память компьютера представляет последовательность 8-битовых байтов. Каждый байт пронумерован, причем нумерация начинается с нуля. Номер байта называется адресом. Иногда говорят, что адрес указывает на определенный байт. Таким образом, указатель является просто адресом байта памяти.
Язык С++ позволяет определять переменные, которые могут хранить адреса памяти. Такие переменные и называются указателями. Значение указателя сообщает о том, где размещен объект, но ничего не говорит о значении самого объекта.
Указатель |
Объект Данные или группа данных |
Присваивая указателю то или иное допустимое значение, можно обеспечить доступ к данным через этот указатель.
Для описания переменной типа указатель используется символ *.
Формат описания:
Тип *имя;
Указатель – адрес переменной какого-то определенного типа. Этот тип сообщается компилятору при объявлении указателя.
int *x;
char *y;
Пример следует понимать так: x – это указатель на ячейку, в которой хранится целое значение, а y – указатель на однобайтовую ячейку, предназначенную для хранения символа.
Двумя наиболее важными операциями, связанными с указателями, являются операция обращения по адресу * и определение адреса &.
Операция обращения по адресу предназначена для записи или считывания значения, размещенного по адресу, содержащемуся в переменной-указателе.
Например:
int *x;
. . .
*x=5;
Операция определения адреса & возвращает адрес памяти своего операнда. Операндом должна быть переменная.
Напимер:
int *x;
int a=5;
x=&a;
Кроме того, над указателями можно выполнять арифметические операции сложения и вычитания.
Если у – указатель на целое, то унарная операция y++ увеличивает значение адреса, хранящегося в переменной-указателе на число равное размеру ячейки целого типа, т.е. на 2 байта; теперь оно является адресом следующей ячейки целого типа. Соответственно, оператор y--; означает, что адрес уменьшается на 2 байта.
Указатели и целые числа можно складывать. Конструкция у + n (у - указатель, n - целое число) задает адрес n-гo объекта, на который указывает у. Это справедливо для любых объектов (int, char, float и др.); транслятор будет масштабировать приращение адреса в соответствии с типом, указанным в определении объекта.
Любой адрес можно проверить на равенство (= =) или неравенство (!=), больше (>) или меньше (<) с любым другим адресом.
Рассмотрим следующий фрагмент программы:
#include "stdafx.h"
int main()
{
int *x, *w;
int y;
*x=16;
y=-15;
w=&y;
. . .
}
Этот текст можно понимать так:
Выделить память под три переменные x, w, y, где x и w –переменные типа указатель. Оператор *x=16; означает, что в ячейку, адрес которой записан в х, помещается значение 16. Затем переменной у присваивается значение -15. После чего в указатель w записывается адрес переменной y. Синтаксически текст записан правильно. Проблема заключается в том, что указатель х не инициализирован. Описание int *x; это лишь указание компилятору резервировать память, необходимую для хранения адреса целой ячейки. Но в этой памяти может оказаться адрес любой ячейки, в том числе и адрес, где хранится полезная информация, например, операционная система. Запись в такую ячейку может привести к сбою в работе компьютера. Поэтому при работе с указателями их надо правильно инициализировать. Существует 4 способа правильного задания начального значения для указателя:
1) Описать указатель глобально, т.е. вне любой функции. При этом указатель будет инициализирован безопасным нулевым адресом. Кроме того любому указателю можно присвоить безопасный нулевой адрес, например:
int *x;
x=NULL;
Гарантируется, что этот адрес не совпадет ни с одним адресом, уже использованным в системе.
2) Присвоить указателю адрес переменной. Например: w=&y;
3) Присвоить указателю значение другого указателя, к этому моменту правильно инициализированного. Например: x=w;
4) Использовать функции выделения динамической памяти malloc() и calloc(). При использовании этих функция необходимо подключать библиотеку <malloc.h>. Рассмотрим пример использования функции malloc():
x=(int*)malloc(sizeof(int));
Приведенный пример означает, что функция выделит область памяти, размер которой определит функция sizeof(). Если вы знаете размер ячейки заданного типа, то можно написать проще: x=(int*)malloc(2);
По окончанию работы программы, память, выделенную функцией malloc() рекомендуется освободить функцией free(x); Вернемся к приведенному ранее фрагменту программы:
#include "stdafx.h"
#include <malloc.h>
int main()
{ int *x, *w;
int y;
x=(int*)malloc(sizeof(int));
*x=16;
y=-15;
w=&y;
. . .
}
Теперь никаких конфликтных ситуаций при работе с указателями не возникнет. В языке С++ существует еще одна пара операторов new и delete для динамического выделения и освобождения памяти. О них мы поговорим чуть позже.
Понятие массива
Массив представляет собой упорядоченное множество однотипных элементов. В языке С++ массив описывается переменной сложной структуры. При описании массива необходимо указать:
• способ объединения элементов в структуру (одномерный, двухмерный и т.д.);
• число элементов;
• тип элементов.
Общий вид описания массива
<тип элементов> имя [число элементов];
Доступ к каждому элементу массива осуществляется с помощью индексов. Индексы задают порядковый номер элемента, к которому осуществляется доступ. В языке С++ первый элемент массива имеет индекс ноль. Число индексов определяет структуру массива: если используется один индекс, то такой массив называется одномерным, если два индекса - двухмерным, и т.д. В общем случае размерность массива может быть произвольной.
Одномерные массивы
В математике одномерному массиву соответствует n-мерный вектор, например:
; i = 1,…, n ,
где хi - компонента (координата) вектора;
i - номер компоненты;
п - число компонент.