指针

在前面的章节中,变量被解释为可以通过其标识符(其名称)来访问在计算机的存储单元。通过这种方式,该程序不需要关心在存储器中的数据的物理地址; 它只是使用每当它需要参考变量的标识符。

为一个C ++程序,一个计算机的存储器是像存储单元的继承,在大小每一个字节,并且每一个独特的地址。这些单字节的存储单元是有序的方式,允许数据表示大于一个字节大的占据具有连续地址的存储器单元。

这种方式,每个单元可以很容易地通过它的唯一地址的装置位于存储器。例如,与地址的存储单元 1776始终与地址的单元后紧接着 1775与前面一个与 1777,并且是后正好一个个细胞 776和正好一个个细胞之前 2776

当一个变量被声明,以存储其值所需要的存储器被分配在存储器(其存储器地址)的特定位置。一般来说,C ++程序不积极决定在哪里它的变量存储的确切内存地址。幸运的是,这一任务留给该程序运行环境-通常,一个操作系统,决定上运行的特定存储器位置。然而,一个程序能够在运行期间获得的变量的地址,以便访问是在相对于它的某个位置的数据单元为它可能是有用的。

地址运算符(&)

变量的地址可以通过一个变量的名称与符号号之前获得( &),被称为 运营商地址的。例如:

 
foo = &myvar;
 


这将赋予变量的地址 myvarfoo; 由变量的名称前面 myvar地址的运营商&),我们不再分配变量本身的内容 foo,但它的地址。

在内存变量的实际地址不能运行之前是已知的,但是让我们假设,以有助于澄清一些概念,即 myvar在内存地址在运行时放置 1776

在这种情况下,可以考虑下面的代码片段:

1
2
3
myvar = 25;
foo = &myvar;
bar = myvar;
 


在包含在此执行后的每个变量的值示于下图中:

 

首先,我们已经分配值 25myvar(其地址在存储器我们假定为一个变量 1776)。

第二条语句指定 foo的地址 myvar,这是我们认为是 1776

最后,第三个语句,指定包含在值 myvarbar。这是一个标准的赋值操作,如已在前面的章节中做了很多次。

在第二和第三语句之间的主要区别是的外观 地址运算符&)。

存储另一个变量的地址(如可变 foo前面的例子中)是在C ++中叫做 指针。指针是在低级编程许多用途的语言非常强大的功能。过了一会儿,我们将看到如何声明和使用指针。

引用操作符(*)

如看到的,存储另一个变量的地址的变量被称为 指针。指针表示为“点”,其地址它们存储变量。

指针的一个有趣的特性是,它们可以被用来访问它们指向直接变量。这是通过指针名称与先前进行 反引用运算符*)。运营商本身可以理解为“价值指向。” 

因此,与前面例子中的值之后,发表声明如下:

 
baz = *foo;
 


这可以被读作:“  baz等于值所指向 foo”,并且陈述实际上赋值 25baz,因为 foo就是 1776,和指向的值由 1776(按照上述的例子)将 25

 
它清楚区分是重要 foo指的值 1776,而 *foo(带星号的 *标识符前述)指的是存储在地址的值 1776,在这种情况下是 25。请注意,包括或不包括的差异 引用操作(我已经添加了如何这些两个表达式可以被理解的说明性注释):

1
2
baz = foo;   // baz equal to foo (1776)
baz = *foo;  // baz equal to value pointed to by foo (25)  
 


参考和间接引用运营商因此补充:
  • &操作者的地址,并且可以简单地读作“的地址”
  • *引用操作,并且可以读作“的价值指向”

因此,他们有某种含义完全相反:与获得的地址 &可以被解除引用 *

此前,我们进行了以下两个赋值操作:

1
2
myvar = 25;
foo = &myvar;
 


这两个语句之后,所有的下列表达式将作为结果给予正确的:

1
2
3
4
myvar == 25
&myvar == 1776
foo == 1776
*foo == 25
 


第一个表达式是很清楚,考虑到赋值操作上进行 myvarmyvar=25。第二个使用地址运算符( &),它返回的地址 myvar,我们假定它有一个值 1776。三是有些显而易见的,因为第二个表达式是真实的,所执行的任务操作 foofoo=&myvar。第四个表达式使用 引用操作*),可以为“的价值指向”读取和价值指向 foo确实 25

所以,毕竟,你也可以推断,只要该地址所指向 foo不变,以下表达式也为真:

 
*foo == myvar
 


声明指针

由于指针直接引用它指向的值的能力,一个指针有不同的特性时,它指向一个 char比当它指向一个 int或一个 float。一旦解除引用的类型需要是已知的。并为该,指针的声明需要包括指针将指向的数据类型。

