Відповіді:
Вони САМО еквівалентні. Однак в
int *myVariable, myVariable2;
Здається очевидним, що myVariable має тип int * , тоді як myVariable2 має тип int . В
int* myVariable, myVariable2;
може здатися очевидним, що обидва мають тип int * , але це невірно, як myVariable2
і тип int .
Тому перший стиль програмування більш інтуїтивний.
int* someVar
особистих проектів. Це має більше сенсу.
int[10] x
. Це просто не синтаксис С. Граматика явно аналізує як:, int (*x)
а не як (int *) x
, тому розміщення зірочки зліва просто вводить в оману і ґрунтується на нерозумінні синтаксису декларації C.
int* myVariable; int myVariable2;
замість цього.
Якщо ви подивитесь на це по-іншому, *myVariable
має тип int
, який має певний сенс.
myVariable
може бути NULL, у цьому випадку *myVariable
викликає помилку сегментації, але немає типу NULL.
int x = 5; int *pointer = &x;
тому що він пропонує нам встановити int *pointer
на якесь значення, а не на pointer
себе.
Оскільки * у цьому рядку більше прив'язується до змінної, ніж до типу:
int* varA, varB; // This is misleading
Як @Lundin вказує нижче, const додає ще більше тонкощів для роздумів. Ви можете повністю пройти цей шлях, оголосивши одну змінну на рядок, що ніколи не буде неоднозначним:
int* varA;
int varB;
Баланс між чітким та лаконічним кодом важко домогтися - десяток зайвих ліній теж int a;
не гарний. Тим не менш, я за замовчуванням до однієї декларації на рядок і переживаю за комбінування коду пізніше.
int *const a, b;
. Звідки * "пов'язується"? Тип a
є int* const
, тож як можна сказати, що * належить до змінної, коли вона є частиною самого типу?
Щось тут ніхто досі не згадував, це те, що ця зірочка насправді є " оператором скидання " у C.
*a = 10;
Рядок вище не означає, що я хочу призначити 10
його a
, це означає, що я хочу призначити 10
будь-яке місце в пам'яті a
. І я ніколи не бачив, щоб хто писав
* a = 10;
чи ти? Тож оператор розвідки, як правило, завжди пишеться без пробілів. Ймовірно, це відрізняє його від множення, розбитого на кілька рядків:
x = a * b * c * d
* e * f * g;
Тут *e
було б введення в оману, чи не так?
Гаразд, що ж насправді означає наступний рядок:
int *a;
Більшість людей сказали б:
Це означає, що a
це вказівник на int
значення.
Це технічно правильно, більшість людей люблять бачити / читати саме так, і саме так визначали б сучасні стандарти C (зауважте, що мова C сама передує всім стандартам ANSI та ISO). Але це не єдиний спосіб на це подивитися. Ви також можете прочитати цей рядок наступним чином:
Значення дереференції a
має тип int
.
Таким чином, зірочка в цій декларації також може розглядатися як оператор скидання, що також пояснює її розміщення. І що a
вказівник насправді взагалі не декларується, це мається на увазі в тому, що єдине, що ви можете насправді знешкодити - це вказівник.
Стандарт C визначає лише два значення для *
оператора:
А опосередкованість - це лише одне значення, немає додаткового значення для оголошення вказівника, є просто непряме, це те, що робить операція перенаправлення, вона здійснює непрямий доступ, тому також в операторі, як int *a;
це непрямий доступ ( *
означає непрямий доступ), і тому друге вище твердження набагато ближче до стандартного, ніж перше.
int a, *b, (*c)();
як щось на зразок "оголошувати такі об'єкти як int
: об'єкт a
, об'єкт , на який вказує b
, і об'єкт, повернутий із функції, на яку вказує c
".
*
В int *a;
не є оператор, і вона не розіменування a
(який навіть ще не визначений)
*
(це дає лише значення в сумарному вираженні, а не в *
межах вираження!). У ньому сказано, що "int a;" оголошує вказівник, який він робить, ніколи не стверджував інакше, але без даного значення *
, читаючи його як * дереференційоване значення a
int все ще є абсолютно дійсним, оскільки це має те саме фактичне значення. Ніщо, насправді нічого, написане в 6.7.6.1, не суперечило б цьому твердженню.
int *a;
- це декларація, а не вираз. a
не відмежовується int *a;
. a
ще не існує в точці *
, коли обробляється. Як ви вважаєте int *a = NULL;
, це помилка, оскільки вона відміняє нульовий покажчик?
Я збираюся вийти на кінцівки тут і сказати , що є пряма відповідь на це питання , як для оголошення змінних і параметрів і типів, які в тому , що зірочка повинна йти поруч з назвою: int *myVariable;
. Щоб зрозуміти, чому, подивіться, як ви декларуєте інші типи символів на C:
int my_function(int arg);
для функції;
float my_array[3]
для масиву.
Загальна закономірність, яку називають декларацією з наступного використання , полягає в тому, що тип символу розбивається на частину перед назвою, а частини навколо імені, і ці частини навколо імені імітують синтаксис, який ви б використали для отримання значення типу зліва:
int a_return_value = my_function(729);
float an_element = my_array[2];
і: int copy_of_value = *myVariable;
.
C ++ кидає ключ до творів із посиланнями, тому що синтаксис у точці, де ви використовуєте посилання, ідентичний типу значень, тому ви можете стверджувати, що C ++ застосовує інший підхід до C. З іншого боку, C ++ зберігає те саме поведінка C у разі покажчиків, тому посилання справді є дивним у цьому відношенні.
Це лише питання переваги.
Коли ви читаєте код, розрізнити змінні та покажчики у другому випадку простіше, але це може призвести до плутанини, коли ви кладете і змінні, і покажчики загального типу в один рядок (що саме по собі часто відлякує керівні принципи проекту, тому що знижується читабельність).
Я вважаю за краще оголошувати покажчики з відповідним знаком поруч із назвою типу, наприклад
int* pMyPointer;
Чудовий гуру якось сказав: "Прочитайте, як слід, компілятор".
http://www.drdobbs.com/conversationsa-midsummer-nights-madness/184403835
Зрозуміло, що це стосувалося розміщення const, але тут діє те саме правило.
Компілятор читає це як:
int (*a);
не як:
(int*) a;
Якщо ви звикли розміщувати зірку поруч зі змінною, це полегшить читання ваших декларацій. Це також уникає очей, таких як:
int* a[10];
- Редагувати -
Точно пояснити, що я маю на увазі, коли я кажу, що це розбирається як int (*a)
, це означає, що *
зв'язується більш щільно, a
ніж це робиться int
, значною мірою таким чином, який у виразі 4 + 3 * 7
3
пов'язується більш щільно, 7
ніж це відбувається 4
через більш високий пріоритет *
.
З вибаченнями за мистецтво ascii, конспект AST для розбору int *a
виглядає приблизно так:
Declaration
/ \
/ \
Declaration- Init-
Secifiers Declarator-
| List
| |
| ...
"int" |
Declarator
/ \
/ ...
Pointer \
| Identifier
| |
"*" |
"a"
Як добре видно, *
зв’язується більш міцно, a
оскільки їх спільним предком є Declarator
, тоді як вам потрібно пройти всю дорогу вгору по дереву, Declaration
щоб знайти спільного предка, до якого належить int
.
(int*) a
.
int
в цьому випадку. Крок 2. Прочитайте декларацію, включаючи прикраси будь-якого типу. *a
в цьому випадку. Крок 3 Прочитайте наступний символ. Якщо ставиться кома, споживайте її та поверніться до кроку 2. Якщо крапка з комою зупиниться. Якщо щось інше, киньте синтаксичну помилку. ...
int* a, b;
та отримати пару покажчиків. Точка, яку я вказую, полягає в тому, що *
прив'язується до змінної і аналізується з нею, а не з типом, щоб сформувати "базовий тип" декларації. Це також є причиною того, що typedefs були введені, щоб дозволити typedef int *iptr;
iptr a, b;
створити пару покажчиків. За допомогою typedef ви можете прив’язати *
до int
.
Declaration-Specifier
з "декораціями" у, Declarator
щоб дійти до остаточного типу для кожної змінної. Однак це не «MOVE» декорації до специфікатор декларації в іншому випадку int a[10], b;
буде виробляти абсолютно безглузді результати, він розбирає , int *a, b[10];
як int
*a
,
b[10]
;
. Іншого способу описати це не має сенсу.
int*a
зрештою читається компілятором як: " a
має тип int*
". Це я мав на увазі під своїм оригінальним коментарем.
Тому що це має більше сенсу, коли у вас є декларації типу:
int *a, *b;
int* a, b;
робив b
покажчик на int
. Але вони цього не зробили. І з вагомою причиною. Що стосується запропонованої вами системи, що таке тип b
у наступній декларації int* a[10], b;
:?
Для оголошення кількох покажчиків в одному рядку я вважаю за краще, int* a, * b;
який більш інтуїтивно оголошує "а" як вказівник на ціле число, а не змішує стилі, коли також оголошує "b". Як хтось сказав, я б ні в якому разі не оголошував два різні типи в одній заяві.
Люди, які віддають перевагу int* x;
, намагаються примусити свій код у вигаданому світі, де тип знаходиться зліва, а ідентифікатор (ім’я) - справа.
Я кажу "вигаданий", тому що:
У C і C ++ в загальному випадку оголошений ідентифікатор оточений інформацією про тип.
Це може здатися божевільним, але ви знаєте, що це правда. Ось кілька прикладів:
int main(int argc, char *argv[])
означає " main
це функція, яка приймає int
і масив покажчиків на char
та повертає" int
. Іншими словами, більша частина інформації про тип знаходиться праворуч. Деякі люди вважають, що декларації функцій не враховуються, оскільки вони якимось чином "особливі". Гаразд, спробуємо змінну.
void (*fn)(int)
означає fn
- вказівник на функцію, яка не приймає int
і нічого не повертає.
int a[10]
оголошує 'a' як масив 10 int
s.
pixel bitmap[height][width]
.
Ясна річ, я набрав вишневих прикладів, які мають багато інформації про тип праворуч, щоб зробити свою думку. Існує безліч декларацій, де більшість - якщо не всі - типу ліворуч, як struct { int x; int y; } center
.
Цей синтаксис декларацій виріс із бажання K&R мати декларації, що відображають використання. Читання простих декларацій є інтуїтивно зрозумілим, а читання складніших можна опанувати, вивчаючи правило справа-ліво-право (іноді називайте правило спіралі або просто правило право-ліво).
C досить простий, що багато програмістів на C сприймають цей стиль і пишуть прості декларації як int *p
.
У C ++ синтаксис став трохи складнішим (з класами, посиланнями, шаблонами, класами enum), і, як реакція на цю складність, ви побачите більше зусиль для відокремлення типу від ідентифікатора у багатьох деклараціях. Іншими словами, ви можете побачити більше int* p
стилів-декларацій, якщо переглянете велику частину коду С ++.
На будь-якій мові ви завжди можете мати тип ліворуч змінних декларацій шляхом (1) ніколи не оголошувати кілька змінних в одному і тому самому операторі та (2) використовувати typedef
s (або декларації псевдоніму, які, як не дивно, ставлять псевдонім ідентифікатори зліва від типів). Наприклад:
typedef int array_of_10_ints[10];
array_of_10_ints a;
(*fn)
зберігається вказівник, пов'язаний із fn
типом повернення, а не з ним.
Я вже відповів на подібне запитання в CP, і, оскільки ніхто про це не згадував, також тут я маю зазначити, що C - це мова вільного формату , який би стиль ви не обрали, тоді як аналізатор може розрізняти кожен маркер. Ця особливість C призводить до дуже особливого типу змагань під назвою C obfuscation Competition .
Багато аргументів у цій темі є простими суб'єктивними, і аргумент про те, що "зірка прив'язується до імені змінної" є наївним. Ось кілька аргументів, які не є лише думками:
Кваліфікатори типу забутого вказівника
Формально "зірка" не належить ні до типу, ні до назви змінної, вона є частиною власного граматичного елемента з назвою покажчик . Формальний синтаксис С (ISO 9899: 2018) такий:
(6.7) декларація:
специфікатори декларації init-декларатор-список опт;
Якщо специфікатори декларації містять тип (і сховище), а список init-декларатор містить вказівник та ім'я змінної. Що ми бачимо, якщо далі розібрати синтаксис цього списку деклараторів:
(6.7.6) декларатор:
вказівник opt direct-декларатор
...
(6.7.6) покажчик:
*
type-kvalifier-list opt
*
type-kvalifier-list opt pointer
Якщо декларатором є вся декларація, прямим декларатором є ідентифікатор (ім'я змінної), а вказівник - зірка, за яким подається необов'язковий список класифікаторів типу, що належить самому покажчику.
Те, що робить різні аргументи стилю про те, що "зірка належить до змінної", суперечить тому, що вони забули про ці класифікатори типу вказівника. int* const x
, int *const x
або int*const x
?
Поміркуйте int *const a, b;
, що таке типи a
та b
? Не так очевидно, що "зірка вже належить до змінної". Швидше, можна було б замислитися над тим, куди const
належить.
Ви, безумовно, можете зробити звуковий аргумент, що зірка належить до класифікатора типу вказівника, але не набагато вище цього.
Список класифікаторів типу для вказівника може спричинити проблеми для тих, хто використовує int *a
стиль. Ті, хто використовує покажчики всередині typedef
(що ми не повинні, дуже погана практика!) І думають, що "зірка належить до імені змінної", схильні писати цю найтоншу помилку:
/*** bad code, don't do this ***/
typedef int *bad_idea_t;
...
void func (const bad_idea_t *foo);
Це складено чисто. Тепер ви можете подумати, що код зроблений правильно. Не так! Цей код є випадково підробленою правильністю const.
Тип foo
фактично є int*const*
- зовнішній самий покажчик робився лише для читання, а не вказував на дані. Отже, всередині цієї функції ми можемо виконувати, **foo = n;
і вона змінить значення змінної у виклику.
Це відбувається тому , що в вираженні const bad_idea_t *foo
, то *
не належить до імені змінної тут! У псевдокоді цей параметр декларації слід читати як, const (bad_idea_t *) foo
а не як (const bad_idea_t) *foo
. У цьому випадку зірка належить до типу прихованого вказівника - тип - це вказівник, а покажчик, який має кваліфікацію за констатом, записується як *const
.
Але тоді корінь проблеми у наведеному вище прикладі - практика приховування покажчиків за стилем a, typedef
а не *
стилем.
Щодо оголошення декількох змінних в одному рядку
Декларування декількох змінних в одному рядку широко визнано поганою практикою 1) . CERT-C підсумовує це як:
DCL04-C. Не оголошуйте більше однієї змінної на декларацію
Тільки читання на англійській мові, то здоровий глузд погоджується з тим , що декларація повинна бути одна декларація.
І не має значення, чи є змінні вказівниками чи ні. Оголошення кожної змінної в одному рядку робить код яснішим майже в кожному випадку.
Тож аргумент про те, що програміст заплутався int* a, b
, поганий. Корінь проблеми полягає у використанні декількох деклараторів, а не розміщенні *
. Незалежно від стилю, вам слід написати це замість:
int* a; // or int *a
int b;
Ще один надійний, але суб'єктивний аргумент - це те, що з урахуванням int* a
типу a
це без сумнівівint*
тому зірка належить до класифікатора типу.
Але в основному мій висновок полягає в тому, що багато аргументів, викладених тут, є просто суб'єктивними та наївними. Насправді ви не можете зробити справедливий аргумент для будь-якого стилю - це справді питання особистих особистих уподобань.
1) CERT-C DCL04-C .
typedef int *bad_idea_t;
void func(const bad_idea_t bar);
Як вчить великий пророк Дан Сакс "Якщо ви завжди ставите const
якомога далі праворуч, не змінюючи смислового значення", це повністю припиняється бути проблемою. Це також робить ваші const
декларації більш послідовними для читання. "Все праворуч від слова const - це те, що є const, все ліворуч - це його тип." Це стосується всіх складових у декларації. Спробуйте зint const * * const x;
Розглянемо
int *x = new int();
Це не означає
int *x;
*x = new int();
Це фактично перекладається на
int *x;
x = new int();
Це робить int *x
позначення дещо непослідовними.
new
є оператором C ++. Його питання позначено "C", а не "C ++".
typedefs
, але це додасть зайвої складності, IMHO.