programing

포인터 선언에 별표 배치

procenter 2022. 8. 10. 22:04
반응형

포인터 선언에 별표 배치

저는 최근에 드디어 C/C++를 배워야겠다고 결심했습니다만, 포인터나 보다 정확하게는 그 정의에 대해 잘 모르는 것이 하나 있습니다.

예를 들어 다음과 같습니다.

  1. int* test;
  2. int *test;
  3. int * test;
  4. int* test,test2;
  5. int *test,test2;
  6. int * test,test2;

제가 알기로는, 처음 세 건 모두 같은 일을 하고 있습니다.테스트는 int가 아니라 int에 대한 포인터입니다.

두 번째 예는 조금 더 까다롭다.케이스 4에서는 test와 test2가 모두 int에 대한 포인터가 되는 반면 케이스 5에서는 test만이 포인터인 반면 test2는 "실제" int입니다.6번 케이스는요?5번 케이스랑 똑같아요?

4, 5, 6은 같은 것으로, 테스트만이 포인터입니다.포인터가 2개 필요한 경우 다음 명령을 사용합니다.

int *test, *test2;

또는 (모든 것을 명확히 하기 위해) 더 좋은 것은:

int* test;
int* test2;

이 퍼즐에는 세 조각이 있다.

첫 번째 부분은 C와 C++의 공백은 일반적으로 구별이 불가능한 인접 토큰을 분리하는 것 이상으로 중요하지 않다는 것입니다.

전처리 단계에서 소스 텍스트는 식별자, 구두점, 숫자 리터럴, 문자열 리터럴 등 일련의 토큰으로 나뉩니다.이 일련의 토큰은 나중에 구문 및 의미에 대해 분석됩니다.토큰라이저는 "욕심"이 있으며 가능한 한 가장 긴 유효한 토큰을 구축합니다.이렇게 쓰면

inttest;

에는 식별자 2개, 즉 2개, 즉 됩니다.inttest '''가 붙습니다'''; 이 안 int이 단계에서 개별 키워드로 사용됩니다(프로세스의 후반부에서 발생합니다).이 , 이 행이 정수의 선언으로 읽혀져야 합니다.test식별자 토큰을 구분하려면 공백 공간을 사용해야 합니다.

int test;

*문자는 식별자의 일부가 아닙니다.문자는 그 자체로 별개의 토큰(유도자)입니다. 이렇게 쓰면

int*test;

토큰을 . 즉, 4개의 토큰이 있습니다.int,*,test , , , , 입니다.;포인터 포인터 선언에서는 하지 않습니다.

int *test;
int* test;
int*test;
int     *     test;

같은 의미로 해석됩니다.


이 퍼즐의 두 번째 조각은 선언이 C와 C++1에서 실제로 어떻게 작동하는가이다.선언은 선언 지정자(스토리지 클래스 지정자, 유형 지정자, 유형 한정자 등)와 쉼표로 구분된 (초기화될 수 있는) 선언자 목록으로 구분됩니다.선언문에

unsigned long int a[10]={0}, *p=NULL, f(void);

선언 지정자는 다음과 같습니다.unsigned long int는 「」입니다.a[10]={0},*p=NULL , , , , 입니다.f(void)a,p , , , , 입니다.f와 함께 , 에 대한 를 제공합니다와 함께 그 배열, 포인터, 기능성에 대한 정보를 제공합니다.선언자는 연관된 이니셜라이저를 가질 수도 있습니다.

a10월 10일~ '10월 10일 '입니다.unsigned long int". 이 유형은 선언 지정자와 선언자의 조합에 의해 완전히 지정되며 초기값은 이니셜라이저를 사용하여 지정됩니다.={0}. 찬 of of of of of of 의 경우, . . . . . . . .p'는 ''와 '아주머니'를 말합니다.unsigned long int 그 되어 「」, 「 「」, 「」, 「」, 「」, 「」, 「」, 「」로 초기화됩니다.NULL type리 of of of of 의 종류f 반환'입니다.unsigned long int같은 추론".같은 추론에 의해.

이것이 핵심입니다. "array-of" 유형 지정자가 없는 처럼 "pointer-to" 유형 지정자가 없습니다. "function-returning" 유형 지정자가 없는 것과 같습니다.어레이를 다음과 같이 선언할 수 없습니다.

int[10] a;

그 피연산자가의 피연산자.[]연산자는오퍼레이터는a아니라,것은 아니다.int. 마찬가지로, declaration마찬가지로 선언문에서도에

