나는 이것이 정말로 기본적인 질문이라는 것을 알고 있지만, 고급 언어로 몇 가지 프로젝트를 코딩 한 후 기본적인 C ++ 프로그래밍으로 시작했습니다.
기본적으로 세 가지 질문이 있습니다.
- 왜 일반 변수에 포인터를 사용합니까?
- 언제 어디서 포인터를 사용해야합니까?
- 배열에 포인터를 어떻게 사용합니까?
나는 이것이 정말로 기본적인 질문이라는 것을 알고 있지만, 고급 언어로 몇 가지 프로젝트를 코딩 한 후 기본적인 C ++ 프로그래밍으로 시작했습니다.
기본적으로 세 가지 질문이 있습니다.
답변:
짧은 대답은 :하지 마십시오. ;-) 포인터는 다른 것을 사용할 수없는 곳에서 사용됩니다. 적절한 기능 부족, 데이터 유형 누락 또는 순수한 성능 때문입니다. 더 아래에 ...
여기에 짧은 대답은 : 다른 것을 사용할 수없는 곳입니다. C에서는 문자열과 같은 복잡한 데이터 유형을 지원하지 않습니다. "참조로"변수를 함수에 전달하는 방법도 없습니다. 그것은 포인터를 사용해야하는 곳입니다. 또한 거의 모든 것, 링크 된리스트, 구조체 멤버 등을 가리 키도록 할 수 있습니다. 그러나 여기에 들어 가지 마십시오.
적은 노력과 많은 혼란으로. ;-) int와 char 같은 간단한 데이터 타입에 대해 이야기하면 배열과 포인터 사이에는 거의 차이가 없습니다. 이러한 선언은 매우 유사하지만 동일하지는 않습니다. 예를 들어 sizeof
다른 값을 반환합니다.
char* a = "Hello";
char a[] = "Hello";
이런 식으로 배열의 모든 요소에 도달 할 수 있습니다
printf("Second char is: %c", a[1]);
배열이 요소 0으로 시작하기 때문에 인덱스 1
아니면 똑같이 할 수 있습니다
printf("Second char is: %c", *(a+1));
printf에게 문자를 인쇄하고 싶다고 알려주기 때문에 포인터 연산자 (*)가 필요합니다. *가 없으면 메모리 주소 자체의 문자 표현이 인쇄됩니다. 이제 캐릭터 자체를 대신 사용하고 있습니다. % c 대신 % s를 사용했다면 printf에게 'a'에 1을 더한 메모리 주소의 내용을 인쇄하도록 요청했을 것입니다 (위의 예에서). *를 입력하지 않아도됩니다. 앞에서 :
printf("Second char is: %s", (a+1)); /* WRONG */
그러나 이것은 두 번째 문자를 인쇄 한 것이 아니라 null 문자 (\ 0)를 찾을 때까지 다음 메모리 주소의 모든 문자를 인쇄 한 것입니다. 그리고 이것은 상황이 위험 해지기 시작하는 곳입니다. 실수로 % s 포맷터가있는 char 포인터 대신 정수 유형의 변수를 인쇄하려고하면 어떻게됩니까?
char* a = "Hello";
int b = 120;
printf("Second char is: %s", b);
메모리 주소 120에서 찾은 것을 인쇄하고 널 문자가 발견 될 때까지 인쇄를 계속합니다. 이 printf 문을 수행하는 것은 잘못되고 불법이지만, 포인터는 실제로 많은 환경에서 int 유형이므로 어쨌든 작동합니다. 대신 sprintf ()를 사용하고이 방법으로 너무 긴 "char array"를 다른 변수에 할당하면 특정 제한된 공간 만 할당되면 발생할 수있는 문제를 상상해보십시오. 아마도 메모리에 다른 것을 덮어 쓰게되어 프로그램이 중단 될 수 있습니다 (행운이 있다면).
아, 그리고 선언 할 때 char 배열 / 포인터에 문자열 값을 할당하지 않으면 값을 제공하기 전에 충분한 양의 메모리를 할당해야합니다. malloc, calloc 또는 이와 유사한 것을 사용합니다. 이것은 배열에서 하나의 요소 / 하나의 단일 메모리 주소 만 가리 키도록 선언했기 때문입니다. 여기 몇 가지 예가 있습니다 :
char* x;
/* Allocate 6 bytes of memory for me and point x to the first of them. */
x = (char*) malloc(6);
x[0] = 'H';
x[1] = 'e';
x[2] = 'l';
x[3] = 'l';
x[4] = 'o';
x[5] = '\0';
printf("String \"%s\" at address: %d\n", x, x);
/* Delete the allocation (reservation) of the memory. */
/* The char pointer x is still pointing to this address in memory though! */
free(x);
/* Same as malloc but here the allocated space is filled with null characters!*/
x = (char *) calloc(6, sizeof(x));
x[0] = 'H';
x[1] = 'e';
x[2] = 'l';
x[3] = 'l';
x[4] = 'o';
x[5] = '\0';
printf("String \"%s\" at address: %d\n", x, x);
/* And delete the allocation again... */
free(x);
/* We can set the size at declaration time as well */
char xx[6];
xx[0] = 'H';
xx[1] = 'e';
xx[2] = 'l';
xx[3] = 'l';
xx[4] = 'o';
xx[5] = '\0';
printf("String \"%s\" at address: %d\n", xx, xx);
할당 된 메모리의 free ()를 수행 한 후에도 변수 x를 계속 사용할 수 있지만 그 안에 무엇이 있는지 모릅니다. 또한 두 번째 printf ()는 다른 주소를 제공 할 수 있습니다. 두 번째 메모리 할당이 첫 번째와 동일한 공간에서 수행된다는 보장이 없기 때문입니다.
a
는 포인터이고 두 번째 경우 a
는 배열입니다. 내가 이미 언급 했습니까? 이건 같은게 아니야! 스스로 확인하십시오 : sizeof (a)를 비교하고 새 주소를 배열에 할당하십시오. 작동하지 않습니다.
char* a = "Hello";
와 char a[] = "Hello";
동일하지 않습니다, 그들은 매우 다르다. 하나는 포인터를 다른 하나는 배열로 선언합니다. 를 시도 sizeof
하면 차이가 보입니다.
포인터를 사용하는 한 가지 이유는 호출 된 함수에서 변수 또는 객체를 수정할 수 있기 때문입니다.
C ++에서는 포인터보다 참조를 사용하는 것이 좋습니다. 참조는 본질적으로 포인터이지만 C ++은 어느 정도 사실을 숨기고 마치 값으로 전달하는 것처럼 보입니다. 이를 통해 호출 의미를 전달하는 의미를 수정하지 않고도 호출 함수가 값을받는 방식을 쉽게 변경할 수 있습니다.
다음 예를 고려하십시오.
참조 사용 :
public void doSomething()
{
int i = 10;
doSomethingElse(i); // passes i by references since doSomethingElse() receives it
// by reference, but the syntax makes it appear as if i is passed
// by value
}
public void doSomethingElse(int& i) // receives i as a reference
{
cout << i << endl;
}
포인터 사용하기 :
public void doSomething()
{
int i = 10;
doSomethingElse(&i);
}
public void doSomethingElse(int* i)
{
cout << *i << endl;
}
다음은 C의 예입니다.
char hello[] = "hello";
char *p = hello;
while (*p)
{
*p += 1; // increase the character by one
p += 1; // move to the next spot
}
printf(hello);
인쇄물
ifmmp
각 문자의 값을 가져 와서 하나씩 증가시키기 때문입니다.
because it takes the value for each character and increments it by one
. ASCII 표현입니까?
포인터는 다른 변수에 대한 간접 참조를 얻는 한 가지 방법입니다. 변수 의 값 을 유지하는 대신 주소 를 알려줍니다 . 이것은 배열을 다룰 때 특히 유용합니다. 배열의 첫 번째 요소 (주소)에 대한 포인터를 사용하면 포인터를 다음 주소 위치로 증가시켜 다음 요소를 빠르게 찾을 수 있습니다.
내가 읽은 포인터 및 포인터 산술에 대한 가장 좋은 설명은 K & R의 The C Programming Language에 있습니다. C ++ 학습을 시작하기에 좋은 책은 C ++ Primer 입니다.
이것도 시도하고 대답하겠습니다.
포인터는 참조와 유사합니다. 다시 말해, 그것들은 사본이 아니라 원래 값을 참조하는 방법입니다.
무엇보다도, 일반적으로 포인터 를 많이 사용해야 하는 곳은 임베디드 하드웨어를 다룰 때 입니다. 디지털 IO 핀의 상태를 전환해야 할 수도 있습니다. 인터럽트를 처리하고 특정 위치에 값을 저장해야 할 수도 있습니다. 당신은 그림을 얻는다. 그러나 하드웨어를 직접 다루지 않고 사용할 유형이 궁금하다면 계속 읽으십시오.
왜 일반 변수가 아닌 포인터를 사용합니까? 클래스, 구조 및 배열과 같은 복잡한 유형을 다룰 때 대답이 명확 해집니다. 일반적인 변수를 사용한다면, 복사를하게 될 수도 있습니다 (컴파일러는 일부 상황에서이를 방지 할만큼 영리하고 C ++ 11도 도움이되지만 지금은 그 논의에서 멀어 질 것입니다).
원래 값을 수정하려면 어떻게됩니까? 다음과 같은 것을 사용할 수 있습니다.
MyType a; //let's ignore what MyType actually is right now.
a = modify(a);
그것은 잘 작동하며 포인터를 사용하는 이유를 정확히 모른다면 사용해서는 안됩니다. "아마도 빠를 것"이유에주의하십시오. 자신의 테스트를 실행하고 실제로 더 빠른 경우 사용하십시오.
그러나 메모리를 할당해야하는 문제를 해결한다고 가정 해 봅시다. 메모리를 할당 할 때는 할당을 해제해야합니다. 메모리 할당은 성공할 수도 있고 성공하지 못할 수도 있습니다. 이것은 포인터 가 유용한 곳입니다-그것들 은 당신 이 할당 한 객체의 존재를 테스트 하고 포인터를 역 참조함으로써 메모리가 할당 된 객체에 접근 할 수있게합니다.
MyType *p = NULL; //empty pointer
if(p)
{
//we never reach here, because the pointer points to nothing
}
//now, let's allocate some memory
p = new MyType[50000];
if(p) //if the memory was allocated, this test will pass
{
//we can do something with our allocated array
for(size_t i=0; i!=50000; i++)
{
MyType &v = *(p+i); //get a reference to the ith object
//do something with it
//...
}
delete[] p; //we're done. de-allocate the memory
}
이것이 포인터 참조를 사용하는 이유의 열쇠 입니다. 참조하는 요소가 이미 존재한다고 가정합니다 . 포인터는 그렇지 않습니다.
포인터를 사용하는 또 다른 이유는 (또는 적어도 포인터를 다루어야하는 이유는) 포인터가 참조 이전에 존재했던 데이터 유형이기 때문입니다. 따라서 라이브러리를 사용하여 자신이 더 잘 알고있는 일을하면 결국이 라이브러리의 수명이 길기 때문에 많은 라이브러리가 포인터를 사용합니다. 그들 중 C ++ 전에 작성되었습니다).
라이브러리를 사용하지 않은 경우 포인터에서 벗어날 수있는 방식으로 코드를 디자인 할 수 있지만 포인터가 언어의 기본 유형 중 하나 인 경우 포인터를 사용하는 것이 더 빠를수록 빠를수록 좋습니다. C ++ 기술을 이식 할 수 있습니다.
유지 관리 성 관점에서 포인터를 사용할 때 유효성을 테스트하고 유효하지 않은 경우를 처리해야하거나 유효하지 않다고 가정하고 사실을 받아 들여야한다고 언급해야합니다. 가정이 깨지면 프로그램이 충돌하거나 더 나빠질 것입니다. 달리 말하면, 포인터로 선택하는 것은 코드가 복잡하거나 문제가 발생할 때 더 많은 유지 보수 노력을 기울이는 것입니다. 포인터가 메모리 손상과 같이 포인터가 유발하는 전체 클래스에 속하는 버그를 추적하려고합니다.
따라서 모든 코드를 제어하는 경우 포인터를 피하고 대신 참조를 사용하여 가능한 한 항상 그대로 유지하십시오. 이렇게하면 객체의 수명에 대해 생각하게되고 코드를 더 쉽게 이해할 수있게됩니다.
이 차이점을 기억하십시오. 참조는 본질적으로 유효한 포인터입니다. 포인터가 항상 유효한 것은 아닙니다.
그래서 잘못된 참조를 만드는 것이 불가능하다는 말입니까? C ++로 거의 모든 작업을 수행 할 수 있기 때문에 가능합니다. 우연히하는 것이 더 어려우며 의도하지 않은 버그 수에 놀랄 것입니다. :)
다음은 약간 다르지만 C의 여러 기능이 이해되는 이유에 대한 통찰력있는 정보입니다. http://steve.yegge.googlepages.com/tour-de-babel#C
기본적으로 표준 CPU 아키텍처는 Von Neumann 아키텍처이며 메모리에서 데이터 항목의 위치를 참조하고 이러한 머신에서이를 계산할 수있는 것이 매우 유용합니다. 어셈블리 언어의 변형을 아는 경우 이것이 낮은 수준에 얼마나 중요한지 빠르게 알 수 있습니다.
C ++은 때때로 포인터를 관리하고 "참조"의 형태로 그 효과를 숨기므로 포인터를 약간 혼란스럽게 만듭니다. C를 직접 사용하는 경우 포인터가 필요합니다. 참조로 호출 할 수있는 다른 방법은 없으며 문자열을 저장하는 가장 좋은 방법이며 배열을 반복하는 가장 좋은 방법입니다.
포인터를 사용하는 한 가지 방법 (다른 사람들의 게시물에서 이미 다룬 내용은 언급하지 않음)은 할당하지 않은 메모리에 액세스하는 것입니다. PC 프로그래밍에는 그다지 유용하지 않지만 내장 프로그래밍에서 메모리 매핑 하드웨어 장치에 액세스하는 데 사용됩니다.
DOS 시절에는 다음에 대한 포인터를 선언하여 비디오 카드의 비디오 메모리에 직접 액세스 할 수있었습니다.
unsigned char *pVideoMemory = (unsigned char *)0xA0000000;
많은 임베디드 장치가 여전히이 기술을 사용합니다.
gsl::span
, 그리고 곧 될 것입니다 std::span
.
대부분의 경우 포인터는 배열 (C / C ++)입니다. 메모리의 주소이며 원하는 경우 배열처럼 액세스 할 수 있습니다 ( "일반"경우).
항목의 주소이므로 크기가 작습니다. 주소의 공간 만 차지합니다. 크기가 작기 때문에 함수로 보내는 것이 저렴합니다. 그런 다음 해당 기능이 사본이 아닌 실제 항목에서 작동하도록합니다.
동적 스토리지 할당 (예 : 링크 된 목록)을 수행하려면 포인터를 사용하는 것이 유일한 방법이기 때문에 포인터를 사용해야합니다.
std::array
.
포인터는 하나의 "노드"를 다른 노드에 효율적으로 연결하거나 연결하는 기능이 필요한 많은 데이터 구조에서 중요합니다. float과 같은 일반적인 데이터 유형을 말하는 것보다 포인터를 "선택"하지 않고 단순히 다른 목적을 갖습니다.
포인터는 고성능 및 / 또는 컴팩트 메모리 공간이 필요한 경우에 유용합니다.
배열에서 첫 번째 요소의 주소를 포인터에 할당 할 수 있습니다. 그러면 기본 할당 된 바이트에 직접 액세스 할 수 있습니다. 배열의 요점은이 작업을 수행하지 않아도되는 것입니다.
당신이 하위 유형 사용하려는 경우 C ++에서 다형성을 , 당신은 이 포인터를 사용 할 수 있습니다. 이 게시물을보십시오 : 포인터가없는 C ++ 다형성 .
실제로, 당신이 그것에 대해 생각할 때, 이것은 의미가 있습니다. 하위 유형 다형성을 사용하면 궁극적으로 실제 클래스가 무엇인지 모르기 때문에 어떤 클래스 또는 하위 클래스의 메소드 구현이 호출되는지 미리 알 수 없습니다.
알 수없는 클래스의 객체를 보유하는 변수를 갖는이 아이디어는 스택에 객체를 저장하는 C ++의 기본 (포인터가 아닌) 모드와 호환되지 않습니다. 여기서 할당 된 공간의 양은 클래스에 직접 해당합니다. 참고 : 클래스에 3 개의 인스턴스 필드와 3 개의 인스턴스 필드가있는 경우 더 많은 공간을 할당해야합니다.
goto
지시 사항은 또한 대상 기계의 지시 사항에서 뒤에서 사용됩니다. 우리는 여전히 그것들을 사용한다고 주장하지 않습니다.
장소 전체에 큰 물체를 복사하면 시간과 메모리가 낭비되기 때문입니다.
여기 내 대답이 있는데, 전문가가 될 것을 약속하지는 않지만, 내가 작성하려고하는 라이브러리 중 하나에서 포인터가 훌륭하다는 것을 알았습니다. 이 라이브러리 (OpenGL :-의 그래픽 API)에서 정점 객체가 전달 된 삼각형을 만들 수 있습니다. draw 메소드는이 삼각형 객체를 취합니다. 내가 만든 정점 객체를 기준으로 그립니다. 글쎄요.
그러나 정점 좌표를 변경하면 어떻게됩니까? 정점 클래스에서 moveX ()를 사용하여 이동합니까? 자, 이제 삼각형을 업데이트해야합니다. 버텍스가 움직일 때마다 삼각형을 업데이트해야하기 때문에 더 많은 메소드를 추가하고 성능이 낭비되고 있습니다. 여전히 큰 문제는 아니지만 그렇게 크지는 않습니다.
이제, 수많은 정점과 수많은 삼각형의 메쉬가 있고 메쉬가 회전하고 움직이고 있다면 어떻게해야합니까? 어떤 정점이 어떤 정점을 사용하는지 모르기 때문에 이러한 정점을 사용하는 모든 삼각형과 장면의 모든 삼각형을 업데이트해야합니다. 그것은 컴퓨터를 엄청나게 많이 사용합니다. 풍경 위에 메쉬가 여러 개 있다면 오 세상에! 이 정점이 항상 변경되기 때문에 거의 모든 프레임을 거의 모든 프레임으로 업데이트하기 때문에 문제가 있습니다!
포인터를 사용하면 삼각형을 업데이트하지 않아도됩니다.
삼각형 클래스 당 3 개의 * Vertex 객체가있는 경우, 거대한 삼각형에는 3 개의 꼭짓점 객체가 없기 때문에 공간을 절약 할 수있을뿐만 아니라이 포인터는 항상 의도 한 꼭짓점을 가리 킵니다. 정점이 얼마나 자주 변경되는지 포인터가 여전히 동일한 정점을 가리 키기 때문에 삼각형은 변경되지 않으며 업데이트 프로세스가보다 쉽게 처리됩니다. 내가 당신을 혼란스럽게한다면 의심의 여지가 없습니다. 전문가 인 척하지 않고 단지 2 센트를 토론에 던져 넣습니다.
C 언어로 된 포인터의 필요성은 여기 에 설명되어 있습니다.
기본 아이디어는 데이터의 메모리 위치를 조작하여 언어의 많은 제한 (예 : 배열, 문자열 사용 및 함수의 여러 변수 수정)을 제거 할 수 있다는 것입니다. 이러한 한계를 극복하기 위해 포인터가 C로 도입되었습니다.
또한 포인터를 사용하면 큰 필드 유형 (예 : 많은 필드가있는 구조)을 함수에 전달하는 경우 코드를 더 빠르게 실행하고 메모리를 절약 할 수 있습니다. 전달하기 전에 이러한 데이터 유형을 복사하면 시간이 걸리고 메모리가 소비됩니다. 이것이 프로그래머가 빅 데이터 유형에 대한 포인터를 선호하는 또 다른 이유입니다.
PS : 샘플 코드에 대한 자세한 설명은 제공된 링크를 참조하십시오 .
두 번째 질문과 관련하여 일반적으로 프로그래밍하는 동안 포인터를 사용할 필요는 없지만 이에 대한 한 가지 예외가 있으며 공개 API를 만들 때입니다.
사람들이 포인터를 대체하기 위해 일반적으로 사용하는 C ++ 구문의 문제점은 사용하는 툴셋에 따라 크게 달라 지지만, 소스 코드에 대해 필요한 모든 제어권을 가지면 정적 라이브러리를 Visual Studio 2008로 컴파일하는 경우 Visual Studio 2010에서 사용하려고하면 새 프로젝트가 이전 버전과 호환되지 않는 최신 버전의 STL과 연결되어 있기 때문에 많은 링커 오류가 발생합니다. DLL을 컴파일하고 사람들이 다른 툴셋에서 사용하는 가져 오기 라이브러리를 제공하면 상황이 더 나빠질 것입니다.
따라서 한 라이브러리에서 다른 라이브러리로 큰 데이터 세트를 이동하기 위해 다른 사용자가 사용하는 동일한 도구를 사용하지 못하게하려면 데이터를 복사하는 함수에 대한 배열에 대한 포인터를 고려할 수 있습니다. . 이것에 대한 좋은 부분은 C 스타일 배열 일 필요조차 없다는 것입니다. std :: vector를 사용하고 예를 들어 첫 번째 요소 & vector [0]의 주소를 제공하여 포인터를 제공하고 사용할 수 있습니다 std :: vector를 사용하여 배열을 내부적으로 관리합니다.
C ++에서 포인터를 다시 사용하는 또 다른 좋은 이유는 라이브러리와 관련이 있습니다. 프로그램이 실행될 때로 드 할 수없는 dll이있는 것을 고려하십시오. 따라서 가져 오기 라이브러리를 사용하면 종속성이 충족되지 않고 프로그램이 충돌합니다. 예를 들어 응용 프로그램과 함께 dll에 공개 API를 제공하고 다른 응용 프로그램에서 액세스하려는 경우입니다. 이 경우 API를 사용하려면 해당 위치 (일반적으로 레지스트리 키에 있음)에서 dll을로드 한 다음 DLL 내에서 함수를 호출하려면 함수 포인터를 사용해야합니다. 때로는 API를 만드는 사람들 이이 프로세스를 자동화하고 필요한 모든 함수 포인터를 제공하는 도우미 함수가 포함 된 .h 파일을 제공하기에 충분할 때가 있습니다.
포인터에는 많은 이유가 있습니다. 언어 간 호환성을 유지하려면 DLL에서 C 이름 맹 글링이 특히 중요합니다.