C指针指针如何工作?
C指针指针如何工作? 你什么时候使用它们?
我们假设一个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 | | +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
你可以在这里看到的是在地址63string“你好”开始。 所以在这种情况下,如果这是内存中唯一发生的“hello”,那么,
const char *c = "hello";
…定义c
是一个指向(只读)string“hello”的指针,因此包含值63. c
必须存储在某处:在上例中的位置58处。当然,我们不能只指向要字符,还要指向其他的指针。 例如:
const char **cp = &c;
现在cp
指向c
,即它包含了c
的地址(这是58)。 我们可以走得更远。 考虑:
const char ***cpp = &cp;
现在cpp
存储cp
的地址。 所以它有价值55(基于上面的例子),你猜对了:它本身存储在地址60。
至于为什么使用指针指针:
- 数组的名称通常会产生其第一个元素的地址。 因此,如果数组包含
t
types的元素,则对数组的引用具有typest *
。 现在考虑一个types为t
的数组的数组:自然对这个二维数组的引用将具有types(t *)*
=t **
,因此是指针的指针。 - 即使一串string听起来是一维的,但它实际上是二维的,因为string是字符数组。 因此:
char **
。 - 一个函数
f
需要接受一个types为t **
的参数,如果它要改变一个types为t *
的variables的话。 - 许多其他的原因,这里不胜枚举。
C指针指针如何工作?
首先,一个指针是一个variables,就像任何其他variables一样,但它包含一个variables的地址。
指向指针的指针是一个variables,就像任何其他variables一样,但是包含一个variables的地址。 那个variables恰好是一个指针。
你什么时候使用它们?
当你需要返回一个指向堆上某个内存的指针时,你可以使用它们,但不能使用返回值。
例:
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指针的指针,其中每个元素都包含一个chars数组,它们是命令行选项。 当你使用指针指向2维数组时,你必须小心,最好使用指向2维数组的指针。
为什么危险?
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) }
下面是一个指向2维数组的指针示例:
int (*myPointerTo2DimArray)[ROWS][COLUMNS]
尽pipe如果您想要支持ROWS和COLUMNS的可变数量的元素,您不能使用指向2维数组的指针。 但是,当你事先知道你会使用2维数组。
我喜欢这个“真实世界”代码示例指针指针的使用,在Git 2.0中, 提交7b1004b :
莱纳斯曾经说过:
我其实希望更多的人了解真正的核心低级编码。 不是大的,像无锁名称查找那样复杂的东西,而是简单地使用指针指针等。
例如,我见过很多人通过跟踪“prev”条目来删除单链表条目,然后删除条目,做类似
if (prev) prev->next = entry->next; else list_head = entry->next;
每当我看到这样的代码,我只是去“这个人不明白指针”。 可悲的是,这很常见。
理解指针的人只是使用“ 指向入口指针的指针 ”,并用list_head的地址初始化它。 然后当他们遍历列表,他们可以删除条目,而不使用任何条件,只是做一个
*pp = entry->next
应用这种简化function,即使在添加2行注释的情况下,也可以从此function中丢失7行。
- struct combine_diff_path *p, *pprev, *ptmp; + struct combine_diff_path *p, **tail = &curr;
克里斯 在 Philip Buuck的2016年video“ Linus Torvalds的双指针问题 ”的评论中指出 。
库马尔 在评论中指出博客文章“ 理解指针的莱纳斯 ”,其中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; }
我们上面做的是:
- 迭代列表直到条目为
NULL
,这意味着我们已经到达列表的最后(第4行)。- 当我们遇到一个我们想要删除的条目(第5行)时,
- 我们将当前下一个指针的值赋给前一个指针,
- 从而消除当前元素(第7行)。
上面有一个特例 – 迭代开始时没有先前的条目(
prev
是NULL
),所以要删除列表中的第一个条目,你必须修改头本身(第9行)。Linus所说的是, 通过将前一个元素指向一个指针而不是指针,可以简化上面的代码 。
代码如下所示:
list_entry **pp = &head; /* pointer to a pointer */ list_entry *entry = head; while (entry) { if (entry->val == to_remove) *pp = entry->next; pp = &entry->next; entry = entry->next; }
上面的代码和前面的代码非常相似,但是请注意,我们不再需要注意列表的第一个元素的特殊情况,因为在开始时
pp
不是NULL
。 简单而聪明。此外,该线程中有人评论说,这是更好的原因是因为
*pp = entry->next
是primefaces。 这当然不是primefaces的 。
上面的expression式包含两个解除引用操作符(*
和->
)和一个赋值,而这三个东西都不是primefaces的。
这是一个常见的误解,但是在C中几乎没有任何东西可以被假设为primefaces的 (包括++
和--
运算符)!
在大学编程课程中,我们提供了两个提示,如何开始学习。 第一个是查看指针与Binky乐趣 。 第二个是想想刘易斯·卡罗尔(Lewis Carroll) 通过 镜子看到的哈达克斯的眼睛
“你很伤心,”奈特急切地说道,“让我给你唱一首歌来安慰你。”
“是不是很长?”爱丽丝问道,那天她听了很多诗。
“很长,”骑士说,“但是非常漂亮。 每个听到我唱歌的人 – 要么把眼泪带到他们眼前,要么 – “
“还有什么?”爱丽丝说,因为奈特突然停了下来。
“否则它不,你知道。 这首歌的名字叫做“黑道的眼睛”。
“哦,那首歌的名字,是吗?”爱丽丝说,试图感到兴趣。
“不,你不明白,”奈特说,看起来有些烦恼。 “这就是所谓的名字。 这个名字真的是'老年人'。“
“那么我应该说'这首歌叫什么'?”爱丽丝纠正自己。
“不,你不该这样做,那又是另一回事了! 这首歌被称为“方式和手段”,但这只是它所谓的,你知道!
“那么,那首歌是什么?”爱丽丝说,直到现在这个时候,他完全被弄糊涂了。
奈特说:“我刚才说到了。 “这首歌真的是'坐在门上',曲调是我自己的发明。
你可能想读这个: 指针指针
希望这有助于澄清一些基本的疑问。
当需要指针的引用时。 例如,当您希望修改调用函数在被调用函数中声明的指针variables的值(地址指向)时。
如果传递一个单指针作为参数,则将修改指针的本地副本,而不是调用范围中的原始指针。 用一个指向指针的指针,可以修改后者。
指向指针的指针也称为句柄 。 它的一个用法是通常当一个对象可以在内存中移动或删除。 人们经常负责locking和解锁对象的使用,所以在访问对象时不会被移动。
它经常用在内存受限的环境中,即Palm OS。
computer.howstuffworks.com链接>>
你有一个包含某个地址的variables。 这是一个指针。
然后你有另一个variables,其中包含第一个variables的地址。 这是一个指针指针。
它是一个指针的地址值的指针。 (我知道这很可怕)
基本上,它可以让你传递一个指针到另一个指针的地址的值,所以你可以修改另一个指针从一个子函数指向的位置,如:
void changeptr(int** pp) { *pp=&someval; }
考虑下图和程序来更好地理解这个概念 。
如图所示, ptr1是一个具有variablesnum地址的单个指针 。
ptr1 = #
类似地, 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 = # // 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
指向指针的指针是指向指针的指针。
someType **的一个有意义的例子是一个二维数组:你有一个数组,填充指向其他数组的指针,所以当你写
dpointer [5] [6]
你访问包含指向其他数组的指针的数组,获取指针(让fpointer他的名字),然后访问数组的第6个元素(fpointer [6])。
它是如何工作的:它是一个可以存储另一个指针的variables。
你什么时候使用它们?许多人使用其中之一就是如果你的函数想要构造一个数组并返回给调用者。
//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; // eg, newly allocated memory 0x2000 store 11 j = *i; j++; *j = 12; ; // eg, 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; }