C++指针系列

来源:互联网 发布:编织撒网起头升眼数据 编辑:程序博客网 时间:2024/05/21 00:50

整理以下两篇文章,同时加上c++ primer:

(1)主要讲指针的常用方法
http://www.cnblogs.com/ggjucheng/archive/2011/12/13/2286391.html

(2)主要讲函数指针和回调函数
http://blog.csdn.net/kkk0526/article/details/17122081

以下内容分为5大部分。

1.指针的基本概念。

2.指针运算。

3.指针和数组、结构类型的关系。

4.函数指针。

5.回调函数。

6.类中含有指针。

前言:

建议:尽量避免使用指针和数组

指针和数组容易产生不可预料的错误。其中一部分是概念上的问题:指针用于低级操作,容易产生与繁琐细节相关的(bookkeeping)错误。其他错误则源于使用指针的语法规则,特别是声明指针的语法。许多有用的程序都可不使用数组或指针实现,现代 C++程序采用 vector类型和迭代器取代一般的数组、采用 string 类型取代 C 风格字符串。

——————-来自于C++ primer

1.指针的基本概念。

C++ 语言使用 * 符号把一个标识符声明为指针,理解指针声明语句时,请从右向左阅读。

string *pstring;

阅读:pstring定义为一个指向string类型对象的指针变量。

推荐,声明指针时,将*和变量放在一起。

下面,从4个角度理解指针:

(1)指针的类型

int *ptr; //指针的类型是int *  char *ptr; //指针的类型是char *  int **ptr; //指针的类型是 int **  int (*ptr)[3]; //指针的类型是 int(*)[3]  int *(*ptr)[4]; //指针的类型是 int *(*)[4] 

只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型

(2)指针所指向的类型

当通过指针来访问指针所指向的内存区时,指针所指向的类型决定了编译器将把那片内存区里的内容当做什么来看待。

int *ptr; //指针所指向的类型是int  char *ptr; //指针所指向的的类型是char  int **ptr; //指针所指向的的类型是 int *  int (*ptr)[3]; //指针所指向的的类型是 int()[3]  int *(*ptr)[4]; //指针所指向的的类型是 int *()[4]

从语法上看,你只须把指针声明语句中的指针名字和名字左边的指针声明符*去掉,剩下的就是指针所指向的类型。

(3)指针的值

指针的值是指针本身存储的数值,这个值将被编译器当作一个地址,而不是一个一般的数值。在32位程序里,所有类型的指针的值都是一个32位整数,因为32位程序里内存地址全都是32位长。

指针所指向的内存区就是从指针的值所代表的那个内存地址开始,长度为sizeof(指针所指向的类型)的一片内存区。以后,我们说一个指针的值是XX,就相当于说该指针指向了以XX为首地址的一片内存区域;我们说一个指针指向了某块内存区域,就相当于说该指针的值是这块内存区域的首地址。

指针所指向的内存区和指针所指向的类型是两个完全不同的概念。在例一中,指针所指向的类型已经有了,但由于指针还未初始化,所以它所指向的内存区是不存在的,或者说是无意义的。

(4)指针本省所占据的内存区

指针本身占了多大的内存?你只要用函数sizeof(指针的类型)测一下就知道了。在32位平台里,指针本身占据了4个字节的长度。

指针本身占据的内存这个概念在判断一个指针表达式是否是左值时很有用。

2.指针运算。

(1)指针的算术操作

指针的算术操作与普通类型的算术操作不一样,指针的操作往往与指针的类型有关。

例1:

int ia[] = {0,2,4,6,8};int *ip = ia; // ip points to ia[0]ip = ia; // ok: ip points to ia[0]int *ip2 = ip + 4; // ok: ip2 points to ia[4], the last element in ia

通常,在指针上加上(或减去)一个整型数值 n 等效于获得一个新指针,
该新指针指向指针原来指向的元素之后(或之前)的第 n 个元素。

指针的算术操作只有在原指针和计算出来的新指针都指向同一个数组的元素,或指向该数组存储空间的下一单元时才是合法的。如果指针指向一对象,我们还可以在指针上加 1 从而获取指向相邻的下一个对象的指针。

例2:

#include <iostream>using namespace std;int main(int argc, char* argv[]){    char a[6] = "abcde";    int *ptr = (int *)a;    ptr++;    cout << (char)(*ptr) << endl;    return 0;}

结果,输出 e 。从中,也能体会到,对指针ptr进行加减n运算,相当于将指针上下移动n × sizeof(*ptr)个字节而得到的新的指针。

(2)指针和const限定符

指向 const 对象的指针

const double *cptr; // cptr may point to a double that is const

另一种写法为,(推荐使用上面一种):

double const *cptr;

从右往左阅读,cptr 是一个指向 double 类型 const 对象的指针。因此,cptr 本身并不是const。在定义时不需要对它进行初始化,如果需要的话,允许给 cptr 重新赋值,使其指向另一个 const 对象。但不能通过 cptr 修改其所指对象的值。

但是,允许把非 const 对象的地址赋给指向 const 对象的指针。因此,不能使用指向 const 对象的指针修改基础对象,然而如果该指针指向的是一个非 const 对象,可用其他方法修改其所指的对象。

double dval = 3.14159; // dval is not constconst double *cptr = &dval; *cptr = 3.14159; // error: cptr is a pointer to constdouble *ptr = &dval; // ok: ptr points at non-const double*ptr = 2.72; // ok: ptr is plain pointercout << *cptr; // ok: prints 2.72

如果把指向 const 的指针理解为“自以为指向 const 的指针”,这可能会对理解有所帮助。在实际的程序中,指向 const 的指针常用作函数的形参。将形参定义为指向 const 的指针,以此确保传递给函数的实际对象在函数中不因为形参而被修改。

