Введение
Недавно ко мне подошли с вопросом, «что это и как этим пользоваться?», показав следующий код:
extern "C" {
void byteMaskDowngrade(byte***const byteMask, const byte *const *const && source) {
// какой-то код.
}
// некоторые фукции.
}
Этим человеком был мой коллега по работе и мы не сразу поняли, что в точности означает параметры в объявлении функции (для тех кому интересно, где вообще может понадобиться такое объявление: в криптографии).
И в упреждение возможных проблем у коллег по цеху, я решил создать данную статью, полагая её в качестве шпаргалки, а именно ответов на два вопроса:
- Как правильно писать такие вот объявления?
- И как их правильно читать?
"*"
Случай с одной звёздочкой самый распространённый, однако и здесь возможны недопонимания:
- Как правильно:
- Так:
const byte p;
- Или так:
byte const p;
- Так:
- Или в случае с записью '*':
- Так:
byte *const p;
- Или так:
const byte *p;
- Так:
Строго говоря, с точки зрения компилятора, наличие звёздочки и её положения в выражении ставит вопрос ребром, т.к. итоговый смысл записи будет отличаться. Наличие спецификатора const в выше указанном случае не имеет никакой разницы при учёте его позиции относительно типа данных, однако ситуация кардинально меняется, когда появляются указатели: один, два, три… ну вы поняли:
void p() {
// указатель на байт.
byte * a = new byte{1};
a++; // допустимо.
a[0]++; // допустимо.
// указатель на константный байт.
const byte * b = new byte{1};
b++; // допустимо.
//b[0]++; // недопустимо.
// константный указатель на байт.
byte *const c = new byte{1};
//c++; // недопустимо.
c[0]++; // допустимо.
// константный указатель на константный байт.
const byte *const d = new byte{1};
//d++; // недопустимо.
//d[0]++; // недопустимо.
}
Всё это выглядит интереснее, когда появляется второй указатель (здесь уже начинает прослеживаться эффективное для чтения правило записи):
void pp() {
// указатель на указатель на байт.
byte ** a = new byte * { new byte{1} };
a++; // допустимо.
a[0]++; // допустимо.
a[0][0]++; // допустимо.
// указатель на указатель на константный байт.
const byte ** b = new const byte * { new byte{1}};
b++; // допустимо.
b[0]++; // допустимо.
//b[0][0]++; // недопустимо.
// указатель на константный указатель на байт.
byte *const * c = new byte * { new byte{1}};
c++; // допустимо.
//c[0]++; // недопустимо.
c[0][0]++; // допустимо.
// константный указатель на указатель на байт.
byte * *const d = new byte * { new byte{1}};
//d++; // недопустимо.
d[0]++; // допустимо.
d[0][0]++; // допустимо.
// указатель на константный указатель на константный байт.
const byte *const * e = new const byte *const { new byte{1}};
e++; // допустимо.
//e[0]++; // недопустимо.
//e[0][0]++; // недопустимо.
// константный указатель на указатель на константный байт.
const byte * *const f = new const byte * { new byte{1}};
//f++; // недопустимо.
f[0]++; // допустимо.
//f[0][0]++; // недопустимо.
// константный указатель на константный указатель на байт.
byte *const *const g = new byte *const { new byte{1}};
//g++; // недопустимо.
//g[0]++; // недопустимо.
g[0][0]++; // допустимо.
// константный указатель на константный указатель на константный байт.
const byte *const *const h = new const byte *const { new byte{1}};
//h++; // недопустимо.
//h[0]++; // недопустимо.
//h[0][0]++; // недопустимо.
}
Как очевидно двойной указатель может быть представлен аж 8-мью различными способами, каждый из которых определяет особый способ использования целевых данных.
Правила чтения подобных выражений следующие:
- ищем в выражении знак '=' и читаем выражение справа налево;
- пропускаем имя переменной;
- далее встречам либо '*', что означает обычный указатель, либо '*const' — константный указатель;
- таким образом читаем до тех пор пока не встретится тип данных (byte);
- и последним словом слева от типа данных может быть const, наличие которого означает, что вся эта конструкция ссылается на данные которые нельзя изменять; если же const нет — то можно.
Подобная форма записи и чтения позволяет с лёгкостью читать и понимать даже самые изысканные выражения)
Вот Вам для примера полный набор выражений с тройным указателем:
"***"
void ppp() {
// указатель на указатель на указатель на байт.
byte *** a = new byte * * { new byte * {new byte{1}} };
a++; // допустимо.
a[0]++; // допустимо.
a[0][0]++; // допустимо.
a[0][0][0]++; // допустимо.
// указатель на указатель на указатель на константный байт.
const byte *** b = new const byte * * { new const byte * {new byte{1}} };
b++; // допустимо.
b[0]++; // допустимо.
b[0][0]++; // допустимо.
//b[0][0][0]++; // недопустимо.
// указатель на указатель на константный указатель на байт.
byte*const * * c = new byte *const * { new byte *const {new byte{1}} };
c++; // допустимо.
c[0]++; // допустимо.
//c[0][0]++; // недопустимо.
c[0][0][0]++; // допустимо.
// указатель на константный указатель на указатель на байт.
byte * *const * d = new byte * *const { new byte * {new byte{1}} };
d++; // допустимо.
//d[0]++; // недопустимо.
d[0][0]++; // допустимо.
d[0][0][0]++; // допустимо.
// константный указатель на указатель на указатель на байт.
byte *** const e = new byte * * { new byte * {new byte{1}} };
//e++; // недопустимо.
e[0]++; // допустимо.
e[0][0]++; // допустимо.
e[0][0][0]++; // допустимо.
// указатель на указатель на константный указатель на константный байт.
const byte *const * * f = new const byte *const * { new const byte *const {new byte{1}} };
f++; // допустимо.
f[0]++; // допустимо.
//f[0][0]++; // недопустимо.
//f[0][0][0]++; // недопустимо.
// указатель на константный указатель на указатель на константный байт.
const byte * *const * g = new const byte * *const{ new const byte * {new byte{1}} };
g++; // допустимо.
//g[0]++; // недопустимо.
g[0][0]++; // допустимо.
//g[0][0][0]++; // недопустимо.
// константный указатель на указатель на указатель на константный байт.
const byte * * *const h = new const byte * *{ new const byte * {new byte{1}}};
//h++; // недопустимо.
h[0]++; // допустимо.
h[0][0]++; // допустимо.
//h[0][0][0]++; // недопустимо.
// константный указатель на указатель на константный указатель на байт.
byte *const * *const i = new byte *const * { new byte *const {new byte{1}}};
//i++; // недопустимо.
i[0]++; // допустимо.
//i[0][0]++; // недопустимо.
i[0][0][0]++; // допустимо.
// константный указатель на константный указатель на указатель на байт.
byte * *const *const j = new byte * *const { new byte * {new byte{1}}};
//j++; // недопустимо.
//j[0]++; // недопустимо.
j[0][0]++; // допустимо.
j[0][0][0]++; // допустимо.
// указатель на константный указатель на константный указатель на байт.
byte *const *const * k = new byte *const *const {new byte *const{new byte{1}}};
k++; // допустимо.
//k[0]++; // недопустимо.
//k[0][0]++; // недопустимо.
k[0][0][0]++; // допустимо.
// здесь ещё надо случаи с когда в выражении присутствуют три const
// константный указатель на константный указатель на константный указатель на константный байт.
const byte *const *const *const m = new const byte *const *const {new const byte *const {new byte{1}}};
//m++; // недопустимо.
//m[0]++; // недопустимо.
//m[0][0]++; // недопустимо.
//m[0][0][0]++; // недопустимо.
}
Автор: AlexeiBulgakov