二重の間接参照を使用する理由 または、なぜポインタへのポインタを使用するのですか?


272

Cで二重の間接参照を使用する必要があるのはいつですか?誰でも例で説明できますか?

私が知っているのは、二重の間接参照がポインターへのポインターであることです。ポインタへのポインタが必要なのはなぜですか?


49
注意してください; 「ダブルポインタ」という語句は、タイプも指しdouble*ます。
キーストンプソン、

1
注意してください:この質問への回答はCとC ++では異なります-この非常に古い質問にはc +タグを追加しないでください。
BЈовић

回答:


479

文字(単語)のリストが必要な場合は、 char *word

単語のリスト(文)が必要な場合は、 char **sentence

文のリスト(モノローグ)が必要な場合は、 char ***monologue

モノローグ(伝記)のリストが必要な場合は、 char ****biography

伝記のリスト(バイオライブラリ)が必要な場合は、 char *****biolibrary

バイオライブラリのリスト(?? lol)が必要な場合は、使用できます char ******lol

... ...

はい、これらは最適なデータ構造ではない可能性があることを知っています


とても退屈な笑の使用例

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int wordsinsentence(char **x) {
    int w = 0;
    while (*x) {
        w += 1;
        x++;
    }
    return w;
}

int wordsinmono(char ***x) {
    int w = 0;
    while (*x) {
        w += wordsinsentence(*x);
        x++;
    }
    return w;
}

int wordsinbio(char ****x) {
    int w = 0;
    while (*x) {
        w += wordsinmono(*x);
        x++;
    }
    return w;
}

int wordsinlib(char *****x) {
    int w = 0;
    while (*x) {
        w += wordsinbio(*x);
        x++;
    }
    return w;
}

int wordsinlol(char ******x) {
    int w = 0;
    while (*x) {
        w += wordsinlib(*x);
        x++;
    }
    return w;
}

int main(void) {
    char *word;
    char **sentence;
    char ***monologue;
    char ****biography;
    char *****biolibrary;
    char ******lol;

    //fill data structure
    word = malloc(4 * sizeof *word); // assume it worked
    strcpy(word, "foo");

    sentence = malloc(4 * sizeof *sentence); // assume it worked
    sentence[0] = word;
    sentence[1] = word;
    sentence[2] = word;
    sentence[3] = NULL;

    monologue = malloc(4 * sizeof *monologue); // assume it worked
    monologue[0] = sentence;
    monologue[1] = sentence;
    monologue[2] = sentence;
    monologue[3] = NULL;

    biography = malloc(4 * sizeof *biography); // assume it worked
    biography[0] = monologue;
    biography[1] = monologue;
    biography[2] = monologue;
    biography[3] = NULL;

    biolibrary = malloc(4 * sizeof *biolibrary); // assume it worked
    biolibrary[0] = biography;
    biolibrary[1] = biography;
    biolibrary[2] = biography;
    biolibrary[3] = NULL;

    lol = malloc(4 * sizeof *lol); // assume it worked
    lol[0] = biolibrary;
    lol[1] = biolibrary;
    lol[2] = biolibrary;
    lol[3] = NULL;

    printf("total words in my lol: %d\n", wordsinlol(lol));

    free(lol);
    free(biolibrary);
    free(biography);
    free(monologue);
    free(sentence);
    free(word);
}

出力:

私の笑いの総単語数:243

7
a arr[a][b][c]はでないことを指摘したかっただけ***arrです。ポインターのポインターは、参照の参照を使用しますがarr[a][b][c]、通常の配列として行優先順に格納されます。
MCCCS 2018年

170

1つの理由は、関数の引数として関数に渡されるポインターの値を変更したいためです。これを行うには、ポインターへのポインターが必要です。

簡単に言うと、関数呼び出しの外でもメモリ割り当てまたは割り当てを保持(または変更を保持)する**とき使用します。(したがって、そのような関数をダブルポインター引数で渡します。)

これは良い例ではないかもしれませんが、基本的な使用法を示します。

void allocate(int** p)
{
  *p = (int*)malloc(sizeof(int));
}

