- •1 Переменные
- •1.1 Базовые типы данных
- •1.1.1 Целочисленные типы
- •1.1.2 Способы записи целых чисел
- •1.1.3 Логический тип
- •1.1.4 Вещественные типы
- •1.1.5 Специальный тип void
- •1.2 Оператор sizeof
- •1.3 Константы
- •1.4 Определение и описание переменных
- •1.5 Классы памяти переменных
- •2 Функции
- •2.1 Объявления и определения функций
- •2.2 Функция main
- •3.1 Разыменование указателя и получение адреса
- •3.1.1 Операции разыменования указателя и получение адреса переменной
- •3.1.2 Инициализация и присваивание указателей
- •3.1.3 Указатели и область действия (время существования) переменных
- •4 Массивы
- •4.1 Основные сведения
- •4.2 Указатель на первый элемент массива
- •4.3 Работа с указателями как с массивами
- •4.4 Арифметика указателей
- •4.5 Разноразмерные массивы
- •5 Структуры
- •6 Логические операции
- •7 Условия
- •8 Операторы сравнения
- •9 Сдвиги
- •10 Циклы
- •10.1 Цикл с предусловием
- •10.2 Цикл с постусловием
- •10.3 Цикл со счётчиком
- •11 Оптимизация и её запрет, ключевое слово volatile
- •12.1.2 Директива #include
- •12.2 Условная компиляция
3Указатели Указатель – это особый тип данных, который хранит адрес ячейки памяти,
начиная с которой располагается переменная связанного с указателем типа. Указатель связывает во едино два понятия: адрес и тип переменной. Внутренне указатели похожи на обычные целочисленные переменные, за исключением того, что хранящееся в них число используется как адрес переменной, а также некоторых особенностей применения к ним арифметических операций.
Переменная-указатель определяется с использованием символа «*» («звёздочка»), следующего за указанием типа и перед именем указателя (см. листинг 5).
Листинг 5. Примеры определения указателей.
unsigned int *intPtr;// Указатель на переменную типа unsigned int.
// Считается, что intPtr имеет тип 'unsigned int *' const char *str = "Text"; // Указатель на переменную типа char,
// через который невозможно изменить её //значение (указатель на константу).
3.1Разыменование указателя и получение адреса
Использование указателей тесно связано с применением двух операций: разыменование указателя и получение адреса переменной.
3.1.1Операции разыменования указателя и получение адреса переменной
Разыменованием указателя называется конструкция, позволяющая получить доступ к значению переменной, на которую ссылается указатель (т. е. адрес которой хранит указатель). Это может потребоваться в случае, если необходимо изменить или прочитать значение переменной через указатель. Разыменование указателя осуществляется с использованием символа «*» («звёздочка») перед именем указателя.
Получением адреса переменной называется конструкция, позволяющая сформировать указатель, хранящий адрес этой переменной. Такая операция осуществляется с использованием символа «&» («амперсанд») перед именем переменной. После этого через сформированный указатель становится возможно изменять значение исходной переменной (через операцию разыменования). В некотором смысле описанные операции являются обратными по отношению друг к другу.
13
Примеры применения операция разыменования указателей и получения адреса переменных приведены в листинге 6.
Листинг 6. Примеры разыменования указателей и получения адреса
//Определяем целочисленную переменную со значением 200 unsigned int value = 200;
//Определение указателя на переменную типа unsigned int
//с одновременной инициализацией адресом переменной value. unsigned int *pointerToValue = &value;
//Теперь pointerToValue указывает на ячейку памяти,
//начиная с которой хранится переменная value.
//Создаём ещё одну переменную с одновременной инициализацией
//значением переменной, на которую указывает pointerToValue unsigned int value2 = *pointerToValue; // Здесь value2 будет равно 200
//Используем разыменование для изменения значения переменной value *pointerToValue = 100;
//Теперь value = 100, а value2 по прежнему 200.
//Возможно поменять значение указателя (сделать так, чтобы он
//ссылался на другую переменную)
pointerToValue = &value2;
// Теперь (*pointerToValue) равно 200
3.1.2Инициализация и присваивание указателей
Одним из способов изменения значения указателя является присвоение ему адреса некоторой переменной. Также часто применяется инициализация (или присваивание) специальным макросом NULL, что делает указатель нулевым. Нулевой указатель – это указатель на адрес памяти 0. Это специальное значение, показывающее, что указатель не указывает ни на какую переменную.
Разыменовывать нулевой указатель нельзя – поведение программы в таком случае непредсказуемо.
3.1.3 Указатели и область действия (время существования) переменных
При выполнении операции получения адреса переменной следует
14
учитывать, как долго эта переменная существует в памяти. Особую осторожность следует проявлять при получении адреса локальных переменных. Такие переменные обычно существуют на стеке и при выходе из функции уничтожаются, после чего на их месте могут оказаться другие данные. Следовательно, разыменование указателя на локальную переменную после выхода из функции, в которой она была определена, может привести к совершенно неожиданному поведению программы. Пример такой ситуации приведён в листинге 7.
Компилятор не проверяет корректность адреса, на который ссылается указатель, поэтому указатели следует использовать с большой осторожностью, т. к. этот удобный инструмент является потенциальным источником трудно обнаруживаемых ошибок.
Листинг 7. Пример ошибки при разыменовании указателя на несуществующую переменную (не делайте так!)
//ВНИМАНИЕ! Программа в данном примере является некорректной. Это
//сделано намеренно для иллюстрации ошибки, связанной с
//разыменованием указателя на несуществующую переменную.
//Объявление функции
void function2 (int *ptr);
// Объявляем функцию, возвращающую указатель на переменную типа int int * function1 (void)
{
int k = 100500; |
|
int *pointer = &k; |
// Указатель, ссылающийся на локальную |
переменную. |
|
function2 (pointer); |
// Нет ошибки, т. к. переменная k ещё |
существует. |
|
return pointer; |
// НЕЛЬЗЯ! |
|
// Переменная, на которую ссылается указатель |
|
// будет уничтожена при выходе из функции. |
|
// Возвращённый указатель нельзя |
} |
// разыменовывать! |
|
void function2 (int *ptr)
{
//Увеличить значение переменной, на которую ссылается
//указатель, на 100
*ptr = *ptr + 100;
15
}
int main (void)
{
//Создаём указатель, и инициализируем его адресом
//уже уничтоженной переменной! Последующее разыменование приведёт
//к непредсказуемым последствиям.
int * pointer = function1();
//Вызов функции, которая изменяет значение переменной через
//указатель. Последствия непредсказуемы (в лучшем случае -
//зависание или сброс микроконтроллера, в худшем – продолжение
//работы программы с последующим возникновением «необъяснимых»
//ошибок в других местах)
function2 (pointer);
// ... продолжение программы
}
16