int* p;

피연산자의 피연산자*있이p아니라,것은 아니다.int그러나 간접 연산자는 단항 연산자이고 공백은 중요하지 않기 때문에 이렇게 작성해도 컴파일러는 불평하지 않습니다.단, 항상 다음과 같이 해석됩니다.int (*p);.

그러므로 당신이 글을 쓴다면

int* p, q;

피연산자의 피연산자*있이p, 그래서as따라서다음과같이 해석됩니다 여겨질 것이다.

int (*p), q;

그 때문에, 모든 것은

int *test1, test2;
int* test1, test2;
int * test1, test2;

세 사건 모두 가지 경우 모두 동일한 작업을 수행합니다 세 이-똑같이 해 보세요.test1의 오퍼랜드입니다의 피연산자이다.*따라서"포인터 즉에, 타입은"type"이 됩니다 type다.int",", 한편,test2은 ★★★★★★★★★★★★★int.

선언자는 임의로 복잡해질 수 있습니다.포인터 배열이 있을 수 있습니다.

T *a[N];

배열에 대한 포인터를 가질 수 있습니다.

T (*a)[N];

포인터를 반환하는 함수를 가질 수 있습니다.

T *f(void);

함수에 대한 포인터를 가질 수 있습니다.

T (*f)(void);

함수에 대한 포인터 배열을 가질 수 있습니다.

T (*a[N])(void);

다음과 같이 배열에 포인터를 반환하는 함수를 가질 수 있습니다.

T (*f(void))[N];

배열에 를 반환하는 을 가질 수 .T:

T *(*(*f(void))[N])(void); // yes, it's eye-stabby.  Welcome to C and C++.

에 '이렇게'가 요.signal:

void (*signal(int, void (*)(int)))(int);

라고 읽는다

       signal                             -- signal
       signal(                 )          -- is a function taking
       signal(                 )          --   unnamed parameter
       signal(int              )          --   is an int
       signal(int,             )          --   unnamed parameter
       signal(int,      (*)    )          --   is a pointer to
       signal(int,      (*)(  ))          --     a function taking
       signal(int,      (*)(  ))          --       unnamed parameter
       signal(int,      (*)(int))         --       is an int
       signal(int, void (*)(int))         --     returning void
     (*signal(int, void (*)(int)))        -- returning a pointer to
     (*signal(int, void (*)(int)))(   )   --   a function taking
     (*signal(int, void (*)(int)))(   )   --     unnamed parameter
     (*signal(int, void (*)(int)))(int)   --     is an int
void (*signal(int, void (*)(int)))(int);  --   returning void
    

그리고 이것은 가능한 것의 표면을 거의 긁지 않습니다.단, 배열-ness, 포인터-ness 및 함수-ness는 항상 선언자의 일부이며 유형 지정자는 아닙니다.

한 가지 주의해야 할 점은-const타입을 모두 할 수 .변경할 수 있습니다.

const int *p;  
int const *p;

의 두 '이러다'를 선언합니다.pconst int물건.새로운 값을 다음에 쓸 수 있습니다.p하다

const int x = 1;
const int y = 2;

const int *p = &x;
p = &y;

단, 가리키는 오브젝트에는 쓸 수 없습니다.

*p = 3; // constraint violation, the pointed-to object is const

하지만,

int * const p;

pconstint에쓸 수 요;; 에 쓸 수 있습니다.p

int x = 1;
int y = 2;
int * const p = &x;

*p = 3;

수는 없어요.p다음과 같이 합니다.

p = &y; // constraint violation, p is const

그래서 우리는 왜 선언이 이렇게 구성되었는지 퍼즐의 세 번째 조각으로 이끈다.

그 목적은 선언의 구조가 코드의 표현 구조("선언은 사용을 모방한다")와 밀접하게 일치해야 한다는 것입니다.를 들어,를 들어, '먹다'에 대한 포인터가 가정해 보겠습니다int '''ap 싶어요.inti 은 다음과 같이합니다.을 사용하다

printf( "%d", *ap[i] );

표현 *ap[i]은 ★★★★★★★★★★★★★int; 의, 즉, 의 선언, 즉ap라고 씁니다.

int *ap[N]; // ap is an array of pointer to int, fully specified by the combination
            // of the type specifier and declarator

" "*ap[N]*ap[i] * ★★★★★★★★★★★★★★★★★」[]표현에서와 같은 방식으로 행동한다.[]보다 *에, 「」의 가 됩니다*ap[N])로 해석합니다.*(ap[N])를 참조해 주세요.

예로, 예를 들면 '보다 낫다'의 해 보겠습니다.int '''pa는 이 하고 싶습니다.i이렇게 '그러다'라고