指针的声明语法如下:

type * name; 

其中, type是由指针指向的数据类型。这种类型不是指针本身的类型,但该数据的指针指向的类型。例如:

1
2
3
int * number;
char * character;
double * decimals;
 


这些都是指针的三个声明。每一个旨在指向一个不同的数据类型,但,实际上,所有的人都指针和所有的人都可能会占据存储器的空间相同数量(在指针的存储器的大小取决于平台当程序运行)。尽管如此,数据它们指向到不占用的空间相同的量也不是相同类型的:第一个指向一个 int,第二个到一个 char,并且最后一个一个 double。因此,虽然这三个实例变量是所有这些指针,它们实际上具有不同的类型: int*char*,和 double*分别,这取决于它们指向的类型。

注意,星号( *声明指针时使用)仅意味着它是一个指针(它是其型化合物说明的一部分),并且不应与混淆 引用操作看出早一点,但其也被写入具有星号( *)。它们仅仅是用相同的符号表示的两个不同的事情。

让我们看看上指针的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// my first pointer
#include <iostream>
using namespace std;

int main ()
{
  int firstvalue, secondvalue;
  int * mypointer;

  mypointer = &firstvalue;
  *mypointer = 10;
  mypointer = &secondvalue;
  *mypointer = 20;
  cout << "firstvalue is " << firstvalue << '\n';
  cout << "secondvalue is " << secondvalue << '\n';
  return 0;
}
firstvalue 10
secondvalue 20


请注意,即使没有 firstvalue,也没有 secondvalue被直接设置在程序中的任何值,都结了通过使用间接设置的值 mypointer。这是如何发生的:

首先, mypointer被分配firstvalue的使用地址运算符(地址 &)。然后,该值指出了通过 mypointer被分配的值 10。因为,在这一刻, mypointer是指向的存储器位置 firstvalue,这实际上改变的值 firstvalue

为了证明一个指针可以在其程序中的寿命期间指向不同的变量,该示例重复与工艺 secondvalue和相同的指针, mypointer

下面是一个例子一点点阐述:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// more pointers
#include <iostream>
using namespace std;

int main ()
{
  int firstvalue = 5, secondvalue = 15;
  int * p1, * p2;

  p1 = &firstvalue;  // p1 = address of firstvalue
  p2 = &secondvalue; // p2 = address of secondvalue
  *p1 = 10;          // value pointed to by p1 = 10
  *p2 = *p1;         // value pointed to by p2 = value pointed to by p1
  p1 = p2;           // p1 = p2 (value of pointer is copied)
  *p1 = 20;          // value pointed to by p1 = 20
  
  cout << "firstvalue is " << firstvalue << '\n';
  cout << "secondvalue is " << secondvalue << '\n';
  return 0;
}
firstvalue 10
secondvalue 20


每个赋值操作包括在每一行如何能够读注释:即替代符号( &)在“地址”,而星号( *)以“价值指向。” 

请注意,有带指针的表达式 p1p2,有和没有的 引用操作*)。使用表达式的含义 引用操作符(*)是一个不很大的不同。当该操作符之前的指针名称中,表达是指所指向的值,而当一个指针名称出现没有这个算子,它指的指针本身的值(即,什么指针指向的地址)。

可以请你注意的另一件事是该行:

 
int * p1, * p2;
 


这声明在前面的例子中所用的两个指针。但是请注意,有一个星号( *每个指针),以便既具有类型 int*(指针 int)。这是由于优先规则要求。请注意,如果,相反,代码为:

 
int * p1, p2;
 


p1确实是型 int*,但 p2将是类型 int。不计空格都用于这一目的。但不管怎么说,只是记得要放一星号每个指针足以满足大多数用户的指针感兴趣的宣布每条语句多个指针。甚至更好:使用不同的statemet每个变量。

指针和数组

阵列的概念是相关于指针。其实,阵列的工作非常像指向他们的第一个元素,而实际上,数组总是可以隐式转换为正确类型的指针。例如,请考虑以下两个声明:

1
2
int myarray [20];
int * mypointer;
 


下面的赋值操作将是有效的: 

 
mypointer = myarray;
 


在此之后, mypointermyarray将是等效,将具有非常类似的性质。的主要区别在于 mypointer,可以分配不同的地址,而 myarray无法被分配的任何东西,并且将始终代表类型的20个元素的相同块 int。因此,下面的分配将是无效的:

 
myarray = mypointer;
 


让我们来看看它融合数组和指针的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// more pointers
#include <iostream>
using namespace std;

