请在说明中包含一个示例。
最佳答案
回顾基本术语
通常情况下就足够了 - 除非您正在编写程序集 - 设想一个 指针包含一个数字内存地址,其中 1 表示进程内存中的第二个字节,2 表示第三个字节,3 表示第四个字节,依此类推....
当您想访问指针指向的内存中的数据/值时 - 具有该数字索引的地址的内容 - 那么您 取消引用 指针。
不同的计算机语言有不同的符号来告诉编译器或解释器您现在对指向对象的(当前)值感兴趣 - 我在下面关注 C 和 C++。
指针场景
考虑在 C 中,给定一个指针,例如
p
以下...const char* p = "abc";
...带有用于编码字母“a”、“b”、“c”的数值的四个字节和一个表示文本数据结尾的 0 字节,存储在内存中的某处,以及它的数字地址数据存储在
p
.这种 C 在内存中编码文本的方式被称为 ASCIIZ .例如,如果字符串文字恰好位于地址 0x1000 和
p
0x2000 处的 32 位指针,内存内容将是:Memory Address (hex) Variable name Contents
1000 'a' == 97 (ASCII)
1001 'b' == 98
1002 'c' == 99
1003 0
...
2000-2003 p 1000 hex
请注意,地址 0x1000 没有变量名称/标识符,但我们可以使用存储其地址的指针间接引用字符串文字:
p
.取消引用指针
引用字符
p
指向,我们取消引用 p
使用这些符号之一(同样,对于 C):assert(*p == 'a'); // The first character at address p will be 'a'
assert(p[1] == 'b'); // p[1] actually dereferences a pointer created by adding
// p and 1 times the size of the things to which p points:
// In this case they're char which are 1 byte in C...
assert(*(p + 1) == 'b'); // Another notation for p[1]
您还可以在指向的数据中移动指针,随时取消引用它们:
++p; // Increment p so it's now 0x1001
assert(*p == 'b'); // p == 0x1001 which is where the 'b' is...
如果您有一些可以写入的数据,那么您可以执行以下操作:
int x = 2;
int* p_x = &x; // Put the address of the x variable into the pointer p_x
*p_x = 4; // Change the memory at the address in p_x to be 4
assert(x == 4); // Check x is now 4
上面,您一定在编译时就知道您需要一个名为
x
的变量。 ,并且代码要求编译器安排它应该存储的位置,确保地址可以通过 &x
获得。 .取消引用和访问结构数据成员
在 C 中,如果您有一个变量是指向具有数据成员的结构的指针,您可以使用
->
访问这些成员。解引用运算符:typedef struct X { int i_; double d_; } X;
X x;
X* p = &x;
p->d_ = 3.14159; // Dereference and access data member x.d_
(*p).d_ *= -1; // Another equivalent notation for accessing x.d_
多字节数据类型
要使用指针,计算机程序还需要深入了解所指向的数据类型——如果该数据类型需要多个字节来表示,那么指针通常指向数据中编号最低的字节。
所以,看一个稍微复杂的例子:
double sizes[] = { 10.3, 13.4, 11.2, 19.4 };
double* p = sizes;
assert(p[0] == 10.3); // Knows to look at all the bytes in the first double value
assert(p[1] == 13.4); // Actually looks at bytes from address p + 1 * sizeof(double)
// (sizeof(double) is almost always eight bytes)
++p; // Advance p by sizeof(double)
assert(*p == 13.4); // The double at memory beginning at address p has value 13.4
*(p + 2) = 29.8; // Change sizes[3] from 19.4 to 29.8
// Note earlier ++p and + 2 here => sizes[3]
指向动态分配内存的指针
有时你不知道你需要多少内存,直到你的程序运行并看到什么数据被抛出......然后你可以使用
malloc
动态分配内存。 .通常的做法是将地址存储在指针中......int* p = (int*)malloc(sizeof(int)); // Get some memory somewhere...
*p = 10; // Dereference the pointer to the memory, then write a value in
fn(*p); // Call a function, passing it the value at address p
(*p) += 3; // Change the value, adding 3 to it
free(p); // Release the memory back to the heap allocation library
在 C++ 中,内存分配通常使用
new
完成。运算符,并使用 delete
解除分配:int* p = new int(10); // Memory for one int with initial value 10
delete p;
p = new int[10]; // Memory for ten ints with unspecified initial value
delete[] p;
p = new int[10](); // Memory for ten ints that are value initialised (to 0)
delete[] p;
另请参阅下面的 C++ 智能指针。
地址丢失和泄露
通常,指针可能是某些数据或缓冲区存在于内存中的唯一指示。如果需要持续使用该数据/缓冲区,或者能够调用
free()
或 delete
为了避免内存泄漏,程序员必须对指针的拷贝进行操作......const char* p = asprintf("name: %s", name); // Common but non-Standard printf-on-heap
// Replace non-printable characters with underscores....
for (const char* q = p; *q; ++q)
if (!isprint(*q))
*q = '_';
printf("%s\n", p); // Only q was modified
free(p);
...或仔细安排任何更改的逆转...
const size_t n = ...;
p += n;
...
p -= n; // Restore earlier value...
free(p);
C++ 智能指针
在 C++ 中,最好使用 smart pointer对象来存储和管理指针,当智能指针的析构函数运行时自动释放它们。由于 C++11 标准库提供了两个,
unique_ptr
当分配的对象只有一个所有者时......{
std::unique_ptr<T> p{new T(42, "meaning")};
call_a_function(p);
// The function above might throw, so delete here is unreliable, but...
} // p's destructor's guaranteed to run "here", calling delete
...和
shared_ptr
对于股权(使用 reference counting )...{
auto p = std::make_shared<T>(3.14, "pi");
number_storage1.may_add(p); // Might copy p into its container
number_storage2.may_add(p); // Might copy p into its container } // p's destructor will only delete the T if neither may_add copied it
空指针
在 C 中,
NULL
和 0
- 另外在 C++ 中 nullptr
- 可用于指示指针当前不保存变量的内存地址,不应取消引用或在指针运算中使用。例如:const char* p_filename = NULL; // Or "= 0", or "= nullptr" in C++
int c;
while ((c = getopt(argc, argv, "f:")) != -1)
switch (c) {
case f: p_filename = optarg; break;
}
if (p_filename) // Only NULL converts to false
... // Only get here if -f flag specified
在 C 和 C++ 中,就像内置数字类型不一定默认为
0
,也不是 bools
至 false
, 指针并不总是设置为 NULL
.当它们是 static
时,所有这些都设置为 0/false/NULL变量或(仅限 C++)静态对象或其基类的直接或间接成员变量,或进行零初始化(例如 new T();
和 new T(x, y, z);
对包括指针在内的 T 成员执行零初始化,而 new T;
不会)。此外,当您分配
0
时, NULL
和 nullptr
对于指针,指针中的位不一定全部复位:指针在硬件级别可能不包含“0”,或者在您的虚拟地址空间中引用地址 0。如果有理由,允许编译器在那里存储其他东西,但无论它做什么 - 如果你来比较指针到 0
, NULL
, nullptr
或分配了其中任何一个的另一个指针,比较必须按预期工作。因此,在编译器级别的源代码之下,“NULL”在 C 和 C++ 语言中可能有点“神奇”......更多关于内存地址,以及为什么你可能不需要知道
更严格地说,初始化的指针存储一个位模式标识
NULL
或(通常是 virtual )内存地址。简单的情况是,这是进程整个虚拟地址空间的数字偏移量;在更复杂的情况下,指针可能与某个特定的内存区域相关,CPU 可以根据 CPU“段”寄存器或位模式中编码的某种段 ID 方式选择该内存区域,和/或根据使用地址的机器代码指令。
例如,一个
int*
正确初始化为指向 int
变量可能 - 在转换为 float*
之后- 访问“GPU”内存中的内存与 int
中的内存截然不同变量是,然后一旦转换为函数指针并用作函数指针,它可能会指向程序的其他不同内存保存机器操作码(int*
的数值实际上是这些其他内存区域内的随机、无效指针)。像 C 和 C++ 这样的 3GL 编程语言倾向于隐藏这种复杂性,例如:
关于c++ - "dereferencing"指针是什么意思?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4955198/