Особенности применения указателей

Обращение к объектам любого типа в языке C может проводиться по имени, как мы до сих пор делали, и по указателю (косвенная адресация).

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

Указатель объявляется следующим образом:

тип * ID указателя;

Перед применением указатель должен быть инициирован или конкретным адресом, или значением NULL (0) – отсутствие указателя.

С указателями связаны две унарные операции: и *. Операция свидетельствует «забрать адрес», а операция разадресации * – «значение, расположенное по адресу», к примеру:

int x, *y; // х – переменная типа int , у – указатель типа int

y = x; // y – адрес переменной x

*y = 1; // по адресу y записать 1, в следствии x = 1

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

Операции сложения, сравнения и вычитания (больше/меньше) имеют суть лишь для последовательно расположенных данных – массивов. Операции сравнения «==» и «!=» имеют суть для любых указателей, т.е. в случае, если два указателя равны между собой, то они показывают на одну и ту же переменную.

Сообщение указателей с массивами

массивы и Указатели тесно связаны между собой. Идентификатор массива есть указателем на его первый элемент, т.е. для массива int a[10], выражения a и a[0] имеют однообразные значения, т.к. адрес первого (с индексом 0) элемента массива – это адрес начала размещения его элементов в ОП.

Пускай заявлены – массив из 10 элементов и указатель типа double:

double a[10], *p;

в случае, если p = a; (установить указатель p на начало массива a), то следующие обращения: a[i] , *(a+i) и *(p+i) эквивалентны, т.е. для любых указателей возможно применять две эквивалентные формы доступа к элементам массива: a[i] и *(a+i). Очевидна эквивалентность следующих выражений:

a[0] « (*p) « p

Декларация многомерного массива:

тип ID[размер 1][размер 2]…[размер N];

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

int a[2][3] = {{0,1,2},{3,4,5}};

в ОП будет размещен следующим образом:

a[0][0]=0, a[0][1]=1, a[0][2]=2, a[1][0]=3, a[1][1]=4, a[1][2]=5.

В случае, если в перечне инициализаторов данных не достаточно, то соответствующему элементу присваивается значение 0.

Указатели на указатели

Сообщение массивов и указателей с одним измерением справедливо и для массивов с бoльшим числом измерений.

В случае, если разглядывать прошлый массив (int a[2][3];) как массив двух массивов размерностью по три элемента любой, то обращение к элементу а[i][j] соответствует эквивалентное выражение *(*(а+i)+j), а объявление этого массива с применением указателей будет иметь вид

int **а;

Так, имя двухмерного массива – ID указателя на указатель.

Динамическое размещение данных

Для массивов с переменной размерностью употребляется динамическое размещение данных, декларируемых указателями.

Для работы с динамической памятью употребляются стандартные функции библиотеки alloc.h:

void *malloc(size) и void *calloc(n, size) – выделяют блок памяти размером size и n´sizeбайт соответственно; возвращают указатель на выделенную область, при неточности – значение NULL;

void free(bf); – освобождает ранее выделенную память с адресом bf.

Вторым, более предпочтительным подходом к динамическому распределению памяти есть применение операций языка С++ new и delete.

Операцияnewвозвращает адрес ОП, отведенной под динамически размещенный объект, при неточности – NULL, а операция deleteосвобождает память.

Минимальный комплект действий, нужных для динамического размещения одномерного массива настоящих чисел размером n:

double *а;

. . .

а = new double[n]; // Захват памяти для n элементов

. . .

delete []а; // Освобождение памяти

Минимальный комплект действий, нужных для динамического размещения двухмерного массива настоящих чисел размером n´m:

int i, n, m; // n, m – размеры массива

double **a;

a = new double *[n]; // Захват памяти под указатели

