指针
(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 = ⅈ
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 = ⅈ //错误做法,不可以修改指针的指向
*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)
思路:
- 首先我们知道a是一个数组,数组类型先忽略,即
a[10]
- 其次,来看数组的类型,是个指针,指向一个函数,即
int (*f)(int)
- 然后把数组类型带进数组的声明中,即
int (*a[10])(int)
声明一个指向含有10个元素的数组的指针,其中每个元素是一个函数指针,该函数的返回值是int,参数是int *。
答案:
思路:
- 首先我们先完成含有10个元素数组的书写,即 int (*a[10])(int *)
- 指向数组的指针,即 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;