Что обозначает запись int p1 x

Правильный способ чтения int * ptr = & x

То есть, похоже, это интерпретируется как присвоение адреса x указанному значению * ptr (int *ptr )= &x Правильная интерпретация, когда объявление и инициализация разделены, должна быть записана как-то вроде int *(ptr = &x) , чтобы было ясно, что присвоение относится к указателю, а не к указанному месту, но это дает ошибку, почему? И как лучше всего читать и думать о int *ptr=&x ?

3 ответа

int * — это тип, поэтому он имеет смысл. Просто к такой нотации указателя в C может потребоваться некоторое время, чтобы привыкнуть. Но посмотрите на этот код

То же самое. Однако часто рекомендуется НЕ вводить указатели typedef.

Путаница возникает из-за того, что * выполняет две роли. Это ОБА — назвать тип И разыменовать указатель. Пример первого: sizeof(int*)

Если вы объявляете сразу несколько указателей, это выглядит запутаннее, но все равно. Как правило, не рекомендуется объявлять более одного указателя одновременно. Потому что, если бы мы хотели проделать то же самое с двумя указателями, это выглядело бы так:

Это то же самое, что и

И это делает что-то совершенно другое и генерирует предупреждение, потому что вы назначаете адрес переменной переменной pb , имеющей тип int

Однако, используя typedef сверху, вы можете (но, вероятно, не должны) делать это:

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

И как лучше всего читать и думать о int *ptr=&x ?

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

Хорошо, я понимаю, еще кое-что. Если типом является int* , почему он почти всегда записывается как * рядом с переменной-указателем, например int *ptr ?, это имело бы больший смысл, даже если это то же самое написать это как int* ptr .

Потому что тогда будет НАМНОГО легче забыть звездочку, если вы объявите сразу несколько указателей. Это может создать впечатление, что int* p,q; объявляет два указателя.

Программирование на чистом Си (без ++)

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

Это не учебник по Си, а скорее краткая справка , где перечислены основные моменты, которые необходимо знать для программирования на этом языке. Что касается базовых основ, то их читайте у классиков — Шилдт, Страуструп и т.п.

Пример простой программы на Си

Функция main — это точка входа в программу, с которой компьютер начинает выполнение программы.

Допускается из main возвращать void, хотя это не по стандарту, так что лучше int.

В функцию main можно передавать аргументы командной строки:

Структура памяти программы:

— куча — для динамического выделения памяти

— стек — локальные переменные класса памяти auto (включая аргументы функций)

— CODE — исполняемый код, инструкции процессора

Некоторые элементы синтаксис языка:

\ — команда продолжается на следующей строке

«go» «to» — воспринимается как одна строка «goto»

Типы данных в Си

-Базовые типы данных: char, int, float, double.

-Модификаторы знака: signed, unsigned.

-Модификаторы знака: long, short.

void — тип без значения

В Си логический тип реализован неявно (с помощью int): false = нуль, true = не нуль.

Введение псевдонимов для ранее описанных типов данных:

typedef тип имя

где тип — любой существующий тип данных, имя — новое имя для этого типа.

Пример: typedef unsigned char byte;

Преобразование типов:

Если операнды операции имеют разные типы, то происходит неявное приведение типов:

(чтобы здесь получить 0.4 нужно было бы написать x=2.0/5 или 2/5.0)

Явное приведение типов:

Принудительное преобразование типов:

(желательно вообще избегать преобразования типов)

Переменные и константы

Переменная представляет собой блок памяти, на который мы ссылаемся по её имени (идентификатору).

Декларация переменных (вместе с инициализацией):

[класс памяти] [квалификаторы] [модификаторы] тип идентификатор = инициатор;

Здесь «;» — составляющая часть конструкции, завершающая часть.

Допустима (хотя и редко используется) запись: const x = 100; (по умолчанию int).

Квалификаторы (или «модификаторы доступа»): const, volatile.

