————————————————————————
该系列教程为翻译c++官方教程,点击参考英文原版,水平有限,翻译不通的地方敬请体谅!
————————————————————————
指针算法
对指针进行算术操作和整数类型稍有不同。首先,指针只有加法和减法运算是允许的;其他的对指针来讲是没成心义的。但是,不管是加法和减法对指针,根据他们指向数据类型的大小,都有1个略微不同的表现。
当引入基本数据类型,我们看到类型有不同的大小。例如:char大小为1字节,short1般更大,int和long更大;具体大小依赖于系统的精确。例如,让在1个给定的系统中,假设char需要1个字节,那末short需要2个字节,而long需要4个字节。
现在加入我们在编译器中有3个指针:
char *mychar;
short *myshort;
long *mylong;
指针的初始地址分别为:1000,2000和3000.
那末如果我们履行下面操作:
++
++
++
那末和我们期待的1样,mychar将会指向1001地址,但是myshort将会指向2002,而mylong将会指向3004. 虽然他们都只被增加了1次。缘由是,当向指针添加1个时,指针指向同1类型的下1个元素,因此,它所指向的类型的字节的大小将被添加到指针上。
当增加和减去任何数字到1个指针也是适用的。如果我们写成下面这样,那末实现的功能和上面将会完全相同:
mychar = mychar + 1;
myshort = myshort + 1;
mylong = mylong + 1;
关于自增(+ +)和自减(–)操作,他们都可以被用来作为前缀或后缀的表达,在功能上有轻微的差异:作为1个前缀,表达式在增量产生之前的履行,作为1个后缀,表达式在增量产生后履行。这也适用于表达自增和自减指针,它可以成为更复杂的表达式,还包括部份援用操作符()。记住运算符优先级的规则,我们可以回想1下,后缀式操作符,如自增和自减,比前缀操作符的优先级更高的,如解援用操作符()。因此,下面的表达式:
*p++
等价于 *(p++),它所做的就是增加P值(所以它现在指向下1个元素),但由于++作为后缀,全部表达式被求值后指向原来的指针(它指向的地址递增)。
从本质上讲,这些是用的前缀和后缀增量运算符和与解援用操作符的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
1个典型的-但不是那末简单的语句是:
++ ++
由于++的优先级比*更高,p和q是先自增,但由于增量运算符(++)作为后缀而不是前缀,分配到*p值和*Q是自增的。这样都自增。这大致相当于:
++
++
一样,加括号更能清晰表达以减少混乱。
指针与常量
指针可以被用来访问1个变量的地址,包括修改指向地址的值。但是也能够在访问指向地址指针读取的时候声明,这样就不可以修改它。对这1点,那末所指向的类型就为const指针。例如:
int x;
int y = 10;
const int * p = &y;
x = *p;
*p = x;
这里p指向1个变量,但指出它是const方式,这意味着它是可读的指针,但不能修改它。注意,这表达&y是int*类型,但这是赋给1个指针类型const int *。这是允许的:1个指向非const可以隐式转换为指向const对象的指针。但不是其他方式!为了安全,指向const指针不可隐式转换为非const指针。
指向const元素的1个指针例子是作为函数的参数:1个函数接受1个指向非const参数作为传递参数的值,而函数不能接受1个指向const对象的指针作为参数。
#include <iostream>
using namespace std;
void increment_all (int* start,int* stop)
{
int * current = start;
while (current != stop) {
++(*current);
++current;
}
}
void print_all (const int* start,const int* stop)
{
const int * current = start;
while (current != stop) {
cout << *current << '\n';
++current;
}
}
int main ()
{
int numbers[] = {10,20,30};
increment_all (numbers,numbers+3);
print_all (numbers,numbers+3);
return 0;
}
注意,print_all使用指针指向常量元素。这些指针指向常量的内容不能修改,但他们不是常量本身:即指针依然可以递增或分配不同的地址,虽然他们他们指向的内容不能修改。
下面是将第2维度添加到指针:指针也能够是const指针。这是通过添加const指出类型指定(星号后面):
int x;
int * p1 = &x;
const int * p2 = &x;
int * const p3 = &x;
const int * const p4 = &x;
用const与指针的语法是很辣手的,合适的使用常常会需要1些经验。在任何情况下,取得指针常量(援用)的权限宜早不宜迟,但也不应当过分担心,如果是第1次接触到const和指针的组合,那末更多例子在后面的章节中显示出来。
const指针的语法更混乱的操作是,const限定符可以位于前面或后面,具有的意义相同:
const int * p2a = &x;
int const * p2b = &x;
与星号相邻,在这类情况下,const的顺序是1件简单的风格。本章使用前缀const,由于历史的缘由,可以扩大很多种,但二者是完全等价的。每种风格的优点在互联网上仍有剧烈的争辩。
指针和字符串
前面指出,字符串是包括空终止字符序列的数组。在前面的章节,字符串被用来直接插入cout来初始化字符串和字符数组。
但也能够直接访问。字符串数组类型是包括所有字符加终止null字符的数组,每一个元素的类型是const char(像字面表示的1样,永久不会被修改)。例如:
const char * foo = "hello";
这里声明1个“hello”的字符串数组,然后Foo指针指向其第1个元素。如果我们假定“hello”是存储在地址1702的内存位置,我们可以表示为:
注意foo是1个值为1702的指针变量,不是‘h’,也不是‘hello’,虽然地址1702是指向这些元素的地址。
Foo是指向这1序列的指针,由于指针与数组的表示方式是相通的,foo可以1下面两种方式访问:
*(foo+4)
foo[4]
上面两种方式都可以得到值‘o’(即数组的第5个元素)。
指向指针的指针
C++允许定义指向指针的指针,也就是指向了数据(或其他指针),所使用的符号就是在指针前面简单的加上*。
char a;
char * b;
char ** c;
a = 'z';
b = &a;
c = &b;
像这里,随机初始化3个内存地址为7230,8092,10502,那末上面就能够表示为:
每一个变量的值在其相应的单元格中表示的每一个变量,它们在内存中的值所表示的值在他们内存值上上方。
在这个例子中的c是1个指向指针的指针,可间接用于3个不同的层次,它们中的每个对应于1个不同的值:
(1) C是char**类型,值为8092
(2) *c是char*类型,值为7230
(3) **c为char类型,值为‘z’
空指针
空指针是1种特殊的类型,在c++,void代表空类型。因此,void空指针指向的数据没有类型(因此它的长度和相干属性不肯定)。
这就是的空指针有很大的灵活性,可以指向任何数据类型,从1个整型或1个浮点型到1个字符串字符的空指针。一样相反,他们就有很大的局限性:他们指向的数据不能直接援用(这是合乎逻辑的,由于我们不知道类型),由于这个缘由,在1个void指针需要转化为其他1些指针类型时,被援用之前需要指向1个具体的数据类型的地址。
下面是1个传递函数参数的例子:
#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;
}
C++中sizeof是1个获得数据类型大小的操作符。对非动态的数据,这个值是常量。因此例如sizeof(char)是1,由于char型数据占用1个字节大小。
无效的指针和空指针
在原则上,指针需要指向有效的地址,如变量的地址或数组中元素的地址。但指针实际上可以指向任何地址,包括没有任何有效元素的地址。典型的例子是未初始化的指针和指向数组中不存在的元素:
int * p;
int myarray[10];
int * q = myarray+20;
不管p还是q都没有指向某个已知值的地址,但上述任何语句都不会致使毛病。在C++中,指针是可以指向任何地址值,不管在那个地址是不是有值。甚么会致使毛病对的是援用这样1个指针(即,访问他们实际指向的值)。这样的访问指针会致使未定义行动,在运行时出现毛病值访问1些随机值。
有时,1个指针需要显式指向无处,而不单单是1个无效的地址。对这类情况,存在1个特殊的值,即任何指针类型都可以采取:null指针值。这个值可以以两种方式在C++表示:1个整数为零的值,或nullptr关键字:
int * p = 0;
int * q = nullptr;
在这里,P和Q都是空指针,意味着它们不指向任何地方,这里它们实际上是相等的:所有的null指针等于其他的null指针。也很常见的是,在旧的代码中使用定义的常量 NULL来援用空指针值:
int * r = NULL;
NULL包括在标准库中,被定义成null常量指针(比如0或nullptr)
不要将null指针与void指针弄混淆了。1个null指针是1个值,任何指针都可以表示它,不指向任何地方,而1个void指针是1个指向没有特定类型的指针的指针类型。1个是指存储在指针中的值,另外一个是指向它指向的数据类型。
指向函数的指针
C++允许操作的函数指针。典型用处是传递函数作为对另外一个函数的1个参数。函数指针与语法与1个普通的函数相同,除函数的名字是用括号括起来()和在名字之前插入星号(*)。
#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;
}
在上面的例子中,minus就是1个有两个int参数类型的函数指针。它直接初始化到函数subtraction:
int (* minus)(int,int) = subtraction;