原始指標 (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
。 一般而言,除非物件的值可能是 nullptr
,否則 const
參考是將對象傳遞至函式的慣用方法。
函式的指標可讓函式傳遞至其他函式。 這類指標用於 C 樣式程式設計中的「回呼」。 新式 C++ 會針對此目的使用 Lambda 運算式。
初始化和成員存取
下列範例示範如何宣告、初始化及使用原始指標。 指標會使用 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
指標,使其指向另一個記憶體位置。 指標會使用++
、+=
、-=
和 --
運算子遞增和遞減。 這項技術可用於陣列,在不具類型的資料緩衝區中特別有用。 void*
會以 char
(1 位元組) 的大小遞增。 具類型的指標會依所指向的類型大小遞增。
下列範例示範如何使用指標算術來存取 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
的指標只會指向原始記憶體位置。 有時必須使用 void*
指標,例如在 C++ 程式碼與 C 函式之間傳遞時。
當具類型的指標轉換成 void
指標時,記憶體位置的內容會保持不變。 不過,類型資訊會遺失,因此您無法執行遞增或遞減作業。 記憶體位置可以轉換,例如從 MyClass*
到 void*
,以及轉換回 MyClass*
。 這類作業原本就容易發生錯誤,因此需要非常小心才能避免錯誤。 現代 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++ 中,Lambda 運算式提供相同的功能,具有更大的類型安全性和其他優點。
函式指標宣告會指定指向函式必須具有的簽章:
// 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";
}