const — означает, что переменные не могут изменяться во время выполнения программы; инициалиировать можно только при декларации;

volatile — содержимое переменной может измениться само собой (используется в многопоточных программах при взаимодействии процессов)

Возможен вариант const volatile, когда писать могут только снаружи.

Спецификторы хранения (описатель класса памяти): auto, register, extern, static.

auto — локальные переменных (по умолчанию) — программный стек.

register — просьба компилятору положить переменную в регистр ЦПУ (но он эту просьбу редко выполняет);

extern — объявление (declaration) переменных, но не определение (definition) (определение где-то в другом месте); определение может идти ниже по файлу (но как глобальная) или в другом файле.

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

Внешние и статические объекты существуют и сохраняют свои значения на протяжении всего времени выполнения программы.

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

Описание области действия идентификаторов (имен):

— внутреннее (локальное) — внутри блока

— внешнее (глобальное) — вне всех блоков

Идентификатор, описанный внутри блока, известен только в этом блоке (локальный идентификатор).

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

Вообще стоит избегать использования глобальных имен.

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

В языке C/C++ предусмотрено три категории связей: внешние, внутренние связи и их отсутствие.

Глобальные объекты, объявленные с помощью спецификатора static, имеют внутренние связи. Они доступны лишь внутри файла, в котором описаны.

Ключевое слово extern указывает, что объявляемый объект обладает внешними связями в рамках всей программы.

Локальные переменные не имеют связей и, следовательно, видимы лишь внутри своего блока.

Глобальные переменные по умолчанию имеют класс памяти extern (но ключевое слово ставить не надо) и располагаются в сегменте данных (Data). Такие переменные по умолчанию инициализируются нулем при запуске программы (т. е. один раз только). Область видимости идентификатора extern — от точки появления до конца файла.

Однако ключевое словое extern ставится только для объявления, но при определении переменной слово extern не ставится:

Определение должно быть только одним. Объявлений может быть много в пределах одного файла.

Спецификатор extern сообщает компилятору, что следующие за ним типы и имена переменных объявляются где-то в другом месте.

Если компилятор С встречает переменную, которая не была объявлена, то компилятор проверяет, соответствует ли она какой-либо глобальной переменной. Если это так, то компилятор предполагает, что эта переменная ссылается на глобальную.

Связь по внешним именам реализует линкер . Линкер работает только с именами и не обращает внимание не тип переменных. Поэтому если в разных файлах будет одно именя с разными типами, то программа запустится, но будет работать непредсказуемо. Чтобы это избежать можно все объявления (со словом extern) вынести в отдельный h-файл и включать его во все c-файлы.

Переменные с классом памяти static видны только в пределах текущего блока (для локальных) или в пределах файла (для объявленных глобально).

Статические переменные хранятся в сегменте данных (data) и по умолчанию инициализируются нулем. Т.е. память под static-переменные выделяется при старте программы и существует до конца программы.

Замечание: Инициализация выполняется одни раз при выделении памяти!

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

Следует различать присваивание и инициализацию:

— Присваивание: имя_переменной = выражение;

— Многочисленное присваивание: x = y = z = 0;

— Инициализация переменных: тип имя_переменной = константа;

Константы

Константы являются частью машинных команд и под них память не выделяется.

10-я система: 127; -127; +127;

8-я система: 0127; (начинается с нуля — значит 8-ричная!)

16-я система: 0x7F; (x или X, f или F — регистр не влияет)

— вещественные: 3.14; 2. ; .25 (0 можно опускать); 2E3; 2e3; 2E-3; 2.0E+3;

— символьные: 8-битные ASCII: ‘A’, ‘=’, ‘\n’, ‘\t’, ‘\370’, ‘\xF8’ (символ градуса);

— строковые литералы (в двойных кавычках): «Hello, world!\n». Строки заканчиваются нулевым байтом — ‘\0’.

Операции и операторы

