在C语言中指针对指针是如何工作的?(你什么时候会用到它们?)

在C语言中指针对指针是如何工作的?< br / > 什么时候可以使用它们?< / p >
153519 次浏览

指针到指针的指针就是指针到指针的指针。

someType**的一个有意义的例子是一个二维数组:您有一个数组,其中充满指向其他数组的指针,因此当您写入时

dpointer [5] [6]

你访问一个数组,该数组包含指向其他数组的指针,在它的第5个位置,获得指针(让fpointer他的名字),然后访问引用该数组的数组的第6个元素(所以,fpointer[6])。

它是指向指针地址值的指针。(我知道这很糟糕)

基本上,它让你传递一个指针到另一个指针的地址值,所以你可以修改另一个指针从子函数指向的地方,比如:

void changeptr(int** pp)
{
*pp=&someval;
}

指针指向指针

由于我们可以有指向int型的指针,指向char型的指针,以及指向我们定义的任何结构的指针,实际上还有指向C语言中任何类型的指针,所以我们可以有指向其他指针的指针,这就不足为奇了。

指向指针的指针也称为处理。它的一种用法通常是当对象可以在内存中移动或删除时。一个通常负责锁定和解锁对象的使用,以便在访问它时不会移动它。

它通常用于内存受限的环境,例如Palm OS。

computer.howstuffworks.com Link>

www.flippinbits.com Link>>

在C语言中指针的指针是如何工作的?

首先,指针是一个变量,就像其他变量一样,但它保存了变量的地址。

指向指针的指针和其他变量一样是一个变量,但它保存了变量的地址。这个变量恰好是一个指针。

你什么时候使用它们?

当需要返回指向堆上某些内存的指针时,可以使用它们,但不使用返回值。

例子:

int getValueOf5(int *p)
{
*p = 5;
return 1;//success
}


int get1024HeapMemory(int **p)
{
*p = malloc(1024);
if(*p == 0)
return -1;//error
else
return 0;//success
}

你这样称呼它:

int x;
getValueOf5(&x);//I want to fill the int varaible, so I pass it's address in
//At this point x holds 5


int *p;
get1024HeapMemory(&p);//I want to fill the int* variable, so I pass it's address in
//At this point p holds a memory address where 1024 bytes of memory is allocated on the heap

还有其他用途,比如每个C程序的main()参数都有一个指向argv指针的指针,其中每个元素都包含一个字符数组,这些字符是命令行选项。当你使用指针的指针指向二维数组时,你必须小心,最好使用指向二维数组的指针。

为什么这很危险?

void test()
{
double **a;
int i1 = sizeof(a[0]);//i1 == 4 == sizeof(double*)


double matrix[ROWS][COLUMNS];
int i2 = sizeof(matrix[0]);//i2 == 240 == COLUMNS * sizeof(double)
}

下面是一个正确使用指向二维数组的指针的例子:

int (*myPointerTo2DimArray)[ROWS][COLUMNS]

但是,如果您希望为ROWS和COLUMNS支持可变数量的元素,则不能使用指向二维数组的指针。但如果你事先知道你会用二维数组。

你有一个变量,它包含某个东西的地址。这是一个指针。

然后你有另一个变量,它包含第一个变量的地址。这是一个指针到指针。

让我们假设一台8位计算机有8位地址(因此只有256字节的内存)。这是内存的一部分(顶部的数字是地址):

  54   55   56   57   58   59   60   61   62   63   64   65   66   67   68   69
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
|    | 58 |    |    | 63 |    | 55 |    |    | h  | e  | l  | l  | o  | \0 |    |
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+

你可以在这里看到,在地址63处字符串“hello”开始。在这种情况下,如果这是内存中唯一出现的"hello"

const char *c = "hello";

... 将c定义为指向(只读)字符串"hello"的指针,因此包含值63。c本身必须存储在某个地方:在上面的例子中,在位置58。当然,我们不仅可以指向字符,还可以指向其他指针。例如:

const char **cp = &c;

现在cp指向c,也就是说,它包含了c的地址(它是58)。我们可以更进一步。考虑:

const char ***cpp = &cp;

现在cpp存储了cp的地址。因此,它的值为55(基于上面的示例),并且您猜到了:它本身存储在地址60。


对于为什么,使用指针指向指针:

  • 数组的名称通常是其第一个元素的地址。因此,如果数组包含类型为t的元素,则该数组的引用类型为t *。现在考虑一个t类型数组的数组:对这个2D数组的引用自然具有(t *)* = t **类型,因此是指向指针的指针。
  • 尽管字符串数组听起来是一维的,但它实际上是二维的,因为字符串是字符数组。因此:char **
  • 函数f如果要更改类型为t *的变量,则需要接受类型为t **的实参。
  • 还有很多其他原因,不胜枚举。

当需要引用指针时,使用指针到指针。例如,当您希望修改在被调用函数的作用域中声明的指针变量的值(指向的地址)时。

如果将单个指针作为参数传入,则将修改指针的本地副本,而不是调用范围内的原始指针。对于指向指针的指针,可以修改后者。

在大学编程课程中讨论指针时,我们得到了两个关于如何开始学习指针的提示。第一个是查看指针乐趣与宾基。第二步是思考刘易斯·卡罗尔的《镜中奇遇记中的黑线鳕的眼睛段落

“你很伤心,”骑士焦急地说,“让我给你唱首歌来安慰你。”

“很长吗?”爱丽丝问,因为那天她听了很多诗。

“它很长,”骑士说,“但它非常非常漂亮。每个听到我唱这首歌的人——要么这首歌让他们热泪盈眶,要么——”

“不然呢?”爱丽丝说,因为骑士突然停住了。

“不然的话,你知道的。这首歌的名字叫《黑线鳕的眼睛》。”

“哦,那是那首歌的名字,是吗?”爱丽丝说,努力表现出感兴趣的样子。

“不,你不明白,”骑士说,看起来有点恼火。“这就是它的名字。这个名字真的是‘老年老人’。”

“那我应该说‘这首歌就是这么叫的’?”爱丽丝纠正了自己的话。

“不,你不应该,那完全是另一回事!这首歌叫《方法和手段》,但你知道,这只是它的名字!”

“那么,那首歌是什么呢?”爱丽丝说,这时她完全懵了。

“我正要说呢。”骑士说。“这首歌真的是‘A-sitting On A Gate’,这首曲子是我自己发明的。”

我喜欢这个“真实的世界”。指针到指针使用的代码示例,在Git 2.0中,提交7 b1004b:

莱纳斯曾经说过:

我真的希望有更多的人了解真正核心的低级编码。不是像无锁名称查找那样的大而复杂的东西,只是很好地使用了指针到指针等 例如,我见过太多的人通过跟踪“prev"条目,然后删除条目,执行如下操作:


if (prev)
prev->next = entry->next;
else
list_head = entry->next;

每当我看到这样的代码,我就会说“这个人不懂指针”。不幸的是,这很常见。

理解指针的人只需使用“__abc0”,并用list_head的地址初始化它。然后,当它们遍历列表时,它们可以删除条目而不使用任何条件,只需要执行a

*pp =  entry->next

指针

应用这种简化可以让我们在添加2行注释的同时从函数中减少7行。

- struct combine_diff_path *p, *pprev, *ptmp;
+ struct combine_diff_path *p, **tail = &curr;

克里斯指向在评论中到2016年的视频“__abc2”。


库马尔指出了在评论中的博客文章"莱纳斯对指针的理解",其中Grisha Trubetskoy解释:

假设你有一个链表,定义为:

   typedef struct list_entry {
int val;
struct list_entry *next;
} list_entry;
你需要从头到尾遍历它,并删除一个值等于to_remove值的特定元素。
更明显的方法是:

   list_entry *entry = head; /* assuming head exists and is the first entry of the list */
list_entry *prev = NULL;
   

while (entry) { /* line 4 */
if (entry->val == to_remove)     /* this is the one to remove ; line 5 */
if (prev)
prev->next = entry->next; /* remove the entry ; line 7 */
else
head = entry->next;      /* special case - first entry ; line 9 */
   

/* move on to the next entry */
prev = entry;
entry = entry->next;
}

我们在上面所做的是:

  • 遍历列表,直到entry为NULL,这意味着我们已经到达列表的末尾(第4行)。
  • 当我们遇到想要删除的条目时(第5行),
  • 我们将当前next指针的值赋给前一个指针,
  • 这样就消除了当前元素(第7行)。

上面有一个特殊的情况-在迭代开始时没有前面的条目(prevNULL),因此要删除列表中的第一个条目,您必须修改head本身(第9行)。

Linus说的是通过将前面的元素作为指向指针的指针而不仅仅是指针,可以简化上面的代码
代码如下所示:

   list_entry **pp = &head; /* pointer to a pointer */
list_entry *entry = head;


while (entry) {
if (entry->val == to_remove)
*pp = entry->next;
else
pp = &entry->next;
entry = entry->next;
}

上面的代码与前面的变体非常相似,但请注意,我们不再需要注意列表的第一个元素的特殊情况,因为pp在开始时不是NULL。简单而聪明。

此外,该线程中有人评论说,这样更好的原因是因为*pp = entry->next是原子的。它肯定不是原子的
上面的表达式包含两个解引用操作符(*->)和一个赋值操作符,这三个操作符都不是原子的。
这是一个常见的误解,但是C中几乎没有任何东西可以被认为是原子的(包括++--操作符)!

如何工作: 它是一个可以存储另一个指针的变量 什么时候你会使用它们: 如果你的函数想要构造一个数组并将其返回给调用者,则通常使用其中之一

//returns the array of roll nos {11, 12} through paramater
// return value is total number of  students
int fun( int **i )
{
int *j;
*i = (int*)malloc ( 2*sizeof(int) );
**i = 11;  // e.g., newly allocated memory 0x2000 store 11
j = *i;
j++;
*j = 12; ;  // e.g., newly allocated memory 0x2004 store 12


return 2;
}


int main()
{
int *i;
int n = fun( &i ); // hey I don't know how many students are in your class please send all of their roll numbers.
for ( int j=0; j<n; j++ )
printf( "roll no = %d \n", i[j] );


return 0;
}

考虑下面的图和程序,以更好地理解这个概念

双指针图

如图所示,ptr1是一个单一的指针,它具有变量全国矿工工会的地址。

ptr1 = &num;

类似地,ptr2是一个指针到指针(双指针),它的地址是指针ptr1

ptr2 = &ptr1;

指向另一个指针的指针称为双指针。在本例中,ptr2是一个双指针。

上图值:

Address of variable num has : 1000
Address of Pointer ptr1 is: 2000
Address of Pointer ptr2 is: 3000

例子:

#include <stdio.h>


int main ()
{
int  num = 10;
int  *ptr1;
int  **ptr2;


// Take the address of var
ptr1 = &num;


// Take the address of ptr1 using address of operator &
ptr2 = &ptr1;


// Print the value
printf("Value of num = %d\n", num );
printf("Value available at *ptr1 = %d\n", *ptr1 );
printf("Value available at **ptr2 = %d\n", **ptr2);
}

输出:

Value of num = 10
Value available at *ptr1 = 10
Value available at **ptr2 = 10

有这么多有用的解释,但我没有找到一个简短的描述,所以…

基本上指针是变量的地址。 简短的总结代码:

     int a, *p_a;//declaration of normal variable and int pointer variable
a = 56;     //simply assign value
p_a = &a;   //save address of "a" to pointer variable
*p_a = 15;  //override the value of the variable


//print 0xfoo and 15
//- first is address, 2nd is value stored at this address (that is called dereference)
printf("pointer p_a is having value %d and targeting at variable value %d", p_a, *p_a);

同样有用的信息可以在主题什么是引用和解引用中找到

我不太确定,指针什么时候有用,但通常在你做一些手动/动态内存分配- malloc, calloc等。时使用它们是必要的

所以我希望这也有助于澄清问题:)

解释指针工作原理的5分钟的视频:


指针桶