C++三种类型函数:
成员函数:
1:个人理解,它的出现主要是想让数据和数据操作统一在一起;将this指针入参隐藏。
2:引入private,protect,public。保证封装性,最小权限原则,保证接口稳定;对外部调用隐藏数据和数据操作的逻辑。
Static型函数:C++中本来可以不需要这个static型函数,这个我个人理解是为了保持对C语言的兼容;
C语言中static型函数的特点:<1> 其他文件中可以定义相同名字的函数,不会发生冲突。<2> 静态函数不能被其他文件所用。
因为C语言中函数名就是函数指针,而且没有this指针的概念;所以C++中static型在使用时如何需要调用成员变量只能把this指针作为参数传入。
又因为函数名就是函数指针,方便C++主要用于类中实现回调函数;
#include <iostream>
class A
{
public:
A(){a=100;};
~A(){};
static void show(A* t)
{
std::cout<<t->a<<std::endl;
}
private:
int a; //static型可以访问
};
int main()
{
A a;
A::show(&a);
return 0;
}
[root@vtcp01-njr04-vm-10 ~]# objdump -t a.out|grep show
0000000000400964 w F .text 000000000000002d _ZN1A4showEPS_
Static型回调函数的例子;这样写的方便实现多线程对象;对外每个对象一个线程,用户无感知。如果有兴趣的可以看看java和qt多线程类;我感觉有点类似。
class MyClass
{
pthread_t TID;
public:
void func()
{
//子线程执行代码
}
bool startThread()
{
//启动子线程
int ret = pthread_create(&TID, NULL, callback, this);
if(ret != 0)
return false;
else
return true;
}
};
static void* callback(void *arg)
{
//回调函数
((MyClass*)arg)->func(); //调用成员函数
return NULL;
}
int main()
{
MyClass a;
a.startThread();
}
扩展一下友元函数:
老实说我不知道为什么需要引入友元函数,是不是为了全局范围能够访问一个类里面的private型数据,是弥补static型作用域不足?Static收范围限定了,虽然它可以访问private变量。
为什么需要friend?
普通函数无法访问类的保护成员(protect,private),如果想这么做,必须把类的成员都声明为public,然而这会引起数据安全问题。
c++利用friend修饰符,可以让部分函数或类能够对这些保护数据进行操作,避免把类成员全部设置成public,尽可能保护数据成员的安全。
Friend Class
语法:
friend class class_name; // 在base类中声明
示例:
class GFG {private:
int private_variable;public:
GFG() {
private_variable = 10;
}
friend class F;};
class F {public:
void display(GFG& t) {
cout << t.private_variable << endl;
}};int main() {
GFG g;
F fri;
fri.display(g);
return 0;}
在上面这个例子中,类GFG将类F声明为friend,因此F的对象可以访问GFG对象的private成员。
类GFG的任何区域都可以声明friend,包括public、private、protect。
friend function
friend function有两种:一种是为全局函数global function(全局函数)提供友元访问,另一种是为 member function of another class(class内函数)提供友元访问。
语法:
global function:
friend return_type function_name (arguments);
member function of another class:
friend return_type class_name::function_name (arguments);
小插曲:
_GLOBAL__sub_I__Z6showffP3GFG--感觉像是标识这个函数是全局的意思
0000000000400a16 T _Z6showffP3GFG
虚拟成员函数:弥补成员函数在继承时多态特性的不足;
引入了一个虚表指针,为了多态的时候能够指向正确子类的指针;这个也是为啥经常要求析构函数需要使用虚函数;
主要因为构造的顺序是先构造父类对象,在构造子类对象。析构时先析构子类的在析构父类的。这个过程时编译器做的。如果不是虚函数,会造成调用父类析构,而没有调用子类的析构,假如子类析构函数有自己的资源需要释放,这样就造成了泄漏。
下面我们描述一下函数指针在内存中的空间模型:
函数都是在编译器就确定了自己内存地址,我们需要重点关注的是内存的中
类的对象模型中虚函数对内存模型的影响;以及多重继承时,两个子类如何偏移的计算过程
类对象的函数指针怎么设定:
老实说我还没有想好,为什么需要设定类成员的函数指针,这样做有好处;我理解如果需要设定函数指针,为什么不用纯虚函数用多态的方式去解决。C因为没有这样的接口,需要回调,表驱动这样的方式去解决。在我心中C++不是必须要东西;---欢迎大家点评
首先比较一下C和C++对于相同代码编译后的函数指针区别
[root@promote tenglei]# cat functest.c
//#include <iostream>
//using namespace std;
#include <stdio.h>
/** 加法 */
double addition(double, double);
/** 减法 */
double substraction(double, double);
/** 乘法 */
double multiplication(double, double);
/** 除法 */
double division(double, double);
/** 计算 */
double calculate(double (*)(double, double), double, double);
double addition(double num1, double num2){
return num1 + num2;
}
double substraction(double num1, double num2){
return num1 - num2;
}
double multiplication(double num1, double num2){
return num1 * num2;
}
double division(double num1, double num2){
return num1 / num2;
}
double calculate(double (*ptrCalc)(double, double), double num1, double num2){
return ptrCalc(num1, num2);
}
static int myteststatic()
{
return 100;
}
int main()
{
double num1, num2;
char op;
double (*ptrCalc)(double, double);
op = '+';
switch(op)
{
case '+':
ptrCalc = addition;
break;
case '-':
ptrCalc = substraction;
break;
case '*':
ptrCalc = multiplication;
break;
case '/':
ptrCalc = division;
break;
}
calculate(ptrCalc, num1, num2);
return 0;
}
[root@promote tenglei]# gcc functest.c
[root@promote tenglei]# nm a.out |grep division
0000000000400584 T division
[root@promote tenglei]# nm a.out |grep multiplication
000000000040056a T multiplication
[root@promote tenglei]# g++ functest.c
[root@promote tenglei]# nm a.out |grep division
00000000004005a4 T _Z8divisiondd
[root@promote tenglei]# nm a.out |grep multiplication
000000000040058a T _Z14multiplicationdd
[root@promote tenglei]# gcc functest.c
[root@promote tenglei]# ./a.out
[root@promote tenglei]# nm a.out
[root@promote tenglei]# objdump -t a.out |grep division
0000000000400584 g F .text 000000000000001a division
[root@promote tenglei]# objdump -t a.out |grep myteststatic
00000000004005d4 l F .text 000000000000000b myteststatic
[root@promote tenglei]#
[root@promote tenglei]#
[root@promote tenglei]#
[root@promote tenglei]# g++ functest.c
[root@promote tenglei]# objdump -t a.out |grep division
00000000004005a4 g F .text 000000000000001a _Z8divisiondd
[root@promote tenglei]# objdump -t a.out |grep myteststatic
00000000004005f4 l F .text 000000000000000b _ZL12myteststaticv
[root@promote tenglei]#
可以看到C++没有保存原始的函数名作为函数指针,因为C++命名规则描述的信息更加多;
_ZL12myteststaticv
---_Z—表示C++
--L应该表示local,即static型函数
最后---v应该是返回数值是void型
举个例子:
C++语言
char test(); ----- _Z4testv
_Z表示C++,4代表函数名有4个字节,test是函数名,v代表参数为空
double func(unsigned int a,double *b,char c); ----- _Z4funcjPdc
j代表int,Pd代表double型指针,c代表char**
C语言
只是简单一个函数名,没有其他修饰信息。真的很漂亮,就是简洁
char test(); ----- test
double func(unsigned int a,double *b,char c); ----- func
下面讨论一下C++特有类成员函数和类虚函数的函数指针
C++指向函数的指针定义方式为:
返回类型 (*指针名)(函数参数列表),例如 void (*p)(int)是指向一个返回值为void 参数为int类型的函数。
而若想定义一个指向类成员函数的函数指针该怎么定义呢?对成员函数指针的使用。
(1)非静态成员函数
定义方式:返回类型 (类名::*指针名)(函数参数列表)例如void (A::*p)(int)是一个指向A类中成员函数的函数指针。
赋值方式:p=&A::函数名,而一般的函数指针的赋值是p=函数名即可,注意区别。(成员函数必须是public类型的)
调用方式:成员函数指针的调用必须通过类对象进行调用,a.*p(int)即可调用成员函数(该成员函数是public类型的)
(2)静态成员函数
对于静态成员函数的定义和使用方法都与普通函数指针的相同,只是在赋值的时候和非静态成员们函数指针的赋值方法相同。
因为静态成员函数的指针类型是与普通函数指针类型相同的。
----------------------------------------------------------------------------------------------------------------------
对于单类型的成员函数指针的使用就是上述的内容,但是对于C++来说,继承是其三大特性之一,那么对于继承类来说,成员函数指针怎么实现动态呢?
(1)赋值问题
与正常的派生类指针或引用可以赋于基类指针或引用不同,基类成员函数可以赋于派生类成员函数指针(任何情况下都不会出错),反之派生类成员函数在未覆盖基类函数名的情况下也能赋于基类成员函数指针。
如下例:
[root@promote tenglei]# cat functestcpp.cpp
#include <iostream>
class A
{
public:
A(){std::cout<<"Construct A"<<std::endl;}
~A(){std::cout<<"Delete A"<<std::endl;}
void printA()
{
std::cout<<"a:"<<std::endl;
}
int a;
};
class B
{
public:
B(){std::cout<<"Construct B"<<std::endl;}
~B(){std::cout<<"Delete B"<<std::endl;}
void printB()
{
std::cout<<"b:"<<std::endl;
}
int a;
};
class C:public A,B
{
public:
C(){std::cout<<"Construct C"<<std::endl;}
~C(){std::cout<<"Delete C"<<std::endl;}
void printC()
{
std::cout<<"c:"<<std::endl;
}
virtual void printB()
{
std::cout<<"cb:"<<std::endl;
}
int a;
};
class D :virtual public C
{
public:
D(){std::cout<<"Construct D"<<std::endl;}
~D(){std::cout<<"Delete D"<<std::endl;}
void printD()
{
std::cout<<"d:"<<std::endl;
}
virtual void printB()
{
std::cout<<"db:"<<std::endl;
}
int a;
};
int main()
{
A a;
a.printA();
B b;
b.printB();
C c;
c.printC();
c.printB();
D d;
d.printD();
C* b1 = new D();//对象的虚表指针是D的,所以输出是D的函数
b1->printB();
C c1;
D d;
c1 = d; //虚表指针未被覆盖,输出是c的函数;
c1.printB();
return 0;
}void (A::*Pa)(int);void (B::*Pb)(int);void (C::*Pc)(int)
pa=&C::printA;(正确)相当于将C中A类对象的函数传给指针。
pb=&C::printB;(错误)因为printB已经在C类中修改,两者的类型不同
pc=&A::printA;(正确)
pc=&B::printB;(正确)
总结:只要不被派生类覆盖的函数均可以赋给基类成员函数指针。
(2)多态调用
接上例:
pb=&B::printB;
(b.*pb)(i);(调用b类中的printB)
(c.*pb)(i);(调用c类中的printB)
但是对于pc指针只能由c类对象调用,不能由基类调用。
根据对象实例的类型去判断应该调用的函数。