Оператор (инструкция, англ. statement) — это единица выполнения программы.

В языке Си любое выражение, заканчивающееся символом «точка с запятой» (;), является оператором.

Фигурные скобки — это составной оператор.

Кроме того является отдельным блоком и в нем можно определять локальные переменные.

; — пустой оператор.

Операции:

— Инкрименты и декрименты: ++a, —a, a++, a— (могут выполняться быстрее)

— Операторы сравнения (отн-ний): > >=

— Логические операторы: && || ! (возвращают 1 или 0)

— Оператор ?: x ? y : z, напр.: r = 10>9 ? 100 : 200

sizeof — унарный оператор для вычисления размера переменной или типа

, — оператор запятая (последовательное вычисление): a = (b=3, b+2);

Приоритеты операций:

1) ::->. [] () — разрешение контекста, извлечение; индекс массива, вызов ф-ии и преобр-ие типа;

2) — + (унарные), ++ —

4) ->* .* извлечение;

5) + — (бинарные);

6) > сдвиги;

7) => сравнение;

8) == != равно, не равно;

10) ^ XOR (исключающее ИЛИ);

11) | побитовое ИЛИ;

13) || ИЛИ логическое;

14) ?: тернарная операция (x ? y : z);

15) = *= /= %= += и т.д. — операция присвоения [Справа-налево];

16) , следование.

Порядок выполнения операторов:

— Унарные операторы выполняются справа-налево.

— Бинарные выполняются слева-направо.

— Присваивание выполняется справа-налево.

Порядок можно менять с помощью скобок!

Выражение а + b + c интерпретируется как (а + b) + с.

Замечание: порядок вычисления операндов бинарного оператора произволен.

Например, результат y = (x=10)*2 + x*y будет зависеть от компилятора.

4 исключения: && и || (всегда вычисляет 1-ый оператор; причем если он равен true(false), то 2-ой не вычисляется); условие (x ? y : z); оператор запятая (,).

Поэтому условие if-else можно реализовать так:

sizeof() — возвращает длину в байтах переменной или типа; sizeof(int); sizeof(a);

sizeof выражение; — само выражение не вычисляется.

Оператор запятая:

левая сторона оператора вычисляется как void и не выдаёт значения, переменной x присвается значение выражения в правой стороне, т.е. y+1.

Указатели и ссылки в Си

* — доступ к значению объекта по указанному адресу;

Указатели:

Указатель — такая переменная, которая хранить адрес некоторого объекта и связана с типом этого объекта.

класс_памяти квалификатор тип * квалификатор идентификатор = инициатор;

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

p = q; — копирование адреса. Обычно одно типа. Если разного типа, то это не безопасно!

Указатель p может ссылаться на тип void (используется в C для обобщенных алгоритмов).

p = NULL; — нулевой указатель — это признак отсутствия значения у указателя. Такой указатель нельзя использовать для доступа к памяти, т. к. это приведет к сбою программы во время выполнения.

Арифметика указателей отличается от обычной и зависит от типа:

Унарные операции * и ++ имеют одинаковый приоритет и выполняются справа налево, т. е.

Указатели

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

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

Таким образом, каждая ячейка может быть легко расположена в памяти посредством своего уникального адреса. Например, ячейка памяти с адресом 1776 всегда следует непосредственно за ячейкой с адресом 1775 и предшествует ячейке с адресом 1777, а также находится точно на тысячу ячеек после 776 и точно на тысячу перед 2776.

Когда переменная объявлена, памяти, необходимой для хранения ее значения, назначается определенное место в памяти (ее адрес памяти). Обычно, программы на C++ не принимают активного участия в определении точного адреса памяти, в которой хранится переменная. К счастью, эта задача оставлена ​​для среды, в которой запускается программа, — обычно это операционная система, которая определяет конкретные области памяти во время выполнения. Тем не менее, программе может быть полезно иметь возможность получить адрес переменной во время исполнения, для доступа к ячейкам данных, которые находятся в определенной позиции относительно этой переменной.