const指针

int errNumb = 0;int *const curErr = &errNumb; // curErr is a constant pointercurErr = curErr; // error: curErr is const

这里没什么好说的,errNumb为常量指针,自然不能被修改。唯一需要注意的是上面容易混乱,尤其是double const *cptr 这种鬼。简单记法就是,指针紧挨着const的右方,则为const指针,否则为指向const对象的指针。或者看const的位置,4个字符里面,只有const的位置是变化的,1,2,号位是指向const对象的指针,3号位是const指针。

指向 const 对象的 const 指针

const double pi = 3.14159;// pi_ptr is const and points to a const objectconst double *const pi_ptr = &pi;

这里也很清楚。

指针和 typedef

假设给出以下语句:

typedef string *pstring;const pstring cstr;

自然地理解为:

const string *cstr; // wrong interpretation of const pstring cstr

错误的原因在于将 typedef 当做文本扩展了。声明 const pstring 时,const 修饰的是pstring 的类型,这是一个指针。因此,该声明语句应该是把cstr 定义为指向 string 类型对象的 const 指针,这个定义等价于:

string *const cstr; // equivalent to const pstring cstr

3.指针和数组、结构类型的关系。

指针和数组,这里不详细讲解。这里给出我个人的理解,数组名是一个const指针。

指针和结构类型的关系:

例3:

struct MyStruct  {  int a;  int b;  int c;  }  MyStruct ss={20,30,40};//声明了结构对象ss,并把ss的三个成员初始化为20,30和40。MyStruct *ptr=&ss;//声明了一个指向结构对象ss的指针。它的类型是MyStruct*,它指向的类型是MyStruct。int *pstr=(int*)&ss;//声明了一个指向结构对象ss的指针。但是它的类型和它指向的类型和ptr是不同的。

通过指针ptr来访问ss的三个成员变量:

ptr->a;  ptr->b;  ptr->c;  

通过指针pstr来访问ss的三个成员变量:

*pstr;//访问了ss的成员a。  *(pstr+1);//访问了ss的成员b。  *(pstr+2)//访问了ss的成员c。 

但是,这种访问是不太合适的,有些情况下会出错。原因在于ss的成员在内存中存放的位置有可能不连续。

4.函数指针。

概念:指针是一个变量,是用来指向内存地址的。一个程序运行时,所有和运行相关的物件都是需要加载到内存中,这就决定了程序运行时的任何物件都可以用指针来指向它。函数是存放在内存代码区域内的,它们同样有地址,因此同样可以用指针来存取函数,把这种指向函数入口地址的指针称为函数指针。

例4:

void Invoke(char* s);int main(){    void (*fp)(char* s);    //声明一个函数指针(fp)            fp=Invoke;              //将Invoke函数的入口地址赋值给fp    fp("Hello World!\n");   //函数指针fp实现函数调用    return 0;}void Invoke(char* s){    printf(s);}

由上知道:函数指针函数的声明之间唯一区别就是,用指针名fp代替了函数名Invoke,这样这声明了一个函数指针,然后进行赋值fp=Invoke就可以进行函数指针的调用了。声明函数指针时,只要函数返回值类型、参数个数、参数类型等保持一致,就可以声明一个函数指针了。注意,函数指针必须用括号括起来 void (fp)(char s)。

实际中,为了方便,通常用宏定义的方式来声明函数指针,实现程序如下:

例4:

typedef void (*FP)(char* s);void Invoke(char* s);int main(int argc,char* argv[]){    FP fp;      //通常是用宏FP来声明一个函数指针fp    fp=Invoke;    fp("Hello World!\n");    return 0;}void Invoke(char* s){    printf(s);}

函数指针数组

下面用程序对函数指针数组来个大致了解:

例5:

#include <iostream>#include <string>using namespace std;typedef void (*FP)(char* s);void f1(char* s){cout<<s;}void f2(char* s){cout<<s;}void f3(char* s){cout<<s;}int main(int argc,char* argv[]){    void* a[]={f1,f2,f3};   //定义了指针数组,这里a是一个普通指针    a[0]("Hello World!\n"); //编译错误,指针数组不能用下标的方式来调用函数    FP f[]={f1,f2,f3};      //定义一个函数指针的数组,这里的f是一个函数指针    f[0]("Hello World!\n"); //正确,函数指针的数组进行下标操作可以进行函数的间接调用    return 0;}

5.回调函数。

概念:回调函数,顾名思义,就是使用者自己定义一个函数,使用者自己实现这个函数的程序内容,然后把这个函数作为参数传入别人(或系统)的函数中,由别人(或系统)的函数在运行时来调用的函数。函数是你实现的,但由别人(或系统)的函数在运行时通过参数传递的方式调用,这就是所谓的回调函数。简单来说,就是由别人的函数运行期间来回调你实现的函数。

例6:

//定义带参回调函数void PrintfText(char* s) {    printf(s);}//定义实现带参回调函数的"调用函数"void CallPrintfText(void (*callfuct)(char*),char* s){    callfuct(s);}//在main函数中实现带参的函数回调int main(int argc,char* argv[]){    CallPrintfText(PrintfText,"Hello World!\n");    return 0;}

至此,对回调函数应该有了一个大致的了解。

6.类中含有指针。

类中有指针的情况,后续有时间在补上。简单在这里标记一下:

类可以分为带指针的类,和不带指针的类。

带指针的类,一般标配Big Three, 三个特殊函數。

(1)拷贝构造:ClassWPointer(const ClassWPointer& cwp);

(2)拷贝赋值:ClassWPointer& operator=(const ClassWPointer& cwp);

(3)析构函数:~ClassWPointer();

原创粉丝点击