원시 포인터(C++)
포인터는 변수의 형식입니다. 개체의 주소를 메모리에 저장하고 해당 개체에 액세스하는 데 사용됩니다. 원시 포인터는 스마트 포인터와 같은 캡슐화 개체에 의해 수명이 제어되지 않는 포인터입니다. 원시 포인터는 다른 비 포인터 변수의 주소를 할당하거나 값을 nullptr
할당할 수 있습니다. 값이 할당되지 않은 포인터에는 임의 데이터가 포함됩니다.
포인터를 역참조하여 포인터가 가리키는 개체의 값을 검색할 수도 있습니다. 멤버 액세스 연산자는 개체의 멤버에 대한 액세스를 제공합니다.
int* p = nullptr; // declare pointer and initialize it
// so that it doesn't store a random address
int i = 5;
p = &i; // assign pointer to address of object
int j = *p; // dereference p to retrieve the value at its address
포인터는 형식화된 개체 또는 을 가리킬 수 있습니다 void
. 프로그램이 메모리의 힙에 개체를 할당하면 포인터 형태로 해당 개체의 주소를 받습니다. 이러한 포인터를 소유 포인터라고 합니다. 소유 포인터(또는 해당 복사본)는 더 이상 필요하지 않은 경우 힙 할당 개체를 명시적으로 해제하는 데 사용해야 합니다. 메모리를 해제하지 못하면 메모리 누수가 발생하며 컴퓨터의 다른 프로그램에서 해당 메모리 위치를 사용할 수 없게 됩니다. 사용 new
해서 할당된 메모리는 사용(또는delete[]
)을 사용하여 delete
해제해야 합니다. 자세한 내용은 참조 및 연산자를 참조 new
하세요.delete
MyClass* mc = new MyClass(); // allocate object on the heap
mc->print(); // access class member
delete mc; // delete object (please don't forget!)
포인터(선언 const
되지 않은 경우)는 메모리의 다른 위치를 가리키도록 증가하거나 감소할 수 있습니다. 이 연산을 포인터 산술 연산이라고합니다. 배열 또는 다른 데이터 구조의 요소를 반복하기 위해 C 스타일 프로그래밍에 사용됩니다. 포인터는 const
다른 메모리 위치를 가리키도록 만들 수 없으며, 그런 의미에서 참조와 유사합니다. 자세한 내용은 참조 const
및 volatile
포인터를 참조하세요.
// declare a C-style string. Compiler adds terminating '\0'.
const char* str = "Hello world";
const int c = 1;
const int* pconst = &c; // declare a non-const pointer to const int
const int c2 = 2;
pconst = &c2; // OK pconst itself isn't const
const int* const pconst2 = &c;
// pconst2 = &c2; // Error! pconst2 is const.
64비트 운영 체제에서 포인터의 크기는 64비트입니다. 시스템의 포인터 크기는 주소 지정 가능한 메모리의 양을 결정합니다. 포인터의 모든 복사본은 동일한 메모리 위치를 가리킵니다. 참조와 함께 포인터는 C++에서 광범위하게 사용되어 함수에 더 큰 개체를 전달합니다. 전체 개체를 복사하는 것보다 개체의 주소를 복사하는 것이 더 효율적인 경우가 많습니다. 함수를 정의할 때는 함수가 개체를 const
수정하지 않는 한 포인터 매개 변수를 지정합니다. 일반적으로 const
참조는 개체 값이 가능하지 않으면 함수에 개체 nullptr
를 전달하는 기본 방법입니다.
함수에 대한 포인터를 사용하면 함수를 다른 함수에 전달할 수 있습니다. C 스타일 프로그래밍의 "콜백"에 사용됩니다. 최신 C++는 이 용도로 람다 식을 사용합니다.
초기화 및 멤버 액세스
다음 예제에서는 원시 포인터를 선언, 초기화 및 사용하는 방법을 보여줍니다. 명시적으로 해야 하는 힙에 할당된 개체를 가리키도록 사용하여 new
초기화됩니다 delete
. 이 예제에서는 원시 포인터와 관련된 몇 가지 위험도 보여 줍니다. (이 예제는 최신 C++가 아닌 C 스타일 프로그래밍입니다.)
#include <iostream>
#include <string>
class MyClass
{
public:
int num;
std::string name;
void print() { std::cout << name << ":" << num << std::endl; }
};
// Accepts a MyClass pointer
void func_A(MyClass* mc)
{
// Modify the object that mc points to.
// All copies of the pointer will point to
// the same modified object.
mc->num = 3;
}
// Accepts a MyClass object
void func_B(MyClass mc)
{
// mc here is a regular object, not a pointer.
// Use the "." operator to access members.
// This statement modifies only the local copy of mc.
mc.num = 21;
std::cout << "Local copy of mc:";
mc.print(); // "Erika, 21"
}
int main()
{
// Use the * operator to declare a pointer type
// Use new to allocate and initialize memory
MyClass* pmc = new MyClass{ 108, "Nick" };
// Prints the memory address. Usually not what you want.
std:: cout << pmc << std::endl;
// Copy the pointed-to object by dereferencing the pointer
// to access the contents of the memory location.
// mc is a separate object, allocated here on the stack
MyClass mc = *pmc;
// Declare a pointer that points to mc using the addressof operator
MyClass* pcopy = &mc;
// Use the -> operator to access the object's public members
pmc->print(); // "Nick, 108"
// Copy the pointer. Now pmc and pmc2 point to same object!
MyClass* pmc2 = pmc;
// Use copied pointer to modify the original object
pmc2->name = "Erika";
pmc->print(); // "Erika, 108"
pmc2->print(); // "Erika, 108"
// Pass the pointer to a function.
func_A(pmc);
pmc->print(); // "Erika, 3"
pmc2->print(); // "Erika, 3"
// Dereference the pointer and pass a copy
// of the pointed-to object to a function
func_B(*pmc);
pmc->print(); // "Erika, 3" (original not modified by function)
delete(pmc); // don't forget to give memory back to operating system!
// delete(pmc2); //crash! memory location was already deleted
}
포인터 산술 및 배열
포인터와 배열은 밀접하게 관련되어 있습니다. 배열이 함수에 값으로 전달되면 첫 번째 요소에 대한 포인터로 전달됩니다. 다음 예제에서는 포인터 및 배열의 다음과 같은 중요한 속성을 보여 줍니다.
- 연산자는
sizeof
배열의 총 크기(바이트)를 반환합니다. - 요소 수를 확인하려면 총 바이트를 한 요소의 크기로 나눕니다.
- 배열이 함수 에 전달되면 포인터 형식으로 감쇠됩니다 .
- 연산자가
sizeof
포인터에 적용되면 포인터 크기(예: x86의 경우 4바이트 또는 x64의 8바이트)를 반환합니다.
#include <iostream>
void func(int arr[], int length)
{
// returns pointer size. not useful here.
size_t test = sizeof(arr);
for(int i = 0; i < length; ++i)
{
std::cout << arr[i] << " ";
}
}
int main()
{
int i[5]{ 1,2,3,4,5 };
// sizeof(i) = total bytes
int j = sizeof(i) / sizeof(i[0]);
func(i,j);
}
비 포인터에서const
특정 산술 연산을 사용하여 다른 메모리 위치를 가리킬 수 있습니다. 포인터는 , +=
-=
및 연산자를 사용하여 ++
증가 및 --
감소됩니다. 이 기술은 배열에서 사용할 수 있으며 형식화되지 않은 데이터의 버퍼에서 특히 유용합니다. A void*
는 (1 바이트)의 char
크기로 증가합니다. 형식화된 포인터는 포인터가 가리키는 형식의 크기로 증가합니다.
다음 예제에서는 포인터 산술 연산을 사용하여 Windows의 비트맵에서 개별 픽셀에 액세스하는 방법을 보여 줍니다. 및 역참조 연산자의 new
delete
사용 및 역참조 연산자를 확인합니다.
#include <Windows.h>
#include <fstream>
using namespace std;
int main()
{
BITMAPINFOHEADER header;
header.biHeight = 100; // Multiple of 4 for simplicity.
header.biWidth = 100;
header.biBitCount = 24;
header.biPlanes = 1;
header.biCompression = BI_RGB;
header.biSize = sizeof(BITMAPINFOHEADER);
constexpr int bufferSize = 30000;
unsigned char* buffer = new unsigned char[bufferSize];
BITMAPFILEHEADER bf;
bf.bfType = 0x4D42;
bf.bfSize = header.biSize + 14 + bufferSize;
bf.bfReserved1 = 0;
bf.bfReserved2 = 0;
bf.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER); //54
// Create a gray square with a 2-pixel wide outline.
unsigned char* begin = &buffer[0];
unsigned char* end = &buffer[0] + bufferSize;
unsigned char* p = begin;
constexpr int pixelWidth = 3;
constexpr int borderWidth = 2;
while (p < end)
{
// Is top or bottom edge?
if ((p < begin + header.biWidth * pixelWidth * borderWidth)
|| (p > end - header.biWidth * pixelWidth * borderWidth)
// Is left or right edge?
|| (p - begin) % (header.biWidth * pixelWidth) < (borderWidth * pixelWidth)
|| (p - begin) % (header.biWidth * pixelWidth) > ((header.biWidth - borderWidth) * pixelWidth))
{
*p = 0x0; // Black
}
else
{
*p = 0xC3; // Gray
}
p++; // Increment one byte sizeof(unsigned char).
}
ofstream wf(R"(box.bmp)", ios::out | ios::binary);
wf.write(reinterpret_cast<char*>(&bf), sizeof(bf));
wf.write(reinterpret_cast<char*>(&header), sizeof(header));
wf.write(reinterpret_cast<char*>(begin), bufferSize);
delete[] buffer; // Return memory to the OS.
wf.close();
}
void*
포인터
단순히 원시 메모리 위치를 가리키는 포인터 void
입니다. C++ 코드와 C 함수 간에 전달하는 경우와 같이 포인터를 사용해야 void*
하는 경우도 있습니다.
형식화된 포인터가 포인터로 void
캐스팅되면 메모리 위치의 내용이 변경되지 않습니다. 그러나 증분 또는 감소 작업을 수행할 수 없도록 형식 정보가 손실됩니다. 예를 들어 MyClass*
메모리 위치를 캐스팅할 void*
MyClass*
수 있습니다. 이러한 작업은 본질적으로 오류가 발생하기 쉬며 오류에 주의해야void 합니다. 최신 C++는 거의 모든 상황에서 포인터 사용을 void
권장하지 않습니다.
//func.c
void func(void* data, int length)
{
char* c = (char*)(data);
// fill in the buffer with data
for (int i = 0; i < length; ++i)
{
*c = 0x41;
++c;
}
}
// main.cpp
#include <iostream>
extern "C"
{
void func(void* data, int length);
}
class MyClass
{
public:
int num;
std::string name;
void print() { std::cout << name << ":" << num << std::endl; }
};
int main()
{
MyClass* mc = new MyClass{10, "Marian"};
void* p = static_cast<void*>(mc);
MyClass* mc2 = static_cast<MyClass*>(p);
std::cout << mc2->name << std::endl; // "Marian"
delete(mc);
// use operator new to allocate untyped memory block
void* pvoid = operator new(1000);
char* pchar = static_cast<char*>(pvoid);
for(char* c = pchar; c < pchar + 1000; ++c)
{
*c = 0x00;
}
func(pvoid, 1000);
char ch = static_cast<char*>(pvoid)[0];
std::cout << ch << std::endl; // 'A'
operator delete(pvoid);
}
함수에 대한 포인터
C 스타일 프로그래밍에서 함수 포인터는 주로 함수를 다른 함수에 전달하는 데 사용됩니다. 이 기술을 사용하면 호출자가 함수를 수정하지 않고 함수의 동작을 사용자 지정할 수 있습니다. 최신 C++ 에서 람다 식은 더 큰 형식 안전성 및 기타 장점으로 동일한 기능을 제공합니다.
함수 포인터 선언은 뾰족한 함수에 있어야 하는 서명을 지정합니다.
// Declare pointer to any function that...
// ...accepts a string and returns a string
string (*g)(string a);
// has no return value and no parameters
void (*x)();
// ...returns an int and takes three parameters
// of the specified types
int (*i)(int i, string s, double d);
다음 예제에서는 매개 변수를 수락하고 반환하는 함수를 매개 변수로 std::string
사용하는 함수를 std::string
보여 combine
줍니다. 전달되는 combine
함수에 따라 문자열 앞에 추가하거나 추가합니다.
#include <iostream>
#include <string>
using namespace std;
string base {"hello world"};
string append(string s)
{
return base.append(" ").append(s);
}
string prepend(string s)
{
return s.append(" ").append(base);
}
string combine(string s, string(*g)(string a))
{
return (*g)(s);
}
int main()
{
cout << combine("from MSVC", append) << "\n";
cout << combine("Good morning and", prepend) << "\n";
}
참고 항목
피드백
https://aka.ms/ContentUserFeedback
출시 예정: 2024년 내내 콘텐츠에 대한 피드백 메커니즘으로 GitHub 문제를 단계적으로 폐지하고 이를 새로운 피드백 시스템으로 바꿀 예정입니다. 자세한 내용은 다음을 참조하세요.다음에 대한 사용자 의견 제출 및 보기