Напишу-ка я небольшую темку.
Итак: что такое указатель в сишечке? Указатель -- переменная особого типа, которая численно равна адресу в памяти той переменной, на которую она(эта переменная-указатель) указывает.
Важно понимать, что в стандарте не определёна никакая "память", и никакие "адреса" в ней. Потому, вышеуказанное "определение" нужно считать просто как объяснением ситуации, в большинстве современных CPU.
Как определить указатель?
int value = 17;
int *pointer = &value;Здесь я определил сначала целое value, а затем указатель(pointer) на это value.
Указатель можно переназначить на другую переменную
int value2 = 236;
pointer = &value2;Как и зачем использовать указатели?
В большинстве случаев, указатели нужны для косвенной адресации. Дело в том, что по правилам сишечки, функции принимают ЗНАЧЕНИЯ аргументов. И потому, функция не может работать с самими аргументами. Например, мы хотим написать функцию, которая меняет местами значения x<->y. Если мы напишем
void swap1(int x, int y)
{
int tmp = x;
x = y;
y = tmp;
}то всё будет работать, но значения местами НЕ поменяются. Потому-что внутри функции СВОИ int x и int y.
Для решения, надо просто передать указатели:
void swap1(int *x, int *y)
{
int tmp = *x;
*x = *y;
*y = tmp;
}Вот теперь всё работает
#include <stdio.h>
void swap2(int *x, int *y)
{
· int tmp = *x;
· *x = *y;
· *y = tmp;
}
int main()
{
· int v1 = 25;
· int v2 = 51;
· swap2(&v1, &v2);
· printf("v1: %d, v2: %d\n",
· · · v1,
· · · v2);
· return 0;
}
Указатель на NULL
Указатель может указывать В НИКУДА. Для этого туда надо записать NULL. Как-то так:
int *pinter = NULL;Важно: не стоит путать NULL с нулём. Это разные вещи! В стандарте прямо сказано, что значение указателя NULL не определено (точнее, определено, но для каждой конкретной системы по своему. Часто оно действительно 0, потому ваш быдлокод, где вы путаете тёплое с мягким, иногда даже будет работать)
И тем не менее, в стандарте также определено, что преобразовав NULL в целое, вы гарантированно получите ноль. Если-же преобразовать в целое указатель, который на что-то указывает, вы гарантированно получите НЕ НОЛЬ (Важно: вы получите НЕ НОЛЬ, но вовсе НЕ обязательно, что вы получите адрес, или вообще хоть что-то осмысленное! Вы получите ☣, которая не ноль). Таким образом, вы можете узнать, что указатель куда-то указывает
if(pointer)
// указатель куда то указывает
else
// указатель никуда не указывает, и равен NULLНапример вы можете попросить систему(точнее glibc) найти вам символ в строке(man 3 strchr), и она вам отдаст указатель на этот символ если найдёт, и даст NULL, если не сможет найти.
Если разименовать NULL (посмотреть, на что оно указывает), то мир сдвинется с места. Тоже самое произойдёт если поделить на ноль. В программирование же ничего страшного не случается, обычно ваш быдлокод банально рухнет. Впрочем, случится может всё, что угодно. (кому угодно? кому угодно.)
Опасайся ☣ !
Указатель void*
Каждый указатель имеет свой тип, например выше у нас были указатели, которые указывали на int. Иногда нужны указатели, которые указывают на что угодно. Называются они void*.
Естественно, любой указатель можно без вопросов перевести в void*. Обратно надо переводить в явном виде(если надо).
Если у вас был указатель одного типа(не void*), а вы его перевели в указатель другого типа(не void*), то всё будет хорошо. До тех пор пока вы его не использовали. Тогда случится ☣.
Опасайся ☣ !
Указатель на указатель
Указатель может указывать на что угодно. В том числе и на другой указатель. Это особый тип, который называется "указатель на указатель". Типа того:
int value = 1;
int *pointer = &value;
int **pp = &pointer;Нужно больше const!
Вообще говоря, для обычных переменных const не слишком нужен(они переменные, К.О.) Однако, для указателей всё меняется, ибо один указатель можно менять двумя способами:
1. можно передвинуть указатель на другое число
2. можно изменить само число, на которое указывает указатель.
Поэтому существует 4 типа указателей разной степени константности:
1. обычный указатель
2. указатель на константу (нельзя менять значение)
3. константный указатель(нельзя двигать указатель)
4. константный указатель на константу
Последний -- самый лучший. Ибо такой указатель никогда и никак не меняется. Самый плохой -- первый, ибо может меняться как угодно.
И да, можно преобразовать константный указатель в обычный. Получится ☣.
Опасайся ☣ !
Массивы в сишечке
Массивов в сишечке нет. Массивы в сишечке есть. Оба высказывания истинны. Странно, да?
На самом деле, ничего странного тут нет, ибо массивы конечно есть, но их смысл абсолютно другой, нежели в других ЯП. На самом деле, самая обычная переменная -- это УЖЕ массив из одного эл-та. Массив типа
int a[3]={1,2,3};это три переменные, которые хранятся в памяти вплотную (это важно!)
Показать
Обращаться к такому массиву можно как и в остальных ЯП
a[1]=8;
printf("%d", a[2]);
int y = a[0];Однако, надо помнить, что внутри [скобок] вовсе НЕ целое число, а некий "безразмерный тип", т.е. "просто количество", иначе "штуки". Называется такой тип size_t. Он имеет важное отличие от int -- в этом типе можно сохранить количество чего угодно (в рамках C). К примеру, можно сохранить длину любого массива. Ещё 10 лет назад это различие было очень слабо заметно, и быдлокодеры постоянно мешали size_t и int (как сейчас мешают NULL и 0). ВНЕЗАПНО появились 64х битные процессоры amd64(не только AMD, intel тоже), и теперь size_t вырос в 4 миллиарда раз (был 4 миллиарда, стал в 4 миллиарда раз больше. Ибо 64 бита). Не менее ВНЕЗАПНО(для быдлокодеров) тип int так и остался 32х битным. Такие дела...
Массивы и указатели
Массивы мистическим образом связанны с указателями. Это очень сильное колдунство. Как оно в магии IRL? Колдун говорит СЛОВА, самые обычные, которые может сказать каждый, и ВНЕЗАПНО кто-то умирает в страшных мучениях. В сишечке -- тоже самое. Абсолютно. В примере выше, вот это вот наша a -- указатель. И не указатель, а массив. Нет, никакой не массив, а указатель.
Ладно. На самом деле всё проще. На самом деле, a это всё-же массив, который по совместительству, одновременно, является ещё и указателем. Но не просто указателем, а с дополнительно прибитым гвоздями размером этого массива. Его можно даже узнать:
int main()
{
int a[3];
printf("%d\n",
sizeof(a));
return 0;
}
напишет 12. Почему 12? Потому-что 12 char'ов. В одном int'е 4 char'а (32 бита), т.е. 3*4==12. Естественно это только на i686 и amd64 так. На других платформах -- ☣.
Что-бы платформонезависимо посчитать размер массива, надо его поделить на sizeof(a[0]), т.е. размер первого эл-та. Который всегда есть. Получится естественно 3.
Нужно помнить, что это не просто особый указатель, а особый константный указатель. Т.е. определив массив, вы его уже НИКОГДА не сдвинете. Ну вот так вот.
Кроме того, нужно знать, что когда массив передаётся в функцию, он с помощью магии превращается в самый обычный указатель. Вот там(внутри функции) его размер будет всего 1(один эл-т, а не один байт). Как обычно, утерянная информация не возвращается. ВСЁ, что поглотила ☣ уже не вернуть. Эта та самая речка Лета, что рядом с адом протекает. Т.е. вернув из функции указатель, вы получите указатель(а не массив). И вернув массив, вы тоже получите указатель.
Т.е. если нужна функция, которая обрабатывает N эл-ный массив, то в эту функцию мало передать массив, нужно ещё и передать это ваше N. Иначе -- никак. Явно, или неявно, но размер передавать придётся. Ибо его пожрёт ☣
Опасайся ☣ !
Указатель в/на массив
Указатель в/на массив обладает особой магией. Связано это прежде всего с самими колдунами (а особенно с надмозгами-переводчиками, которые K&R переводили). Все 146% программистов сишечников говорит "указатель НА массив", но на самом деле, это указатель В массив. Само имя массива, это и есть такой указатель В массив. Что естественно не мешает наделать ещё Over9000 других указателей в/на массив. Эти другие указатели можно уже даже двигать. Чем мы сейчас и займёмся.
Но сначала, откроем тайну ещё одного великого колдунства, а именно -- [скобок]. Кажется, в них нет ничего таинственного, но так оно только для непосвящённых. Вот две строки:
int a[3];
a[1] = 7;На первый взгляд, скобки одинаковые? Присмотритесь внимательно: во второй строке скобки СОВСЕМ ИНЫЕ. Не?
Да.
В первой строке скобки ОБЪЯВЛЯЮТ массив, и в них пишется его длинна. Во второй строке скобки разименовывают массив, и 7 пишется В этот массив. А вот единица тут играет роль сдвига a в сторону увеличения на 1. Потому 7 пишется не в *a, а во второй элемент массива.
т.е. получается, что на самом деле, вторые скобки означают
*(a+1) = 7;Вот тут начинающие волшебники очень любят рвать шаблон ещё более начинающим, любимой шуткой
1[a] = 7;Да, этот быдлоколд работает. Почему? Потому-что a+1==1+a. Почему быдлокод? Да потому, что мы ещё НЕ ОПРЕДЕЛИЛИ сложение указателей и целых(точнее size_t, хотя это даже и не size_t, а пока для нас ☣). Ну а раз не определили, то и применять какие-то законы никак не можем.
Опасайся ☣ !
Арифметика указателей
Арифметика указателей -- суть тайная магия, неподвластная непосвящённым. На самом деле, она проста как кирпич. Рассмотрим пример:
int a[3]={111,222,333};
int *p1 = &a[0];
int *p2 = &a[2];
??? D = p2 - p1;// чему равно D и вообще, что это такое???В этом примере, очевидно D равно 2 (2-0, потому-что расстояние от нулевого эл-та(111) до второго(333) очевидно будет 2). Но вот тип уже совсем НЕ очевиден. Это НЕ "штуки"(size_t), это что-то иное, ибо имеет физический смысл РАССТОЯНИЯ, а вовсе не количества. В сишечке для этого нечто есть тип ptrdiff_t.
Таким образом определяется вычитание указателей, дающее расстояние между ними, выраженное в ptrdiff_t. Важно отметить, что указатели ОБЯЗАНЫ указывать на один и тот же массив. В противном случае, при их вычитании, получается ☣.
Показать
Опасайся ☣ !
Определив таким образом вычитание, можно очевидно и определить сложение, через это вычитание. НО. Не нужно забывать про типы. Т.е. складывать указатели можно, но только с числами ptrdiff_t. К счастью, обычно целые числа без проблем преобразуются в этот самый ptrdiff_t. На самом деле, ptrdiff_t это и есть целое, такого размера, что в него может поместится любая разность между любыми указателями(допустимыми, на один и тот же массив. См. выше).
Сложение указателей в сишечке НЕ определено, как и остальные арифметические действия.
Опасайся ☣ !
Строки в сишечке
В C/C++ нет встроенного типа string(строка), в сишечке, вместо него используются массивы из char'ов. Дабы определить размер этого массива, используют символ NUL aka '\0' (не надо путать его с '0', и с NULL). Данный символ определяет конец строки.
Размер строки в char'ах (байтах/октетах обычно) определяется с помощью функции strlen(3).
Показать
NAME
strlen - calculate the length of a string
SYNOPSIS
#include <string.h>
size_t strlen(const char *s);
DESCRIPTION
The strlen() function calculates the length of the string s, excluding the termi-
nating null byte ('\0').
RETURN VALUE
The strlen() function returns the number of bytes in the string s.
(и далее см. man 3 strlen)
Также в glibc имеются и все прочие функции для таких строк. Ну а для определения таких строк имеется специальная форма
const char *str = "Hello world!";
char str[] = "Hello";(ага, даже две. Вторая форма -- массив. Размер компилятор сам определит в данном случае)
Динамические массивы
Выше были лишь такие массивы, размер которых задавался во время компиляции. Это конечно хорошо, но не очень. Часто нужны такие массивы, размер которых заранее неизвестен и/или может изменяться. В сишечке таких массивов нет.
Показать
Что делать? Да как всегда -- просить ОС дать нужной памяти. Угу, через glibc, как обычно. Нам понадобятся (сначала) две функции: malloc(3) и free(3). Первая выделяет кусок памяти, вторая его возвращает обратно.
Важно: эти две функции должны быть РЯДОМ. Что-бы не забыть одну из них. Если вы забудете malloc(3), то ничего страшного -- ваша программа просто рухнет, т.к. вы используете память не выделив её предварительно. Рухнет она в GNU/Linux, а вообще, это как всегда ☣. Куда как страшнее, если вы забудете free(3) -- программа будет работать(во всяком случае первое время), но рано или поздно, пользователи заметят, что ваш быдлокод течёт. Т.е. жрёт память... Что удивительного, если вы заб(ы|и)ли её отдать обратно? Впрочем, тоже ничего страшного, подумаешь, канделябром по голове?