for(i=0; i

a[i] = new double [m]; // и под элементы

. . .

for(i=0; i

delete []a;

Для освобождения памяти, занятой массивами громадной размерности, в современных компиляторах достаточно выполнить следующее воздействие (для прошлого примера)

delete []a;

Функции пользователя

Подпрограмма – это именованная и в некотором роде оформленная несколько операторов, вызываемая по мере необходимости.

В языке С++ в качестве подпрограмм применяют функции, каковые должны быть декларированы до их первого применения. Предварительное описание функции именуется прототипом, что в большинстве случаев размещается в начале программы (*.cpp) или в заголовочном файле (*.h) и информирует компилятору о том, что потом в программе будет приведен ее полный текст, т.е. реализация.

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

тип_результата ID_функции (перечень типов параметров);

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

тип_результата ID_функции (перечень параметров)

{

код функции

return итог;

}

Итог возвращается из функции в точку вызова при помощи оператора returnи преобразуется к типу, указанному в заголовке функции. В случае, если тип функции не указан, то по умолчанию устанавливается тип int, в случае, если же функция не возвращает результата, то направляться указать безлюдный тип void. Перечень параметров складывается из списка типов и ID параметров, поделённых запятыми.

Из функции возможно передать лишь одно значение, при необходимости возвратить пара – в перечне параметров применяют указатели.

Пример реализации функции, определяющей мельчайшее из двух целых чисел:

int Min_x_y(int x, int y) {

return (x

}

Вызов функции имеет следующий формат:

ID_функции(перечень доводов);

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

Вызов прошлой функции может иметь вид: min = Min_x_y(a, b);

Область действия переменных

Область действия переменной – это правила, каковые устанавливают, какие конкретно эти дешёвы из текущего места программы, и определяют переменные двух типов: глобальные и локальные.

Глобальные переменные объявляются вне какой-либо функции и смогут быть использованы в любом месте программы, но перед их первым применением они должны быть заявлены и инициализированы. Область действия глобальных переменных – вся программа с момента их объявления. При объявлении глобальные переменные обнуляются.

Область действия локальных переменных – это блоки, где они заявлены. К локальным относятся и формальные параметры в заголовке функций пользователя с бластью действия – кодом функции. При выходе из блока значения локальных переменных теряются.

В языке С любая переменная в собственности одному из четырех классов памяти – автоматической (auto), внешней (extern), статической (static) и регистровой (register). Тип памяти указывается перед спецификацией типа, к примеру, register int a; либо static double b; По умолчанию устанавливается класс auto и переменные размещаются в стеке.

5.2. Пример исполнения задания

В целочисленном двухмерном динамическом массиве (матрице) размером N´М отыскать сумму четных элементов и их количество. Ввод данных (его элементы и размеры массива) и вывод результатов выполнить в главной функции. Ответ поставленной задачи оформить в фунции пользователя.

Текст программы может иметь следующий вид:

#include

#include

int Fun_Sum_Kol(int, int, int**, int*); // Описание прототипа функции

void main()

{

int **a, i, j, n, m, sum, kol;

cout

cin n m;

a = new int*[n]; // Захват памяти под указатели

for(i=0; i

a[i] = new int[m]; // Захват памяти под элементы

cout

for(i=0; i

for(j=0; j

cout

cin a[i][j];

}

cout

for(i=0; i

for(j=0; j

cout

cout

}

// Обращение к функции с указанием фактических доводов

sum = Fun_Sum_Kol(n, m, a, kol);

cout

delete []a; // Освобождение памяти

cout

cout

getch();

}

/* Реализация обрисованной выше функции, в заголовке которой указаны формальные параметры, идентификаторы которых обрабатываются в ее коде */

int Fun_Sum_Kol(int a, int b, int **x, int *k)

{

int i, j, s = 0;

*k = 0;

for(i=0; i

for(j=0; j

if(x[i][j] % 2 ==0){

(*k)++; // Скобки необходимы

s += x[i][j];

}

return s;

}

Обратите внимание на то, что из функции мы должны взять два скалярных результата – посчитанные количество и сумму четных чисел. Посредством оператора return мы возвращаем первое значение (сумму), а второе значение мы передаем в точку вызова посредством указателя (адреса): при обращении к функции в качестве четвертого параметра передаем адрес kol, а в функции используем «содержимое, находящееся по указанному адресу» *k ( * – операция разадресации).

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

Особенности применения указателей

5.3. Личные задания

В двухмерном целочисленном массиве (размеры массива N, M и значения его элементов вводить с клавиатуры) отыскать указанное значение.

Массив в памяти разместить динамически (с применением операций new и delete), ввод данных и вывод взятых результатов выполнить в главной функции, а ответ задачи оформить в виде отдельной функции пользователя. Не применять глобальных переменных.

1. Отыскать сумму элементов, расположенных на основной диагонали.

2. Отыскать произведение элементов, расположенных на основной диагонали.

3. Отыскать большой элемент и поменять его с первым элементом.

4. Отыскать минимальный элемент и поменять его с первым элементом.

5. Отыскать большой элемент и поменять его с последним элементом.

6. Отыскать минимальный элемент и поменять его с последним элементом.

7. Отыскать количество отрицательных и хороших элементов массива.

8. Отыскать количество 0-й и 1-ц в массиве, и сумму единиц.

9. Отыскать число элементов массива, громадных T (вводится с клавиатуры) и просуммировать эти элементы.

10. Отыскать число элементов массива T* и их произведение.

11. Отыскать число элементов массива T* и их сумму.

12. Отыскать число элементов массива T* и перемножить эти элементы.

13. Отыскать число элементов массива = T* и сложить эти элементы.

14. Отыскать число элементов массива = T* и перемножить эти элементы.

15. Отыскать большой элемент среди лежащих ниже основной диагонали.

16. Отыскать минимальный элемент среди лежащих выше основной диагонали.

Дополнительное задание №6. Обработка структур с применением файлов

Цель работы: изучить обработки и правила создания данных структурного типа с использованеием файлов. Разработать и отладить программу по созданию файлов.

Теоретические сведения

Структура объединяет логически связанные эти различных типов. Структурный тип данных определяется описанием шаблона:

struct Рerson {

char Fio[30];

double sball;

};

Объявление переменных созданного структурного типа:

Person Stud, *p_Stud;

Обращение к элементам структур производится при помощи:

1) операции принадлежности ( .) в виде:

ID_структуры . ID_поля либо (*указатель) .ID_поля

2) операции косвенной адресации (–) в виде:

указатель – ID_поля либо (ID_структуры) . ID_поля

Для вышеприведенного примера

1) Stud.Fio = “Иванов А.И.”; //Инициализация данных

Stud.sball = 5.75;

2) р_Stud – Fio = “Иванов А.И.”;

р_Stud – sball =5.75;

В языке C/С++ файл рассматривается как поток (stream), воображающий собой последовательность считываемых либо записываемых байт. Наряду с этим последовательность записи определяется самой программой.

Работа с файлами

Файл – это комплект данных, размещенный на внешнем носителе и разглядываемый в ходе обработки и пересылке как единое целое. Прототипы большинства функций по обработке файлов обрисованы в библиотеках stdio.h и io.h.

Перед тем как трудиться с файлом, его необходимо открыть для доступа, т.е. создать и инициализировать область данных, которая содержит данные о файле: имя, путь и т.д. В языке С/С++ это делает функция fopen(), которая связывает физический файл на носителе с логическим именем в программе. Логическое имя – это указатель на файл, т.е. на область памяти, где хранится информация о файле. Указатели на файлы нужно декларировать:

FILE *указатель на файл;

Формат функции

fopen( “строчок 1” , “строчок 2” );

в строчке 1 указывается место, в которое мы планируем поместить файл, к примеру: “d:\\work\\sved.txt” – файл с именем sved.txt, что будет пребывать на диске d, в папке work; в случае, если путь к файлу не показывать, то он будет размещен в рабочей папке проекта.

В строке 2 указывается код открытия файла:

w – для записи, в случае, если файла с заданным именем нет, то он будет создан, в случае, если же таковой файл существует, то перед открытием прошлая информация уничтожается;

r– для чтения; в случае, если файла нет, то появляется неточность;

a – для добавления новой информации в финиш;

r+, w+ – вероятны запись и чтение информации;

a+ – то же, что и для a, лишь запись возможно делать в любое место файла, доступно и чтение файла.

По умолчанию файл раскрывается в текстовом режиме (t), указав b – файл раскрывается в бинарном режиме.

В случае, если при открытии файла случилась неточность, функция fopen возвращает значение NULL.

По окончании работы доступ к файлу нужно закрыть посредством функции fclose(указатель файла), к примеру, fclose ( f );

Для закрытия нескольких файлов введена функция: void fcloseall(void);

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

#include

. . .

FILE *f_my;

if( ! ( f_my = fopen(“rez.txt”, “r+t” ) ) ) {

puts(“\n Неточность открытия файла!”);

return;

}

. . . // Работа с файлом

fclose(f_my);

. . .

Для работы с текстовыми файлами в консольном приложении эргономичнее всего пользоваться функциями fprintf() и fscanf(), параметры и делаемые действия подобны функциям printf() и scanf(), (см. лаб.раб.№1), лишь первым параметром добавлен указатель файла, к которому используется эта функция.

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

Для баз разрешённых удобнее пользоваться функциями работы с двоичными файлами. Разглядим кое-какие из них, обозначив указатель файла – fp (FILE *fp;):

1) int fread(void *ptv, int size, int n, fp) – считывает n блоков по size байт любой из файла fp в область памяти, на которую показывает ptv (нужно заблаговременно отвести память под считываемый блок);

2) int fwrite(void *ptv, int size, int n, fp) – записывает n блоков по size байт любой из области памяти, на которую показывает ptv в файл fp;

3) int fileno(fp) – возвращает значение дескриптора файла fp (дескриптор –число, определяющее номер файла);

4) long filelength(int дескриптор) – возвращает длину файла в байтах;

