#c #pointers
Вопрос:
У меня возникли некоторые проблемы с пониманием этого фрагмента кода.
double F(double k,void *params){ double *p=(double *)params; return p[0]; }
У меня есть лишь небольшое представление об указателях из класса, и я понимаю, что в функции он использует указатель *в качестве аргумента. Я также понимаю, что он использует double *p для объявления переменной. Чего я не могу понять, так это того, что делают (двойные *)параметры или как p превратился в вектор.
Комментарии:
1. Это бросок от
void*
доdouble*
. Которые не нужны и могут быть удалены (ну, некоторые любят добавлять их, чтобы продемонстрировать намерение).2. Нигде не видно ни вектора (ни массива, как вы, вероятно, имеете в виду).
Ответ №1:
Выражение (double *)params
означает «рассматривать params
как указатель на double
вместо указателя на void
«. (double *)
Это актерский состав.
В большинстве случаев вы не можете скопировать значение указателя одного типа в переменную указателя другого типа (вы не можете присвоить double *
значение int *
переменной) без явного приведения.
Единственным исключением в C является присвоение void *
значения другому типу указателя или наоборот — в данном конкретном случае приведение не требуется. Однако C не допускает неявного преобразования из void *
в другие типы указателей, поэтому в этом языке приведение необходимо.
Указатели на разные типы не обязательно должны иметь одинаковый размер и представление; единственными правилами являются
char *
иvoid *
имеют одинаковый размер и выравнивание;- Указатели на квалифицированные типы имеют тот же размер и выравнивание, что и указатели на неквалифицированный эквивалент (например,
const int *
иvolatile int *
имеют тот же размер и выравнивание,int *
что и ); - Указатели на все
struct
типы имеют одинаковый размер и выравнивание; - Указатели на все
union
типы имеют одинаковый размер и выравнивание;
Комментарии:
1. «Относиться
params
как» немного вводит в заблуждение относительно семантики, как их определяет спецификация языка: преобразование значенияparams
в другой тип. Различие более выражено, если вы рассматриваете приведения среди арифметических типов, где, скажем, рассматривать afloat
как anint
даже не имеет смысла.
Ответ №2:
(double *)params
инструктирует компилятор думать о params
(который изначально является указателем void — компилятор понятия не имеет, на какой тип он указывает) как о указателе на double только при обработке этого выражения. Это очень широко известно как «приведение в стиле Си от одного типа к другому». В этом случае мы «переводим» params
тип из void*
типа в double*
тип, так как мы сохраняем его в указателе на double double *p
.
Без приведения компилятор, скорее всего, выдаст вам type mismatch
ошибку/предупреждение, так как вы попытаетесь сохранить значение указателя void в указатель на double.
Более конкретно, когда у вас есть двойной указатель или указатель на что-либо, кроме void, компилятор связывает с ним число, чтобы иметь возможность выполнять арифметику указателя на нем, когда вы пишете что-то подобное (ptr )
. Например, при ( ptr)
указателе на удвоение компилятор знает, на сколько байтов нужно увеличить, потому что он знает размер удвоения. Однако пустота* не имеет связанного с ней размера, поэтому компилятор не может выполнять арифметику указателей на нее. Таким образом , при приведении указателя void к двойному указателю с помощью (double*)params
вы позволяете компилятору связывать стандартный размер переменных типа double
с тем, на что указывает этот указатель void, только во время обработки этого выражения, с целью сохранения его значения внутри фактического указателя на double. После того, как компилятор закончит чтение этого выражения, он вернется к мысли params
, что это указатель на пустоту, а не двойной указатель, т. Е. Эффект приведения его к двойному указателю не является постоянным, он действителен только во время обработки этого выражения компилятором.
Комментарии:
1. В C это просто «слепок» или «типаж». Только в контексте C имеет смысл добавлять «C-стиль». Кроме того, ваше описание с точки зрения понимания ценности в некоторых других терминах вводит в заблуждение. Приведенное выражение преобразует значение операнда в другой тип. В этом случае он преобразует значение типа
void *
в типdouble *
.2. «преобразовать» — это вводящее в заблуждение слово, которое следует использовать здесь, потому что можно подумать, что оно постоянно становится указателем на double. Я не использую «преобразовать» в своем объяснении по очень веской причине — указатель void становится указателем на удвоение только во время обработки этого выражения компилятором.
3. Вы бы не сказали, что написание
(2 * k)
заставляет компилятор думатьk
по-другому. Аналогично, приведение кdouble *
не меняет того, как думает компиляторparams
… приведенное значение-это его собственная, отдельная вещь.4. «Преобразовать» — это именно то слово, которое здесь следует использовать, поскольку именно так спецификация языка описывает эффект приведения. А «преобразование» — это определенная процедура в спецификации языка, поэтому выбор этого слова не случаен и не может быть изменен без потери смысла. Но обратите внимание, что я специально сказал «преобразует значение операнда», а не сам операнд.
5. Первый ответ на вопрос о stackoverflow, и я уже получаю старых динозавров на хвосте без видимой причины 🙂