干掉指针,才算学了C++
冯诺依曼体系,数据和指令是放在同一块区域里。在Linux中就是虚拟内存空间。在C++里,数据那部分内存(数据),函数的那部分内存存储(指令)。C++指针是对内存区域的抽象,指针对象中存放着目标对象的内存地址。
定义
跟普通变量类似,只需在前面加 * 解引用符号来标记这是个指针。
int *p1, *p2; //指向int类型变量的指针
double x, *p3; //double类型的x和指向double类型的指针p3
内存空间内容有两个要素:类型和地址。上面确定了类型(指向类型)后,我们需要一个地址。
获取对象地址
C++中我们用&(取地址符)来获取对象地址。
int val = 42;
int *p = &val; //指针p记录了变量val的地址
绝大多数情况指针类型和对象类型匹配,int指针指向int类型变量。
double dval = 0.0;
double *pd1 = &dval;
double *pd2 = pd1;
int *pi1 = &dval; //error
int *pi2 = pd1; //error
指针访问和修改对象值
解引用 *p, 指针可以修改被指向对象的值
int val=42;
int *p=&val;
cout<<*p<<endl; //42
*p=360;
cout<<*p<<endl; //360
cout<<val<<endl; //360
空指针和指向空类型的指针
空指针:不指向任何对象的指针,实际编程中,用来做指针是否有效的标准。新版C++用nullptr or 0:
int *p1=nullptr;
int *p2=0;
if(nullptr==p1) //为什么不能是p1==nullptr??
空类型指针:形如void *pv的,可以用于存储任意类型对象地址。
double pi=3.14;
void *pv=π //空类型指针
double *pd=π //double类型指针
pd=pv; //错误
pv=pd; //正确
pd=(double *)pv; //C风格强制类型转换
pd=reinterpret_cast<double *>(pv); //C++风格强制转换
再探变量声明
变量声明包含:基本数据类型+声明符。 在同一个变量定义语句中,基本数据类型只能有一个,声明符可以有好几个。
int *p; //*p为声明符
int *p=nullptr, a=1024;
复杂的指针定义
指针本身也是变量,也是存在虚拟空间里。指针的指针:
int a=1024;
int *p=&a;
int **pp=&p; //pp是指向p的指针
const与指针
const的值在生存期不允许被改变,防止程序意外修改这个值。
const double pi=3.14;
int val=0; //int变量
const int cnst=1; //int常量
int *pi=val; //正确,变量指针可以指向变量
pi=&cnst; //错误,因为pi是变量指针,不能指向常量!
const int *pci=&cnst; //正,常量指针指向常量
pci=&val; //正确!~ 常量指针可以指向变量
int *const cpi= &val; //正,指向变量的指针常量指向了变量val
int fake=2;
cpi=&fake; //错! 指向不能变啦
*cpi=20; //按理说应该可以,虽说是指针常量,但指向的是变量,存储地址里的数据可以通过指针改编
const int *const cicp= &val; //本身是指针常量又指向了常量,可以存储变量地址
cpci=&fake; //错,指向不能变
cpci=&cnst; //错,指向不能变
指针与数组
数组名被当做指针使用:使用数组的时候,编译器通常都在操作指针,数组名其实就是一个指针;
int nums[]={1,2,3};
int *p=&(nums[0]);
p==nums; //true 数组名被当做一个值来使用就相当于一个指针;
数组指针自增:数组的指针进行自增、自减运算,实际是将指针所指的位置,沿着数组向后或者向前移动一个元素。
int nums[]={0,1,2,3,4};
size_t len=sizeof(nums)/sizeof(nums[0]);
int *end=nums+len;
int *iter;
for(iter=nums; iter!=end; ++iter){
printf("%d\n",*iter); // 遍历nums (有点像std::vector迭代器)
}
数组访问:提供了另一种访问数组元素方法。
int nums[]={1,2,3,4,5};
size_t len=sizeof(nums)/sizeof(nums[0]);
int *p=nums;
for(size_t i=0;i!=len;i++){
printf(nums[i]==*(p+i));
}
函数与指针(重点)
函数返回一个数组的指针
函数在return的过程会对返回值进行拷贝,所以一个无法拷贝的值是无法被函数返回的。数组是不能被拷贝的,所以函数无法直接返回数组。所以,为了让函数返回数组,我们只能让函数返回数组的指针(或者引用)。
一组指针的定义:
int arr[10]; // an array of 10 int
int *p[10]; //an array of 10 pointer which point to int
int (*p)[10]; //a pointer which point to an int[10]array 只一个指针
int *(func(param_list))[10]; //❌尝试返回一个指针数组,数组是不能被返回的
int (*func(param_list))[10]; //✅函数返回一个10个int组成的数组的指针 (*p)
此类函数的一般形式:
element_type(*func_name(paramlist))[dimension]
函数的指针
无论数据还是指令,都是放在虚存空间,变量有指针,函数也有。
一个函数类型取决于输入(paramlist)输出(returntype)。
函数定义去掉函数名字就是类型。
bool isEqual(int, int); //defination
bool (*p) (int, int) = &isEqual; //指向函数的指针 只需把函数名替换成指针
bool (*p) (int, int) = isEqual; //等价定义
函数名作为值(地址)使用时,自动转为指针(类似数组),所以取地址符可以省略。
bool isEqual(int, int);
bool (*p)(int, int);
bool res1=isEqual(1,2);
bool res2=(*p)(1,2); //解引用调用
bool res3=p(1,2); //等价于上
值得注意的是,不同类型的函数指针不能类型转换。因此,重载的同名函数,不能是同一个指针。
函数指针作为参数传给另一个函数
传参的过程也伴随着拷贝,函数无法拷贝,所以无法直接作为参数传递给另一个函数。但是函数的指针可以~
int addIfEqual(int l, int r, bool isEqual(int, int) ); //函数名自动转为指针
int addIfEqual(int l, int r, bool (*p)(int, int) ); //等价
所以你可以这样使用
addIfEqual(1,1,isEqual);
函数返回一个函数指针
isEqual函数指针的定义:(p是一个指针,指向bool isEqual(int,int)这个函数。
bool (*p)(int,int)=isEqual; //&可省
根据返回数组指针的函数的定义——— int (*func(paramlist))[10],
可得返回函数指针的函数的定义:
returnfunc_type (*func(paramlist))(returnfun_param)
登峰造极的 (*(void (*)())0)();
c风格的强制转换 (double)a 能把a转换为double;
double a;
double *b;
(double)c;
(double *)d; //d被转换为指向double的指针
我们知道void(*)()定义了一个函数指针,该函数param列表为空,返回值为空。
(void(*)())0 把0强制转换为上述指针,然后交给*解引用得到该函数,然后传入()调用该函数。