Оператор взятия адреса (&)

Адрес переменной можно получить, поставив перед именем переменной знак амперсанда (&), известный как оператор взятия адреса. Например:

Это присвоит адрес переменной myvar переменной foo; ставя оператор взятия адреса (&) перед именем переменной myvar, мы присваиваем переменной foo не содержимое переменной myvar, а её адрес.

Фактический адрес переменной в памяти неизвестен до выполнения, однако, для выяснения некоторых понятий, давайте предположим, что myvar во время выполнения расположена в памяти по адресу 1776.

В этом случае, рассмотрим следующий фрагмент кода:

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

Сначала мы присвоили значение 25 переменной myvar (переменная, адрес которой мы предположили равным 1776).

Второе выражение присваивает переменной foo адрес переменной myvar, равный 1776.

Наконец, третье выражение присваивает значение, содержащееся в переменной myvar переменной bar. Это обычная операция присваивания, которая выполнялась множество раз в предыдущих главах.

Главное отличие между вторым и третьим выражениями — появление оператора взятия адреса (&).

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

Оператор разыменования (*)

Как только что сказано, переменная, хранящая адрес другой переменной, называется указателем. Говорят, что указатели «указывают на» переменную, адрес которой они хранят.

Интересным свойством указателей является то, что они могут быть использованы для доступа к переменной, на которую они указывают, напрямую. Это делается путём добавления перед именем указателя оператора разыменования (*). Сам оператор может быть прочтен как «значение, на которое указывает».

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

Это может быть прочтено как «baz равен значению, на которое указывает foo», а выражение, в действительности, присвоит значение 25 переменной baz, т.к. foo равна 1776, а значение по адресу 1776 равно 25.

Важно четко различать, что foo ссылается на значение 1776, тогда как *foo (со звездочкой *, предшествующей идентификатору) ссылается на значение, хранящееся по адресу 1776, в данном случае это 25. Обратите внимание на разницу включения или не включения оператора разыменования (Я добавил пояснительный комментарий о том, как можно прочитать каждое из этих двух выражений).

Операторы взятия адреса и разыменования, таким образом, дополняют друг друга:
& это оператор взятия адреса, и может быть прочтен просто как «адрес»
* это оператор разыменования, и может быть прочтен как «значение, на которое указывает»

Таким образом, они имеют своего рода противоположные значения: адрес, полученный с помощью &, может быть разыменован с помощью *.

Ранее мы выполняли следующие две операции присваивания:

Сразу после этих двух выражений, все следующие выражения будут иметь значение true в качестве результата:

Первое выражение вполне понятно, учитывая, что была произведена операция присваивания myvar=25. Второе выражение использует оператор взятия адреса (&), который возвращает адрес myvar, который мы предположили равным 1776. Третье выражение очевидно, т.к. второе выражение было истинным, и была произведена операция присваивания foo=&myvar. Четвертое выражение использует оператор разыменования (*), который может быть прочтен как «значение, на которое указывает». а значение, на которое указывает foo в действительности 25.

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

Объявление указателей

Благодаря способности указателя напрямую ссылаться на значение, на которое он указывает, указатель имеет разные свойства, когда он указывает на char или когда он указывает на int или float. После разыменования тип должен быть известен. И для этого в объявлении указателя должен быть указан тип данных, на который будет указывать указатель.

Объявление указателя имеет следующий синтаксис:

где type — это тип данных, на которые будет ссылаться указатель. Этот тип является типом не самого указателя, а типом данных, на которые он ссылается. Например:

