Например, можно вызвать эту функцию для частично инициализированного массива
int A[][100] = {{1,3,5}, {15,2,3}};
int res = max( A, 2, 3);
2. Функция находит сумму элементов двумерного массива
При передаче двумерного массива здесь использовано явное преобразование типа двумерного массива к типу одномерного массива. Это позволяет вызывать функцию для любых двумерных массивов.
intsum(intA[], intKolStroc, intKolStolb)
{
int s= 0;
for (int i = 0; i < KolStroc; i++)
for (int j = 0; j < KolSolb; j++)
s += A[i* KolSolb + j];
return s;
};
void main()
{
int B[2][3]={{1,4,2}, {4,1,2}};
int res = sum((int *)A, 2, 3);
printf(“%d”, s);
}
Определение. Указателем называется переменная, объявленная следующим образом
type *имя_указателя;
Определение. Типом указателя называется type*.
Определение. Базовым типом указателя называется тип type данного, на который указывает указатель.
Примеры.
1. Одинарный указатель int *pi имеет тип int* и базовый тип int.
2. Одинарный указатель structdate *pd имеет тип date* и базовый тип date.
3. Двойной указатель float **ppf имеет тип float** и базовый тип float*.
4. Редкий тройной указатель char ***pppc имеет тип char *** и базовый тип char **.
Наиболее часто используются одинарные и двойные указатели, крайне редко тройные указатели. Указатели с 4 звездочками - признак ошибочной ситуации.
· Применение к любой переменной name операции взятия адреса &name добавляет к типу результата одну *.
· Применение к любому указателю ptr операции разыменования *ptr удаляет из типа результата одну *.
· Применение к любому указателю ptr операции индексации ptr[0] удаляет из типа результата одну *.
Примеры.
int **ptr; // тип ptr – это int **
// тип *ptr – это int *
// тип **ptr – это int
// выражение ***ptrошибочно
// тип ptr[0] – это int *
// тип ptr[3][5] – это int
// выражение ptr[1][1][1] ошибочно
// тип *ptr[0] – это int
// тип &(*ptr[0]) – это int*
Указатель типа void * не имеет базового типа и для дальнейшей работы с ним к нему надо применить операцию явного преобразования типа.
Указатель типа void * указывает на все, что угодно. Ключевому слову void здесь приписывается не значение “ничего”, а противоположное значение ”все, что угодно”. Под слово void подпадают базовые типы, пользовательские типы, любые указатели: одинарные, двойные и т.д.
Таким образом, объявление void **ptr не имеет смысла, хотя и не будет являться логической ошибкой. Надо писать void *ptr.
В операциях присваивания указатель на voidможет стоять в левой части. Это означает, что указателю void* можно присвоить любой указатель. При размещении указателя void* в правой части его надо преобразовывать к указательному типу левой части. Например.
int i=5, *pi = &i;
void *ptr;
ptr = pi;
//pi = ptr; ошибка
pi = (int *)ptr; // правильно
При объявлении указателей можно использовать зарезервированное слово const. В отличие от обычных констант и макроподстановок константные объекты размещаются в памяти, но не являются объектами Lvalue, то есть не могут стоять в левой части операции присваивания.
Пример 1.
int *Arr = {1,3,2,4,5}, *B = {1,1};
При данном определении допустимы следующие два оператора
Arr[0]=100;
Arr = B;
Пример 2.
const int *Arr = {1,3,2,4,5}, *B = {1,1};
// Arr[0]=100; ошибка
Arr = B;
Пример 3.
int *const Arr = {1,3,2,4,5}, *B = {1,1};
Arr[0]=100;
//Arr = B; ошибка
Пример 4.
const int *const Arr = {1,3,2,4,5}, *B = {1,1};
//Arr[0]=100; ошибка
//Arr = B; ошибка
Обычно константные указатели используют для строк
constchar* str = “Hello”;
В этом случае защищается содержимое текстовых строк.
Присваивание. Указателю можно присвоить только адрес или указатель того же типа. Если все же необходимо присвоить адреса разных типов, то надо использовать операцию явного преобразования типа. Для указателей неявное преобразование типа не работает.
Пример.
1. int *A = (int *)malloc(20);
2. int **A; char *c; A = (int **)c;
Операция разыменования * возвращает значение, хранящееся в ячейке по адресу, содержащемуся в указателе.
Пример.
int i = 5, *pi = &i;
*pi = 10;// i =10
Получение адреса указателя. Подобно любым переменным, переменная типа указатель имеет адрес и значение. Операция & сообщает нам, где находится сам указатель.
Операция & добавляет к типу результата одну *.
Пример.
int n=20, *pn = &n, **ppn;
ppn = &pn;
Увеличение указателя. К указателю ptr можно прибавлять и вычитать любое целое число n. При этом указатель изменяется на количество байт равное n, умноженному на размер в байтах базового типа указателя ptr.
Соответственно, к указателям применимы операции инкремента ++ и декремента --.
Пример.
int n=20, *pn = &n, **ppn;
pn = pn + 5;
// базовый тип pn – int, занимает 2 байта, поэтому pn увеличится на 10 байт.
ppn = &pn+5;
// базовый тип выражения &pn – int*, занимает 4 байта для модели large, поэтому pn увеличится на 20 байт.
Сравнение указателей на равенство и неравенство применимо только к указателям одного типа.
Разность указателей. Можно находить разность двух указателей одного типа. Результатом является количество элементов базового типа, находящимися между этими указателями. Результат имеет тип int для ближних указателей и тип long для дальних указателей.
Пример.
int A[10];
int *px = &A[1], *py = &A[9];
int n;
n = (int)(py - px); // n = 8
Одинарный указатель можно рассматривать, как одномерный массив и применять к нему операцию индексации.
int n=10, *pi;
pi = &i;
Тогда pi[0] – это переменная i, . pi[1] – это переменная типа int, расположенная справа от i, pi[-1] – это переменная типа int, расположенная слева от i.
1.14 Одномерный массив – это одинарный указатель
Имя одномерного массива, взятое само по себе, является константным указателем на начальный элемент этого массива. К имени массива можно применять операции указательной арифметики, не изменяющие содержимое указателя.
intA[5];
Тип А – это int *, базовый тип - int.
Значение А является адресом элемента A[0], поэтому *A – это начальный элемент массива А.
А+1 – это адрес элемента A[1], а разыменование *(А+1) – это A[1], *(А+4) – это последний элемент A[4], использование выражения *(А+5) в любой части операции присваивания является логической ошибкой выхода индекса массива за диапазон.
Внимание. Существенное различие между указателем и именем массива состоит в том, что указатель является переменной, размещаемой в ОЗУ. Указатель сам имеет адрес и занимает 2 или 4 байта в зависимости от того, ближний это указатель или дальний. Имя массива является адресной константой, не имеет адреса и не занимает места в ОЗУ.
Рассмотрим определение двумерного массива
intA[3][5];
Массив имеет три строки по пять элементов типа int. При этом A[0] – это начальная, строка из 5 элементов типа int, то есть одномерный массив из 5 элементов типа int. Но тип имени одномерного массива не содержит размера этого массива. Поэтому тип указателя A[0] – это int*, а базовый тип int.
Соответственно, А[1] – это первая строка массива, тип А[1] – это int*. Фактически A[0] – это адрес начального элемента нулевой строки, A[1] – это адрес начального элемента первой строки и т.д.
Обратите внимание, тип A[0] не содержит размера 5.
Рассмотрим имя двумерного массива А, взятое само по себе. Идентификатор А – это адрес начальной строки из 5 элементов типа int. Тип А – это int(*)[5]. В данном выражении участвуют три операции: круглые скобки (), индексация [] и разыменование*. Перечисление операций здесь идет по убыванию приоритета. Читать выражение int(*)[5] нужно следующим образом: указатель на массив из пяти элементов типа int.
Таким образом, тип указателя А содержит один из размеров двумерного массива, а именно количество столбцов. Отсюда вытекает, что два массива
intB[10][5], C[3][20];
имеют разные типы. Указатель В имеет тот же тип int(*)[5], а указатель С имеет другой тип int(*)[20].
Далее, применим к двойному константному указателю А указательные операции.
A+1 – это адрес первой строки из 5 элементов типа int. Тип A+1 – это int(*)[5], базовый тип – одномерный массив из пяти элементов типа int, то есть int*.
В общем случае, A+i это адрес i-ой строки из 5 элементов типа int. Тип A+i – это int(*)[5], базовый тип – одномерный массив из пяти элементов типа int, то есть int*.
*(A+i) – это сама i-ая строка из 5 элементов типа int, то есть адрес нулевого элемента первой строки. Тип *(A+i) – это int*, базовыйтип *(A+i) – это int.
*(A+i) + j – это адрес j-го элемента i-ой строки. Тип *(A+i) + j – это int*, базовый тип int.
*(*(A+i) + j) – это сам j-й элемент i-ой строки. Тип *(*(A+i) + j) – int
Таким образом, двойная индексация A[i][j] равносильна записи
*(*(A+i) + j).
Двойные указатели не так часто используются в качестве двумерного массива.
Пример. Рассмотрим двойной указатель
intn=5;
int* pi = &n;
int **ppi = π
Построим схему ОЗУ для всех трех переменных
Рис.1.
Оператор
ppi[1][1] = 10;
синтаксически правильный, но логически ошибочен. В данном случае число 10 записано в наугад выбранной ячейке ОЗУ, что может приводить время от времени к фатальным ошибкам.
Оператор
ppi[0][1] = 20;
также синтаксически правильный, но логически ошибочен.
Пример. Рассмотрим массив строк
char *Arr[] = {“Hello”, “ ”, “World!”};
В соответствии с приоритетом операций тип Arr – это char*[], то есть массив типов char*, другими словами массив строк. Но строка – это одномерный массив элементов типа char, то есть тип строки – это char*. Поэтому тип Arr – это также и char**. Таким образом, мы показали, что Arr – двойной указатель.