Лекция 6

Do

Begin

Withv.BirthDate do

Begin

Withv do

Begin

Begin

Withv do

Begin

Var

Type

Пример

. . .

Record

Begin

Два кода, второй из которых также должен быть извлечен

Код «0» означает, что клавиша отправила в буфер клавиатуры

Begin

Begin

Else

End

Begin

Begin

IfmG[j] andmDP[i + j] andmDM[i - j] then

Begin

Begin

Var

Var

c: char;

n: integer;

mG: array[1..8] ofboolean; // Вертикали

mDP: array[2..16] ofboolean; // Диагонали, параллельные побочной

mDM: array[-7..7] ofboolean; // Диагонали, параллельные главной

x: array[1..8] ofinteger;

 

procedurePutNextQueen (i: integer);

j, k: integer;

forj := 1 to8 do

x[i] := j;

 

ifi < 8 then

mG[j] := false; mDP[i + j] := false; mDM[i - j] := false;

PutNextQueen (i + 1);

mG[j] := true; mDP[i + j] := true; mDM[i - j] := true;

fork:=1 to8 dowrite(x[k]:5);

writeln;

inc(n);

ifn mod 20 = 0 then

// Останавливать вывод после показа очередной порции из 20 вариантов

c := ReadKey;

ifc = #27 thenhalt;

ifc = #0 thenc := ReadKey;

end;

end;

end;

end;

end;

 

forn := 1 to8 dox[n]:=0;

forn := 1 to8 domG[n] := true;

forn := 2 to16 domDP[n] := true;

forn := -7 to7 domDM[n] := true;

n := 0;

PutNextQueen (1);

writeln(‘n=’, n);

end.

 

 

Тип «Запись»

 

<Тип «Запись»> : : =

<Список имён полей 1>: <Тип 1>;

<Список имён полей 2>: <Тип 2>;

end;

<Обращение к полю переменой типа «Запись»> : : =

<Имя переменной>.<Имя поля>

или

with<Имя переменной> do<Оператор, содержащий Имя поля>


 

{ TP7 }

programP0701;

 

DateTime = record // Delphi: TDateTime

Year, Month, Day, Hour, Min, Sec: word ;

end;

MyRec = record

N: longint ;

FIO: string[40] ;

BirthDate: DateTime ;

end;


N: longint ;

 

v1: MyRec;

v2: record

N: longint ;

FIO: string[40] ;

BirthDate: DateTime ;

end;

 


procedureProc1(v: MyRec) ;

Writeln(v.FIO) ;

Writeln(N) ;

Writeln(BirthDate.Day:2,’.’, BirthDate.Month:2,’.’, BirthDate.Year:4) ;

end;

end;

 


procedureProc2(v: MyRec) ;

Writeln(v.FIO) ;

Writeln(N) ;

Writeln(Day,’.’, Month,’.’, Year) ;

end;

end;


N := 2 ;

v1.N := 1;

v1.FIO := ‘Ivanov I.I.’;

v1.BirthDate.Year := 1950;

v1.BirthDate.Month := 10;

v1.BirthDate.Day := 2;

Proc1(v1) ; // Нормально

// Proc1(v2) ; // Ошибка

Proc2(v1) ; // Нормально

readln ;

end.

 

 

 

 


 

{

cout << “\nКупи слоника ! “; cin >> answer;

} while (answer != ‘y’);

return 0;

}

Цикл с параметром имеет следующий формат:

for(инициализация; выражение; модификации) оператор

В части инициализации можно записать опе­раторы, разделенные запятой (операцией «последовательное выполнение»). На­пример:

for (int i = 0, j = 2; ...

……………..

int k, m;

for (k = 1, m = 0; ...

Областью действия объявленных в инициализации переменных является весь цикл. Инициализация выполняется один раз в начале выполнения цикла.

Выражение определяет условие выполнения цикла: если его результат соответствует true, цикл выполняется. В этом смысле цикл с параметром аналогичен циклу с предусловием.

Модификации выполняются после каждой итерации цикла и служат обычно для изменения параметров цикла. В части модификаций также можно записать несколько операторов через запятую.

Простой или составной оператор представляет собой тело цикла. Любая из частей оператора for может быть опущена (точки с запя­той остаются).

Пример (нахождение всех делителей целого положительного числа):

#include <iostream.h>

int main ()

{

int num, half, div;

cout << “\nВведите число: “; cin >> num;

for (half = num / 2, div = 2; div <= half; div++)

if (!(num % div)) cout << div << “\n”;

return 0;

}

 

4.5.4. Операторы передачи управления

 

В C++ есть четыре оператора, изменяющих естественный порядок выполнения вычислений:

· безусловный переход goto;

· выход из цикла break;

· переход к следующей итерации continue;

· возврат из функции return.

Применение этих операторов (особенно goto) противоречит структурному программированию, поэтому их рекомендуется использовать лишь при необходимости. В связи с этим в настоящем курсе вообще не рассматривается оператор goto.

Оператор break используется внутри операторов цикла или switch для обеспе­чения перехода в точку программы, находящуюся непосредственно за операто­ром, внутри которого находится break. Оператор continue пропускает все опера­торы, оставшиеся до конца тела цикла, и передает управление на начало следую­щей итерации.

Оператор возврата из функции return завершает выполнение функции и переда­ет управление в точку, следующую за ее вызовом. Его вид таков:

return[выражение];

Выражение должно иметь тип возвращаемого функцией значения. Если последний описан как void, выражение в return должно отсутствовать.

 

 

4.6. Указатели и массивы

 

4.6.1. Указатели

 

Указатель – это элемент данных (константа или переменная), предназначенный для хранения адресов областей памяти (констант, переменных, функций). Это не самостоятельный тип. Указатель всегда связан с некоторым конкретным типом, адреса которого он может содержать.

Указатель на данные содержит адрес памяти, в которой хранятся данные определенного типа. Простейшее объявление такого указателя имеет вид:

тип *имя;

где тип может быть любым, кроме ссылки и битового поля. Звездочка относится к имени, поэтому для объявления нескольких указателей требуется ставить ее перед именем каждого из них. На­пример, в операторе

int *a, b, *с;

описаны два указателя на целое с именами а и c, а также целая переменная b.

Указатель на функцию также содержит адрес. Он используется для косвенного вызова (не по имени функции, а посредством переменной, содержащей ее адрес), а также для передачи одной функции в другую функцию в качестве пара­метра. Его объявление имеет вид

тип (*имя) (список_типов_аргументов);

Например, объявление:

int (*fun) (double, double);

определяет переменную fun как указатель на функцию, возвращающую значение типа int и имеющую два аргумента типа double.

Можно также определить указатель на указатель и т. д. Возможно определение указателя на void. Оно применяется в случае, когда конкретный тип адресуемых элементов не определен (например, если в одной и той же переменной возможно хранение адресов данных раз­личных типов). Указателю на void можно присвоить значение указателя любого типа, а также сравнивать его с любыми указателями. Однако перед выполнением каких-либо дейст­вий с областью памяти, на которую он ссылается, требуется преобразовать его к конкретному типу явным образом.

Рассмотрим примеры:

int i; // целая переменная

const int ci = 1; // целая константа

int *pi; // указатель на целую переменную

const int *pci; // указатель на целую константу

int * const cp = &i; // указатель-константа на целую переменную

const int * const cpc = &ci; // указатель-константа на целую константу

void f (int a) { /* */ } // определение функции

void (*pf) (int), // указатель на функцию

pf = f; // присваивание адреса функции

Указатели часто используются при работе с динамической памятью – кучей (от английского heap). Это свободная память, которую во время выполнения программы можно использовать в соответствии с ее потребностями. Доступ к выделенным участкам динами­ческой памяти, называемым динамическими переменными, производится через указатели. Время жизни динамических переменных – от точки создания до конца программы или до явного освобождения памяти. В C++ используется два способа работы с динамической памятью. Первый использует функции mallос и free (достался в наследство от С), второй – операции new и delete.

Указателю можно присваивать следующие значения:

· адрес существующего элемента;

· явный адрес области памяти;

· пустое значение (0);

· адрес выделенного участка динамической памяти;

С указателями можно выполнять ряд операций: разадресация, или кос­венное обращение к объекту (*), присваивание, сложение с константой, вычита­ние, инкремент/декремент (++/--), сравнение, приведение типов. При работе с указателями часто используется операция получения адреса (&).

Операция разадресации предназначена для доступа к вели­чине, адрес которой содержится в указателе. Например:

char a; // переменная типа char

char *p = new char, /* объявление указателя и его инициализация адресом

динамической переменной типа char */

*p = ‘Ю’; a = *p; // присваивание значений обеим переменным

На одну и ту же область памяти может ссылаться несколько указателей различного типа. Применяемая к ним операция разадресации может давать различные результаты. Например:

#include <stdio.h>

int main()

{

unsigned long int A = 0xCC77FFAA;

unsigned short int *pInt = (unsigned short int*) &A;

unsigned char *pChar = (unsigned char *) &A;

printf (“ | %x | %x | %x |", A, *pInt, *pChar);

return 0;

}

Результаты: | CC77FFAA | FFAA | AA |

В этом примере при инициализации указателей были использова­ныоперации приведения типов. Синтаксис операции явного приведения типа таков: перед именем переменной в скобках указывается тип, к которому ее требу­ется преобразовать. Поскольку при этом не гарантируется сохранение информации, то без необходимости не рекомендуется использовать явные преобразования типов.

При смешивании в выражении указателей разных типов явное преобразование типов требуется для всех указателей, кроме void*. Указатель может неявно пре­образовываться в значение типа bool (например, в выражении условного опера­тора), при этом ненулевой указатель преобразуется в true, а нулевой в false. Зна­чение 0 (пустой указатель) неявно преобразуется к указателю любого типа.

Арифметические операциинад указателями автоматически учитывают размер адресуемых величин. Так инкремент перемещает указатель к следующему элементу в памяти, декремент к предыдущему. Если указатель на определенный тип увеличивается или уменьшает­ся на константу, его значение изменяется на величину этой константы, умножен­ную на размер данного типа, например:

short *р = new short[5];

р++; // значение р увеличивается на 2

p += 5; // значение р увеличивается на 10

long *q = new long[5];

q++; // значение q увеличивается на 4

Разность двух указателей – это разность их значений, деленная на размер типа в байтах. Суммирование двух указателей не допускается.

Унарная операция получения адреса & применима лишь к величинам, имеющим имя и размещенным в оперативной памяти. Невозможно получить адрес выражения, неименованной константы или регистровой переменной.

Наряду с указателем, в C++ имеется элемент данных ссылка (в C его не было). Ссылку можно рассматривать как указатель, который всегда автоматически разадресуется, и поэтому при работе с ней операция * не нужна. Формат объявления ссылки таков:

тип& имя;

где тип – это тип величины, на которую указывает ссылка, & – признак ссылки. Например:

int kol, &pal = kol; // ссылка pal – альтернативное имя для kol

const char & CR = '\n'; // ссылка на константу

Имеют место следующие правила:

· переменная-ссылка должна явно инициализироваться при описании, кро­ме случаев, когда она является параметром функции, описана как extern или ссылается на поле данных класса;

· после инициализации ссылке не может быть присвоена другая переменная;

· тип ссылки должен совпадать с типом величины, на которую она ссылается;

· не разрешается определять указатели на ссылки, создавать массивы ссылок и ссылки на ссылки.

Ссылки чаще всего применяются в качестве параметров функций и типов возвращаемых функциями значений. Ссылки позволяют использовать в функциях переменные, передаваемые по адресу, без операции разадресации. Это улучшает читаемость программы.

Ссылка, в отличие от указателя, не занимает дополнительного пространства в па­мяти и является просто другим именем (синонимом) величины. Операция над ссылкой приво­дит к изменению величины, на которую она ссылается.

 

4.6.2. Массивы

 

Конечная именованная последовательность однотипных вели­чин называется массивом. Описание массива в программе отличается от описа­ния простой переменной наличием после имени квадратных скобок, в которых задается количество элементов массива (размер). Например,

float a[10]; // Вещественный массив из 10 элементов

Элементы массива всегда нумеруются с нуля. При описании массива используются те же модификаторы (класс памяти, const и инициализатор), что и для простых переменных. Инициализирующие значения для массивов записываются в фигур­ных скобках. Значения элементам присваиваются по порядку. Если элементов в массиве больше, чем инициализаторов, элементы, для которых значения не ука­заны, обнуляются:

int b[5] = {3, 2, 1}; // b[0]=3. b[l]=2, b[2]=l. b[3]=0, b[4]=0

Размер массива вместе с типом его элементов определяют объем памяти, необходимый для размещения массива. Оно выполняется на этапе компиляции, поэтому размер может быть задан только целой положительной констан­той или константным выражением. При описании массива размер можно не указывать, при этом должен присутствовать инициализатор, и компилятор вы­делит память по количеству инициализирующих значений.

Для доступа к элементу массива после его имени указывается номер элемента (индекс) в квадратных скобках. В следующем примере подсчитывается сумма элементов массива.

#include <iostream h>

int main ()

{

const int n = 10;

int i, sum;

int marks[n] = {3, 4, 5, 4, 4};

for (i = 0, sum = 0; i < n; i++) sum += marks[i];

cout << "Сумма элементов- " << sum;

return 0;

}

При обращении к элементам автоматический контроль выхода индекса за грани­цу массива не производится, что может приводить к ошибкам.

Идентификатор массива является константным указателем на его нулевой эле­мент. Например, для массива из предыдущего имя marks – это то же самое, что &marks[0], а к i-му элементу массива можно обратиться, используя выражение *(marks+i). Можно описать указатель, присвоить ему адрес начала массива и работать с массивом через указатель. Например:

int a[100], b[100];

………………………………………………

int *pa = а; // или int *рa = &а[0];

int *pb = b;

for (int i = 0; i < 100; i++) *pb++ = *pa++; // или pb[i] = pa[i];

Динамические массивысоздаются с помощью операции new, при этом необходимо указать тип и размер, например:

int n = 100;

float *p = new float [n];

Преимущество динамических массивов в том, что их размер может быть переменным, то есть нужный объем памяти определяется на этапе выполнения программы. Доступ к элементам динамического массива осу­ществляется так же, как и статического. Например, к 5-му элементу приве­денного выше массива можно обратиться как р[5] или *(р+5).

Альтернативный способ создания динамического массива – использование функции malloc библиотеки С:

int n = 100;

float *q = (float *) malloc (n * sizeof (float));

Поскольку функция malloc возвращает значение указателя void*, то требуется явное преобразование типа.

Память, зарезервированная с помощью new [], должна освобождаться операцией delete[], а память, выделенная функцией malloc – посредством функции free, например:

delete[] p; free (q);

Многомерные массивызадаются указанием каждого измерения в квадратных скобках, например, оператор

int matr [6][8];

объявляет двумерный массив из 6 строк и 8 столбцов. В памяти такой массив располагается в последовательных ячейках построчно. Многомерные массивы размещаются так, что при переходе к следующему элементу раньше изменяется последний индекс. Для доступа к элементу мас­сива указываются все его индексы, например, matr[i][j], *(matr[i] + j) или *(*(matr + i ) + j).

Количество индексов массива называется его размерностью.

При инициализации многомерного массива он представляется либо как массив из массивов, при этом каждый массив заключается в свои фигурные скобки (в этом случае размеры при описании можно не указывать), либо задается общий список элементов в том порядке, в котором элементы располагаются в памяти:

int mass1 [][] = { {1, 1}, {0, 2}, {1, 0} };

int mass2 [3][2] = {1, 1, 0, 2, 1, 0};

Пример. Программа определяет в целочисленной матрице номер строки, которая содержит наибольшее количество нулевых элементов.

#include <stdio.h>

int main ()

{

const int nstr = 4, nstb = 5; // размеры массива

int b[nstr][nstb]; // описание массива

int i, j:

 

for (i = 0; i < nstr; i++)

for (j = 0; j < nstb; j++) scanf (“%d”, &b[i][j]); // ввод массива

 

int istr = -1, MaxKol = 0;

for (i = 0; i < nstr; i++) // просмотр массива по строкам

{

int Kol = 0;

for (j = 0; j < nstb; j++) if (b[i][j] == 0) Kol++; // Можно if (!b[i][j]) …

if (Kol > MaxKol) {istr = i; MaxKol = Kol;}

}

 

printf (“Исходный массив\n”);

for (i = 0; i < nstr; i++)

{

for (j = 0; j < nstb; j++) printf (“%d “, b[i][j]);

printf ("\n");

}

if (istr == -1) printf ("Нулевых элементов нет");

else printf (“Номер строки: %d”, istr);

return 0;

}

4.6.3. Строки

 

Строка представляет собой массив символов, заканчивающийся нулем (символом с кодом 0). Нуль-символ при необходимости записывается в виде ‘\0’. По расположению нуль-символа определяется фактическая длина строки. Строку можно инициализировать строковым литера­лом:

char str[10] = “Vasia”; // выделено 10 элементов с номерами от 0 до 9

// первые элементы – ‘V’, ‘a’, ‘s’, ‘i’, 'а', '\0'

...

char str[] = “Vasia”; // выделено и заполнено б байтов

Оператор

char *str = “Vasia”;

создает не строковую переменную, а указатель на строковую константу, изме­нить которую невозможно. Знак равенства перед строковым литералом означает инициализацию, а не присваива­ние. Операция присваивания одной строки другой не определена (поскольку строка является массивом) и может выполняться с помощью цикла или функций стандартной библиотеки.

Пример. Программа запрашивает пароль не более трех раз.

#include <stdio.h>

#include <string.h>

int main ()

{

char s[10], passw[] = “kuku”; // passw – пароль, можно также написать *passw = “kuku”

int i, k = 0;

for (i = 0; !k && i < 3; i++)

{

printf (“\nвведите пароль:\n”);

gets (s); // Стандартная функция ввода строки с клавиатуры

if (strstr (s, passw) ) k = 1; // “сравнение” строк (указатель вхождения passw в s)

}

if (k) printf (“\nпароль принят”); else printf (“\nпароль не принят”);

return 0;

}