int main ()
{
  int numbers[5];
  int * p;
  p = numbers;  *p = 10;
  p++;  *p = 20;
  p = &numbers[2];  *p = 30;
  p = numbers + 3;  *p = 40;
  p = numbers;  *(p+4) = 50;
  for (int n=0; n<5; n++)
    cout << numbers[n] << ", ";
  return 0;
}
10,20,30,40,50, 


指针和数组支持相同的操作集,具有相同的意义为两者。主要的区别在于指针可分配新地址,而数组则不能。

大约在数组一章中,括号( [])为指定数组的元素的索引进行了解释。唉,其实这些支架被称为对其操作 抵消运营商。它们解除引用可变它们按照同样 *的做法,但它们也括号之间添加的数量,以该地址被取消引用。例如:

1
2
a[5] = 0;       // a [offset of 5] = 0
*(a+5) = 0;     // pointed to by (a+5) = 0  
 


这两个表达式是等效和有效的,不仅如果 a是一个指针,而且如果 a是一个数组。请记住,如果一个数组,它的名字可以用来就像一个指向它的第一个元素。

指针初始化

指针可以初始化在它们被定义的非常时刻指向特定的位置:

1
2
int myvar;
int * myptr = &myvar;
 


此代码后的变量所得到的状态是相同的后:

1
2
3
int myvar;
int * myptr;
myptr = &myvar;
 


当指针被初始化,什么是初始化是它们指向的地址(即 myptr),从来没有被指向的值(即, *myptr)。因此,上面的代码不得与困惑:

1
2
3
int myvar;
int * myptr;
*myptr = &myvar;
 


反正这也没有太大的意义(而不是有效的代码)。

星号( *在指针声明(第2行))仅表示它是一个指针,它是不引用操作(如在第3行)。两件事只是碰巧使用相同的登录: *。与往常一样,空格是不相关的,并不会改变表达式的含义。

指针可以被初始化要么一个变量的地址(如在上述情况下),或到另一个指针(或阵列)的值:

1
2
3
int myvar;
int *foo = &myvar;
int *bar = foo;
 


指针算术

要在指针进行算术运算有一点不同,而不是他们进行定期的整数类型。首先,只有加减运算允许开始; 别人使指针的世界没有任何意义。但既加减与指针略微不同的行为,根据其所指向的数据类型的大小。

当基本数据类型进行了介绍,我们看到的类型有不同的尺寸。例如: char总的大小为1字节的, short一般比大, int并且 long甚至更大; 这些依赖系统上的确切大小。例如,让我们想象一下,在一个给定的系统, char需要1个字节, short占用2个字节,并 long采取4. 

现在我们在这个编译器定义三分假设:

1
2
3
char *mychar;
short *myshort;
long *mylong;
 


而且我们知道,它们指向的存储器位置 10002000以及 3000,分别。

因此,如果我们写:

1
2
3
++mychar;
++myshort;
++mylong;
 


mychar,正如人们所期望的,将包含价值1001但不是所以很明显, myshort将包含值2002, mylong将包含3004,即使他们已分别被递增一次。其原因在于,加一的指针时,该指针是由指向同一类型的下列元件,以及,因此,在该类型的字节大小它指向被添加到指针。

 
这是既适用加减任意数量的指针时。如果我们写这会发生一模一样的:

1
2
3
mychar = mychar + 1;
myshort = myshort + 1;
mylong = mylong + 1;
 


关于增量( ++)和减量( --)的运营商,它们都可以被用作表达的任前缀或后缀,以在行为略有不同:作为前缀,增量发生的表达式进行求值之前,并且作为后缀,表达式求值后,增量发生。这也适用于表达式递增和递减的指针,它可以成为更复杂的表达式还包括取消引用符(的一部分 *)。记住运算符优先级规则,我们可以记得后缀运算符,如递增和递减,具有较高的优先级比前缀运算符,如引用操作( *)。因此,下面的表达式:

 
*p++
 


相当于 *(p++)。而它的作用是增加值 p(所以它现在指向下一个元素),但由于 ++作为值由指针(它递增之前指出的地址原来指向作为后缀,整个表达式求值)。

本质上,这些是与两个增量运算符的前缀和后缀版本的引用操作的(相同的是也适用于递减操作者)的四个可能的组合:

1
2
3
4
*p++   // same as *(p++): increment pointer, and dereference unincremented address
*++p   // same as *(++p): increment pointer, and dereference incremented address
++*p   // same as ++(*p): dereference pointer, and increment the value it points to
(*p)++ // dereference pointer, and post-increment the value it points to 
 