printf( "%d", (*pa)[i] );

" " " "(*pa)[i]int따라서 선언문은 다음과 같이 작성됩니다.

int (*pa)[N];

마찬가지로 우선순위와 연관성에 대한 동일한 규칙이 적용됩니다.에는 '아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, i」의 pai'pa 그래서 우리는 명시적으로 그룹화해야 한다.*.pa.

*,[] ★★★★★★★★★★★★★★★★★」()연산자는 모두 코드에서 표현식의 일부이므로 선언에서 모두 선언자의 일부입니다.선언자는 식에서 개체를 사용하는 방법을 알려줍니다.만약 당신이 다음과 같은 선언서를 가지고 있다면int *p; ', '라는 *p당신의 코드에 따라int 더 '돈 벌다'라는입니다. 장하 、 그은은p 값 ""을 ""으로 지정합니다.int ",int *.


그럼 이나 cast cast cast cast 같은 ?sizeof은요, 표현은요, 쓰다, 쓰다, 쓰다, 이런 .(int *) ★★★★★★★★★★★★★★★★★」sizeof (int [10])뭐뭐??? something how how게 어어 어어 어 how 。

void foo( int *, int (*)[10] );

* ★★★★★★★★★★★★★★★★★」[]퍼레이터터 ?형??? ????

음, 아니요 - 빈 식별자(추상 선언자라고 함)가 있는 선언자가 여전히 있습니다.빈 식별자를 기호 ,로 나타내면 다음과 같이 읽을 수 있습니다.(int *λ),sizeof (int λ[10]) , 그리고.

void foo( int *λ, int (*λ)[10] );

리른 int *[10]는 10개의 포인터의 배열을 나타냅니다.int (*)[10]는 배열에 대한 포인터를 나타냅니다.


그리고 이제 이 답변의 고집스러운 부분을 말씀드리겠습니다.간단한 포인터를 다음과 같이 선언하는 C++ 규약은 좋아하지 않습니다.

T* p;

다음과 같은 이유로 잘못된 관행으로 간주합니다.

  1. 구문과 일치하지 않습니다.
  2. 혼동을 일으킵니다(이 질문에 의해 증명되었듯이, 이 질문에 대한 모든 중복, 의미에 대한 질문).T* p, q;, 이러한 질문에 대한 모든 중복사항 등).
  3. 내부적으로 일관성이 없습니다. 포인터의 배열을 다음과 같이 선언합니다.T* a[N](글을 쓰는 습관이 없는 한) 사용의 비대칭이다.* a[i]);
  4. 어레이 포인터 또는 함수 포인터 유형에는 적용할 수 없습니다(단, typedef를 생성하여T* p규칙, 즉...없음)
  5. 이렇게 하는 이유는 "객체의 포인터성을 강조한다"는 거짓입니다.어레이나 기능 타입에는 적용할 수 없고, 그 품질도 마찬가지로 중요하다고 생각합니다.

결국, 그것은 두 언어의 유형 체계가 어떻게 작동하는지에 대한 혼란스러운 생각을 나타낼 뿐이다.

아이템을 개별적으로 신고하는 데는 충분한 이유가 있습니다.악습에 대처하기 위해서입니다).T* p, q;)는 그 중 하나가 아닙니다.선언문을 올바르게 쓰는 경우(T *p, q;)를 사용하면 혼란을 일으킬 가능성이 낮아집니다.

난 그게 고의로 너의 간단한 모든 것을 쓰는 것과 같다고 생각해for로서 루프하다.

i = 0;
for( ; i < N; ) 
{ 
  ... 
  i++; 
}

구문론적으로는 타당하지만 혼란스러우며 의도가 잘못 해석될 수 있습니다.하지만, 그T* p;C++ 커뮤니티에서는 통념이 확립되어 있으며, 코드 베이스 전체의 일관성은 좋은 것이지만, 그것을 할 때마다 신경이 쓰입니다.


  1. C용어를 사용합니다.C++용어는 조금 다르지만 개념은 거의 동일합니다.

