次の方法で共有


生ポインター (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 演算子は、配列の合計サイズ (バイト単位) を返します
  • 要素の数を決定するには、合計バイト数を 1 つの要素のサイズで除算します
  • 配列が関数に渡された場合、ポインター型に対して "減衰" します
  • ポインターに 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 のビットマップ内の個々のピクセルにアクセスする方法を示します。 newdelete の使用と、逆参照演算子に注意してください。

#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";
}

関連項目

スマート ポインター間接演算子: *
Address-of 演算子: >
C++ へようこそ