Одной из наиболее распространенных конструкций с использованием указателей являются массивы [8.1]. Результатом использования указателей для массивов является меньшее количество используемой памяти и высокая производительность [8.1].
По краткому определению, указатель – это переменная, содержащая адрес другой переменной [8.2]. Так как указатель содержит
В языке С массивы – это упорядоченные данные (элементы) одного типа. Компилятор языка С рассматривает имя массива как адрес его первого элемента (в языке С нумерация элементов массива начинается с нуля). Например, если имя массива Arr с десятью элементами, то i -й элемент (0 $$\le$$ i < 10) компилятор преобразует его по правилам работы с указателями с операцией *(Arr + i). Здесь Arr как бы указатель, а i – целочисленная переменная. Сумма (Arr + i) указывает на i -й элемент массива, а операция * ) дает значение самого элемента.
Имя массива без индекса образует указатель на начало этого массива.
Следует помнить следующее: отличие имени массива от указателя состоит в том, что имя массива не может быть изменено. Имя массива всегда указывает на одно и то же место в памяти – на нулевой элемент.
Пусть, например, массив Arr содержит 10 целочисленных переменных:
int Arr[10];
Тогда можно объявить указатель ptr, который будет указывать на элементы массива Arr:
int *ptr;
Тип указателя (в примере это int ) должен соответствовать типу объявленного массива.
Для того, чтобы указатель ptr ссылался на первый элемент (с нулевым индексом) массива Arr, можно использовать утверждение:
ptr = Arr;
В то же время можно использовать прямую адресацию:
ptr = Arr[0];
Обе формы записи эквивалентны.
Аналогичные утверждения будут справедливы для других char, float, double и пр.
Если указатель ptr указывал на первый элемент (с нулевым индексом) массива Arr, то для обращения к следующему элементу массива допустимы следующие формы утверждений:
ptr = Arr[1]; ptr += 1;
Соответственно, выражение *(ptr+1) будет ссылаться на значение, содержащееся в элементе Arr[1].
Утверждение
ptr += n;
заставит указатель *ptr ссылаться на элемент массива, находящийся на расстоянии n от того, на который ранее ссылался указатель, независимо от типа элементов, содержащихся в массиве [8.1]. Разумеется, значение n должно быть в допустимых пределах для данного объема массива.
При работе с указателями и массивами особенно удобны операторы инкремента "++" и декремента "––" [8.1]. Использование оператора инкремента с указателем аналогично операции суммирования с единицей, а операция декремента имеет тот же эффект, что и вычитание единицы из указателя.
В языке программирования С вполне корректной операцией является сравнение указателей. К указателям применяются операции сравнения ">", ">=", "!=", "==", "<=", "<" [3]. Сравнивать указатели допустимо только с другими указателями того же типа или с константой NULL, обозначающей значение условного нулевого адреса. Константа NULL – это особое значение переменной-указателя, присваиваемое ей в том случае, когда она не должна иметь никакого значения. Его можно присвоить переменной-указателю любого типа. Оно представляет собой целое число нуль.
Особое значение имеет сравнение двух указателей, которые связаны с одним и тем же массивом данных.
Рассмотрим инициализацию указателей типа char:
char *ptr = "hello, world";
Переменная *ptr является указателем, а не массивом. Поэтому строковая константа "hello, world" не может храниться в указателе *ptr. Тогда возникает вопрос, где хранится строковая константа. Для этого следует знать, что происходит, когда компилятор встречает строковую константу. Компилятор создает так называемую таблицу строк. В ней он сохраняет строковые константы, которые встречаются ему по ходу чтения текста программы [8.4]. Следовательно, когда встречается объявление с инициализацией, то компилятор сохраняет "hello, world" в таблице строк, а указатель *ptr записывает ее адрес.
Поскольку указатели сами по себе являются переменными, их можно хранить в массивах, как и переменные других типов [8.2]. Получается массив указателей.
Массив указателей фиксированных размеров вводится одним из следующих определений [8.4]:
тип *имя_массива [размер]; тип *имя_массива [ ] = инициализатор; тип *имя_массива [размер] = инициализатор;
В данной инструкции тип может быть как одним из базовых типов, так и
имя_массива – идентификатор, определяемый пользователем по правилам языка С ;
размер – константное выражение, вычисляемое в процессе трансляции программы;
инициализатор – список в фигурных скобках значений элементов заданного типа (т.е. тип ).
Рассмотрим примеры [8.4]:
int data[7]; // обычный массив
int *pd[7]; // массив указателей
int *pi[ ] = { data[0], data[4], data[2] };
В приведенных примерах каждый элемент массивов pd и pi является указателем на объекты типа int.
Значением каждого элемента pd[j] и pi[k] может быть адрес объекта типа int. Все 6 элементов массива pd указателей не инициализированы.В массиве pi три элемента, и они инициализированы адресами конкретных элементов массива data.
В случае обработки строк текста они, как правило, имеют различную длину, и их нельзя сравнить или переместить одной элементарной операцией в отличие от целых чисел. В этом случае эффективным средством является массив указателей. Например, если сортируемые строки располагаются в одном длинном символьном массиве вплотную — начало одной к концу другой, то к каждой строке можно обращаться по указателю на ее первый символ [8.2]. Сами же указатели можно поместить в массив, т.е. создать массив указателей. Две строки можно сравнить, рассмотрев указатели на них.
Массивы указателей часто используются при работе со строками. Можно привести пример массива строк о студенте, задаваемый с помощью массива указателей.
char *ptr[ ] = {
"Surname", //Фамилия
"Name", // Имя
"group", // группа
"ACOUY" // специальность
};
С помощью массива указателей можно инициализировать строки различной длины. Каждый из указателей массива указателей указывает на одномерный
В языке программирования С предусматриваются ситуации, когда указатели указывают на указатели. Такие ситуации называются многоуровневой адресацией. Пример объявления указателя на указатель:
int **ptr2;
В приведенном объявлении **ptr2 – это указатель на указатель на число типа int. При этом наличие двух звездочек свидетельствует о том, что имеется двухуровневая адресация. Для получения значения конкретного числа следует выполнить следующие действия:
int x = 88, *ptr, **ptr2;
ptr = x;
ptr2 = ptr;
printf("%d", **ptr2);
В результате в выходной поток (на дисплей пользователя) будет выведено число 88. В приведенном фрагменте переменная *ptr объявлена как указатель на целое число, а **ptr2 – как указатель на указатель на целое. Значение, выводимое в выходной поток (число 88), осуществляется операцией **ptr2.
Для многомерных массивов указатели указывают на адреса элементов массива построчно. Рассмотрим пример двухмерного целочисленного массива М размера 3 $$\times$$ 5, т.е. состоящего из 3 строк и 5 столбцов, и определим указатель:
int M[3][5]= {{1,2,3,4,5},{–6,–7,–8,–9,–10},{11,12,13,14,15}};
int *ptr;
Элементы массива (по индексам) располагаются в ячейках памяти по строкам в следующем порядке:
M[0][0], M[0][1], M[0][2], M[0][3], M[0][4], M[1][0], M[1][1], M[1][2], M[1][3], M[1][4], M[2][0], M[2][1], M[2][2], M[2][3], M[2][4].
Сначала запоминается первая строка, затем вторая, затем третья. В данном случае двухмерный массив – это массив трех одномерных массивов, состоящих из 5 элементов.
Указатель указывает на адреса элементов в порядке расположения их в памяти. Поэтому тождественны равенства:
ptr == M[0][0]; //?1-я строка, 1-й столбец ptr + 1 == M[0][1]; // 1-я строка, 2-й столбец ptr + 2 == M[0][2]; // 1-я строка, 3-й столбец ptr + 3 == M[0][3]; // 1-я строка, 4-й столбец ptr + 4 == M[0][4]; // 1-я строка, 5-й столбец ptr + 5 == M[1][0]; // 2-я строка, 1-й столбец ptr + 6 == M[1][1]; // 2-я строка, 2-й столбец ptr + 7 == M[1][2]; // 2-я строка, 3-й столбец ptr + 8 == M[1][3]; // 2-я строка, 4-й столбец ptr + 9 == M[1][4]; // 2-я строка, 5-й столбец ptr + 10 == M[2][0]; // 3-я строка, 1-й столбец ptr + 11 == M[2][1]; // 3-я строка, 2-й столбец ptr + 12 == M[2][2]; // 3-я строка, 3-й столбец ptr + 13 == M[2][3]; // 3-я строка, 4-й столбец ptr + 14 == M[2][4]; // 3-я строка, 5-й столбец
Практически следует произвести инициализацию указателя, например, взяв адрес первого элемента матрицы, а затем – обращение к элементам матрицы, можно производить через указатель:
ptr = M[0][0]; *(ptr + i*n + j);
где i – номер строки заданной матрицы, j – номер столбца, n – число столбцов в матрице.
Пример 1. Напишите программу считывания строк разной длины с использованием арифметики указателей.
Программный код решения примера:
#include <stdio.h>
#include <conio.h>
int main (void)
{
int i, n;
char *ptr[ ] = {"one", "two", "three", "four", "five",\
"six", "seven", "eight", "nine", "ten"};
n = sizeof(ptr)/sizeof(ptr[0]);
printf("\n\t Strings of various length:\n");
for (i = 0; i < n; ++i)
printf("\n%12d) %s", i+1, ptr[i]);
printf("\n\n Press any key: ");
_getch();
return 0;
}
В программе использован одномерный массив указателей. Функция printf() и %s допускают использование в качестве параметра указатель на строку. При этом на дисплей выводится не значение указателя, а содержимое адресуемой им строки. Обратный слэш "\" служит для переноса содержимого операторной строки на новую строку (для удобства восприятия). Оператор sizeof() вычисляется во время компиляции программы. Во время компиляции он обычно превращается в целую константу, значение которой равно размеру типа или объекта [8.4], в данном случае соответствует размеру массива указателей.
Следует обратить внимание на инициализацию массива указателей. Содержимое, заключенное в фигурные скобки, представляют собой строки, для каждой из которой служит указатель, входящий в массив указателей.
Результат выполнения программы показан на рис 8.1.
(рис 8.1) Пример считывания строк различной длины Задание 1
for применить оператор while.Пример 2. Напишите программу сортировки одномерного массива, состоящего из 10 равномерно распределенных случайных чисел из интервала [–8; 8], с помощью указателей.
Программный код решения примера:
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <time.h>
#define N 10
int main (void)
{
double a = -8.0, b = 8.0;
double arr[N], *pmin[N], *temp;
int i, j;
long int T;
T = (long)time(NULL); // использование системного времени
srand((unsigned int) T);
// Заполнение массива случайными числами
for(i = 0; i < N; ++i)
arr[i] = a + (b - a)*(double)rand()/RAND_MAX;
printf("\n\t The initial array of [%1.4f, %1.4f]:\n", a, b);
for (i = 0; i < N; ++i)
printf("\n\t%2d) %8.4f", i+1, arr[i]);
// Взятие адресов элементов исходного массива
//в предположении, что они образуют отсортированный массив
for (i = 0; i < N; ++i)
pmin[i] = arr[i];
//Сортировка массива по убыванию
for (i = 0; i < N-1; ++i)
for (j = i+1; j < N; ++j) {
if (*pmin[i] < *pmin[j])
{
temp = pmin[i];
pmin[i] = pmin[j];
pmin[j] = temp;
}
}
//Вывод отсортированного массива по убыванию
printf("\n\n\t Assorted array of descending:\n");
for (i = 0; i < N; ++i)
printf("\n\t%2d) %8.4f", i+1, *pmin[i]);
printf("\n\n Press any key: ");
_getch();
return 0;
}
В программе следует обратить внимание на то, что при сортировке производятся операции с адресами элементов массива, т.е. с указателями, а в самом исходном массиве элементы не сортируются.
Возможный результат выполнения программы показан на рис 8.2.
(рис 8.2) Сортировка массива по убываниюЗадание 2
Пример 3. Напишите программу заполнения матрицы по спирали натуральными числами с помощью массива указателей.
Программный код решения примера:
#include <stdio.h>
#include <conio.h>
#define n 15
int main(void) {
int i = 1,
I, j, k,
p = n/2;
int M[n][n], *ptr[n*n];
// Обнуление матрицы и инициализация указателя
for (I = 0; I < n; ++I)
for (j = 0; j < n; ++j)
{ M[I][j] = 0; ptr[I*n + j] = M[I][j];}
for (k = 1; k <= p; k++) // Число спиралей
{
// Верхний горизонтальный столбец
for (j = (k-1); j < (n-k+1); j++)
*ptr[(k-1)*n + j] = i++;
// Правый верхний столбец
for (j = k; j < (n-k+1); j++)
*ptr[j*n + (n-k)] = i++;
// Нижний горизонтальный столбец
for (j = (n-k-1); j >= (k-1); --j)
*ptr[(n-k)*n + j] = i++;
// Левый верхний столбец
for (j = (n-k-1); j >= k; j--)
*ptr[j*n + (k-1)] = i++; }
if ( n % 2 ) *ptr[p*n + p] = n*n;
printf("\n\t Spiral matrix of dimention (%d x %d):\n\n",n,n);
for (i = 0; i < n; ++i)
for (j = 0; j < n; ++j)
{ if (n*n < 20*20){
printf("%4d", *ptr[i*n + j]);
if (j == (n-1)) printf("\n");}
else if (n*n >= 20*20)
goto mes;
}
mes: if (n > 19)
printf("\n\t It is a large matrix. Can not to see on display.\n");
printf("\n Press any key: ");
_getch();
return 0;
}
Результат выполнения программы показан на рис 8.3.
(рис 8.3) Заполнение матрицы по спиралиВ программе использован goto, чтобы выйти из вложенных циклов, когда размерность матрицы велика и не может быть размещена на экране дисплея.
Задание 3
M[n][n]. Объясните результат.М[n][n]. Объявите матрицу с начальной буквой вашей фамилии (с латинским написанием). Выведите на дисплей матрицу.Примечание. Сумма диагональных элементов квадратной матрицы называется следом (шпуром) матрицы.
Пример 4. Напишите программу заполнения целочисленной прямоугольной матрицы размером не более 15 $$\times$$ 14 из интервала [–12; 12] с помощью операции
Программный код решения примера:
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <time.h>
#define N 15
#define M 14
const int Left = -12;
const int Right = 12;
int main(void) {
int i, j, n, m;
int matr[N][M];
time_t t; //переменная системного времени
srand((unsigned)time(t));
printf("\n Enter the number of lines of the matrix is not more than %d: ", N);
scanf_s("%d", n);
printf(" Enter the number of columns of the matrix is not more than %d: ", M);
scanf_s("%d", m);
// Контроль ввода допустимой размерности
if (n > N || m > M || n < 1 || m < 1)
{ printf("\n\t Data error! Repeat please.\n");
printf("\n Press any key: ");
_getch(); return 0; }
for (i = 0; i < n; ++i)
for (j = 0; j < m; ++j)
*(*(matr + i) + j) = 0;
for (i = 0; i < n; ++i)
for (j = 0; j < m; ++j)
*(*(matr + i) + j) = (rand() % (2*Right+1)) + Left;
printf("\n The matrix of random whole numbers from the entire [%d, %d]:\n", Left, Right);
for (i = 0; i < n; ++i) {
printf("\n ");
for (j = 0; j < m; ++j)
printf("%5d", *(*(matr + i) + j));
}
printf("\n\n Press any key: ");
_getch();
return 0;
}
В программе использованы спецификаторы const для объявления неизменяемых переменных. Функция srand() устанавливает начальное значение программного time(t), которая практически всегда различна.
Возможный результат выполнения программы показан на рис 8.4.
(рис 8.4) Матрица случайных целых чиселЗадание 4
rand(), для чего предусмотрите ввод границ интервала с клавиатуры.Пример 5. Напишите программу умножения целочисленных матриц с использованием указателей [8.5].
Условием перемножения двух матриц А и В является равенство числа столбцов матрицы А и числа строк матрицы В. Если первая матрица А имеет размер n $$\times$$ k, то вторая матрица В должна иметь размер k $$\times$$ m. В результате перемножения получим матрицу С размера n $$\times$$ m. Приведем следующую схему по размерностям:

Поэлементное перемножение двух матриц в стандартной математической форме:
$$C_{i j}=\sum^{n}_{k=1}a_{jk}*b_{kj}, i=1,2\cdots,n, j =1,2,\cdots,m.$$С учетом синтаксиса формирования массивов в языке С индексация должна начинаться с нуля, поэтому формулу перепишем в следующем виде:
$$C_{i j}=\sum^{n-1}_{k=0}a_{jk}*b_{kj}, i=0,1,2\cdots,n-1, j =0,1,2,\cdots,m-1.$$Программный код решения примера:
#include <stdio.h>
#include <conio.h>
#define n 7
#define k 12
#define m 4
int main(void) {
int i, j, p;
int A[n*k], B[k*m];
long int *C[n*m];
//Обнуление матриц
for (i = 0; i < n; ++i)
for(j = 0; j < m; ++j)
C[i*m + j] = 0;
for (i = 0; i < n; ++i)
for (j = 0; j < k; ++j)
A[i*k + j] = 0;
for (i = 0; i < k; ++i)
for (j = 0; j < m; ++j)
B[i*m + j] = 0;
// Заполнение матриц целыми числами
for (i = 0; i < n; ++i)
for (j = 0; j < k; ++j)
A[i*k + j] = i + j - 6;
for (i = 0; i < k; ++i)
for (j = 0; j < m; ++j)
B[i*m + j] = i + j - 11;
// Циклы перемножения с накоплением суммы
for (i = 0; i < n; ++i)
for (j = 0; j < m; ++j) { C[i*m + j] = 0;
for (p = 0; p < k; ++p)
C[i*m + j] += (A[i*k + p] * B[p*m + j]);
}
printf("\n\t The result of multiplying the two matrices:\n\t\t C = A*B (%dx%d = %dx%d * %dx%d)\n", n, m, n, k, k, m);
for (i = 0; i < n; ++i) {
printf("\n\t");
for (j = 0; j < m; ++j)
printf("%6ld", C[i*m + j]); }
printf("\n\n Press any key: ");
_getch();
return 0; }
Обнуление матриц и массива указателей позволяет заранее выделить память. Для вывода результата перемножения предусмотрен спецификатор ld.
Результат выполнения программы показан на рис 8.5.
(рис 8.5) Матрица случайных целых чисел Задание 5
Пример 6. Напишите программу формирования трех массивов, состоящих из матриц размерностей 4 $$\times$$ 8, и заполненных натуральными числами. Заполнение матриц выполнить по строкам последовательно для каждого из массивов. Предусмотрите вывод всех трех массивов на консоль и вывод одного из массивов, номер которого задается пользователем с клавиатуры.
Для решения примера сформируем трехмерный массив размером 3 $$\times$$ 4 $$\times$$ 8 с помощью указателя, в котором левый размер будет равен 3, центральный размер равен 4, а крайний правый размер равен 8, например *PTR[3*4*8].
Программный код решения примера:
#include <stdio.h>
#include <conio.h>
#define p 3 // Количество "подшитых" матриц
#define n 4 // Число строк матрицы
#define m 8 // Число столбцов матрицы
int main (void){
int i, j, k, r;
int *PTR[p*n*m], *M2[n*m];
// Заполнение одномерного массива
for (i = 0; i < p*n*m; ++i)
PTR[i] = (int*)(i + 1);
printf("\n An array of dimension %dx%dx%d:\n", p, n, m);
puts("===========================================");
// Разбивка массива
for (k = 0; k < p; ++k){ printf("\n");
for (i = 0; i < n; ++i) { printf(" ");
for (j = 0; j < m; ++j){
printf(" %3d ",PTR[k*n*m + i*m + j]);
}
printf("\n");
}
}
puts("===========================================");
printf("\n Select the number of arrays of a series of numbers (%d...%d): ",1, p);
scanf_s("%d", r);
printf("\n Matrix number %d from three-dimensional array:\n", r);
// Выбор матрицы из 3-мерного массива
for (k = 0; k < p; ++k)
for (i = 0; i < n; ++i)
for (j = 0; j < m; ++j)
if ( k == (r-1))
{M2[i*m + j] = PTR[k*n*m + i*m + j];}
for (i = 0; i < n; ++i) { printf("\n ");
for (j = 0; j < m; ++j)
printf(" %3d ", M2[i*m + j]);
}
printf("\n\n Press any key: ");
_getch();
return 0;
}
Трехмерный массив можно рассматривать как книгу с заданным количеством страниц (размер p = 3 ), размер которых определяется как матрица с заданным числом строк (размер n = 4 ) и с заданным количеством столбцов (размер m = 8 ). Страницы (матрицы) "сшиваются". С помощью указателя *PTR сначала формируется одномерный массив, а затем он разбивается на заданное количество матриц. В функциях puts() допускается перевод на новую строку с помощью символа форматирования \n.
Результат выполнения программы показан на рис 8.6.
(рис 8.6) Пример вывода данных трехмерного массиваЗадание 6
Пример 7.Напишите программу поиска подстроки в строке, сформированной по случайному закону из 15 букв латинского алфавита, с помощью функции . В качестве подстроки принять первые три буквы своей фамилии.
Для работы с функцией требуется подключение заголовочного файла string.h.
Программный код решения примера:
#include <stdio.h>
#include <conio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
int main (void) {
int i,
k = 0,
n, in;
int numSTR;
int N = 1000;
int numA, numZ;
char str[16];
char sub[4];
char *ptr;
srand((unsigned)time(NULL));
numA = (int)'a';
numZ = (int)'z';
printf("\n Enter the three letters: ");
in = scanf_s("%s", sub, sizeof(sub));
if (in == 0)
{
printf("\n Error input. Press any key: ");
_getch();
exit(1);
}
printf("\n\t substring is \"%s\"\n", sub);
for (n = 0; n < N; n++) {
for (i = 0; i < 15; i++)
str[i] = numA + rand() % (numZ - numA) + 1;
str[i] = '\0';
ptr = strstr(str, sub);
if (ptr != NULL)
{
numSTR = (int)(ptr - str +1);
k++;
break;
}
}
if (k == 0)
printf("\n\t Substring \"%s\" not found", sub);
else
printf("\n\t Substring \"%s\" found at positions %d, %d, %d", \
sub, numSTR, numSTR+1, numSTR+2);
puts("\n");
for (i = 0; i < 15; i++)
printf(" %3d)", i+1);
puts("");
for (i = 0; i < 15; i++)
printf(" %3c ", str[i]);
printf("\n\n ... Press any key: ");
_getch();
return 0;
}
Подобный пример рассматривался в предыдущей работе. Видно, что применение указателя и функции существенно привело к уменьшению программного кода.
Возможный положительный результат работы программы показан на рис 8.7.
(рис 8.7) Результат поиска подстроки в строкеЗадание 7
numSTR.for операторы цикла while.Одной из наиболее распространенных конструкций с использованием указателей являются массивы [8.1]. Результатом использования указателей для массивов является меньшее количество используемой памяти и высокая производительность [8.1].
По краткому определению, указатель – это переменная, содержащая адрес другой переменной [8.2]. Так как указатель содержит
В языке С массивы – это упорядоченные данные (элементы) одного типа. Компилятор языка С рассматривает имя массива как адрес его первого элемента (в языке С нумерация элементов массива начинается с нуля). Например, если имя массива Arr с десятью элементами, то i -й элемент (0 $$\le$$ i < 10) компилятор преобразует его по правилам работы с указателями с операцией *(Arr + i). Здесь Arr как бы указатель, а i – целочисленная переменная. Сумма (Arr + i) указывает на i -й элемент массива, а операция * ) дает значение самого элемента.
Имя массива без индекса образует указатель на начало этого массива.
Следует помнить следующее: отличие имени массива от указателя состоит в том, что имя массива не может быть изменено. Имя массива всегда указывает на одно и то же место в памяти – на нулевой элемент.
Пусть, например, массив Arr содержит 10 целочисленных переменных:
int Arr[10];
Тогда можно объявить указатель ptr, который будет указывать на элементы массива Arr:
int *ptr;
Тип указателя (в примере это int ) должен соответствовать типу объявленного массива.
Для того, чтобы указатель ptr ссылался на первый элемент (с нулевым индексом) массива Arr, можно использовать утверждение:
ptr = Arr;
В то же время можно использовать прямую адресацию:
ptr = Arr[0];
Обе формы записи эквивалентны.
Аналогичные утверждения будут справедливы для других char, float, double и пр.
Если указатель ptr указывал на первый элемент (с нулевым индексом) массива Arr, то для обращения к следующему элементу массива допустимы следующие формы утверждений:
ptr = Arr[1]; ptr += 1;
Соответственно, выражение *(ptr+1) будет ссылаться на значение, содержащееся в элементе Arr[1].
Утверждение
ptr += n;
заставит указатель *ptr ссылаться на элемент массива, находящийся на расстоянии n от того, на который ранее ссылался указатель, независимо от типа элементов, содержащихся в массиве [8.1]. Разумеется, значение n должно быть в допустимых пределах для данного объема массива.
При работе с указателями и массивами особенно удобны операторы инкремента "++" и декремента "––" [8.1]. Использование оператора инкремента с указателем аналогично операции суммирования с единицей, а операция декремента имеет тот же эффект, что и вычитание единицы из указателя.
В языке программирования С вполне корректной операцией является сравнение указателей. К указателям применяются операции сравнения ">", ">=", "!=", "==", "<=", "<" [3]. Сравнивать указатели допустимо только с другими указателями того же типа или с константой NULL, обозначающей значение условного нулевого адреса. Константа NULL – это особое значение переменной-указателя, присваиваемое ей в том случае, когда она не должна иметь никакого значения. Его можно присвоить переменной-указателю любого типа. Оно представляет собой целое число нуль.
Особое значение имеет сравнение двух указателей, которые связаны с одним и тем же массивом данных.
Рассмотрим инициализацию указателей типа char:
char *ptr = "hello, world";
Переменная *ptr является указателем, а не массивом. Поэтому строковая константа "hello, world" не может храниться в указателе *ptr. Тогда возникает вопрос, где хранится строковая константа. Для этого следует знать, что происходит, когда компилятор встречает строковую константу. Компилятор создает так называемую таблицу строк. В ней он сохраняет строковые константы, которые встречаются ему по ходу чтения текста программы [8.4]. Следовательно, когда встречается объявление с инициализацией, то компилятор сохраняет "hello, world" в таблице строк, а указатель *ptr записывает ее адрес.
Поскольку указатели сами по себе являются переменными, их можно хранить в массивах, как и переменные других типов [8.2]. Получается массив указателей.
Массив указателей фиксированных размеров вводится одним из следующих определений [8.4]:
тип *имя_массива [размер]; тип *имя_массива [ ] = инициализатор; тип *имя_массива [размер] = инициализатор;
В данной инструкции тип может быть как одним из базовых типов, так и
имя_массива – идентификатор, определяемый пользователем по правилам языка С ;
размер – константное выражение, вычисляемое в процессе трансляции программы;
инициализатор – список в фигурных скобках значений элементов заданного типа (т.е. тип ).
Рассмотрим примеры [8.4]:
int data[7]; // обычный массив
int *pd[7]; // массив указателей
int *pi[ ] = { data[0], data[4], data[2] };
В приведенных примерах каждый элемент массивов pd и pi является указателем на объекты типа int.
Значением каждого элемента pd[j] и pi[k] может быть адрес объекта типа int. Все 6 элементов массива pd указателей не инициализированы.В массиве pi три элемента, и они инициализированы адресами конкретных элементов массива data.
В случае обработки строк текста они, как правило, имеют различную длину, и их нельзя сравнить или переместить одной элементарной операцией в отличие от целых чисел. В этом случае эффективным средством является массив указателей. Например, если сортируемые строки располагаются в одном длинном символьном массиве вплотную — начало одной к концу другой, то к каждой строке можно обращаться по указателю на ее первый символ [8.2]. Сами же указатели можно поместить в массив, т.е. создать массив указателей. Две строки можно сравнить, рассмотрев указатели на них.
Массивы указателей часто используются при работе со строками. Можно привести пример массива строк о студенте, задаваемый с помощью массива указателей.
char *ptr[ ] = {
"Surname", //Фамилия
"Name", // Имя
"group", // группа
"ACOUY" // специальность
};
С помощью массива указателей можно инициализировать строки различной длины. Каждый из указателей массива указателей указывает на одномерный
В языке программирования С предусматриваются ситуации, когда указатели указывают на указатели. Такие ситуации называются многоуровневой адресацией. Пример объявления указателя на указатель:
int **ptr2;
В приведенном объявлении **ptr2 – это указатель на указатель на число типа int. При этом наличие двух звездочек свидетельствует о том, что имеется двухуровневая адресация. Для получения значения конкретного числа следует выполнить следующие действия:
int x = 88, *ptr, **ptr2;
ptr = x;
ptr2 = ptr;
printf("%d", **ptr2);
В результате в выходной поток (на дисплей пользователя) будет выведено число 88. В приведенном фрагменте переменная *ptr объявлена как указатель на целое число, а **ptr2 – как указатель на указатель на целое. Значение, выводимое в выходной поток (число 88), осуществляется операцией **ptr2.
Для многомерных массивов указатели указывают на адреса элементов массива построчно. Рассмотрим пример двухмерного целочисленного массива М размера 3 $$\times$$ 5, т.е. состоящего из 3 строк и 5 столбцов, и определим указатель:
int M[3][5]= {{1,2,3,4,5},{–6,–7,–8,–9,–10},{11,12,13,14,15}};
int *ptr;
Элементы массива (по индексам) располагаются в ячейках памяти по строкам в следующем порядке:
M[0][0], M[0][1], M[0][2], M[0][3], M[0][4], M[1][0], M[1][1], M[1][2], M[1][3], M[1][4], M[2][0], M[2][1], M[2][2], M[2][3], M[2][4].
Сначала запоминается первая строка, затем вторая, затем третья. В данном случае двухмерный массив – это массив трех одномерных массивов, состоящих из 5 элементов.
Указатель указывает на адреса элементов в порядке расположения их в памяти. Поэтому тождественны равенства:
ptr == M[0][0]; //?1-я строка, 1-й столбец ptr + 1 == M[0][1]; // 1-я строка, 2-й столбец ptr + 2 == M[0][2]; // 1-я строка, 3-й столбец ptr + 3 == M[0][3]; // 1-я строка, 4-й столбец ptr + 4 == M[0][4]; // 1-я строка, 5-й столбец ptr + 5 == M[1][0]; // 2-я строка, 1-й столбец ptr + 6 == M[1][1]; // 2-я строка, 2-й столбец ptr + 7 == M[1][2]; // 2-я строка, 3-й столбец ptr + 8 == M[1][3]; // 2-я строка, 4-й столбец ptr + 9 == M[1][4]; // 2-я строка, 5-й столбец ptr + 10 == M[2][0]; // 3-я строка, 1-й столбец ptr + 11 == M[2][1]; // 3-я строка, 2-й столбец ptr + 12 == M[2][2]; // 3-я строка, 3-й столбец ptr + 13 == M[2][3]; // 3-я строка, 4-й столбец ptr + 14 == M[2][4]; // 3-я строка, 5-й столбец
Практически следует произвести инициализацию указателя, например, взяв адрес первого элемента матрицы, а затем – обращение к элементам матрицы, можно производить через указатель:
ptr = M[0][0]; *(ptr + i*n + j);
где i – номер строки заданной матрицы, j – номер столбца, n – число столбцов в матрице.
Пример 1. Напишите программу считывания строк разной длины с использованием арифметики указателей.
Программный код решения примера:
#include <stdio.h>
#include <conio.h>
int main (void)
{
int i, n;
char *ptr[ ] = {"one", "two", "three", "four", "five",\
"six", "seven", "eight", "nine", "ten"};
n = sizeof(ptr)/sizeof(ptr[0]);
printf("\n\t Strings of various length:\n");
for (i = 0; i < n; ++i)
printf("\n%12d) %s", i+1, ptr[i]);
printf("\n\n Press any key: ");
_getch();
return 0;
}
В программе использован одномерный массив указателей. Функция printf() и %s допускают использование в качестве параметра указатель на строку. При этом на дисплей выводится не значение указателя, а содержимое адресуемой им строки. Обратный слэш "\" служит для переноса содержимого операторной строки на новую строку (для удобства восприятия). Оператор sizeof() вычисляется во время компиляции программы. Во время компиляции он обычно превращается в целую константу, значение которой равно размеру типа или объекта [8.4], в данном случае соответствует размеру массива указателей.
Следует обратить внимание на инициализацию массива указателей. Содержимое, заключенное в фигурные скобки, представляют собой строки, для каждой из которой служит указатель, входящий в массив указателей.
Результат выполнения программы показан на рис 8.1.
(рис 8.1) Пример считывания строк различной длиныЗадание 1
for применить оператор while.Пример 2. Напишите программу сортировки одномерного массива, состоящего из 10 равномерно распределенных случайных чисел из интервала [–8; 8], с помощью указателей.
Программный код решения примера:
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <time.h>
#define N 10
int main (void)
{
double a = -8.0, b = 8.0;
double arr[N], *pmin[N], *temp;
int i, j;
long int T;
T = (long)time(NULL); // использование системного времени
srand((unsigned int) T);
// Заполнение массива случайными числами
for(i = 0; i < N; ++i)
arr[i] = a + (b - a)*(double)rand()/RAND_MAX;
printf("\n\t The initial array of [%1.4f, %1.4f]:\n", a, b);
for (i = 0; i < N; ++i)
printf("\n\t%2d) %8.4f", i+1, arr[i]);
// Взятие адресов элементов исходного массива
//в предположении, что они образуют отсортированный массив
for (i = 0; i < N; ++i)
pmin[i] = arr[i];
//Сортировка массива по убыванию
for (i = 0; i < N-1; ++i)
for (j = i+1; j < N; ++j) {
if (*pmin[i] < *pmin[j])
{
temp = pmin[i];
pmin[i] = pmin[j];
pmin[j] = temp;
}
}
//Вывод отсортированного массива по убыванию
printf("\n\n\t Assorted array of descending:\n");
for (i = 0; i < N; ++i)
printf("\n\t%2d) %8.4f", i+1, *pmin[i]);
printf("\n\n Press any key: ");
_getch();
return 0;
}
В программе следует обратить внимание на то, что при сортировке производятся операции с адресами элементов массива, т.е. с указателями, а в самом исходном массиве элементы не сортируются.
Возможный результат выполнения программы показан на рис 8.2.
(рис 8.2) Сортировка массива по убываниюЗадание 2
Пример 3. Напишите программу заполнения матрицы по спирали натуральными числами с помощью массива указателей.
Программный код решения примера:
#include <stdio.h>
#include <conio.h>
#define n 15
int main(void) {
int i = 1,
I, j, k,
p = n/2;
int M[n][n], *ptr[n*n];
// Обнуление матрицы и инициализация указателя
for (I = 0; I < n; ++I)
for (j = 0; j < n; ++j)
{ M[I][j] = 0; ptr[I*n + j] = M[I][j];}
for (k = 1; k <= p; k++) // Число спиралей
{
// Верхний горизонтальный столбец
for (j = (k-1); j < (n-k+1); j++)
*ptr[(k-1)*n + j] = i++;
// Правый верхний столбец
for (j = k; j < (n-k+1); j++)
*ptr[j*n + (n-k)] = i++;
// Нижний горизонтальный столбец
for (j = (n-k-1); j >= (k-1); --j)
*ptr[(n-k)*n + j] = i++;
// Левый верхний столбец
for (j = (n-k-1); j >= k; j--)
*ptr[j*n + (k-1)] = i++; }
if ( n % 2 ) *ptr[p*n + p] = n*n;
printf("\n\t Spiral matrix of dimention (%d x %d):\n\n",n,n);
for (i = 0; i < n; ++i)
for (j = 0; j < n; ++j)
{ if (n*n < 20*20){
printf("%4d", *ptr[i*n + j]);
if (j == (n-1)) printf("\n");}
else if (n*n >= 20*20)
goto mes;
}
mes: if (n > 19)
printf("\n\t It is a large matrix. Can not to see on display.\n");
printf("\n Press any key: ");
_getch();
return 0;
}
Результат выполнения программы показан на рис 8.3.
(рис 8.3) Заполнение матрицы по спиралиВ программе использован goto, чтобы выйти из вложенных циклов, когда размерность матрицы велика и не может быть размещена на экране дисплея.
Задание 3
M[n][n]. Объясните результат.М[n][n]. Объявите матрицу с начальной буквой вашей фамилии (с латинским написанием). Выведите на дисплей матрицу.Примечание. Сумма диагональных элементов квадратной матрицы называется следом (шпуром) матрицы.
Пример 4. Напишите программу заполнения целочисленной прямоугольной матрицы размером не более 15 $$\times$$ 14 из интервала [–12; 12] с помощью операции
Программный код решения примера:
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <time.h>
#define N 15
#define M 14
const int Left = -12;
const int Right = 12;
int main(void) {
int i, j, n, m;
int matr[N][M];
time_t t; //переменная системного времени
srand((unsigned)time(t));
printf("\n Enter the number of lines of the matrix is not more than %d: ", N);
scanf_s("%d", n);
printf(" Enter the number of columns of the matrix is not more than %d: ", M);
scanf_s("%d", m);
// Контроль ввода допустимой размерности
if (n > N || m > M || n < 1 || m < 1)
{ printf("\n\t Data error! Repeat please.\n");
printf("\n Press any key: ");
_getch(); return 0; }
for (i = 0; i < n; ++i)
for (j = 0; j < m; ++j)
*(*(matr + i) + j) = 0;
for (i = 0; i < n; ++i)
for (j = 0; j < m; ++j)
*(*(matr + i) + j) = (rand() % (2*Right+1)) + Left;
printf("\n The matrix of random whole numbers from the entire [%d, %d]:\n", Left, Right);
for (i = 0; i < n; ++i) {
printf("\n ");
for (j = 0; j < m; ++j)
printf("%5d", *(*(matr + i) + j));
}
printf("\n\n Press any key: ");
_getch();
return 0;
}
В программе использованы спецификаторы const для объявления неизменяемых переменных. Функция srand() устанавливает начальное значение программного time(t), которая практически всегда различна.
Возможный результат выполнения программы показан на рис 8.4.
(рис 8.4) Матрица случайных целых чиселЗадание 4
rand(), для чего предусмотрите ввод границ интервала с клавиатуры.Пример 5. Напишите программу умножения целочисленных матриц с использованием указателей [8.5].
Условием перемножения двух матриц А и В является равенство числа столбцов матрицы А и числа строк матрицы В. Если первая матрица А имеет размер n $$\times$$ k, то вторая матрица В должна иметь размер k $$\times$$ m. В результате перемножения получим матрицу С размера n $$\times$$ m. Приведем следующую схему по размерностям:

Поэлементное перемножение двух матриц в стандартной математической форме:
$$C_{i j}=\sum^{n}_{k=1}a_{jk}*b_{kj}, i=1,2\cdots,n, j =1,2,\cdots,m.$$С учетом синтаксиса формирования массивов в языке С индексация должна начинаться с нуля, поэтому формулу перепишем в следующем виде:
$$C_{i j}=\sum^{n-1}_{k=0}a_{jk}*b_{kj}, i=0,1,2\cdots,n-1, j =0,1,2,\cdots,m-1.$$Программный код решения примера:
#include <stdio.h>
#include <conio.h>
#define n 7
#define k 12
#define m 4
int main(void) {
int i, j, p;
int A[n*k], B[k*m];
long int *C[n*m];
//Обнуление матриц
for (i = 0; i < n; ++i)
for(j = 0; j < m; ++j)
C[i*m + j] = 0;
for (i = 0; i < n; ++i)
for (j = 0; j < k; ++j)
A[i*k + j] = 0;
for (i = 0; i < k; ++i)
for (j = 0; j < m; ++j)
B[i*m + j] = 0;
// Заполнение матриц целыми числами
for (i = 0; i < n; ++i)
for (j = 0; j < k; ++j)
A[i*k + j] = i + j - 6;
for (i = 0; i < k; ++i)
for (j = 0; j < m; ++j)
B[i*m + j] = i + j - 11;
// Циклы перемножения с накоплением суммы
for (i = 0; i < n; ++i)
for (j = 0; j < m; ++j) { C[i*m + j] = 0;
for (p = 0; p < k; ++p)
C[i*m + j] += (A[i*k + p] * B[p*m + j]);
}
printf("\n\t The result of multiplying the two matrices:\n\t\t C = A*B (%dx%d = %dx%d * %dx%d)\n", n, m, n, k, k, m);
for (i = 0; i < n; ++i) {
printf("\n\t");
for (j = 0; j < m; ++j)
printf("%6ld", C[i*m + j]); }
printf("\n\n Press any key: ");
_getch();
return 0; }
Обнуление матриц и массива указателей позволяет заранее выделить память. Для вывода результата перемножения предусмотрен спецификатор ld.
Результат выполнения программы показан на рис 8.5.
(рис 8.5) Матрица случайных целых чиселЗадание 5
Пример 6. Напишите программу формирования трех массивов, состоящих из матриц размерностей 4 $$\times$$ 8, и заполненных натуральными числами. Заполнение матриц выполнить по строкам последовательно для каждого из массивов. Предусмотрите вывод всех трех массивов на консоль и вывод одного из массивов, номер которого задается пользователем с клавиатуры.
Для решения примера сформируем трехмерный массив размером 3 $$\times$$ 4 $$\times$$ 8 с помощью указателя, в котором левый размер будет равен 3, центральный размер равен 4, а крайний правый размер равен 8, например *PTR[3*4*8].
Программный код решения примера:
#include <stdio.h>
#include <conio.h>
#define p 3 // Количество "подшитых" матриц
#define n 4 // Число строк матрицы
#define m 8 // Число столбцов матрицы
int main (void){
int i, j, k, r;
int *PTR[p*n*m], *M2[n*m];
// Заполнение одномерного массива
for (i = 0; i < p*n*m; ++i)
PTR[i] = (int*)(i + 1);
printf("\n An array of dimension %dx%dx%d:\n", p, n, m);
puts("===========================================");
// Разбивка массива
for (k = 0; k < p; ++k){ printf("\n");
for (i = 0; i < n; ++i) { printf(" ");
for (j = 0; j < m; ++j){
printf(" %3d ",PTR[k*n*m + i*m + j]);
}
printf("\n");
}
}
puts("===========================================");
printf("\n Select the number of arrays of a series of numbers (%d...%d): ",1, p);
scanf_s("%d", r);
printf("\n Matrix number %d from three-dimensional array:\n", r);
// Выбор матрицы из 3-мерного массива
for (k = 0; k < p; ++k)
for (i = 0; i < n; ++i)
for (j = 0; j < m; ++j)
if ( k == (r-1))
{M2[i*m + j] = PTR[k*n*m + i*m + j];}
for (i = 0; i < n; ++i) { printf("\n ");
for (j = 0; j < m; ++j)
printf(" %3d ", M2[i*m + j]);
}
printf("\n\n Press any key: ");
_getch();
return 0;
}
Трехмерный массив можно рассматривать как книгу с заданным количеством страниц (размер p = 3 ), размер которых определяется как матрица с заданным числом строк (размер n = 4 ) и с заданным количеством столбцов (размер m = 8 ). Страницы (матрицы) "сшиваются". С помощью указателя *PTR сначала формируется одномерный массив, а затем он разбивается на заданное количество матриц. В функциях puts() допускается перевод на новую строку с помощью символа форматирования \n.
Результат выполнения программы показан на рис 8.6.
(рис 8.6) Пример вывода данных трехмерного массиваЗадание 6
Пример 7.Напишите программу поиска подстроки в строке, сформированной по случайному закону из 15 букв латинского алфавита, с помощью функции . В качестве подстроки принять первые три буквы своей фамилии.
Для работы с функцией требуется подключение заголовочного файла string.h.
Программный код решения примера:
#include <stdio.h>
#include <conio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
int main (void) {
int i,
k = 0,
n, in;
int numSTR;
int N = 1000;
int numA, numZ;
char str[16];
char sub[4];
char *ptr;
srand((unsigned)time(NULL));
numA = (int)'a';
numZ = (int)'z';
printf("\n Enter the three letters: ");
in = scanf_s("%s", sub, sizeof(sub));
if (in == 0)
{
printf("\n Error input. Press any key: ");
_getch();
exit(1);
}
printf("\n\t substring is \"%s\"\n", sub);
for (n = 0; n < N; n++) {
for (i = 0; i < 15; i++)
str[i] = numA + rand() % (numZ - numA) + 1;
str[i] = '\0';
ptr = strstr(str, sub);
if (ptr != NULL)
{
numSTR = (int)(ptr - str +1);
k++;
break;
}
}
if (k == 0)
printf("\n\t Substring \"%s\" not found", sub);
else
printf("\n\t Substring \"%s\" found at positions %d, %d, %d", \
sub, numSTR, numSTR+1, numSTR+2);
puts("\n");
for (i = 0; i < 15; i++)
printf(" %3d)", i+1);
puts("");
for (i = 0; i < 15; i++)
printf(" %3c ", str[i]);
printf("\n\n ... Press any key: ");
_getch();
return 0;
}
Подобный пример рассматривался в предыдущей работе. Видно, что применение указателя и функции существенно привело к уменьшению программного кода.
Возможный положительный результат работы программы показан на рис 8.7.
(рис 8.7) Результат поиска подстроки в строкеЗадание 7
numSTR.for операторы цикла while.Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.