#c #struct #constants #typedef
#c #структура #константы #typedef
Вопрос:
У меня есть структура на C, которая представляет диапазон целых чисел:
typedef struct {
int* data;
unsigned start;
unsigned end;
} Range;
У меня есть одна функция, которая записывает значения в диапазон
bool range_put(Range* const r, int value, unsigned idx)
{
if(r->start <= idx amp;amp; idx < r->end)
{
r->data[idx] = value;
return true;
}
else
{
return false;
}
}
и одна функция, которая считывает данные из диапазона
bool range_get(const Range* const r, int* const out, unsigned idx)
{
if(r->start <= idx amp;amp; idx < r->end)
{
*out = r->data[idx];
return true;
}
else
{
return false;
}
}
Что я хотел бы сделать, так это расширить эти функции, чтобы я мог иметь диапазон до целых чисел const. Что-то вроде
typedef struct {
const int* data;
unsigned start;
unsigned end;
} ConstRange;
Как я могу заставить range_get()
работать с обоими Range
и ConstRange
?
Одним из решений было бы просто дублировать функциональность range_get()
в новую функцию bool crange_get(const ConstRange*...)
, но я хотел бы сохранить количество функций на низком уровне.
Другим решением было бы объединение Range
(сомнительно, разрешено это или нет)
typedef union {
ConstRange const_range;
struct {
int* data;
unsigned start;
unsigned end;
}
} Range;
Недостатком здесь является то, что синтаксис для вызова range_get()
становится немного сложнее при передаче Range
Range range;
...
range_get(amp;range.const_range ...);
Каковы другие альтернативы, снижающие количество функций и сложность использования функций?
Range range;
ConstRange const_range;
...
range_get(amp;range ...);
range_get(amp;const_range ...);
Комментарии:
1. Вы знаете, что если у вас есть a
const int *
, который указывает на некоторую память, вы не можете изменить или даже инициализировать память с помощью своего указателя. Если вы используете, напримерmalloc
, для выделения памяти, то вы застряли с неинициализированной памятью на все время ее существования. Решением может быть отслеживание того, какие элементы в диапазоне, которые были записаны один раз , и не позволяют записывать их снова. Это может быть достигнуто с помощью массива флагов, по одному флагу для каждого элемента в диапазоне. Что касается постоянной / неконстантной части, простого логического флага должно быть достаточно.2. @JoachimPileborg «… тогда вы не сможете изменить …» — Вы не должны .
const
это гарантия, предоставляемая программистом, а не принудительная компилятором. Это просто позволяет компилятору предупреждать пользователя, но не мешает ему стрелять в ногу.3. Вы могли бы использовать a
union
с двумя значениями. Используйтеconst int *
для всех функций, кроме функций модификации.
Ответ №1:
С большим количеством функций проблем нет. Единственной (потенциальной) проблемой является дублирование кода. Итак, напишите не одну, а еще две функции:
Напишите новую функцию bool range_get2(const int* data, unsigned start, unsigned end, const r, int* const out, unsigned idx)
Затем сделайте свою существующую bool range_get(const Range* const r, int* const out, unsigned idx)
работу, делегировав range_get2()
. Обратите внимание, что это range_get2()
будет принимать либо a const int* data
, либо an int* data
; это не возражает.
Затем представьте свой новый bool crange_get(const ConstRange*...)
и заставьте его также работать, делегировав range_get2()
.
Таким образом, у вас может быть больше функций, но, по крайней мере, у вас не будет дублирования кода.
Ответ №2:
Как я могу заставить range_get() работать как с диапазоном, так и с ConstRange?
TL; DR: вы не можете, по крайней мере, напрямую.
const
-квалифицированные типы не являются «совместимыми» с неквалифицированными const
типами. Таким образом int
, и const int
не совместимы. Однако для некоторых целей имеет значение, что последняя является квалифицированной версией первой.
Два типа указателей, относящихся к несовместимым типам, несовместимы друг с другом, поэтому int *
и const int *
несовместимы. В этом случае ни одна const
из версий, отвечающих требованиям a, не является версией другой.
Типы структур с разными тегами или члены которых с соответствующими именами не имеют совместимых типов, также несовместимы друг с другом, поэтому ваши Range
и ConstRange
несовместимы.
Поэтому вы не можете написать соответствующую программу, которая обращается к объекту типа ConstRange
, как если бы он был типа Range
, или наоборот. Это запрещено C2011, 6.5 / 7 («строгое правило сглаживания»). Действительно, даже не требуется, чтобы представления этих двух типов структур были расположены соответственно (хотя на практике они почти наверняка будут).
Вы также не можете получить доступ к объекту типа int *
, как если бы он был типа const int *
, или наоборот, хотя вы можете преобразовать значение одного из этих типов указателей в другой тип. Поведение такого преобразования четко определено в каждом случае, если новый тип не требует большего выравнивания, чем старый, что здесь было бы очень удивительно. И такое преобразование может быть полезным, потому что вам разрешено обращаться к an int
как const int
к — , например, путем разыменования a, const int *
полученного путем преобразования из an int *
.
Таким образом, вы можете написать соответствующие вспомогательные функции, которые служат общим фоном для чтения (но не изменения) data
членов объектов ConstRange
и Range
, как предложил @MikeNakis , но вы не можете написать соответствующую функцию, которая принимает параметр одного из этих типов и каким-либо образом учитывает соответствующий аргумент, являющийсядругой тип.
Самое близкое, что вы могли бы сделать, это использовать объединение, но не совсем так, как вы представляете. Использование объединения по-прежнему не позволяет Range
получить доступ к a, как если бы это было a ConstRange
, и никакие ухищрения вокруг сноски 95 не обходят тот факт, что эти два не обязательно должны быть выложены соответственно, и что их data
члены не имеют соответствующего типа. На практике такие махинации, скорее всего, приводят к ожидаемому результату, но программа, использующая их, тем не менее, не соответствует требованиям и полагается на неопределенное поведение.
Однако существует множество подходов, основанных на объединении, которые действительно работают, поскольку основаны на предоставлении способа установить, а затем вспомнить, какой член объединения является активным. Рассмотрим:
typedef struct {
union {
int *data;
const int *cdata;
};
unsigned start;
unsigned end;
_Bool is_const;
} Range;
_Bool range_get(const Range* const r, int * const out, unsigned idx) {
if (r->start <= idx amp;amp; idx < r->end) {
const int *data = r->is_const ? r->cdata : r->data;
*out = data[idx];
return true;
} else {
return false;
}
}
Если вы настаиваете на том, чтобы иметь совершенно отдельный тип для диапазонов постоянных целых чисел, вы все равно можете сделать что-то подобное. Ключ в том, что вам нужно средство, чтобы указать функции, к какому члену объединения обращаться.
Комментарии:
1. Обратите внимание, кстати, что я скопировал ваш стиль, сделав параметры
r
andout
range_get
равными beconst
, но это не служит никакой полезной цели (в отличие от того, чтобыr
также быть указателем наconst
объект, который служит определенной цели).