int main()
{
  int* p = NULL;
  allocate(&p);
  *p = 42;
  free(p);
}

14
割り当てがvoid allocate(int *p)あり、それを次のように呼び出した場合、何が異なるでしょうallocate(p)か?
アレックス

@AlexanderSupertrampはい。コードはsegfaultします。Silviuの回答をご覧ください。
アビシェク2014

@Asha allocate(p)とallocate(&p)の違いは何ですか?
user2979872 2017年

1
@Asha-ポインターだけを返すことはできませんか?それを無効にしなければならない場合、このシナリオの実用的なユースケースは何ですか?
Shabirmean 2018年

91
  • ポインタがあるとしましょう。その値はアドレスです。
  • しかし、あなたはそのアドレスを変更したいと思います。
  • あなたは出来る。を実行するとpointer1 = pointer2、pointer1にpointer2のアドレスが割り当てられます。
  • だが!関数内でそれを行い、関数が完了した後も結果を保持したい場合は、追加の作業が必要です。pointer1を指すだけの新しいpointer3が必要です。pointer3を関数に渡します。

  • ここに例があります。理解するには、まず以下の出力を見てください。

#include <stdio.h>

int main()
{

    int c = 1;
    int d = 2;
    int e = 3;
    int * a = &c;
    int * b = &d;
    int * f = &e;
    int ** pp = &a;  // pointer to pointer 'a'

    printf("\n a's value: %x \n", a);
    printf("\n b's value: %x \n", b);
    printf("\n f's value: %x \n", f);
    printf("\n can we change a?, lets see \n");
    printf("\n a = b \n");
    a = b;
    printf("\n a's value is now: %x, same as 'b'... it seems we can, but can we do it in a function? lets see... \n", a);
    printf("\n cant_change(a, f); \n");
    cant_change(a, f);
    printf("\n a's value is now: %x, Doh! same as 'b'...  that function tricked us. \n", a);

    printf("\n NOW! lets see if a pointer to a pointer solution can help us... remember that 'pp' point to 'a' \n");
     printf("\n change(pp, f); \n");
    change(pp, f);
    printf("\n a's value is now: %x, YEAH! same as 'f'...  that function ROCKS!!!. \n", a);
    return 0;
}

void cant_change(int * x, int * z){
    x = z;
    printf("\n ----> value of 'a' is: %x inside function, same as 'f', BUT will it be the same outside of this function? lets see\n", x);
}

void change(int ** x, int * z){
    *x = z;
    printf("\n ----> value of 'a' is: %x inside function, same as 'f', BUT will it be the same outside of this function? lets see\n", *x);
}

これが出力です:(これを最初に読んでください

 a's value: bf94c204

 b's value: bf94c208 

 f's value: bf94c20c 

 can we change a?, lets see 

 a = b 

 a's value is now: bf94c208, same as 'b'... it seems we can, but can we do it in a function? lets see... 

 cant_change(a, f); 

 ----> value of 'a' is: bf94c20c inside function, same as 'f', BUT will it be the same outside of this function? lets see

 a's value is now: bf94c208, Doh! same as 'b'...  that function tricked us. 

 NOW! lets see if a pointer to a pointer solution can help us... remember that 'pp' point to 'a' 

 change(pp, f); 

 ----> value of 'a' is: bf94c20c inside function, same as 'f', BUT will it be the same outside of this function? lets see

 a's value is now: bf94c20c, YEAH! same as 'f'...  that function ROCKS!!!. 

4
これは素晴らしい答えであり、二重ポインタの目的と有用性を視覚化するのに本当に役立ちました。
ジャスティン

1
@ジャスティン、あなたはこれ以上私の答えをチェックしましたか?そのクリーナー:)
ブライアンジョセフスピノス2017

10
その答えは、<code> void cant_change(int * x、int * z)</ code>が失敗することを説明するだけでは不十分です。これは、そのパラメーターが、aおよびfポインターと同様に初期化される新しいローカルスコープポインターであるためです(そうではないため) aおよびf)と同じです。
ペドロ・レイス

1
簡単?本当に?;)
alk

1
この回答は、ポインターへのポインターの最も一般的な使用法の1つを本当に説明しています。ありがとうございます!
tonyjosi