별자리 주변의 공백은 중요하지 않습니다.이 세 가지는 모두 같은 것을 의미합니다.

int* test;
int *test;
int * test;

"int *var1, var2"는 단지 사람들을 혼란스럽게 하기 위한 사악한 구문이므로 피해야 합니다.다음과 같이 확장됩니다.

int *var1;
int var2;

많은 코딩 가이드라인에서는 한 줄에 하나의 변수만 선언할 것을 권장합니다.이렇게 하면 이 질문을 하기 전에 겪었던 혼란을 피할 수 있습니다.제가 함께 일했던 대부분의 C++ 프로그래머들은 이 점을 고수하는 것 같습니다.


조금 다른 측면도 있지만, 제가 유용하다고 찾은 것은 선언문을 거꾸로 읽는 것입니다.

int* test;   // test is a pointer to an int

이것은 특히 const pointer를 선언하기 시작하고 그것이 const인지 또는 pointer가 가리키는 것이 const인지를 알기가 어려울 때 매우 잘 작동하기 시작합니다.

int* const test; // test is a const pointer to an int

int const * test; // test is a pointer to a const int ... but many people write this as  
const int * test; // test is a pointer to an int that's const

다른 사람들이 언급했듯이 4, 5, 6은 같다.사람들은 종종 이러한 예를 들어 다음과 같은 주장을 한다.*는 유형이 아닌 변수에 속합니다.이것이 스타일의 문제이지만, 당신이 이것을 이렇게 생각하고 써야 하는지에 대해서는 몇 가지 논란이 있다.

int* x; // "x is a pointer to int"

또는 다음과 같습니다.

int *x; // "*x is an int"

WWIW 저는 첫 번째 캠프에 있지만, 다른 사람들이 두 번째 폼을 주장하는 이유는 (대부분이) 이 특정한 문제를 해결하기 때문입니다.

int* x,y; // "x is a pointer to int, y is an int"

오해의 소지가 있습니다.그 대신에, 어느쪽인가를 기입해 주세요.

int *x,y; // it's a little clearer what is going on here

아니면 정말 두 가지 조언을 원하신다면

int *x, *y; // two pointers

개인적으로는 한 줄에 한 가지 변수로 유지하면 어떤 스타일을 좋아하든 상관없습니다.

C/C++ 선언의 해석을 지원하려면 "시계방향 나선 규칙"을 사용하십시오.

다음의 3개의 간단한 순서가 있습니다.

  1. 알 수 없는 요소부터 시작하여 나선/시계 방향으로 이동합니다. 다음 요소가 발견되면 해당 영문 문장으로 대체합니다.

    [X]또는[]: 어레이 X 사이즈...또는 어레이의 정의되지 않은 크기...

    (type1, type2): 함수가 type1 및 type2를 통과하여 반환...

    *: 포인터...

  2. 모든 토큰이 덮일 때까지 나선/시계 방향으로 계속 이 작업을 수행하십시오.
  3. 항상 괄호 안에 있는 것을 먼저 해결하세요!

또한, 선언은 가능하면 별도의 진술로 해야 한다(대부분의 경우 사실이다).

최초 규칙은 별을 포인터 이름(선언문 오른쪽)에 넣는 것이라고 말할 수 있습니다.

  • 데니스 M의 c프로그래밍 언어로.Ritchie 별들은 선언문 오른쪽에 있다.

  • https://github.com/torvalds/linux/blob/master/init/main.c의 Linux 소스 코드를 보면 오른쪽에도 별이 있음을 알 수 있습니다.

같은 규칙을 따를 수 있지만, 활자 쪽에 별을 붙이면 별 문제가 되지 않습니다.일관성이 중요하기 때문에 어느 쪽을 선택하든 항상 같은 쪽에 있는 별을 기억하십시오.

C의 근거는 변수를 사용하는 방식으로 선언한다는 것입니다.예를들면

char *a[100];

라고 한다*a[42]가 될 것이다char.그리고.a[42]문자 포인터그래서a는 char pointer 배열입니다.

이는 원래 컴파일러 라이터가 식과 선언에 동일한 파서를 사용하기를 원했기 때문입니다.(언어 설계를 선택해야 하는 합리적인 이유는 아님)

제 생각에, 상황에 따라 답은 둘 다입니다.일반적으로 IMO는 타입이 아닌 포인터 이름 옆에 아스타리스크를 붙이는 것이 좋습니다.비교 예:

int *pointer1, *pointer2; // Fully consistent, two pointers
int* pointer1, pointer2;  // Inconsistent -- because only the first one is a pointer, the second one is an int variable
// The second case is unexpected, and thus prone to errors

두 번째 케이스는 왜 모순되는가?왜냐하면 예를 들면. int x,y;는 같은 유형의 2개의 변수를 선언하지만 이 유형은 선언에1회만 기재되어 있습니다.이로 인해 전례와 예상되는 동작이 생성됩니다.그리고.int* pointer1, pointer2;모순되는 것은 그것이 그것을 선언하기 때문이다.pointer1포인터로서, 하지만pointer2는 정수 변수입니다.오류가 발생하기 쉬우므로 (타입이 아닌 포인터 이름 옆에 아스타리스크를 붙임으로써) 피해야 합니다.

, 오브젝트 이름 옆에 아스타리스크를 붙일 수 없는 예외도 있습니다(및 오브젝트 이름을 어디에 붙이는지가 중요함).다음은 예를 제시하겠습니다.

MyClass *volatile MyObjName

void test (const char *const p) // const value pointed to by a const pointer

마지막으로 다음과 같이 유형 이름 옆에 아스타리스크를 붙이는 것이 분명할 수 있습니다.

void* ClassName::getItemPtr () {return &item;} // Clear at first sight

#include <type_traits>

std::add_pointer<int>::type test, test2;

포인터는 유형의 수정자입니다.아스타리스크가 어떻게 유형을 수정하는지 이해하기 위해서는 오른쪽에서 왼쪽으로 읽는 것이 가장 좋습니다.'int *'는 'int to int'로 읽을 수 있습니다.여러 선언에서는 각 변수가 포인터이거나 표준 변수로 작성되도록 지정해야 합니다.

1, 2, 3) 테스트가 유형(int *)입니다.공백은 중요하지 않습니다.

4, 5, 6) 테스트는 유형(int *)입니다.test2는 int 타입입니다.다시 말하지만 공백은 중요하지 않다.

좋은 경험칙으로 많은 사람들이 이러한 개념을 이해하는 것 같다: C++에서는 많은 의미적 의미가 키워드나 식별자의 왼쪽 바인딩에 의해 도출된다.

예를 들어 다음과 같습니다.

int const bla;

const는 "int" 단어에 적용됩니다.포인터의 아스타리스크도 마찬가지로 포인터의 왼쪽 키워드에 적용됩니다.실제 변수 이름은요?네, 그건 남은 걸로 알 수 있어요.

4, 5, 6에test항상 포인터이고test2포인터가 아닙니다.C++에서는 공백이 (거의) 중요하지 않습니다.

나는 항상 다음과 같은 포인터를 선언하는 것을 선호해 왔다.

int* i;

이 글을 읽으면서...i타입 int-interface" 입니다.선언마다 변수를 하나만 선언하면 이 해석을 피할 수 있습니다.

그러나 이 글이 틀렸다는 것은 불편한 사실이다.C Programming Language, 2nd Ed. (94)는 C 표준에서 사용되는 반대 패러다임을 설명합니다.

포인터의 선언ip,

int *ip;

니모닉으로 의도된 표현입니다.*ip는 입니다.int변수의 선언 구문은 변수가 표시될 수 있는 식 구문을 모방합니다.이 추론은 함수 선언에도 적용됩니다.예를들면,

double *dp, atof(char *);

라는 표현으로 말한다*dp그리고.atof(s)활자의 가치가 있다double, 그리고 그 주장은atof에 대한 포인터입니다.char.

그래서, C언어의 추론에 따르면, 당신이 선언할 때,

int* test, test2;

두 가지 유형의 변수를 선언하지 않았습니다.int*에 대해 평가하다2개의 식을 도입하고 있습니다.int의 할당에 첨부되지 않은 형식int기억 속에.

컴파일러는 다음 사항을 기꺼이 수락합니다.

int *ip, i;
i = *ip;

왜냐하면 C 패러다임에서, 컴파일러는 오직 타입의 추적만을 기대할 수 있기 때문이다.*ip그리고.i프로그래머는 의 의미를 추적해야 합니다.*ip그리고.i이 경우,ip초기화되지 않았기 때문에 참조하기 전에 의미 있는 것을 지적하는 것은 프로그래머의 책임입니다.

언급URL : https://stackoverflow.com/questions/180401/placement-of-the-asterisk-in-pointer-declarations

반응형