5) int chsize(int дескриптор, long pos) – делает изменение размера файла fp, показатель финиша файла устанавливается по окончании байта с номером pos;

6) intfseek(fp, long size, int kod) – делает смещение указателя на size байт в направлении показателя kod: 0 – от начала файла; 1 – от текущей позиции; 2 – от финиша файла;

7) long ftell(fp) – возвращает значение указателя на текущую позицию файла fp (-1 – неточность);

8) intfeof(указатель файла) – возвращает ненулевое значение при верной записи показателя финиша файла;

9) intfgetpos(указатель файла, long *pos) – определяет значение текущей позиции pos файла; при успешном завершении возвращает значение 0.

6.2. Пример исполнения задания

Разработать программу обработки файла, содержащего данные о рейтинге студентов. Любая запись обязана содержать Ф.И.О. и полученный балл рейтинга. Вывести данные, упорядоченную в порядке повышения рейтинга. Результаты исполнения программы сохранить в текстовом файле. При работе с файлом должны выполняться следующие действия: создание, просмотр, добавление новой записи, сортировка, сохранение результатов.

Для текстового файла в консольном приложении используем функцию fprintf(). Текст программы может иметь следующий вид:

. . .

#include

#include

. . .

struct TZap{

char FIO[30];

double s_b;

} Zap;