49

Ashaの応答に加えて、以下の例への単一のポインター(例えばalloc1())を使用すると、関数内で割り当てられたメモリへの参照が失われます。

void alloc2(int** p) {
   *p = (int*)malloc(sizeof(int));
   **p = 10;
}

void alloc1(int* p) {
   p = (int*)malloc(sizeof(int));
   *p = 10;
}

int main(){
   int *p = NULL;
   alloc1(p);
   //printf("%d ",*p);//undefined
   alloc2(&p);
   printf("%d ",*p);//will print 10
   free(p);
   return 0;
}

このように発生する理由はalloc1、ポインターが値で渡されるためです。したがって、それがmalloc内の呼び出しの結果に再割り当てされた場合alloc1、変更は別のスコープのコードには関係しません。


1
pが静的整数ポインターの場合はどうなりますか?セグメンテーション違反を取得しています。
kapilddit 2016

free(p)十分ではありません、あなたif(p) free(*p)も同様に必要です
Shijing Lv

@ShijingLv:いいえ。値は10の保持に*p評価され、intこれintをfree() ` に渡すこと は悪い考えです。
alk

で行われた割り当てalloc1()により、メモリリークが発生します。フリーで渡されるポインタ値は、関数から戻ると失われます。
alk

(!)C.にmalloc関数の結果をキャストする必要はありません
アルク

23

今日、このブログ投稿から非常に良い例を見ました以下に要約するように、私は。

リンクされたリストにノードの構造があると想像してください。

typedef struct node
{
    struct node * next;
    ....
} node;

次に、引数の1つとしてremove_if削除基準を受け入れrm、リンクされたリストをトラバースする関数を実装します。エントリが基準(などrm(entry)==true)を満たす場合、そのノードはリストから削除されます。最後remove_ifに、リンクリストのヘッド(元のヘッドとは異なる場合があります)を返します。

あなたは書くかもしれません

for (node * prev = NULL, * curr = head; curr != NULL; )
{
    node * const next = curr->next;
    if (rm(curr))
    {
        if (prev)  // the node to be removed is not the head
            prev->next = next;
        else       // remove the head
            head = next;
        free(curr);
    }
    else
        prev = curr;
    curr = next;
}

あなたのforループとして。メッセージは、ダブルポインターがない場合、prev変数を維持してポインターを再編成し、2つの異なるケースを処理する必要があることです。

しかし、ダブルポインターを使用すると、実際に書くことができます

// now head is a double pointer
for (node** curr = head; *curr; )
{
    node * entry = *curr;
    if (rm(entry))
    {
        *curr = entry->next;
        free(entry);
    }
    else
        curr = &entry->next;
}

指すものを直接変更できるprevので、今は必要ありません。prev->next

わかりやすくするために、コードを少し見てみましょう。削除中:

  1. if entry == *head:それは*head (==*curr) = *head->next- head新しい見出しノードのポインタを指すようになります。これを行うには、headのコンテンツを直接新しいポインタに変更します。
  2. 場合entry != *head:同様に、*curr何かprev->nextを指摘し、そして今を指しますentry->next

どちらの場合でも、ダブルポインターを使用して統一された方法でポインターを再編成できます。


22

1.基本コンセプト-

次のように宣言すると:-

1. char * ch-(文字ポインターと呼ばれる)
-chには、単一の文字のアドレスが含まれます。
-(* ch)は文字の値を逆参照します。

2. char ** ch-
'ch'には、文字ポインターの配列のアドレスが含まれます。(1と同様)
'* ch'には、1文字のアドレスが含まれます。(宣言が異なるため、1とは異なります)。
(** ch)は、文字の正確な値を逆参照します。

ポインタを追加すると、文字から文字列、文字列の配列など、データ型の次元が拡張されます。1次元、2次元、3次元の行列に関連付けることができます。

したがって、ポインタの使用法は、それを宣言する方法によって異なります。

これは簡単なコードです。

int main()
{
    char **p;
    p = (char **)malloc(100);
    p[0] = (char *)"Apple";      // or write *p, points to location of 'A'
    p[1] = (char *)"Banana";     // or write *(p+1), points to location of 'B'

    cout << *p << endl;          //Prints the first pointer location until it finds '\0'
    cout << **p << endl;         //Prints the exact character which is being pointed
    *p++;                        //Increments for the next string
    cout << *p;
}

2.ダブルポインターの別のアプリケーション-
(これには参照渡しも含まれます)

関数から文字を更新するとします。次を試してみてください:-

void func(char ch)
{
    ch = 'B';
}

int main()
{
    char ptr;
    ptr = 'A';
    printf("%c", ptr);

    func(ptr);
    printf("%c\n", ptr);
}

出力はAAになります。関数に「Passed By Value」があるため、これは機能しません。

それを行う正しい方法は-

void func( char *ptr)        //Passed by Reference
{
    *ptr = 'B';
}

int main()
{
    char *ptr;
    ptr = (char *)malloc(sizeof(char) * 1);
    *ptr = 'A';
    printf("%c\n", *ptr);

    func(ptr);
    printf("%c\n", *ptr);
}

次に、この要件を拡張して、文字ではなく文字列を更新します。
そのためには、関数内のパラメーターをダブルポインターとして受け取る必要があります。

void func(char **str)
{
    strcpy(str, "Second");
}

int main()
{
    char **str;
    // printf("%d\n", sizeof(char));
    *str = (char **)malloc(sizeof(char) * 10);          //Can hold 10 character pointers
    int i = 0;
    for(i=0;i<10;i++)
    {
        str = (char *)malloc(sizeof(char) * 1);         //Each pointer can point to a memory of 1 character.
    }

    strcpy(str, "First");
    printf("%s\n", str);
    func(str);
    printf("%s\n", str);
}

この例では、メソッドは文字列の値を更新するためのパラメーターとしてダブルポインターを想定しています。


#include <stdio.h> int main() { char *ptr = 0; ptr = malloc(255); // allocate some memory strcpy( ptr, "Stack Overflow Rocks..!!"); printf("%s\n", ptr); printf("%d\n",strlen(ptr)); free(ptr); return 0; } しかし、ダブルポインターを使用しなくてもそれを行うことができます。
クマール2014年

" char ** ch-'ch'には、文字ポインターの配列のアドレスが含まれます。 "いいえ、charポインターの配列の最初の要素のアドレスが含まれます。の配列へのポインタは、char*たとえば次のように入力されます。へのポインタ42の配列へのポインタとしてchar(*(*p)[42])定義pしますchar
alk

最後のスニペットは完全に壊れています。初心者向け:*str = ... str未定義の動作を呼び出す、初期化されていない未参照の動作を以下に示します。
alk

これはmalloc(sizeof(char) * 10);、10のポインタのための部屋を割り当てませんcharが、10のためのchar唯一の...
アルク

このループfor(i=0;i<10;i++) { str = ... はインデックスの使用に失敗しますi
alk

17

ポインタへのポインタは、再配置可能なメモリへの関数間の「ハンドル」を渡したいメモリへの「ハンドル」としても役立ちます。つまり、関数はハンドル変数内のポインターが指すメモリを変更でき、ハンドルを使用するすべての関数またはオブジェクトは、新しく再配置された(または割り当てられた)メモリを正しく指します。ライブラリは、「不透明な」データ型でこれを行うのが好きです。つまり、データ型は、ポイントされているメモリで何をしているのか心配する必要がなく、単に「ハンドル」をそのメモリに対していくつかの操作を実行するためのライブラリの関数...

例えば:

#include <stdlib.h>

typedef unsigned char** handle_type;

//some data_structure that the library functions would work with
typedef struct 
{
    int data_a;
    int data_b;
    int data_c;
} LIB_OBJECT;

handle_type lib_create_handle()
{
    //initialize the handle with some memory that points to and array of 10 LIB_OBJECTs
    handle_type handle = malloc(sizeof(handle_type));
    *handle = malloc(sizeof(LIB_OBJECT) * 10);

    return handle;
}

void lib_func_a(handle_type handle) { /*does something with array of LIB_OBJECTs*/ }

void lib_func_b(handle_type handle)
{
    //does something that takes input LIB_OBJECTs and makes more of them, so has to
    //reallocate memory for the new objects that will be created

    //first re-allocate the memory somewhere else with more slots, but don't destroy the
    //currently allocated slots
    *handle = realloc(*handle, sizeof(LIB_OBJECT) * 20);

    //...do some operation on the new memory and return
}

void lib_func_c(handle_type handle) { /*does something else to array of LIB_OBJECTs*/ }

void lib_free_handle(handle_type handle) 
{
    free(*handle);
    free(handle); 
}


int main()
{
    //create a "handle" to some memory that the library functions can use
    handle_type my_handle = lib_create_handle();

    //do something with that memory
    lib_func_a(my_handle);

    //do something else with the handle that will make it point somewhere else
    //but that's invisible to us from the standpoint of the calling the function and
    //working with the handle
    lib_func_b(my_handle); 

    //do something with new memory chunk, but you don't have to think about the fact
    //that the memory has moved under the hood ... it's still pointed to by the "handle"
    lib_func_c(my_handle);

    //deallocate the handle
    lib_free_handle(my_handle);

    return 0;
}

お役に立てれば、

ジェイソン


ハンドルタイプがunsigned char **である理由は何ですか?void **も同様に機能しますか?
Connor Clark

5
unsigned char生のバイトとして表されるバイナリデータへのポインタを格納しているため、特に使用されます。を使用voidするには、ある時点でキャストが必要であり、通常、何が行われているのかという目的ほど読みやすくはありません。
Jason

7

これまでに何度も見たことのある簡単な例

int main(int argc, char **argv)

2番目のパラメーターには、charへのポインターへのポインターがあります。

ポインタ表記(char* c)と配列表記(char c[])は、関数の引数で交換可能であることに注意してください。だからあなたも書くことができますchar *argv[]。言い換えればchar *argv[]char **argv交換可能です。

上記の内容は、実際には文字シーケンス(起動時にプログラムに与えられるコマンドライン引数)の配列です。

上記の関数シグネチャの詳細については、この回答も参照してください。


2
char* cchar c[]関数の引数では、「ポインタ表記()と配列表記()は交換可能です」(まったく同じ意味です)。それらは関数の引数以外では異なります。
pmg 2017

6

文字列は、ダブルポインターの使用の良い例です。文字列自体はポインタであるため、文字列を指す必要がある場合は常に、ダブルポインタが必要になります。


5

たとえば、何かのメモリを解放するときに、後でポインタをnullに設定したことを確認したい場合があります。

void safeFree(void** memory) {
    if (*memory) {
        free(*memory);
        *memory = NULL;
    }
}

この関数を呼び出すときは、ポインターのアドレスを指定して呼び出します。

void* myMemory = someCrazyFunctionThatAllocatesMemory();
safeFree(&myMemory);

現在myMemoryはNULLに設定されており、それを再利用しようとすると、明らかに間違いになります。


1
それはそうである必要がif(*memory)ありますfree(*memory);
アシャ

1
良い点は、脳とキーボードの間の信号損失です。もう少し理解できるように編集しました。
ジェフフォスター

なぜ次のことができないのでしょうか... void safeFree(void * memory){if(memory){free(memory); メモリ= NULL; }}
Peter_pk 2015

@Peter_pk nullにメモリを割り当てても、参照ではなく値でポインタが渡されたため、役に立ちません(したがって、ポインタへのポインタの例)。
ジェフフォスター

2

たとえば、不連続なデータへのランダムアクセスが必要な場合です。

p -> [p0, p1, p2, ...]  
p0 -> data1
p1 -> data2

-Cで

T ** p = (T **) malloc(sizeof(T*) * n);
p[0] = (T*) malloc(sizeof(T));
p[1] = (T*) malloc(sizeof(T));

ポインタpの配列を指すポインタを格納します。各ポインタはデータの一部を指しています。

sizeof(T)が大きい場合、sizeof(T) * nバイトの連続したブロックを割り当てる(つまり、mallocを使用する)ことができない場合があります。


1
(!)C.にmalloc関数の結果をキャストする必要はありません
アルク

2

オブジェクトの配列があり、さまざまなフィールドでそれらのオブジェクトに対してルックアップ(バイナリ検索)を実行する必要がある場合に、それらを常に使用する1つのことです。
元の配列を保持しています...

int num_objects;
OBJECT *original_array = malloc(sizeof(OBJECT)*num_objects);

次に、オブジェクトへのソートされたポインタの配列を作成します。

int compare_object_by_name( const void *v1, const void *v2 ) {
  OBJECT *o1 = *(OBJECT **)v1;
  OBJECT *o2 = *(OBJECT **)v2;
  return (strcmp(o1->name, o2->name);
}

OBJECT **object_ptrs_by_name = malloc(sizeof(OBJECT *)*num_objects);
  int i = 0;
  for( ; i<num_objects; i++)
    object_ptrs_by_name[i] = original_array+i;
  qsort(object_ptrs_by_name, num_objects, sizeof(OBJECT *), compare_object_by_name);

ソートされたポインター配列を必要な数だけ作成し、ソートされたポインター配列でバイナリ検索を使用して、データで必要なオブジェクトにアクセスできます。オブジェクトの元の配列はソートされないままでかまいませんが、各ポインター配列は指定されたフィールドでソートされます。


2

なぜダブルポインターなのですか?

目的は、関数を使用して、studentAが指すものを変更することです。

#include <stdio.h>
#include <stdlib.h>


typedef struct Person{
    char * name;
} Person; 

/**
 * we need a ponter to a pointer, example: &studentA
 */
void change(Person ** x, Person * y){
    *x = y; // since x is a pointer to a pointer, we access its value: a pointer to a Person struct.
}

void dontChange(Person * x, Person * y){
    x = y;
}

int main()
{

    Person * studentA = (Person *)malloc(sizeof(Person));
    studentA->name = "brian";

    Person * studentB = (Person *)malloc(sizeof(Person));
    studentB->name = "erich";

    /**
     * we could have done the job as simple as this!
     * but we need more work if we want to use a function to do the job!
     */
    // studentA = studentB;

    printf("1. studentA = %s (not changed)\n", studentA->name);

    dontChange(studentA, studentB);
    printf("2. studentA = %s (not changed)\n", studentA->name);

    change(&studentA, studentB);
    printf("3. studentA = %s (changed!)\n", studentA->name);

    return 0;
}

/**
 * OUTPUT:
 * 1. studentA = brian (not changed)
 * 2. studentA = brian (not changed)
 * 3. studentA = erich (changed!)
 */

1
(!)C.にmalloc関数の結果をキャストする必要はありません
アルク

2

以下は、オブジェクトを指すようにポインターを設定する関数を使用する場合、ポインターへのポインターが必要であることを示す非常に単純なC ++の例です。それ以外の場合、ポインタはnullに戻り続けます

(C ++の回答ですが、Cでも同じだと思います。)

(また、参考のために:Google( "値渡しc ++")= "デフォルトでは、C ++の引数は値渡しされます。引数が値渡しされると、引数の値が関数のパラメーターにコピーされます。")

したがって、ポインタbを文字列と等しく設定したいとしますa

#include <iostream>
#include <string>

void Function_1(std::string* a, std::string* b) {
  b = a;
  std::cout << (b == nullptr);  // False
}

void Function_2(std::string* a, std::string** b) {
  *b = a;
  std::cout << (b == nullptr);  // False
}

int main() {
  std::string a("Hello!");
  std::string* b(nullptr);
  std::cout << (b == nullptr);  // True

  Function_1(&a, b);
  std::cout << (b == nullptr);  // True

  Function_2(&a, &b);
  std::cout << (b == nullptr);  // False
}

// Output: 10100

ラインで何が起こりFunction_1(&a, b);ますか?

  • &main::a(アドレス)の「値」がパラメーターにコピーされますstd::string* Function_1::a。したがって、Function_1::aは、文字列(へのメモリアドレス)へのポインタmain::aです。

  • main::b(メモリ内のアドレス)の「値」がパラメータにコピーされますstd::string* Function_1::b。したがって、メモリにはこれらのアドレスが2つあり、どちらもnullポインタです。行b = a;で、ローカル変数Function_1::bは次に等しいFunction_1::a(= &main::a)に変更されますが、変数main::bは変更されません。呼び出した後Function_1main::bまだヌルポインタです。

ラインで何が起こりFunction_2(&a, &b);ますか?

  • a変数の扱いは同じです。関数内Function_2::aでは、文字列のアドレスですmain::a

  • しかし、変数bは現在、ポインターへのポインターとして渡されています。&main::bポインタアドレスmain::b)の「値」がにコピーされstd::string** Function_2::bます。したがって、Function_2内で、これを逆参照すると、*Function_2::bにアクセスして変更しmain::bます。したがって、行*b = a;は実際にmain::b(アドレス)をFunction_2::a(=のアドレス)に等しく設定していますmain::a

関数を使用してオブジェクトやアドレス(ポインター)を変更する場合は、そのオブジェクトへのポインターを渡す必要があります。ローカルコピーが作成されるため、実際に渡すものは(呼び出しスコープ内で)変更できません。

(パラメータが参照などの場合は例外です。 std::string& a通常はconstです。通常、を呼び出したf(x)場合x、がオブジェクトである場合は、それf 変更されないと想定できるはずxです。ただしx、ポインタである場合は、が指すオブジェクトを変更するf 可能性があると仮定しますx。)


Cの質問に答えるためのC ++コードは最善のアイデアではありません。
alk

1

パーティーに少し遅れましたが、うまくいけばこれが誰かを助けるでしょう。

Cの配列では常にスタックにメモリを割り当てます。したがって、スタックに割り当てられたメモリが現在のブロックの最後に到達すると自動的に解放されるため、関数は(非静的)配列を返すことができません。2次元配列(つまり、行列)を処理し、行列を変更して返すことができるいくつかの関数を実装する場合、これは本当に煩わしいことです。これを実現するために、ポインターを指すポインターを使用して、動的に割り当てられたメモリを備えた行列を実装できます。

/* Initializes a matrix */
double** init_matrix(int num_rows, int num_cols){
    // Allocate memory for num_rows float-pointers
    double** A = calloc(num_rows, sizeof(double*));
    // return NULL if the memory couldn't allocated
    if(A == NULL) return NULL;
    // For each double-pointer (row) allocate memory for num_cols floats
    for(int i = 0; i < num_rows; i++){
        A[i] = calloc(num_cols, sizeof(double));
        // return NULL if the memory couldn't allocated
        // and free the already allocated memory
        if(A[i] == NULL){
            for(int j = 0; j < i; j++){
                free(A[j]);
            }
            free(A);
            return NULL;
        }
    }
    return A;
} 

これがイラストです:

double**       double*           double
             -------------       ---------------------------------------------------------
   A ------> |   A[0]    | ----> | A[0][0] | A[0][1] | A[0][2] | ........ | A[0][cols-1] |
             | --------- |       ---------------------------------------------------------
             |   A[1]    | ----> | A[1][0] | A[1][1] | A[1][2] | ........ | A[1][cols-1] |
             | --------- |       ---------------------------------------------------------
             |     .     |                                    .
             |     .     |                                    .
             |     .     |                                    .
             | --------- |       ---------------------------------------------------------
             |   A[i]    | ----> | A[i][0] | A[i][1] | A[i][2] | ........ | A[i][cols-1] |
             | --------- |       ---------------------------------------------------------
             |     .     |                                    .
             |     .     |                                    .
             |     .     |                                    .
             | --------- |       ---------------------------------------------------------
             | A[rows-1] | ----> | A[rows-1][0] | A[rows-1][1] | ... | A[rows-1][cols-1] |
             -------------       ---------------------------------------------------------

ダブルポインタからダブルポインタへのAは、メモリブロックの最初の要素A [0]を指します。その要素は、それ自体がダブルポインタです。これらのダブルポインターは、行列の行と考えることができます。これが、すべてのダブルポインターがdouble型のnum_cols要素にメモリを割り当てる理由です。さらに、A [i]はi番目の行を指します。つまり、A [i]はA [i] [0]を指します。これは、i番目の行のメモリブロックの最初のdouble要素です。最後に、A [i] [j]を使用すると、i行j列の要素に簡単にアクセスできます。

使い方を示す完全な例を次に示します。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

/* Initializes a matrix */
double** init_matrix(int num_rows, int num_cols){
    // Allocate memory for num_rows double-pointers
    double** matrix = calloc(num_rows, sizeof(double*));
    // return NULL if the memory couldn't allocated
    if(matrix == NULL) return NULL;
    // For each double-pointer (row) allocate memory for num_cols
    // doubles
    for(int i = 0; i < num_rows; i++){
        matrix[i] = calloc(num_cols, sizeof(double));
        // return NULL if the memory couldn't allocated
        // and free the already allocated memory
        if(matrix[i] == NULL){
            for(int j = 0; j < i; j++){
                free(matrix[j]);
            }
            free(matrix);
            return NULL;
        }
    }
    return matrix;
}

/* Fills the matrix with random double-numbers between -1 and 1 */
void randn_fill_matrix(double** matrix, int rows, int cols){
    for (int i = 0; i < rows; ++i){
        for (int j = 0; j < cols; ++j){
            matrix[i][j] = (double) rand()/RAND_MAX*2.0-1.0;
        }
    }
}


/* Frees the memory allocated by the matrix */
void free_matrix(double** matrix, int rows, int cols){
    for(int i = 0; i < rows; i++){
        free(matrix[i]);
    }
    free(matrix);
}

/* Outputs the matrix to the console */
void print_matrix(double** matrix, int rows, int cols){
    for(int i = 0; i < rows; i++){
        for(int j = 0; j < cols; j++){
            printf(" %- f ", matrix[i][j]);
        }
        printf("\n");
    }
}


int main(){
    srand(time(NULL));
    int m = 3, n = 3;
    double** A = init_matrix(m, n);
    randn_fill_matrix(A, m, n);
    print_matrix(A, m, n);
    free_matrix(A, m, n);
    return 0;
}

0

今日は仕事用に何かをプログラミングしているときにダブルポインターを使用したので、なぜそれらを使用しなければならなかったのかを答えることができます(実際にダブルポインターを使用しなければならないのは初めてです)。一部の構造体のメンバーであるバッファーに含まれるフレームのリアルタイムエンコーディングを処理する必要がありました。エンコーダでは、これらの構造の1つへのポインタを使用する必要がありました。問題は、別のスレッドからの他の構造を指すようにポインターが変更されていたことです。エンコーダーで現在の構造を使用するには、別のスレッドで変更されているポインターを指すために、ダブルポインターを使用する必要がありました。最初は、少なくとも私たちにとって、このアプローチをとらなければならなかったことは明らかではありませんでした。たくさんのアドレスがプロセスで印刷されました:))。

アプリケーションの他の場所で変更されたポインターを操作する場合は、ダブルポインターを使用する必要があります。また、返送して対処するハードウェアを扱う場合は、ダブルポインタが必要になることがあります。


0

変数の変更値とポインターの変更値を比較します

#include <stdio.h>
#include <stdlib.h>

void changeA(int (*a))
{
  (*a) = 10;
}

void changeP(int *(*P))
{
  (*P) = malloc(sizeof((*P)));
}

int main(void)
{
  int A = 0;

  printf("orig. A = %d\n", A);
  changeA(&A);
  printf("modi. A = %d\n", A);

  /*************************/

  int *P = NULL;

  printf("orig. P = %p\n", P);
  changeP(&P);
  printf("modi. P = %p\n", P);

  free(P);

  return EXIT_SUCCESS;
}

これは、呼び出された関数(単一リンクリストで使用)によってポインターが変更されたときに、ポインターの値を返さないようにするのに役立ちました。

古い(悪い):

int *func(int *P)
{
  ...
  return P;
}

int main(void)
{
  int *pointer;
  pointer = func(pointer);
  ...
}    

新しい(より良い):

void func(int **pointer)
{
  ...
}

int main(void)
{
  int *pointer;
  func(&pointer);
  ...
}    
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.