有没有人有指针算术的好文章或解释(博客,例子)?图中的观众是一群学习C和C ++的Java程序员。
答案 0 :(得分:56)
这是我学习指针的地方:http://www.cplusplus.com/doc/tutorial/pointers.html
一旦理解了指针,指针算法就很容易了。它与常规算术之间的唯一区别是,您添加到指针的数字将乘以指针所指向的类型的大小。例如,如果指针指向int
且int
的大小为4个字节,则(pointer_to_int + 4)
将计算为前面16个字节(4个字节)的内存地址。
所以当你写
(a_pointer + a_number)
在指针算术中,真正发生的是
(a_pointer + (a_number * sizeof(*a_pointer)))
在常规算术中。
答案 1 :(得分:51)
首先,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因为我们只对指针的值感兴趣。通常,只指向元素时使用递增/递减一个数组)。
以下显示了两个重要概念
一个实际的例子。假设你编写了一个函数,人们为你提供了一个开始和结束指针(在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
。
答案 2 :(得分:6)
应用NLP,称之为地址算术。人们担心和误解“指针”主要是因为他们是由错误的人和/或在错误的阶段以错误的方式用错误的例子教导的。难怪没有人“得到”它。
当教学指针时,教师继续讲述“p是指向a的指针,p的值是a的地址”,依此类推。它不会工作。这是您构建的原材料。用它来练习,你的学生就能得到它。'int a',a是一个整数,它存储整数类型的值。 'int * p',p是'int star',它存储'int star'类型值。
'a'是你如何得到'什么'整数存储在一个(尽量不使用'a'的值') '& a'是你如何得到自己存储的'地方'(试着说'地址')
'b = a'为此起作用,双方必须属于同一类型。如果a是int,则b必须能够存储int。 (所以______ b,空白填充'int')
'p =& a'为此起作用,双方必须属于同一类型。如果a是整数,& a是地址,则p必须能够存储整数的地址。 (所以______ p,空白填充'int *')
现在以不同方式编写int * p以显示类型信息:
int * | P
什么是'p'? ans:它是'int *'。所以'p'是整数的地址。int | * P
什么是'* p'? ans:它是'int'。所以'* p'是一个整数。现在进行地址算术:
int a; 一个= 1; A = A + 1;
我们在'a = a + 1'中做了什么?把它想象成“下一个”。因为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));
所以地址的'next'将向前移动sizeof(数据类型)。
这对我和我曾经教过的所有人都有用。
答案 3 :(得分:3)
我认为指针算法的一个很好的例子是以下字符串长度函数:
int length(char *s)
{
char *str = s;
while(*str++);
return str - s;
}
答案 4 :(得分:1)
因此,要记住的关键是指针只是一个字型大小的变量,用于解除引用。这意味着无论它是一个void *,int *,long long **,它仍然只是一个字大小的变量。这些类型之间的区别是编译器认为解除引用的类型。只是为了澄清,字大小意味着虚拟地址的宽度。如果您不知道这意味着什么,请记住在64位机器上,指针是8个字节,而在32位机器上,指针是4个字节。理解指针时,地址的概念非常重要。地址是能够唯一地识别存储器中的特定位置的数字。记忆中的一切都有一个地址。出于我们的目的,我们可以说每个变量都有一个地址。这不一定总是正确的,但编译器让我们假设这一点。地址本身是字节粒度,意味着0x0000000指定内存的开头,0x00000001是内存中的一个字节。这意味着通过向指针添加一个,我们将一个字节向前移动到内存中。现在,让我们采取数组。如果你创建一个类型为quux的数组,其大小为32个元素,它将从它的分配开始到它的分配开始加上32 * sizeof(quux),因为数组的每个单元都是sizeof(quux)big。所以,实际上当我们用array [n]指定一个数组元素时,这只是*(array + sizeof(quux)* n)的语法糖(简写)。指针算法实际上只是改变了你所指的地址,这就是我们可以用
实现strlen的原因while(*n++ != '\0'){
len++;
}
因为我们只是逐字节扫描,直到我们达到零。希望有所帮助!
答案 5 :(得分:0)
有几种方法可以解决它。
大多数C / C ++程序员都想到的直观方法是指针是内存地址。 litb的例子采用了这种方法。如果你有一个空指针(在大多数机器上对应于地址0),并且你添加一个int的大小,你得到地址4.这意味着指针基本上只是花哨的整数。
不幸的是,这有一些问题。首先,它可能无法正常工作。 空指针不能保证实际使用地址0.(尽管将常量0赋给指针会产生空指针)。
此外,不允许增加空指针,或者更一般地说,指针必须始终指向已分配的内存(或过去的一个元素),或特殊的空指针常量0。
因此,更正确的思考方式是指针只是迭代器,允许您迭代已分配的内存。 这实际上是STL迭代器背后的关键思想之一。它们被建模为非常像指针,并提供修补原始指针以作为正确迭代器的特化。
例如,here给出了更详细的解释。
但后一种观点意味着你应该真正解释STL迭代器,然后简单地说指针是这些的一个特例。您可以将指针递增以指向缓冲区中的下一个元素,就像std::vector<int>::iterator
一样。它可以将一个元素指向数组末尾,就像任何其他容器中的结束迭代器一样。您可以减去指向同一缓冲区的两个指针以获取它们之间的元素数量,就像使用迭代器一样,就像使用迭代器一样,如果指针指向单独的缓冲区,则可以不有意义地比较它们。 (有关其原因的实际示例,请考虑分段存储空间中发生的情况。指向不同段的两个指针之间的距离是多少?)
当然,在实践中,CPU地址和C / C ++指针之间存在非常密切的关联。但他们并非完全同样的事情。指针有一些限制,可能在您的CPU上不是必需的。
当然,大多数C ++程序员在第一次理解时都会混淆,即使它在技术上是不正确的。它通常足够接近你的代码如何最终表现出人们认为他们得到它,并继续前进。
但是对于那些来自Java的人,只是从头开始学习指针,后一种解释可能同样容易理解,并且稍后会减少对它们的惊喜。
答案 6 :(得分:0)
这是关于指针算术的链接here非常好的
例如:
指针和数组
用于计算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数组。 int arr [10]; int * p1,* p2;
p1 = arr + 3 ; // p1 == & arr[ 3 ]
p2 = p1 - 2 ; // p1 == & arr[ 1 ]