跳转到内容

C++ 编程/运算符/指针

来自维基教科书,开放书籍,开放世界

指针,运算符 *

[编辑 | 编辑源代码]

* 运算符用于声明指针类型,但也用于获取指针指向的变量。

指针 a 指向变量 b。请注意,b 存储数字,而 a 存储 b 在内存中的地址(1462)

指针是重要的数据类型,因为它们具有特殊的特性。它们可用于指示变量,而无需实际创建该类型的变量。由于指针可能难以理解,因此应花一些精力来理解它们赋予程序员的强大功能。

指针具有非常描述性的名称。指针变量只存储内存地址,通常是其他变量的地址。本质上,它们指向另一个变量的内存位置,即计算机内存上的一个保留位置。可以使用指针 将变量的位置传递给函数,这使得函数的指针可以使用变量空间,以便它可以检索或修改其数据。你甚至可以拥有指向指针的指针,以及指向指向指针的指针等等。

通过在声明中的变量名前添加一个*来声明指针,如下例所示

int* x;  // pointer to int.
int * y; // pointer to int. (legal, but rarely used)
int *z;  // pointer to int.
int*i;   // pointer to int. (legal, but rarely used)

注意
* 的邻接或空格的使用没有影响,只有在类型(关键字或定义)之后使用。
由于历史原因,一些程序员将特定用法称为

// C codestyle
int *z;

// C++ codestyle
int* z;

如之前在 编码风格规范部分 所示,建议坚持使用单一风格。

注意,* 仅与以下声明相关联

int* i, j;  // CAUTION! i is pointer to int, j is int.
int *i, *j; // i and j are both pointer to int.

你还可以将多个指针链接在一起,如下例所示

int **i;  // Pointer to pointer to int.
int ***i; // Pointer to pointer to pointer to int (rarely used).

每个人都会对指针感到困惑,因为将值赋给指针可能有点棘手,但如果你知道基础知识,你可以更容易地进行操作。通过仔细阅读示例,而不是简单的描述,尝试理解所呈现给你的要点。

将值赋给指针(非字符类型)
double vValue = 25.0;// declares and initializes a vValue as type double
double* pValue = &vValue;
cout << *pValue << endl;

第二条语句使用 “&” 引用运算符和 "*" 来告诉编译器这是一个指针变量,并将 vValue 变量的地址赋给它。在最后一条语句中,它通过使用 "*" 运算符解引用指针来输出 vValue 变量的值。

将值赋给指针(字符类型)
char pArray[20] = {"Name1"};
char* pValue(pArray);// or 0 in old compilers, nullptr is a part of C++0X
pValue = "Value1";
cout << pValue  << endl ;// this will return the Value1;

正如前面提到的,指针是一个存储另一个变量地址的变量,因此你需要初始化数组,因为你不能直接将值赋给它。你需要使用指针直接或在混合上下文中使用指向数组的指针,为了单独使用指针,请查看下一个示例。

char* pValue("String1");
pValue = "String2";
cout << pValue << endl ;

请记住,你不能让指针单独存在或将其初始化为 nullptr,因为这会导致错误。编译器认为它是一个内存地址持有者变量,因为你没有指向任何东西,并且会尝试将值赋给它,这会导致错误,因为它没有指向任何地方。

解引用

[编辑 | 编辑源代码]

这是*运算符。它用于获取指针指向的变量。它也用于声明指针类型。

当你拥有一个指针时,你需要某种方法来访问它指向的内存。当它放在指针前面时,它会给出它指向的变量。这是一个左值,因此你可以将值赋给它,甚至可以从它初始化一个引用。

#include <iostream>

int main()
{
  int i;
  int * p = &i;
  i = 3;

  std::cout<<*p<<std::endl; // prints "3"

  return 0;
}

由于&运算符的结果是一个指针,*&i是有效的,尽管它没有任何效果。

现在,当你将*运算符与类结合使用时,你可能会注意到一个问题。它的优先级低于.!请参见示例

struct A { int num; };

A a;
int i;
A * p;

p = &a;
a.num = 2;

i = *p.num; // Error! "p" isn't a class, so you can't use "."
i = (*p).num;

错误发生是因为编译器首先查看 p.num(“.” 的优先级高于 “*”),并且因为 p 没有名为 num 的成员,所以编译器会向你显示错误。使用分组符号来更改优先级可以解决此问题。

如果必须编写(*p).num很多次,尤其是当你有很多类时,这将非常耗时。想象一下编写(*(*(*(*MyPointer).Member).SubMember).Value).WhatIWant!因此,存在一个特殊的运算符,->。而不是(*p).num,你可以编写p->num,它在所有方面都是完全相同的。现在你可以编写MyPointer->Member->SubMember->Value->WhatIWant。这对大脑来说容易得多!

空指针

[编辑 | 编辑源代码]

空指针是指针的一种特殊状态。这意味着指针不指向任何东西。尝试解引用(使用*->运算符)空指针是错误的。可以使用常量零引用空指针,如下例所示

int i;
int *p;

p = 0; //Null pointer.
p = &i; //Not the null pointer.

请注意,你不能将指针赋给整数,即使是零。它必须是常量。以下代码是错误的

int i = 0;
int *p = i; //Error: 0 only evaluates to null if it's a pointer

有一个旧的宏,在标准库中定义,它源自 C 语言,并且不一致地发展为 #define NULL ((void *)0),这使得NULL始终等于空指针值(本质上是 0)。

注意
建议尽可能避免使用宏和定义。在当前情况下,NULL 不是类型安全的。任何使用它的理由(例如,为了查看指针的使用)都可以通过正确命名指针变量来解决。

由于空指针是 0,因此它始终与 0 进行比较。就像整数一样,如果你在真/假表达式中使用它,如果它是空指针,它将返回假,如果它是非空指针,则返回真

#include <iostream>

void IsNull (int * p)
{
  if (p)
    std::cout<<"Pointer is not NULL"<<std::endl;
  else
    std::cout<<"Pointer is NULL"<<std::endl;
}

int main()
{
  int * p;
  int i;

  p = NULL;
  IsNull(p);
  p = &i;
  IsNull(&i);
  IsNull(p);
  IsNull(NULL);

  return 0;
}

此程序将输出指针为 NULL,然后输出两次指针不为 NULL,然后再次输出指针为 NULL。


Clipboard

要完成
简要介绍指针作为数据成员(以便它可以从文本的函数和类部分进行交叉链接)


指针和多维数组

[编辑 | 编辑源代码]
指针和多维非字符数组

需要了解如何初始化二维数组、将值赋给数组以及从数组返回值。有关数组的详细信息,请参见 1.4.10.1.1 数组 部分。但是,在与指针理解相关的部分,数组也会在此处提及。

主要对象是
  1. 将值赋给多维指针
  2. 如何在多维数组中使用指针
  3. 返回值
  4. 初始化指针和数组
  5. 如何安排它们的值
  1. 将值赋给多维指针。

在非字符类型中,你需要使用数组和指针,因为指针以特殊方式处理 char* 类型,而以其他方式处理其他类型,例如,仅引用地址或获取地址并通过间接方法获取值。

如果你以这种方式声明它

double (*pDVal)[2] = {{1,2},{1,2}};

这可能会产生错误!因为非字符类型中使用的指针仅直接使用,而在字符类型中,通过首先分配一个变量来引用另一个变量的地址,然后你可以间接获取它的(已分配变量的)值!

double ArrayVal[5][5] = {
{1,2,3,4,5},
{1,2,3,4,5},
{1,2,3,4,5},
{1,2,3,4,5},
{1,2,3,4,5},
};

double(*pArray)[5] = ArrayVal;
*(*(pArray+0)+0) = 10;
*(*(pArray+0)+1) = 20;
*(*(pArray+0)+2) = 30;
*(*(pArray+0)+3) = 40;
*(*(pArray+0)+4) = 50;
*(*(pArray+1)+0) = 60;
*(*(pArray+1)+1) = 70;
*(*(pArray+1)+2) = 80;
*(*(pArray+1)+3) = 90;
*(*(pArray+1)+4) = 100;
*(*(pArray+2)+0) = 110;
*(*(pArray+2)+1) = 120;
*(*(pArray+2)+2) = 130;
*(*(pArray+2)+3) = 140;
*(*(pArray+2)+4) = 150;
*(*(pArray+3)+0) = 160;
*(*(pArray+3)+1) = 170;
*(*(pArray+3)+2) = 180;
*(*(pArray+3)+3) = 190;
*(*(pArray+3)+4) = 200;
*(*(pArray+4)+0) = 210;
*(*(pArray+4)+1) = 220;
*(*(pArray+4)+2) = 230;
*(*(pArray+4)+3) = 240;
*(*(pArray+4)+4) = 250;

还有另一种方法,而不是

*(*(pArray+0)+0)

它是

*(pArray[0]+0)

你可以使用其中之一通过指针将值赋给数组,为了返回结果,可以使用适当的数组或指针。

指针和多维字符数组

这有点难,甚至很难记住,所以我建议你不断练习,直到你掌握指针的精神!你不能将指针 + 多维数组与字符类型一起使用。仅用于非字符类型。

具有字符类型的多维指针
char* pVar[5] = { "Name1" , "Name2" , "Name3", "Name4", "Name5" }

pVar[0] = "XName01";
cout << pVar[0] << endl ; //this will return the XName01 instead Name1 which was replaced with Name1.

这里第一个语句中的 5 是行数(指针中不需要指定列数,只有在数组中),下一个语句将另一个字符串赋给位置 0,即第一个语句的第一个位置。最后返回结果。

动态内存分配

在你的系统内存中,每个内存块都有一个地址,因此当你编译代码时,所有变量在开始时都会在内存中保留一些空间,但在动态内存分配中,只有在需要时才会保留,这意味着在该语句的执行时间,它会在你的空闲空间区域(未使用空间)中分配内存,因此这意味着如果没有空间或没有连续的块,编译器将生成错误消息。

动态内存分配和非字符类型指针

这与将非字符一维数组赋给指针相同

double* pVal = new double[5];
//or double* pVal = new double; // this line leaves out the necessary memory allocation
*(pVal+0) = 10;
*(pVal+1) = 20;
*(pVal+2) = 30;
*(pVal+3) = 40;
*(pVal+4) = 50;

cout << *(pVal+0) << endl;

第一条语句的 Lside(左侧)声明了一个变量,Rside 请求为双精度型变量分配空间,并在您的内存中的空闲空间区域分配它。因此,下一个和接下来的语句,您可以看到它增加了整数值,这意味着 *(pVal+0) pVal -> 如果单独使用它将返回对应于第一个内存块的地址。(用于存储 10)并且 0 表示前进 0 个块,但它的 0 表示不要移动,停留在当前内存块中,并且您使用 () 括号,因为 + < * < () 考虑优先级,因此您需要使用括号避免先计算 *

  • 称为间接运算符,它解引用指针并返回与内存块相对应的值。

(内存块地址+步长)

  • -> 解引用。
动态内存分配和指针字符类型
char* pVal = new char;
pVal = "Name1";
cout << pVal << endl;
delete pVal; //this will delete the allocated space
pVal = nullptr //null the pointer

您可以看到这与静态内存声明相同,在静态声明中它会

char* pVal("Name1");
动态内存分配和指针非字符数组类型
double (*pVal2)[2]= new double[2][2]; //this will add 2x2 memory blocks to type double pointer
*(*(pVal2+0)+0) = 10;
*(*(pVal2+0)+1) = 10;
*(*(pVal2+0)+2) = 10;
*(*(pVal2+0)+3) = 10;
*(*(pVal2+0)+4) = 10;
*(*(pVal2+1)+0) = 10;
*(*(pVal2+1)+1) = 10;
*(*(pVal2+1)+2) = 10;
*(*(pVal2+1)+3) = 10;
*(*(pVal2+1)+4) = 10;
delete [] pVal; //the dimension does not matter; you only need to mention []
pVal = nullptr

注意

Never use a multidimensional pointer array with char type, as it will generate an error.
char (*pVal)[5] ;// this is different from pointer of array

// which is
char* pVal[5] ;

但两者不同。

指向类的指针

[编辑 | 编辑源代码]

间接运算符 ->

[编辑 | 编辑源代码]

此指针间接运算符用于访问类指针的成员。

成员解引用运算符 .*

[编辑 | 编辑源代码]

此指向成员的解引用运算符用于访问与特定类实例关联的变量,前提是提供了相应的指针。

指向成员的间接运算符 ->*

[编辑 | 编辑源代码]

此指向成员的间接运算符用于访问与一个指针指向的类实例关联的变量,前提是提供了另一个合适的指向成员的指针。

指向函数的指针

[编辑 | 编辑源代码]

当用于指向函数时,指针可以非常强大。可以在程序中的任何位置调用函数,只需要知道它接受的参数类型。 指向函数的指针 在标准库中被多次使用,并为其他需要适应任何用户代码的库提供了强大的系统。本例将在 函数部分 中进行更深入的探讨。

华夏公益教科书