Здесь объявлено три указателя. Каждый из них предназначен для указания на различные типы данных, но, в действительности, каждый из них является указателем и все они, вероятно, будут занимать одинаковое количество памяти (размер указателя в памяти зависит от платформы, на которой работает программа). Тем не менее, данные, на которые они указывают, не занимают одинаковое количество памяти и не имеют одинаковый тип: первый указывает на int, второй на char, а третий на double. Следовательно, хотя все эти три переменных являются указателями, в действительности, они имеют различные типы: int*, char* и double* соответственно, в зависимости от типа, на который они указывают.

Обратите внимание, что звездочка (*), используемая при объявлении указателя, означает только то, что это указатель (она является частью составного спецификатора его типа), и её не нужно путать с оператором разыменования, описанным немного ранее, который также записывается как звездочка (*). Это просто две разные вещи, представленные одним и тем же знаком.

Давайте посмотрим на пример с указателями:

Каждая операция присваивания включает комментарий о том, как можно прочитать каждую строку: т.е. амперсанды (&) заменены на «адрес», а звездочки (*) на «значение, адресуемое».

Обратите внимание, что существуют выражения с указателями p1 и p2, как с оператором разыменования (*), так и без него. Значение выражения, использующего оператор разыменования (*), очень отличается от выражения, не использующего его. Когда этот оператор предшествует имени указателя, выражение ссылается на значение по адресу указателя, тогда как без него, выражение ссылается на значение самого указателя (т.е. адрес, на который указывает указатель).

Ещё одна строка, которая может привлечь ваше внимание:

Она объявляет два указателя, использованные в предыдущем примере. Но обратите внимание, что для каждого указателя есть звездочка (*), чтобы оба указателя имели тип int * (указатель на int). Это требование обусловлено правилами приоритета. Обратите внимание, что если вместо этого код был таким:

p1 действительно будет иметь тип int *, но p2 будет иметь тип int. Пробелы не имеют значения в данном контексте. Но в любом случае, просто помнить о том, чтобы поставить одну звездочку на указатель, достаточно при объявлении множества указателей в одном выражении. Или даже лучше: используйте разные выражения для каждой переменной.

Указатели и массивы

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

Следующая операция присваивания будет корректной:

Давайте посмотрим на пример, который смешивает массивы и указатели:

Указатели и массивы поддерживают одинаковый набор операций с одинаковым смыслом для обоих. Основное отличие состоит в том, что указателям могут быть назначены новые адреса, а массивам — нет.
В главе о массивах скобки ( [ ] ) были объяснены как указание на индекс элемента массива. На самом деле эти скобки — это оператор разыменования, известный как оператор смещения. Они разыменовывают переменную, за которой следуют, так же, как и *, но они также добавляют число в скобках к адресу, на который делается разыменование. Например:

Эти два выражения эквивалентны и допустимы не только если a является указателем, но также и если a является массивом. Помните, имя массива можно использовать так же, как указатель на его первый элемент.

Инициализация указателей

Указатели могут быть инициализированы так, чтобы указывать на определенное место в момент определения:

Результирующее состояние переменных после этого кода такое же, как после:

Когда указатели инициализируются, инициализируется адрес, на который они указывают (то есть myptr), а не указываемое значение (то есть * myptr). Поэтому приведенный выше код не следует путать с:

Что в любом случае не имеет большого смысла (и не является корректным кодом).
Звездочка ( * ) в объявлении указателя (строка 2) показывает только то, что это указатель, а не оператор разыменования (как в строке 3). В обоих случаях просто используется один и то же символ: *. Как всегда, пробелы не имеют значения и никогда не изменяют смысла выражения.
Указатели могут быть инициализированы либо по адресу переменной (например, в приведенном выше случае), либо по значению другого указателя (или массива):

Адресная арифметика

Выполнение арифметических операций с указателями немного отличается от выполнения их с обычными целочисленными типами. Для начала, разрешены только операции сложения и вычитания; другие не имеют смысла в мире указателей. Но и сложение, и вычитание ведут себя немного иначе с указателями в зависимости от размера типа данных, на который они указывают.
Когда были представлены основные типы данных, мы увидели, что типы имеют разные размеры. Например: char всегда имеет размер 1 байт, short обычно больше него, а int и long еще больше; точный размер зависит от системы. Например, давайте представим, что в данной системе char занимает 1 байт, short занимает 2 байта, а long 4.
Предположим теперь, что мы определили три указателя в этом компиляторе:

и что мы знаем, что они указывают на ячейки памяти 1000, 2000 и 3000 соответственно.
Следовательно, если мы напишем:

mychar, как и следовало ожидать, будет содержать значение 1001. Но не так очевидно, что myshort будет содержать значение 2002, а mylong будет содержать 3004, хотя каждый из них был увеличен только один раз. Причина заключается в том, что при добавлении единицы к указателю, он будет указывать на следующий элемент того же типа, и поэтому к указателю добавляется размер в байтах того типа, на который он указывает.

Это применимо как при сложении, так и при вычитании любого числа в указателе. Это произошло бы точно так же, если бы мы написали:

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

эквивалентно *(p++). Это увеличивает значение p (так что теперь оно указывает на следующий элемент), но так как используется постфиксная запись оператора ++, все выражение вычисляется как значение, на которое указывал указатель изначально (адрес, на который он указывал до инкремента).
По сути, это четыре возможные комбинации оператора разыменования с префиксной и суффиксной версиями оператора инкремента (то же самое применимо и к оператору декремента):

Типичное, но не очень простое выражение, включающее эти операторы:

Поскольку ++ имеет более высокий приоритет, чем *, оба значения p и q увеличиваются, но поскольку оба оператора инкремента (++) используются в качестве постфикса, а не префикса, значение, присваиваемое *p, равно *q до увеличения как p, так и q. А затем оба увеличиваются. Это было бы примерно эквивалентно:

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

Указатели и const

Указатели могут использоваться для доступа к переменной по ее адресу, и этот доступ может включать изменение значения, на которое они указывают. Но также возможно объявить указатели, которые могут получить доступ к указанному значению, чтобы прочитать его, но не изменить его. Для этого достаточно обозначить тип, на который указывает указатель, как const. Например:

Здесь p указывает на переменную, но указывает на нее константно, что означает, что он может прочитать указанное значение, но не может изменить его. Также обратите внимание, что выражение &y имеет тип int*, но оно присваивается указателю типа const int*. Это разрешено: указатель на не-константу может быть неявно преобразован в указатель на константу. Но не наоборот! Из соображений безопасности указатели на const неявно не конвертируются в non-const указатели.
Один из вариантов использования указателей на константные элементы — это параметры функции: функция, которая принимает указатель на non-const в качестве параметра, может изменить значение, переданное в качестве аргумента, а функция, которая принимает указатель на const в качестве параметра, не может.

Обратите внимание, что print_all использует указатели, которые указывают на константные элементы. Эти указатели указывают на константное содержимое, которое они не могут изменить, но сами они не являются константами: то есть указатели все еще могут увеличиваться или назначаться другим адресам, хотя они не могут изменять содержимое, на которое они указывают.
И здесь к указателям добавляется второе измерение константности: указатели также могут быть константами. И это указывается добавлением const к указанному типу (после звездочки):

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

Как и в случае пробелов, окружающих звездочку, порядок const в этом случае просто вопрос стиля. В этой главе используется префикс const, но оба они в точности эквивалентны. Достоинства каждого стиля до сих пор активно обсуждаются в Интернете.

Указатели и строковые литералы

Как указывалось ранее, строковые литералы — это массивы, содержащие символьные последовательности, заканчивающиеся нулем. В предыдущих разделах строковые литералы использовались для непосредственной вставки в cout, для инициализации строк и для инициализации массивов символов.
Но они также могут быть доступны напрямую. Строковые литералы — это массивы соответствующего типа, которые содержат все его символы плюс завершающий нулевой символ, причем каждый из элементов имеет тип const char (как литералы, они никогда не могут быть изменены). Например:

Это объявляет массив с литеральным представлением для «hello», а затем указатель на его первый элемент присваивается foo. Если мы представим, что «hello» хранится в ячейках памяти, которые начинаются с адреса 1702, мы можем представить предыдущее объявление как:

Обратите внимание, что здесь foo является указателем и содержит значение 1702, а не «h» или «hello», хотя 1702 действительно является адресом обоих.

Указатель foo указывает на последовательность символов. И поскольку указатели и массивы ведут себя по существу одинаково в выражениях, foo может использоваться для доступа к символам таким же образом, как массивы символов с нулем на конце. Например:

Оба выражения имеют значение «o» (пятый элемент массива).

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

C++ позволяет использовать указатели, которые указывают на указатели, которые, в свою очередь, указывают на данные (или даже на другие указатели). Синтаксис просто требует звездочки (*) для каждого уровня косвенности в объявлении указателя:

Предполагая, что ячейки памяти для каждой переменной 7230, 8092 и 10502 выбраны случайно, это может быть представлено как:

При этом значение каждой переменной представлено внутри соответствующей ей ячейки, а их соответствующие адреса в памяти представлены значением под ними.
Новым в этом примере является переменная c, которая является указателем на указатель и может использоваться на трех разных уровнях косвенности, каждый из которых будет соответствовать своему значению:

  • c типа char** и значением 8092
  • *c типа char* и значением 7230
  • **c типа char и значением ’z’
Указатели на void

Указатель типа void — это специальный тип указателя. В C ++ void представляет отсутствие типа. Следовательно, указатели void — это указатели, которые указывают на значение, которое не имеет типа (и, следовательно, также имеет неопределенную длину и неопределенные свойства разыменования).
Это дает указателям void большую гибкость, поскольку позволяет указывать на любой тип данных, от целочисленного значения или с плавающей точкой до строки символов. В обмен на это у них есть большое ограничение: данные, на которые они указывают, не могут быть напрямую разыменованы (что логично, поскольку у нас нет типа для разыменования), и по этой причине, любой адрес в указателе на void должен быть преобразован в некоторый другой тип указателя, который указывает на конкретный тип данных перед разыменованием.
Одним из возможных применений может быть передача обобщенных параметров функции. Например:

sizeof — это оператор, интегрированный в язык C ++, который возвращает размер в байтах своего аргумента. Для нединамических типов данных это значение является константой. Поэтому, например, sizeof (char) равен 1, потому что char всегда имеет размер в один байт.

Недействительные и нулевые указатели

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

Ни p, ни q не указывают на адреса, о которых известно, что они содержат значение, но ни одно из приведенных выше выражений не приводит к ошибке. В C++ указатели могут принимать любое значение адреса, независимо от того, есть ли что-то по этому адресу или нет. Что может вызвать ошибку при разыменовании такого указателя (т.е. фактически при получении доступа к значению, на которое он указывает). Доступ к такому указателю вызывает неопределенное поведение — от ошибки во время выполнения до доступа к некоторому случайному значению.
Но иногда указатель действительно должен явно никуда не указывать, а не просто указывать на неверный адрес. Для таких случаев существует специальное значение, которое может принимать любой тип указателя: нулевое значение указателя. Это значение может быть выражено в C++ двумя способами: либо с целочисленным значением, равным нулю, либо с ключевым словом nullptr:

Здесь и p, и q являются нулевыми указателями, это означает, что они явно никуда не указывают, и они фактически равны между собой: все нулевые указатели равны любым другим нулевыми указателям. Также привычно видеть, что константа NULL используется в старом коде для ссылки на значение нулевого указателя:

NULL объявлен в нескольких заголовочных файлах стандартной библиотеки и определяется как псевдоним некоторого значения константы нулевого указателя (например, 0 или nullptr).
Не путайте нулевые указатели с указателями на void! Нулевой указатель — это значение, которое любой указатель может принять для представления того, что он указывает на «никуда», в то время как указатель на void — это тип указателя, который может указывать куда-то без определенного типа. Первый относится к значению, хранящемуся в указателе, а второй — к типу данных, на которые он указывает.

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

