05. 指针类型
05. 指针类型
指针
简概
指针
- 定义:指针是一个存储值的地址的变量
- 计算机存储数据时跟踪3种基本属性
- 信息存储在何处
- 存储的值是多少
- 存储信息什么类型
- 连续定义时
- 有的系统先定义的在高位(Windows x86)
- 有的系统先定义的在低位(Widnows x64)
- 有的系统不会把不同类型的变量放在相邻的内存单元中
- 与常规变量区别
- 使用常规变量时:值是指定的量,而地址是派生量
- 使用地址变量时:地址是指定的量,而值是派生量
- 指针与数组
- 基本等价,这是C和C++的优点之
使用
字面量方式
- 初始化:例
int * pi_e = &i_e
中,i_e = *pi_e= 值
,&i_e = pi_e = 地址
补充:定义时*
两侧的空格可有可无,一般都有 补充:这里定义时和函数原型中的*
、&
、[]
,不要理解成地址运算符、解除引用运算符这些,而应理解为类型标识符 - 指定地址初始化:
int * pt = (int *) 0xB8000000
- 定义空指针的三种方式
- 0
- NULL(需要NULL头文件)
- nullptr(C++11新增)
符号操作
- 取地址:用法
&变量
,&
运算符被称为地址运算符
- 地址取值:用法
*地址
,*
运算符被称为间接值或解除引用运算符
指针 x const
两种标注方法
指针 x 异常
C++标准提供了一种在失败时返回空指针的new
深度剖析指针【专题】
内容很庞大的一章
C语言的指针玩得比较花,C++其实还是应该多研究模板、C++11+这些东西
各种指针
参考
- https://blog.csdn.net/a22025340/article/details/124013311
- https://blog.csdn.net/afei__/article/details/81985937
基本数据类型指针
int * pi_e = &i_e;
pi_e = &i_e = 地址; // True
*pi_e = i_e = 值; // True
容器指针
多维容器指针
嵌套指针
函数指针
核心
- 函数指针只在乎声明传入参数和返回参数信息,即相同参数数量类型和返回类型的函数都能用这个指针来表示
- 非静态类函数指针除外,还需要多声明一个所属类
函数指针
函数指针的作用
- 充当回调函数(以函数指针为参数的函数)
- 其他
基本用法
double pam(int); // 函数原型
double (*pf)(int) = pam; // 声明函数指针
pf() // 使用函数指针
// 复杂点的例子如下
void printdata(int (*p)(int,int),int a,int b){
cout<<p(a,b)<<endl;
}
void (*pp) (int(*)(int,int),int,int) = printdata;
使用typedef简化
typedef int (*ptr)(int, int);
ptr p1 = get_sum;
使用auto简化
.
函数参数指针
写法(数组作参和指针作参)
int sum_arr(int arr[], int n); // 数组作参 int sum_arr(int *arr, int n); // 指针作参 int sum_arr(char * str, char ch); // 字符串作参 int sum(int ar2[][4], int size); // 多维数组作参 int sum(int (*ar2)[4], int size); // 多维指针作参 char * buildstr(char c, int n); // 字符串返回值
函数原型中(可省略函数名),这些写法是等价的:
const double * f1(const double ar[], int n); const double * f2(const double [], int n); const double * f3(const double *, int n);
类指针
(需结合 “面向对象_继承_虚” 笔记一起使用)
类指针
类成员字段指针
类成员方法指针
静态的成员方法函数指针语法
(同C语言差不多) 声明时和普通函数指针区别不大。 赋值有所不同,要传的是一个类的静态成员函数的地址,要加上类名限定
void (*ptrStaticFun)() = &ClassName::staticFun; // 声明
ptrStaticFun(); // 调用1
(*ptrStaticFun)(); // 调用2
非静态成员方法函数指针语法
(麻烦一点) 注意调用类中非静态成员函数的时候,使用的是 类名::函数名,而不是 实例名::函数名
void (ClassName::*ptrNonStaticFun)() = &ClassName::nonStaticFun;// 声明
(classObject.*ptrNonStaticFun)(); // 调用1。注意要用括号括起前面,不然优先级不对
(pClassObject->*ptrNonStaticFun)(); // 调用2。注意要用括号括起前面,不然优先级不对
// (*(classObject.*ptrNonStaticFun))(); // 没有这种用法
// (&(classObject.*ptrNonStaticFun))(); // 没有这种用法
// 没有像静态方法那样的变体,左边括号的极不是函数名也不是函数地址
区分两者
两者的本质区别是什么
记住一句最核心的一句话:「静态函数没有this
指针。」 类内使用非静态成员变量或函数前面其实是有一个隐藏的 this->
非静态成员函数
与静态函数不同,成员函数在被调用时,必须要提供this指针 不是仅仅需要函数地址就可以调用的
因为在它被调用之前,自己也不知道哪个对象的此函数被调用。所以通过
&
拿到的不是实际的内存地址 只有调用的时候,C++才会结合this指针通过固定的偏移量找到函数的真实地址调用为了支持这种调用方式,这里C++给专门提供了特殊的几个操作符:
::*
.*
->*
声明
void (Test::*fptr)();
,类成员函数指针的声明,就必须加上类名限定,这就声明了一个函数指针变量fptr,他只能指向Test类的成员函数赋值
fptr = &Test::function
调用 类的成员函数是无法直接调用的,必须要使用对象或者对象指针调用(这样函数才能通过对象获取到this指针)。
(t.*fptr)();
,t是Test类的一个实例,通过对象调用。(pt->*fptr)();
,pt是一个指向Test类对象的指针,通过指针调用。
C++成员函数的调用需要至少3个要素:
- this指针;
- 函数参数(也许为空);
- 函数地址
应用举例
#include <stdio.h>
#include <iostream>
using namespace std;
class MyClass {
public:
static int FunA(int a, int b) {
cout << "call FunA" << endl;
return a + b;
}
void FunB() {
cout << "call FunB" << endl;
}
void FunC() {
cout << "call FunC" << endl;
}
int pFun1(int (*p)(int, int), int a, int b) {
return (*p)(a, b);
}
void pFun2(void (MyClass::*nonstatic)()) {
(this->*nonstatic)();
}
};
int main() {
MyClass* obj = new MyClass;
// 静态函数指针的使用
int (*pFunA)(int, int) = &MyClass::FunA;
cout << pFunA(1, 2) << endl;
// 成员函数指针的使用
void (MyClass::*pFunB)() = &MyClass::FunB;
(obj->*pFunB)();
// 通过 pFun1 只能调用静态方法
obj->pFun1(&MyClass::FunA, 1, 2);
// 通过 pFun2 就是调用成员方法
obj->pFun2(&MyClass::FunB);
obj->pFun2(&MyClass::FunC);
delete obj;
return 0;
}
虚函数的函数指针
虚函数其实就是一种特殊的成员函数,定义和声明同非虚成员函数
另外,指向虚函数的函数指针在涉及到多继承和指针强转的问题时,使用不当会踩到大坑:
- 不要使用
static_cast
将继承类的成员函数指针赋值给基类成员函数指针,如果一定要使用,首先确定没有问题。(这条可能会限制代码的可扩展性。) - 如果一定要使用
static_cast
, 注意不要使用多继承。 - 如果一定要使用多继承的话,不要把一个基类的成员函数指针赋值给另一个基类的函数指针。
- 单继承要么全部不使用虚函数,要么全部使用虚函数。不要使用非虚基类,却让子类包含虚函数。
类成员方法指针_地址问题
(这一节搜出来的不是我想的那个意思,我想搜的其实是 如何打印函数指针的地址)
参考:
非静态类成员方法指针是特殊的,需要配合实例去使用,而且也不能通过一般方法去取地址
我整理了4种C++中取成员函数地址的方法
- pointer_cast
- union_cast
- cdecl_cast
- 汇编(该方法不能在VS6上编译通过)
pointer_cast - 通过静态转换
template<typename dst_type,typename src_type>
dst_type pointer_cast(src_type src)
{
return *static_cast<dst_type*>(static_cast<void*>(&src));
}
union_cast - 通过联合体的共享储存机制
这种方法是最常规, 也是最好理解的一种方法了, 巧妙地利用了联合体的优点. 当然, 模板的使用恰到好处. 同时,传入参数时注意数据类型大小一致.
template<typename dst_type,typename src_type>
dst_type union_cast(src_type src)
{
union{
src_type s;
dst_type d;
}u;
u.s = src;
return u.d;
}
cdecl_cast - 通过C语言的可变参数不检测参数类型
__declspec(naked) void* __cdecl cdecl_cast(...)
{
__asm{
mov eax,dword ptr[esp+4]
ret
}
}
asm_cast - 通过汇编的offset语句取成员函数偏移得到地址
这个方法也比较巧妙, 不过貌似在VC6上编译不过. VS2012没问题.
#define asm_cast(var,addr) \
{ \
__asm{ \
mov var,offset addr \
} \
}
Q:能否搞出指向构造函数和析构函数的函数指针?
C++标准明确规定:The address of a constructor or destructor shall not be taken.
写出来了编译也会报错:error: taking address of constructor "Test::Test"
用汇编看透成员函数本质
详见:https://blog.csdn.net/tangchuxian080/article/details/80711339
万能指针:空类型指针(仅C语言)
原理
万能指针能操作所有类型指针,但使用前要进行强制类型转换
void print(){
cout<<"万能指针调用函数指针"<<endl;
}
void *p = print;
((void(*)())p)(); // 两种指针调用函数的方法
*((void(*)())p)(); // 两种指针调用函数的方法
// 剖析:
// 1)void print()的函数指针是 void(*pr)();
// 2)去掉变量名,所以函数指针的类型是 void(*)();
// 3)(void(*)())p == print,将万能指针强制类型转换成void(*)()
// 4)((void(*)())p)(); 将万能指针强制类型转换成void(*)(),再调用函数
// 4)*((void(*)())p)(); 取星号运算,也是调用函数
缺陷
函数指针不能直接用void*存,你只能取函数地址整数转成void*的地址,在使用的时候再把void*转成函数指针类型,这样才可以跳用,
关键一点:void*不能调用,只能传递
C++兼容性问题 / 无法将函数指针转换的问题
无法将函数指针转换的问题 将指针转换void *
为不可行的方法,因为成员指针的大小通常大于sizeof(void *)
void *
在任何情况下强制转换它都会丢弃指针表示的一部分,从而不再保证唯一性.
群友说: 这是c类型的,如果你要在cpp里用,cpp是强类型的,不支持隐式转换。 例如像上面的转化函数指针为万能指针会报错:无效的转换从' void(*)() '到' void* '
但是像下面这样只是用来存基本类型指针则没有问题
int a = 5;
void* p = &a;
cout << *(int*)p << endl;
C++替代方案 —— 模板指针
为了避免一些问题,C++不用万能指针的
C++更好的选择是使用模板来代替万能指针,T*
完事
Const修饰指针
指针的传递
智能指针
引用类型
使用场景:
- 改参
- 函数返回多个信息
原理剖析
- Q:指针是地址,为什么声明类型?
- A:以便获取长度
- Q:函数指针的原理是什么,为什么声明变量和返回值?
- A:不知道