为什么使用双指针? 或者为什么使用指针指针?
什么时候应该在C中使用双指针? 任何人都可以用一个例子解释?
我所知道的是一个双指针是一个指针的指针。 为什么我需要一个指针指针?
如果你想有一个字符列表(一个字),你可以使用char *word
如果你想要一个单词列表(一个句子),你可以使用char **sentence
如果你想要一个句子列表(一个独白),你可以使用char ***monologue
如果你想要一个独白列表(传记),你可以使用char ****biography
如果你想要一个传记列表(一个生物图书馆),你可以使用char *****biolibrary
生物图书馆
如果你想要一个生物库列表(一个??哈哈),你可以使用char ******lol
……
是的,我知道这些可能不是最好的数据结构
一个原因是你想要改变传递给一个函数的指针的值作为函数的参数,要做到这一点你需要指针指针。
简单来说, 当你想保存(或保留更改)内存分配或分配甚至在函数调用之外时使用**
。 (所以,用双指针arg传递这样的函数。)
这可能不是一个很好的例子,但会告诉你基本的用法:
void allocate(int** p) { *p = (int*)malloc(sizeof(int)); } int main() { int* p = NULL; allocate(&p); *p = 42; free(p); }
这是一个简单的答案!
- 可以说你有一个指针,它的值是一个地址。
- 但现在你想改变这个地址。
- 你可以通过做pointer1 = pointer2,并且pointer1现在具有pointer2的地址。
-
但! 如果你想为你做一个函数,并且你希望在函数完成之后,结果仍然存在,那么你需要做一些额外的工作,你需要一个新的pointer3来指向pointer1,然后把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("\na's value: %x \n", a); printf("\nb's value: %x \n", b); printf("\nf's value: %x \n", f); printf("\n can we change a?, lets see \n"); printf("\na = b \n"); a = b; printf("\na'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("\na'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("\na'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 = ba'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!!!.
添加到Asha的响应,如果您使用单指针示例波纹pipe(例如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; alloc1(p); //printf("%d ",*p);//value is undefined alloc2(&p); printf("%d ",*p);//will print 10 free(p); return 0; }
像这样发生的原因是在alloc1
,指针是按值传入的。 所以,当它被重新分配给alloc1
中的malloc
调用的结果时,这个改变就不属于不同范围的代码。
今天我从这篇博文中看到了一个很好的例子,下面我总结一下。
想象一下,你有一个链接列表中的节点的结构,这可能是
typedef struct node { struct node * next; .... } node;
现在你想实现一个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
variables来重新组织指针 ,并处理两个不同的情况。
但是用双指针,你实际上可以写
// 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
指向的内容 。
为了使事情更清楚,让我们按照代码一点点。 在移除过程中:
- 如果
entry == *head
:它将是*head (==*curr) = *head->next
–head
现在指向新的标题节点的指针。 您可以通过将head
的内容直接更改为新的指针来实现此head
。 - 如果
entry != *head
:类似地,*curr
是prev->next
指向的,现在指向entry->next
。
不pipe在哪种情况下,都可以用双指针统一重新组织指针。
1.基本概念 –
当你申报如下:
1. char * ch – (称为字符指针)
– ch包含单个字符的地址。
– (* ch)将取消引用字符的值
2. char ** ch –
'ch'包含字符指针数组的地址。 (如1)
'* ch'包含单个字符的地址。 (注意,由于声明的不同,与1不同)。
(** ch)将取消引用字符的确切值
添加更多的指针将扩展数据types的维度,从字符到string,再到string数组,等等…您可以将其与1d,2d,3dmatrix关联起来。
所以,指针的用法取决于你如何声明它。
这是一个简单的代码
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。 这是行不通的,因为你已经有了“通过价值传递”的function。
正确的做法是 –
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); }
现在扩展这个更新string而不是字符的要求。
为此,您需要在函数中接收参数作为双指针。
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); }
在这个例子中,方法需要一个双指针作为参数来更新string的值。
指向指针的指针也可以作为“处理”到存储器的地方,在那里你要传递函数之间的“句柄”到可重定位的存储器。 这基本上意味着该函数可以改变句柄variables中指针所指向的内存,并且使用该句柄的每个函数或对象都将正确地指向新重定位(或分配)的内存。 像“不透明”数据types这样的数据库,就是数据types,你不必担心他们正在做什么内存被指向做什么,你只需要绕过“处理”库函数对内存执行一些操作…库函数可以在内存中分配和取消分配内存,而不必明确担心内存pipe理过程或者句柄指向的位置。
例如:
#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; }
希望这可以帮助,
贾森
string是双指针使用的一个很好的例子。 string本身就是一个指针,所以无论何时你需要指向一个string,你都需要一个双指针。
例如,你可能想要确保当你释放内存的时候,你将指针设置为null。
void safeFree(void** memory) { if (*memory) { free(*memory); *memory = NULL; } }
当你调用这个函数时,你会用指针的地址来调用它
void* myMemory = someCrazyFunctionThatAllocatesMemory(); safeFree(&myMemory);
现在myMemory
被设置为NULL,任何尝试重用它将是非常明显的错误。
例如,如果你想随机访问不连续的数据。
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)。
有一件事我经常使用它们是当我有一个对象数组,我需要执行查找(二进制search)在他们不同的领域。
我保持原始数组…
int num_objects; OBJECT *original_array = malloc(sizeof(OBJECT)*num_objects);
然后创build一个指向对象的sorting指针数组。
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);
您可以根据需要制作任意数量的已sorting指针数组,然后在已sorting的指针数组上使用二进制search来访问您拥有的数据所需的对象。 对象的原始数组可以保持未sorting,但每个指针数组将按其指定的字段进行sorting。
今天我使用了双指针,而我正在编写一些工作,所以我可以回答为什么我们必须使用它们(这是我第一次实际上不得不使用双指针)。 我们不得不处理包含在某些结构成员的缓冲区中的帧的实时编码。 在编码器中,我们必须使用指向其中一个结构的指针。 问题是我们的指针正在改变,指向另一个线程的其他结构。 为了在编码器中使用当前结构,我必须使用双指针,以便指向另一个线程正在修改的指针。 起初,至less对我们来说,我们不得不采取这种做法。 在这个过程中打印了很多地址:))。
你应该使用双指针,当你在应用程序的其他地方改变的指针工作。 在处理返回并解决问题的硬件时,您也可能会发现双指针是必须的。
为什么使用双指针?
目标是使用函数来改变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!) */
以下是一个非常简单的C ++示例,它显示如果要使用函数来设置指向对象的指针 ,则需要指向指针的指针 。 否则, 指针将保持恢复为空 。
(一个C ++的答案,但我相信在C中是一样的)
(另外,供参考:Google(“pass by value c ++”)=“默认情况下,C ++中的参数是按值传递的,当参数传值时,参数的值被复制到函数的参数中。
所以我们要设置指针b
等于stringa
。
#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
是一个指向stringmain::a
(即内存地址)的指针。 -
main::b
(内存中的地址)的“值”被复制到参数std::string* Function_1::b
。 所以现在在内存中有两个地址,都是空指针。 在行b = a;
,局部variablesFunction_1::b
被改变为等于Function_1::a
(=&main::a
),但variablesmain::b
不变。 在对Function_1
的调用之后,main::b
仍然是一个空指针。
在Function_2(&a, &b);
行会发生什么? ?
-
对
a
variables的处理是一样的:在函数内,Function_2::a
是stringmain::a
的地址。 -
但variables
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
地址),这就是我们想要的。
如果你想使用一个函数来修改一个东西,无论是一个对象还是一个地址(指针),你都必须传入一个指向该东西的指针。 实际传入的内容不能被修改(在调用范围内),因为创build了本地副本。
(例外是如果参数是一个引用,如std::string& a
,但通常这些是const
。通常,如果你调用f(x)
,如果x
是一个对象,你应该能够假设f
won'但是如果x
是一个指针,那么你应该假设f
可能会修改x
指向的对象。)
简单的例子,你可能以前看过很多次
int main(int argc, char **argv)
在第二个参数中有:指向char的指针的指针。
请注意,指针表示法( char* c
)和数组表示法( char c[]
)在函数参数中是可以互换的。 所以你也可以写char *argv[]
。 换句话说, char *argv[]
和char **argv
是可以互换的。
上面的代码实际上是一个字符序列的数组(在启动时给程序的命令行参数)。
有关上述function签名的更多详细信息,另请参阅此答案 。
希望下面的例子将清除一些关于指针和双指针的概念,以及它们在常见场景中的差异和用法。
int* setptr(int *x) { printf("%u\n",&x); x=malloc(sizeof(int)); *x=1; return x; } In the above function setptr we can manipulate x either 1. by taking fn arg as int *x , doing malloc and setting value of x and return x Or 2. By taking arg as int ** and malloc and then set **x value to some value. Note: we cant set any general pointer directly without doing malloc.Pointer indicates that it is a type of variable which can hold address of any data type.Now either we define a variable and give reference to it or we declare a pointer(int *x=NULL) and allocate some memory to it inside the called function where we pass x or a reference to it .. In either case we need to have address of a memory in the pointer and in the case pointer initially points to NULL or it is defined like int *x where it points to any random address then we need to assign a valid memory address to pointer 1. either we need to allocate memory to it by malloc int *x=NULL means its address is 0. Now we need to either o following 1. void main() { int *x; x=malloc *x=some_val; } Or void main() { int *x Fn(x); } void Fn(int **x) { *x=malloc; **x=5; } OR int * Fn(int *x) { x=malloc(); *x=4; Return x; } 2. Or we need to point it to a valid memory like a defined variable inside the function where pointer is defined. OR int main() { int a; int *x=&a; Fn(x); printf("%d",*x); } void Fn(int *x) { *x=2; } in both cases value pointed by x is changed inside fn But suppose if we do like int main() { int *x=NULL; printf("%u\n",sizeof(x)); printf("%u\n",&x); x=setptr(x); //*x=2; printf("%d\n",*x); return 0; } /* output 4 1 */ #include<stdio.h> void setptr(int *x) { printf("inside setptr\n"); printf("x=%u\n",x); printf("&x=%u\n",&x); //x=malloc(sizeof(int)); *x=1; //return x; } int main() { int *x=NULL; printf("x=%u\n",x); printf("&x=%u\n",&x); int a; x=&a; printf("x=%u\n",x); printf("&a=%u\n",&a); printf("&x=%u\n",&x); setptr(x); printf("inside main again\n"); //*x=2; printf("x=%u\n",x); printf("&x=%u\n",&x); printf("*x=%d\n",*x); printf("a=%d\n",a); return 0; }