有没有人有任何关于指针运算的好文章或解释(博客、示例)?图的听众是一群学习C和C++的Java程序员。
7 回答
这是我学习指针的地方:http ://www.cplusplus.com/doc/tutorial/pointers.html
一旦你理解了指针,指针算术就很容易了。它与常规算术之间的唯一区别是您添加到指针的数字将乘以指针指向的类型的大小。例如,如果您有一个指向 an 的指针,int
并且 anint
的大小为 4 个字节,(pointer_to_int + 4)
则将评估为提前 16 个字节(4 个整数)的内存地址。
所以当你写
(a_pointer + a_number)
在指针算术中,真正发生的是
(a_pointer + (a_number * sizeof(*a_pointer)))
在常规算术中。
首先,binky视频可能会有所帮助。这是一个关于指针的好视频。对于算术,这里有一个例子:
int * pa = NULL;
int * pb = NULL;
pa += 1; // pa++. behind the scenes, add sizeof(int) bytes
assert((pa - pb) == 1);
print_out(pa); // possibly outputs 0x4
print_out(pb); // possibly outputs 0x0 (if NULL is actually bit-wise 0x0)
(请注意,严格递增包含空指针值的指针是未定义的行为。我们使用 NULL 是因为我们只对指针的值感兴趣。通常,仅在指向数组元素时使用递增/递减)。
下面展示了两个重要的概念
- 整数与指针的加/减意味着将指针向前/向后移动 N 个元素。因此,如果一个 int 是 4 字节大,pa 在我们的平台上可能包含 0x4 后递增 1。
- 另一个指针减去一个指针意味着得到它们的距离,由元素测量。所以从 pa 中减去 pb 将得到 1,因为它们有一个元素距离。
举一个实际的例子。假设您编写了一个函数并且人们为您提供了一个开始和结束指针(在 C++ 中很常见):
void mutate_them(int *begin, int *end) {
// get the amount of elements
ptrdiff_t n = end - begin;
// allocate space for n elements to do something...
// then iterate. increment begin until it hits end
while(begin != end) {
// do something
begin++;
}
}
ptrdiff_t
是什么类型的(结束 - 开始)。对于某些编译器,它可能是“int”的同义词,但对于另一种编译器可能是另一种类型。一个人不知道,所以选择通用 typedef ptrdiff_t
。
应用 NLP,称之为地址算术。'指针'之所以令人恐惧和误解,主要是因为它们是由错误的人教的和/或在错误的阶段以错误的方式使用错误的例子。难怪没有人“得到”它。
在教授指针时,教师继续说“p是指向a的指针,p的值是a的地址”等等。它只是行不通。这是供您建造的原材料。练习它,你的学生会得到它。
'int a',a是一个整数,它存储整数类型的值。'int* p', p 是一个'int star',它存储'int star' 类型的值。
'a' 是如何获取存储在 a 中的 'what' 整数(尽量不要使用 'value of a')
'b = a' 为此,双方必须是相同的类型。如果 a 是 int,则 b 必须能够存储 int。(所以______ b,空白处用'int'填充)
'p = &a' 为此,双方必须是相同的类型。如果 a 是整数,&a 是地址,则 p 必须能够存储整数的地址。(所以______ p,空白处用'int *'填充)
现在以不同的方式编写 int *p 以显示类型信息:
整数* | p
什么是'p'?回答:它是'int *'。所以'p'是一个整数的地址。
整数 | *p
什么是'*p'?答:它是一个'int'。所以 '*p' 是一个整数。
现在到地址算术:
诠释一个; a=1; a=a+1;
我们在'a = a + 1'中做什么?将其视为“下一个”。因为 a 是一个数字,这就像说“下一个数字”。由于 a 持有 1,因此说“下一个”将使其成为 2。
// 错误的例子。你被警告了!!!int *p int a; p = &a; p=p+1;
我们在'p = p + 1'中做什么?它仍然在说“下一步”。这一次,p 不是数字而是地址。所以我们所说的是“下一个地址”。下一个地址取决于数据类型,更具体地说取决于数据类型的大小。
printf("%d %d %d", sizeof(char), sizeof(int), sizeof(float));
所以地址的“下一个”将向前移动 sizeof(数据类型)。
这对我和我曾经教过的所有人都有效。
我认为指针算术的一个很好的例子是以下字符串长度函数:
int length(char *s)
{
char *str = s;
while(*str++);
return str - s;
}
所以,要记住的关键是指针只是一个字大小的变量,它是为解除引用而键入的。这意味着无论是void *,int *,long long **,它仍然只是一个字大小的变量。这些类型之间的区别在于编译器认为取消引用的类型。澄清一下,字长意味着虚拟地址的宽度。如果你不知道这是什么意思,只要记住在 64 位机器上,指针是 8 个字节,而在 32 位机器上,指针是 4 个字节。地址的概念对于理解指针非常重要。地址是能够唯一标识内存中某个位置的数字。内存中的所有东西都有一个地址。出于我们的目的,我们可以说每个变量都有一个地址。这不一定总是正确的,但编译器让我们假设这一点。地址本身是字节粒度的,这意味着 0x0000000 指定内存的开头,而 0x00000001 是内存中的一个字节。这意味着通过向指针添加一个,我们将一个字节向前移动到内存中。现在,让我们使用数组。如果你创建一个 quux 类型的数组,它有 32 个元素大,它将从分配的开头跨越到分配的开头加上 32*sizeof(quux),因为数组的每个单元都是 sizeof(quux) 大的。所以,实际上,当我们用 array[n] 指定数组的元素时,这只是 *(array+sizeof(quux)*n) 的语法糖(简写)。指针运算实际上只是更改您所指的地址,这就是我们可以使用 strlen 实现的原因 0x00000001 是内存中的一个字节。这意味着通过向指针添加一个,我们将一个字节向前移动到内存中。现在,让我们使用数组。如果你创建一个 quux 类型的数组,它有 32 个元素大,它将从分配的开头跨越到分配的开头加上 32*sizeof(quux),因为数组的每个单元都是 sizeof(quux) 大的。所以,实际上,当我们用 array[n] 指定数组的元素时,这只是 *(array+sizeof(quux)*n) 的语法糖(简写)。指针运算实际上只是更改您所指的地址,这就是我们可以使用 strlen 实现的原因 0x00000001 是内存中的一个字节。这意味着通过向指针添加一个,我们将一个字节向前移动到内存中。现在,让我们使用数组。如果你创建一个 quux 类型的数组,它有 32 个元素大,它将从分配的开头跨越到分配的开头加上 32*sizeof(quux),因为数组的每个单元都是 sizeof(quux) 大的。所以,实际上,当我们用 array[n] 指定数组的元素时,这只是 *(array+sizeof(quux)*n) 的语法糖(简写)。指针运算实际上只是更改您所指的地址,这就是我们可以使用 strlen 实现的原因 它将从分配的开始跨越到分配的开始加上 32*sizeof(quux),因为数组的每个单元格都是 sizeof(quux) 大的。所以,实际上,当我们用 array[n] 指定数组的元素时,这只是 *(array+sizeof(quux)*n) 的语法糖(简写)。指针运算实际上只是更改您所指的地址,这就是我们可以使用 strlen 实现的原因 它将从分配的开始跨越到分配的开始加上 32*sizeof(quux),因为数组的每个单元格都是 sizeof(quux) 大的。所以,实际上,当我们用 array[n] 指定数组的元素时,这只是 *(array+sizeof(quux)*n) 的语法糖(简写)。指针运算实际上只是更改您所指的地址,这就是我们可以使用 strlen 实现的原因
while(*n++ != '\0'){
len++;
}
因为我们只是一个字节一个字节地扫描,直到我们达到零。希望有帮助!
有几种方法可以解决它。
大多数 C/C++ 程序员认为的直观方法是指针是内存地址。litb 的示例采用了这种方法。如果你有一个空指针(在大多数机器上对应于地址 0),并且你加上一个 int 的大小,你会得到地址 4。这意味着指针基本上只是花哨的整数。
不幸的是,这存在一些问题。首先,它可能行不通。不能保证空指针实际使用地址 0。(尽管将常量 0 分配给指针会产生空指针)。
此外,不允许增加空指针,或者更一般地说,指针必须始终指向分配的内存(或过去的一个元素),或特殊的空指针常量 0。
所以一个更正确的想法是指针只是迭代器,允许你迭代分配的内存。这确实是 STL 迭代器背后的关键思想之一。它们被建模为非常像指针,并提供修补原始指针以作为适当的迭代器工作的特化。
例如,这里给出了更详细的解释。
但是后一种观点意味着您应该真正解释 STL 迭代器,然后简单地说指针是这些迭代器的特例。您可以增加一个指针以指向缓冲区中的下一个元素,就像您可以使用std::vector<int>::iterator
. 它可以指向数组末尾的一个元素,就像任何其他容器中的结束迭代器一样。您可以减去指向同一缓冲区的两个指针以获得它们之间的元素数量,就像使用迭代器一样,并且就像使用迭代器一样,如果指针指向单独的缓冲区,则无法有意义地比较它们。(作为一个实际的例子,为什么不考虑在分段的内存空间中发生了什么。指向不同段的两个指针之间的距离是多少?)
当然,在实践中,CPU 地址和 C/C++ 指针之间存在非常密切的相关性。但它们并不完全相同。指针有一些限制,这些限制在您的 CPU 上可能不是绝对必要的。
当然,大多数 C++ 程序员在第一次理解时都会蒙混过关,即使它在技术上是不正确的。它通常与您的代码最终表现得足够接近,以至于人们认为他们得到了它,然后继续前进。
但是对于来自 Java 并且刚刚从头开始学习指针的人来说,后一种解释可能同样容易理解,并且以后会给他们带来更少的惊喜。
这是关于指针算术的一个非常好的链接
例如:
指针和数组
计算 ptr + i 的地址的公式,其中 ptr 的类型为 T *。那么地址的公式是:
addr( ptr + i ) = addr( ptr ) + [ sizeof( T ) * i ]
对于 32 位平台的 int 类型,addr(ptr+i) = addr(ptr)+4*i;
减法
我们还可以计算 ptr - i。例如,假设我们有一个名为 arr 的 int 数组。诠释 arr [ 10 ] ; 整数 * p1, * p2 ;
p1 = arr + 3 ; // p1 == & arr[ 3 ]
p2 = p1 - 2 ; // p1 == & arr[ 1 ]