涉及这些运营商的典型 - 但并非如此简单 - 的说法是:

 
*p++ = *q++;
 


因为 ++具有比更高的优先级 *,都 pq递增,但由于这两个增量符( ++)被用作后缀而非前缀,分配给该值 *p*q两个前 pq递增。然后两者都递增。这将是大致相当于:

1
2
3
*p = *q;
++p;
++q;
 


像往常一样,括号通过添加可读性,以表达减少混乱。

指针和const

指针可用于通过其地址来访问一个变量,该访问可以包括修改值指出。但它也可以声明可以访问尖值读取它的指针,但不修改它。对于这一点,这是足以与排位由指针指向的类型 const。例如:

1
2
3
4
5
int x;
int y = 10;
const int * p = &y;
x = *p;          // ok: reading p
*p = x;          // error: modifying p, which is const-qualified 
 


这里 p指向一个变量,但指向它在一个 const-qualified方式,这意味着它可以读出的值指出,但它不能修改。还请注意,表达式 &y是类型的 int*,但是这是分配给类型的指针 const int*。这是允许的:一个指向非const可以隐式转换为指针常量。但不是周围的其他方法!作为一项安全功能,指针 const不隐式转换为指针到非 const

其中一个指针的用例 const元素是功能参数:采用一个指针到非函数 const作为参数,可以修改作为参数传递的价值,而需要一个指向函数 const的参数不能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// pointers as arguments:
#include <iostream>
using namespace std;

void increment_all (int* start, int* stop)
{
  int * current = start;
  while (current != stop) {
    ++(*current);  // increment value pointed
    ++current;     // increment pointer
  }
}

void print_all (const int* start, const int* stop)
{
  const int * current = start;
  while (current != stop) {
    cout << *current << '\n';
    ++current;     // increment pointer
  }
}

int main ()
{
  int numbers[] = {10,20,30};
  increment_all (numbers,numbers+3);
  print_all (numbers,numbers+3);
  return 0;
}
11
21
31


请注意, print_all使用指向常量元素的指针。这些指针指向恒定内容不能修改,但它们不是恒定本身:即,指针仍可增加或分配不同的地址,尽管它们不能修改它们指向的内容。

而这正是第二个维度常量性被加入到指针:指针也可以自己常量。这是通过附加常量的尖型(星号之后)指定:

1
2
3
4
5
int x;
      int *       p1 = &x;  // non-const pointer to non-const int
const int *       p2 = &x;  // non-const pointer to const int
      int * const p3 = &x;  // const pointer to non-const int
const int * const p4 = &x;  // const pointer to const int 
 


与语法 const和指针肯定是棘手的,并认识到最适合在每次使用往往需要一定的经验的情况。在任何情况下,得到的指针(和引用)常量性右宜早不宜迟是重要的,但你不应该担心把握一切,如果这是你接触到的组合在第一时间太多 const和指针。更多的用例将在接下来的章节中出现。

要多一点点混乱增加的语法 const与指针,在 const预选赛中可以之前或之后的尖型,具有完全相同的含义:

1
2
const int * p2a = &x;  //      non-const pointer to const int
int const * p2b = &x;  // also non-const pointer to const int 
 


作为与围绕星号的空间,在这种情况下,常数的顺序仅仅是一个风格问题。本章使用前缀 const,因为历史的原因,这似乎是更多的扩展,但两者完全等同。每个款式的优点仍在激烈的辩论在互联网上。

指针和字符串

正如前面所指出的, 字符串都包含空值终止的字符序列阵列。在前面的部分,已经使用字符串来直接插入 cout,初始化字符串和初始化字符阵列。

但它们也可以直接访问。字符串是正确的数组类型包含的所有字符加上终止空字符,每个被类型的元件的阵列 const char(如文字,它们不能被修改)。例如:

 
const char * foo = "hello"; 
 


这声明与用于文字表示的阵列 "hello",然后一个指向它的第一个元素被分配到 foo。如果我们想象, "hello"被存储在该起始地址是1702的内存位置,我们可以表示与先前的声明为:

 
请注意,这里 foo是一个指针,包含值1702,而不是 'h',也不是 "hello",虽然1702确实是两者的地址这些。

指针 foo指向的字符序列。而且由于指针和数组基本上表现以同样的方式在表达, foo可以用于访问在空终止字符序列的相同的方式的阵列的字符。例如:

1
2
*(foo+4)
foo[4]
 


两个表达式具有值 'o'(数组的第五元素)。

指针的指针

C ++允许使用指向指针的指针,这些的,反过来,指向数据(或甚至其它指针)。语法只需要一个星号( *用于指针的声明间接的每个级别):