int size = sizeof(TZap);

FILE *Fz, *Ft;

char File_Zap[] = zapisi.dat;

char File_Rez[] = rezult.txt;

void Out(TZap);

void main()

{

int kod, не сильный, i=0, j, kol;

long len;

TZap st, *mas_Z;

Ft = fopen(File_Rez, w);

while(true) {

puts(\n Create – 1\n Add – 2\n View – 3\n Sort – 4\n EXIT – 0);

scanf(%d, kod);

switch(kod) {

case 1:

if ((Fz=fopen(File_Zap,wb))==NULL) {

puts(\n Create ERROR!);

return;

}

fclose(Fz);

printf(\n Create New File %s !\n,File_Zap);

break;

case 2:

Fz = fopen(File_Zap,ab);

printf(\n F.I.O. – );

fflush(stdin);

gets(Zap.FIO);

printf( Ball – );

scanf(%lf, Zap.s_b);

fwrite(Zap, size, 1, Fz);

fclose(Fz);

break;

case 3:

if ((Fz=fopen(File_Zap,rb))==NULL) {

puts(\n Open ERROR!);

return;

}

// Вывод на экран

printf(\n\t——— Informations ———);

// Запись такой же информации в текстовый файл Ft

fprintf(Ft,\n\t——— Informations ———);

while(1) {

if(!fread(Zap,size,1,Fz)) break;

Out(Zap);

}

fclose(Fz);

break;

case 4:

Fz = fopen(File_Zap,rb);

D_f = fileno(Fz);

len = filelength(D_f);

kol = len/size;

mas_Z = new TZap[kol];

// Считываем записи из файла в динамический массив

for (i=0; i kol; i++)

fread((mas_Z+i), size, 1, Fz);

fclose(Fz);

printf(\n\t—— S O R T ——\n);

fprintf(Ft,\n\t—— S O R T ——\n);

for (i=0; направляться kol-1; i++)

for (j=i+1; j kol; j++)

if (mas_Z[i].s_b mas_Z[j].s_b) {

st = mas_Z[i];

mas_Z[i] = mas_Z[j];

mas_Z[j] = st;

}

for (i=0; i

Out(mas_Z[i]);

delete []mas_Z;

break;

case 0:

fclose(Ft);

return;

}

}

}

//—————— Функция вывода одной записи на экран и в файл ———————

void Out(TZap z)

{

printf(\n %20s , %6.3lf ., z.FIO,z.s_b);

fprintf(Ft, \n %20s , %6.3lf ., z.FIO, z.s_b);

}

Первоначально выбав пункт «1», создаем файл с именем zapisi.dat, что будет размешаться в текущем каталоге (созданной папке). После этого, выбирая пункт «2», последовательно вводим 4 записи. Выбрав пункт «3», просматриваем содержимое файла, а сортированные записи выведем на экран (запишем в файл), выбрав пункт «4». Результаты исполнения программы смогут иметь вид:

Особенности применения указателей

6.3. Личные задания

Разработать программу обработки файла типа запись, содержащую следующие пункты меню: «Создание», «Просмотр», «Коррекция» (добавление новых данных либо редактирование ветхих), «Ответ личного задания».

Любая запись обязана содержать следующую данные о студентах:

– инициалы и фамилия;

– год рождения;

– номер группы;

– оценки за семестр: по физике, математике, информатике, химии;

– средний балл.

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

Содержимое всего файла и результаты ответа индивидувльного задания записать в текстовый файл.

1. Распечатать анкетные эти студентов, сдавших сессию на 8, 9 и 10.

2. Распечатать анкетные эти студентов-отличников, фамилии которых начинаются с интересующей вас буквы.

3. Распечатать анкетные эти студентов-отличников из интересующей вас группы.

4. Распечатать анкетные эти студентов, фамилии которых начинаются с буквы А, и сдавших математику на 8 либо 9.

5. Распечатать анкетные эти студентов, имеющих оценки 4 либо 5 по физике и оценку больше 8 по остальным предметам.

6. Распечатать анкетные эти студентов интересующей вас группы. Фамилии студентов начинаются с букв В, Г и Д.

7. Распечатать анкетные эти студентов, не имеющих оценок меньше 4 по информатике и математике.

8. Вычислить неспециализированный средний балл всех студентов и распечатать перечень студентов со средним баллом выше неспециализированного среднего балла.

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

10. Распечатать анкетные эти студентов интересующей вас группы, имеющих неудовлетворительную оценку (меньше 4).

11. Распечатать анкетные эти студентов интересующей вас группы, имеющих оценку 9 либо 10 по информатике.

12. Распечатать анкетные эти студентов, имеющих оценки 7 либо 8 по физике и оценки 9 либо 10 по высшей математике.

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

14. Распечатать анкетные эти студентов-отличников интересующей вас группы.

15. Распечатать анкетные эти студентов интересующей вас группы, имеющих средний балл выше введенного с клавиатуры.

16. Распечатать анкетные эти студентов интересующей вас группы, имеющих оценку 8 по физике и оценку 9 по высшей математике.

Приложение. Операции языка С/С++

1. Операции приведены в порядке убывания приоритета, операции с различными приоритетами поделены чертой.

Операция Краткое описание Применение Выполне-ние
Первичные (унарные) операции
. Доступ к участнику объект . член Слева направо
Доступ по указателю указатель — член
[ ] Индексирование переменная [выражение]
( ) Вызов функции ID(перечень)
Унарные операции
++ Постфиксный инкремент lvalue++ Справа налево
Постфиксный декремент lvalue—
sizeof Размер объекта (типа) sizeof(ID либо тип)
++ Префиксный инкремент ++lvalue
Префиксный декремент —lvalue
~ Побитовое НЕ ~выражение
! Логическое НЕ !выражение
— (+) Унарный минус (плюс) — (+)выражение
* Разадресация указателя *выражение
Адрес выражение
() Приведение типа (тип)выражение
Двоичные и тернарная операции
* Умножение выражение * выражение Слева направо
/ Деление выражение / выражение
% Получение остатка выражение % выражение
+ ( — ) Сложение (вычитание) выражение + (-) выражение
Сдвиг влево выражение
Сдвиг вправо выражение выражение
Меньше выражение выражение
Меньше либо равняется выражение
Больше выражение выражение
= Больше либо равняется выражение = выражение
== Равняется выражение == выражение
!= Не равняется выражение != выражение
Побитовое И выражение выражение
^ Побитовое исключ. Либо выражение ^ выражение
| Побитовое Либо выражение | выражение
Логическое И выражение выражение
|| Логическое Либо выражение || выражение

Операция Краткое описание Применение Выполне-ние
?: Условная операция (тернарная) выражение ? выражение : выражение Справа налево
= Присваивание lvalue = выражение
*= Умножение с присваиванием lvalue *= выражение
/= Деление с присваиванием lvalue /= выражение
%= Остаток от деления с присв-м lvalue %= выражение
+= Сложение с присваиванием lvalue += выражение
— = Вычитание с присваиванием lvalue -= выражение
Сдвиг влево с присваиванием lvalue
= Сдвиг вправо с присваиванием lvalue = выражение
= Поразрядное И с присваив-м lvalue = выражение
|= Поразрядное Либо с присв-м lvalue |= выражение
^= Поразрядное ИСКЛЮЧАЮЩЕЕ Либо с присваиванием lvalue ^= выражение
, Последовательное вычисление выражение, выражение Слева направо

Главная литература

1. Батура М.П., Бусько В.Л., Корбит А.Г., Кривоносова Т.М. программирования и Основы алгоритмизации. Язык Си : учеб. пособие. – Минск : БГУИР, 2007.

2. Бусько В.Л., Корбит А.Г., Кривоносова Т.М. Конспект лекций по курсу «программирования и Основы алгоритмизации» для студентов всех всех форм и специальностей обучения. — Мн.: БГУИР, 2004.

3. Бусько В.Л., Карцев В.Т., Кривоносова Т.М., Навроцкий А.А. Базы программирования в среде С++ Builder: лаб.практикум по курсу «программирования и Основы алгоритмизации» для студ. 1 – 2-го направлений БГУИР. В 2 ч. Ч.1 . – Минск: БГУИР, 2007.

Дополнительная литература

1. Керниган, Б. Язык программирования СИ / Б. Керниган, Д. Ритчи. – М.: статистика и Финансы, 1992.

2. Страуструп, Б. Язык программирования C++ / Б. Страуструп: 2-е изд.: В 2 т. – Киев: ДиаСофт, 1993.

3. Демидович, Е. М. программирования и Основы алгоритмизации. Язык СИ / Е. М. Демидович. – Минск : Бестпринт, 2001.

4. Шилд, Г. Программирование на Borland С++ / Г. Шилд. – Минск : ПОПУРРИ, 1999.

* Синус гиперболический, а в следующей строчке – косинус гиперболический.

* Значение Т вводится с клавиатуры.

Адреса и указатели в Си. Адресная арифметика


Интересные записи:

Понравилась статья? Поделиться с друзьями: