c++ 指针常量、常量指针、函数指针等详解



指针

(1)指针本身就是个对象,允许对指针赋值和拷贝。生命周期内它可以先后指向几个不同的对象。
(2)指针无需在定义时赋初值。和其他内置类型一样,在块作用域内定义的指针如果没有被初始化,也将拥有一个不确定的值。
(3)指针存放某个对象的地址。可以使用取地址符 & 来获取地址
(4)如果指针指向了一个对象,可以使用解引用符 * 来访问该对象
(5)void* 可以存放任意对象的地址。但是我们对该地址中到底是个什么类型的对象并不了解。可以拿它和别的指针比较、作为函数的输入输出,或者赋值给另一个void指针。但是不能直接操作void指针所指向的对象,因为我们并不知道这个对象到底是什么类型。
(6)**表示指向指针的指针。***表示指向指针的指针的指针,以此类推
(7)指向指针的引用。引用不是对象,没有实际地址,所以不能定义指向引用的指针。但是指针是对象,因此存在对指针的引用
(8)空指针:空指针不指向任何对象。得到空指针最直接的办法就是用字面值 nullptr 来初始化指针(c++ 11引入),nullptr 是一种特殊类型的字面值,可以被转换成任意其他的指针类型。
(9)我们之前一直用NULL来给指针赋值。NULL是一个预处理变量,在 cstdlib 中定义,它的值就是0。预处理器在编译过程之前运行的,预处理变量不属于命名空间std,由预处理器负责管理,因此在使用预处理变量之前不需要加上std::。当遇到一个预处理变量时,预处理器会自动地将它替换为实际值,用NULL和0初始化指针是一样的。



指针简单的定义和使用:

#include <iostream>
#include <cstdlib>

using namespace std;

int main(){
	int i = 23;
	int *p1 = &i;	//p1被初始化,存放i的地址 
	int *p2;		//p2被初始化,没有指向任何对象 
	p2 = p1;		//p2和p1指向同一个对象i 
	
	*p2 = 12;		//p2所指向的对象被改变了(此处是指针指向的对象被改变了) 
	p2 = 0;		    //p2不指向任何对象(此处是指针改变了) 
	
    return 0;
}


指向指针的引用:

//指向指针的引用
#include <iostream>
#include <cstdlib>
using namespace std;
int main(){
	int i = 1024;
	int *p;
	int *&r = p;	//r是对指针p的引用。此处从右向左解读,&r可以知道r是一个引用,*&r可以知道 r引用的是一个指针 
	
	r = &i;			//r引用了一个指针,也就是p指向i
	*r = 0;			//解引用r得到i,也就是将i的值改为0
	 
	cout << i << endl;
	
	return 0;
}



指向指针的指针

#include <iostream>
#include <cstdlib>

using namespace std;

int main()
{
    double* (*a)[3][6];             // a 是指向二维指针的数组,数组中存储的元素都是double*
    
    cout << sizeof(a) << endl;      // a 是一个指针,所以sizeof(a)的值是4
    cout << sizeof(*a) << endl;     // *a 表示一个二维数组,元素类型是double*,是一个指针,占用字节数为4。sizeof(*a) = 3 * 6 * 4 = 72
    cout << sizeof(**a) << endl;    // *a 表示一个二维数组,**a为数组的首元素,即*a[0],*a[0]也是一个数组,长度为6,元素类型为double *,即6 * 4 = 24
    cout << sizeof(***a) << endl;   // ***a是数组的一个元素,类型为double *,故sizeof(***a) = 4
    cout << sizeof(****a) << endl;  // ****a 类型为double,故sizeof(****a) = 8

    return 0;
}



指向数组的指针

对于一个数组 array[10]
array 是数组的名字,&array 获取的是这个数组的地址,而且这个地址的值等于 &array[0] (首元素的地址)。虽然他们的值相等,但是含义是不一样的:
如果 &array + 1 这个时候,地址偏移的是 &array + sizeof(array)
如果 &array[0] + 1 这个地址偏移是 &array[0] + sizeof(array[0]) ,也就是偏移到地址 &array[1]上。

 int a[5] = {1 , 2 , 3 , 4 ,5};
 int *p = (int *)(&a + 1);            // &a:表示数组a的首地址,&a + 1:= &a + sizeof(a),即p指向数组最后一个元素的后一个位置
 printf("%d \n" , *(a + 1 ));         // a+1表示指向数组的第二个元素的地址,因此*(a+1)就是2。
 printf("%d \n" ,  *(p -1));          // p -1就指向数组的最后一个元素,即5



常量指针

解读方法:* 为分隔符,const关键字在 * 左边,说明指针所指向的对象是const类型,如果const关键字在 * 右边,说明指针是个const类型。

指向常量的指针,即指针指向的对象是const对象,即不允许用指针来改变其所指的const对象的值。

比如:

int i = 1;
int const * a = &i; // const在*的左边,说明是a指向的对象是const类型,即不可修改i的值,但是可以修改a的指向

// *a = 2;             // 错误操作

int ii = 2;
a = &ii;

a是一个指针,指向一个const int,不需要被初始化,因为a可以指向任何一个东西(即a不是一个const),但是a指向的东西是不能被改变的




指针常量

解读方法:* 为分隔符,const关键字在 * 左边,说明指针所指向的对象是const类型,如果const关键字在 * 右边,说明指针是个const类型。

指针常量值得是指针本身类型是const指针,需要给指针初始化值。
比如:

int i = 1;
int * const a = &i;		// const在*右边,说明const指的是a是const类型,即不可以给a重新赋值

int ii = 2;
//a = &ii;		//错误做法,不可以修改指针的指向

*a = 2;			// 正确,可以修改指针指向的对象的值



指针常量 常量指针

double value = 1;

double * ptr = &value;                  // ptr是一个指向double类型的指针,ptr的值可以改变,ptr所指向的value的值也可以改变
const double * ptr1 = &value;           // ptr1是一个指向const double类型的指针,ptr1的值可以改变,不能通过ptr1改变value的值(const在*左边,说明const修饰的是指针指向的对象value)
double * const ptr2 = &value;           // ptr2是一个指向double类型的const指针,ptr2的值不可以改变,可以通过ptr2改变value的值(const在*右边,说明const修饰的是指针本身)
const double * const ptr3 = &value;     // ptr3是一个指向const double类型的const指针,ptr3的值不可以改变,也不能通过ptr3改变value的值




指向类的指针

一个指向 C++ 类的指针与指向结构的指针类似,访问指向类的指针的成员,需要使用成员访问运算符 ->

#include <iostream>
 
using namespace std;

class Box{
   public:
      // 构造函数定义
      Box(string s){
         cout <<"Constructor called." << endl;
      }
      
      string Volume(){
         return s + "test";
      }
    private:
		string s;
};

int main(void){
   Box Box1("box1");    
   Box Box2("box2");    
   Box *ptrBox;          

   // 保存第一个对象的地址
   ptrBox = &Box1;
   cout << "Volume of Box1: " << ptrBox->Volume() << endl;

   // 保存第二个对象的地址
   ptrBox = &Box2;
   cout << "Volume of Box2: " << ptrBox->Volume() << endl;
  
   return 0;
}

输出:

Constructor called.
Constructor called.
Volume of Box1: test
Volume of Box2: test



函数指针

函数指针:指向函数的指针。函数指针指向某个特定的函数类型,函数类型由其返回类型以及参数表确定,与函数名无关
函数指针只能通过同类型的函数指针或0进行初始化或赋值。将函数指针初始化为0表示该指针不指向任何函数。


将 pf 声明为指向函数的指针,指向的函数带有两个 const string & 类型的形参,和 bool 类型的返回值。 `bool (*pf)(const string & , const string &);` // ***pf 两侧的圆括号是必须的**。
可以用 typedef 简化函数指针的定义。pFunc 是一种指向函数的指针类型的名字。使用这种函数指针类型时,只需直接使用 pFunc 即可,不必每次都把整个类型声明写出来 `typedef bool (*pFunc)(const string & , const string &);`

函数指针的简单使用

typedef bool (*cmpFun)(const string & , const string &);		// 定义函数指针,别名为cmpFun
bool lengthCompare(const string & , const string  &);			// 定义lengthCompare函数

// 直接引用函数名等效于在函数名上应用取地址符
cmpFun pf = lengthCompare;
cmpFun pf1 = &lengthCompare;

lengthCompare("hi" , "hello");			//直接调用lengthCompare函数
pf("hi" , "hello");						//通过函数指针调用lengthCompare函数,未使用*
(*pf)("hi" , "hello");					//通过函数指针调用lengthCompare函数,使用*

函数指针的复杂使用

#include <iostream>
using namespace std;

class Father
{
    public:
        void func()
        {
            cout << "Father func" << endl;
        }
        void  func2()
        {
            cout << "Father func2" << endl;
        }

};

class Son:public Father
{
    public:
        void func2()
        {
            cout << "Son func2" << endl;
        }

};


typedef void(Father::*PF1)();
typedef void(Son::*PF2)();

int main()
{
    Father  f;
    Son s;

    PF1 pf1 = &Father::func;
    (f.*pf1)();
    (s.*pf1)();
    
    pf1 = &Father::func2;
    (f.*pf1)();
    (s.*pf1)();

    PF2 pf2 = &Son::func2;
    (s.*pf2)();
    
    return 0;
}

控制台打印:

Father func
Father func
Father func2
Father func2
Son func2


函数指针,对象指针

#include <iostream>
#include <algorithm>
using namespace std;


//类指针的用法
class  CObjPoin
{
    public:
        typedef int (CObjPoin::*Proc)(int);             //*Proc:表示一个指向方法名的指针
    public:
        int add(int b)
        {
            return (a + b);
        }
        int sub(int b)
        {
            return (a - b);
        }
    public:
        int a;
};


//调用类指针
void  CalcClassPoint()
{
    CObjPoin cObj;
    cObj.a = 2;

    CObjPoin::Proc myproc[2] = {&CObjPoin::add, &CObjPoin::sub};      //Proc:表示指针,因此数组中的内容应该是函数名的引用

    for(int i = 0;i < 2;i++)
    {
        cout << (cObj.*myproc[i])(20) << endl;            //*myproc[i]表示解引用。得到函数名。
    }
}




//函数指针用法
int mul(int a,int b)
{
    return a * b;
}
int ormeth(int a,int b)
{
    return a ^ b;
}

typedef int (*MethodHandler) (int lhs,int rhs);


//调用函数指针
void TestMethod()
{
    MethodHandler myHandler[2] = {mul,ormeth};
    for(int i = 0;i < 2;i++)
    {
        cout << myHandler[i](2,4) << endl;
    }

}

int main()
{
    CalcClassPoint();
    TestMethod();
}



函数指针作为形参

//函数指针做为函数形参的2中声明方式,这两种写法等价:
void FunParam(const string & , const string & , bool (const string & , const string &));
void FunParam1(const string & , const string & , bool (*)(const string & ,const string &));


返回指向函数的指针
阅读函数指针的方法:从声明的名字开始 由内而外 的理解

int (*ff(int))(int * , int);
// ff(int):可以发现ff声明为一个函数,带有一个int类型的实参
// ff函数的返回值为:int (*)(int * ,int) 它是一个指向函数的指针

声明函数的返回值是函数指针最好是通过typedef,可以使定义简明易懂,就不会那么难以理解。上面声明可改写成

typedef int (*PF)(int * , int);
PF ff(int);		//函数ff返回一个函数指针

用变量a给出下面定义,一个有10个指针的数组,每个指针指向一个函数,该函数有一个整形参数并返回一个整形。
答案:int (*a[10])(int)
思路:

  1. 首先我们知道a是一个数组,数组类型先忽略,即 a[10]
  2. 其次,来看数组的类型,是个指针,指向一个函数,即 int (*f)(int)
  3. 然后把数组类型带进数组的声明中,即 int (*a[10])(int)

声明一个指向含有10个元素的数组的指针,其中每个元素是一个函数指针,该函数的返回值是int,参数是int *。
答案:
思路:

  1. 首先我们先完成含有10个元素数组的书写,即 int (*a[10])(int *)
  2. 指向数组的指针,即 int (*(*a)[10])(int *)


函数和函数指针

可以把函数形参定义为函数类型,但函数的返回类型必须是指向函数的指针,不能是函数类型。
具有函数类型的形参 所对应的的实参将被自动转换为指向相应函数类型的指针。

typedef int func(int * ,int);		//func是一个函数,而不是一个函数指针
void f1(func);						//函数的形参可以使函数,也可以是函数指针
//func f2(int);						// 错误写法,函数的返回值必须是函数指针不能是函数类型
func * f3(int);						//正确,f3返回一个函数指针



指向重载函数的指针

c++允许使用函数指针指向重载的函数。
指针的类型必须和重载函数的一个版本精确匹配。如果不精确匹配,则对该指针的初始化或赋值都将会导致编译报错

// 重载的函数ff
extern void ff(std::vector<double>);
extern void ff(unsigned int);

//定义函数指针pf1, pf1指向参数为unsigned int版本的函数ff
void (*pf1)(unsigned int) = &ff;		

// 错误示例: 参数不匹配
// void (*pf2)(int) = &ff;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章