1
2
3
4
5
6
char a;
char * b;
char ** c;
a = 'z';
b = &a;
c = &b;
 


此,假设为每个变量的随机选择的存储单元 72308092以及 10502,可以表示为:

 
随着其相应小区内的表示的每个变量的值,并在存储器各自的地址由下它们的值表示。

在本实施例的新的事情是变量 c,它是一个指向指针的指针,并且可在三个不同的层次的间接使用,他们中的每一个将对应于一个不同的值:

  • c是类型char**和一个值8092
  • *c是类型char*和一个值7230
  • **c是类型char和一个值'z'

空指针

void类型的指针是一种特殊类型的指针。在C ++中, void表示不存在类型。因此, void指针指向一个没有类型的值(因此也是一个不确定的长度和未定解引用属性)指针。

这给 void指针一个很大的灵活性,通过能够指向任何数据类型,从整数值或一个浮动的字符的字符串。作为交换,他们有很大的局限性:数据指向它们不能直接解除引用(这是合乎逻辑的,因为我们没有任何类型的取消引用到),因为这个原因,在任何地址 void需要转换成一些指针指向一个具体的数据类型被dereferenced之前其他指针类型。

它的一个可能的用途可能是通用的参数传递给函数。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// increaser
#include <iostream>
using namespace std;

void increase (void* data, int psize)
{
  if ( psize == sizeof(char) )
  { char* pchar; pchar=(char*)data; ++(*pchar); }
  else if (psize == sizeof(int) )
  { int* pint; pint=(int*)data; ++(*pint); }
}

int main ()
{
  char a = 'x';
  int b = 1602;
  increase (&a,sizeof(a));
  increase (&b,sizeof(b));
  cout << a << ", " << b << '\n';
  return 0;
}
Y,1603


sizeof是集成在在其参数的字节返回大小C ++语言的操作员。对于非动态数据类型,该值是一个常数。因此,例如, sizeof(char)是1,因为 char始终一个大小为一字节。

无效的指针和空指针

原则上,指针旨在指向有效的地址,如一个变量的地址或元件的阵列中的地址。但是指针实际上可以指向任何地方,包括不涉及任何有效的元素地址。这个典型的例子是 未初始化的指针和指向数组的元素不存在:

1
2
3
4
int * p;               // uninitialized pointer (local variable)

int myarray[10];
int * q = myarray+20;  // element out of bounds 
 


既不 p也不 q点已知含有一个值的地址,但没有上述语句导致错误。在C ++中,指针被允许采取任何地址值,无论是否有实际的东西在该地址与否。什么会导致错误的取消引用这样的指针(即实际访问它们指向的值)。访问这样的指针会导致不确定的行为,运行时访问某些随机值时,从一个错误。

但是,有时候,一个指向真正需要明确地指向无处,而不仅仅是一个无效的地址。对于这样的情况,存在一个特殊值,任何指针类型可以采取:所述 空指针值。此值可在C ++中以两种方式表示:要么为零的整数值,或与 nullptr关键词:

1
2
int * p = 0;
int * q = nullptr;
 


在这里,无论是 pq空指针,这意味着它们明确地指向无处,他们实际上都比较平等的:所有的 空指针比较等同于其他 空指针。它也是很平常看到限定恒定 NULL在旧的代码被用来指 空指针值:

 
int * r = NULL;
 


NULL在标准库的几个头文件中定义,并定义为一些别名 空指针常数值(如 0nullptr)。

不要混淆 空指针void指针!一个 空指针是,任何指针可以采取代表它是指向“无门”,而值 void指针是一个类型的指针可以指向的地方没有一个具体的类型。一个参照存储在所述指针的值,以及其他所指向的数据的类型。

函数指针

C ++允许操作与函数指针。典型的用途是用于传递一个函数作为参数传递给另一个函数。函数指针声明具有相同的语法作为一个普通函数声明,除了函数的名称被括号()和一个星号(封闭之间 *)的名称前插入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// pointer to functions
#include <iostream>
using namespace std;

int addition (int a, int b)
{ return (a+b); }

int subtraction (int a, int b)
{ return (a-b); }

int operation (int x, int y, int (*functocall)(int,int))
{
  int g;
  g = (*functocall)(x,y);
  return (g);
}

int main ()
{
  int m,n;
  int (*minus)(int,int) = subtraction;

  m = operation (7, 5, addition);
  n = operation (20, m, minus);
  cout <<n;
  return 0;
}
8


在上面的例子中, minus是一个指针到具有类型的两个参数的函数 int。它直接初始化为指向该功能 subtraction

 
int (* minus)(int,int) = subtraction;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值