C++ позволяет работать с указателями на функции. Типичное использование — передача функции в качестве аргумента другой функции. Указатели на функции объявляются с тем же синтаксисом, что и обычное объявление функции, за исключением того, что имя функции заключено в круглые скобки (), а перед именем стоит звездочка (*):

В приведенном выше примере minus — указатель на функцию, имеющую два параметра типа int. Он непосредственно инициализируется для указания на функцию subtraction:

Что такое int*

Что это такое int (*func4 (void)(int)
Это примеры указателей на функцию. Понятно.void (*func1) (void *, int *); void (*func2) (int, int.

Что такое int*a ?
С++ что такое int*a . заранее спс

что такое возможности типа Int?
Здравствуйте, хм глупый вопрос скажите пожалуйста все возможности типа int языка с++:pardon:

Что значит такое объявление typedef int (*A) ( )?
typedef int (*A) ( ) Как его понять? Есть варианты ответа А. Объявление типа, что являет.

Сообщение от lepufuxa

Добавлено через 2 минуты
а понял, это переменная типа int глобальная или может член класса-владельца функции

Сообщение от lepufuxa
Сообщение от lepufuxa
Сообщение от ZaMaZaN4iK
Сообщение от ZaMaZaN4iK

Сообщение от UserAK
Сообщение от lepufuxa

Сообщение от ZaMaZaN4iK
Сообщение от Croessmah

вот это правильней ибо сам БиллГейтс не сможет гарантировать что в этой памяти лежит
но если знать эти подводные камни можно написать програмки которые будут работать

Добавлено через 9 минут

Сообщение от lepufuxa

а что так не бывает?
выдели на стеке массив на миллиард интов

Сообщение от lepufuxa

А кто то предлагал отказаться

Сообщение от lepufuxa
Сообщение от ValeryS

Ну так это был сарказм.

Сообщение от ValeryS
Сообщение от ValeryS
Сообщение от ValeryS

Сообщение от ValeryS
Сообщение от ValeryS

изначально так был вопрос сформулирован, что тут поделать?
может класс хранит все указатели и вообще под этим указателем может что угодно в итоге скрываться

Добавлено через 14 минут

Сообщение от Croessmah

Сообщение от UserAK

в функции выделили память
под переменную x допустим она лежит по адресу 100
функция вернула 100 адрес переменной х
m стала равна 100 адрес валидный
а вот что лежит по этому адресу? может 2( если никто не посягнул на эту память) а может и нет, никто не гарантирует
Это как в файловой системе когда удаляешь файл удаляется запись ссылки на этот файл и сектора помечаются как свободные, но сами сектора никто не переписывает и можно восстановить файл, до тех пор пока другая программа решившая что то записать на диск и увидев пустой сектор не записала свои данные

Сообщение от UserAK

в нормальном программировании так делать нельзя а вот в хакерском нередко используют например вернуть несколько значений (но это сильно платформенно зависимо)

Добавлено через 19 минут

Сообщение от UserAK

она не уничтожается, а объявляется свободной

вот маленький примерчик

Сообщение от ValeryS

это адрес объявляется свободным, то есть это уже не int* а void*, а переменной больше не существует. она осталась там внутри функции. а вот указатель на переменную в куче так и останется int*

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

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

что такое int argc, char *argv[] в качестве параметров главной (main) функции?
И зачем

Что такое "volatile int"
есть объявление и инициализация переменной: volatile int Sklad = 0; что такое "volatile"? с msdn.

Что такое файловый буфер? Что такое режим (модификатор) доступа, при работе с файлами?
Что такое файловый буфер? Что такое режим (модификатор) доступа, при работе с файлами?

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *