函数指针实例 + 静态成员变量实例 + 纯虚函数和抽象类定义 + 基类析构函数声明为virtual的作用《转载》...

《慢慢啃》 2011.11.29

函数指针

  
函数指针是指向函数的指针变量。
  因而“函数指针”本身首先应是指针变量,只不过该指针变量指向函数。这正如用指针变量可指向整型变量、字符型、数组一样,这里是指向函数。 如前所述,C在编译时,每一个函数都有一个入口地址,该入口地址就是函数指针所指向的地址。有了指向函数的指针变量后,可用该指针变量调用函数,就如同用 指针变量可引用其他类型变量一样,在这些概念上一致的。函数指针有两个用途:调用函数和做函数的参数。函数指针的说明方法为:
  数据类型标志符 (指针变量名)(形参列表);
  注1:“函数类型”说明函数的返回类型,由于“()”的优先级高于“*”,所以指针变量名外的括号必不可少,后面的“形参列表”表示指针变量指向的函数所带的参数列表。例
  int func(int x); /* 声明一个函数 */
  int (*f) (int x); /* 声明一个函数指针 */
  f=func; /* 将func函数的首地址赋给指针f */
  赋值时函数func不带括号,也不带参数,由于func代表函数的首地址,因此经过赋值以后,指针f就指向函数func(x)的代码的首地址。
  注2:函数括号中的形参可有可无,视情况而定。
  下面的程序说明了函数指针调用函数的方法:
  例一、
  #include<stdio.h>
  int max(int x,int y){ return(x>y?x:y); }
  void main()
  {
  int (*ptr)(int, int);
  int a,b,c;
  ptr=max;
  scanf("%d,%d",&a,&b);
  c=(*ptr)(a,b);
  printf("a=%d,b=%d,max=%d",a,b,c);
  }
  ptr是指向函数的指针变量,所以可把函数max()赋给ptr作为ptr的值,即把max()的入口地址赋给ptr,以后就可以用ptr 来调用该函数,实际上ptr和max都指向同一个入口地址,不同就是ptr是一个指针变量,不像函数名称那样是死的,它可以指向任何函数,就看你像怎么做 了。在程序中把哪个函数的地址赋给它,它就指向哪个函数。而后用指针变量调用它,因此可以先后指向不同的函数, 不过注意,指向函数的指针变量没有++和--运算,用时要小心
  不过,在某些编译器中这是不能通过的。这个例子的补充如下。
  应该是这样的:
  1.定义函数指针类型:
  typedef int (*fun_ptr)(int,int);
  2.申明变量,赋值:
  fun_ptr max_func=max;
  也就是说,赋给函数指针的函数应该和函数指针所指的函数原型是一致的。
  例二、
  #include<stdio.h>
  void FileFunc()
  {
  printf("FileFunc/n");
  }
  void EditFunc()
  {
  printf("EditFunc/n");
  }
  void main()
  {
  void (*funcp)();
  funcp=FileFunc;
  (*funcp)();
  funcp=EditFunc;
  (*funcp)();
  }
   指针函数和函数指针的区别
  1,这两个概念都是简称,指针函数是指带指针的函数,即本质是一个函数。我们知道函数都又有返回类型(如果不返回值,则为无值型),只不过指针函数返回类型是某一类型的指针。
  其定义格式如下所示:
  返回类型标识符 *返回名称(形式参数表)
  { 函数体 }
  返回类型可以是任何基本类型和复合类型。返回指针的函数的用途十分广泛。事 实上,每一个函数,即使它不带有返回某种类型的指针,它本身都有一个入口地址,该地址相当于一个指针。比如函数返回一个整型值,实际上也相当于返回一个指针变量的值,不过这时的变量是函数本身而已,而整个函数相当于一个“变量”。例如下面一个返回指针函数的例子:
  #include
  float *find();
  main()
  {
  static float score[][4]={{60,70,80,90},{56,89,34,45},{34,23,56,45}};
  float *p;
  int i,m;
  printf("Enter the number to be found:");
  scanf("%d",&m);
  printf("the score of NO.%d are:/n",m);
  p=find(score,m);
  for(i=0;i<4;i++)
  printf("%5.2f/t",*(p+i));
  }
  float *find(float(*pionter)[4],int n)/*定义指针函数*/
  {
  float *pt;
  pt=*(pionter+n);
  return(pt);
  }
  学生学号从0号算起,函数find()被定义为指针函数,起形参pointer是指针指向包含4个元素的一维数组的指针变量。 pointer+1指向 score的第一行。*(pointer+1)指向第一行的第0个元素。pt是一个指针变量,它指向浮点型变量。main()函数中调用find()函 数,将score数组的首地址传给pointer.
  2,“函数指针”是指向函数的指针变量,因而“ 函数指针”本身首先应是指针变量,只不过该指针变量指向函数。 这正如用指针变量可指向整型变量、字符型、数组一样,这里是指向函数。如前所述,C在编译时,每一个函数都有一个入口地址,该入口地址就是函数指针所指向 的地址。有了指向函数的指针变量后,可用该指针变量调用函数,就如同用指针变量可引用其他类型变量一样,在这些概念上一致的。函数指针有两个用途:调用函 数和做函数的参数。
  函数指针的说明方法为:
  数据类型标志符 (*指针变量名)(参数);
  注1:函数括号中的参数可有可无,视情况而定。下面的程序说明了函数指针调用函数的方法:
  #include
  int max(int x,int y){ return(x>y?x:y); }
  void main()
  {
  int (*ptr)();
  int a,b,c;
  ptr=max;
  scanf("%d,%d",&a,&b);
  c=(*ptr)(a,b);
  printf("a=%d,b=%d,max=%d",a,b,c);
  }
  ptr是指向函数的指针变量,所以可把函数max()赋给ptr作为ptr的值,即把max()的入口地址赋给ptr,以后就可以用ptr来调用该函数,实际上ptr和max都指向同一个入口地址,不同就是 ptr是一个指针变量,不像函数名称那样是死的,它可以指向任何函数,就看你像怎么做了。在程序中把 哪个函数的地址赋给它,它就指向哪个函数。而后用指针变量调用它,因此可以先后指向不同的函数, 不过注意,指向函数的指针变量没有++和--运算,用时要小心。
   关于函数指针数组的定义
  关于函数指针数组的定义方法,有两种:一种是标准的方法;一种是蒙骗法。
  第一种,标准方法:
  {
  分析:函数指针数组是一个其元素是函数指针的数组。那么也就是说,此数据结构是是一个数组,且其元素是一个指向函数入口地址的指针。
  根据分析:首先说明是一个数组:数组名[]
  其次,要说明其元素的数据类型指针:*数组名[].
  再次,要明确这每一个数组元素是指向函数入口地址的指针:函数返回值类型 (*数组名[])().请注意,这里为什么要把“*数组名[]”用括号扩起来呢?因为圆括号和数组说明符的优先级是等同的,如果不用圆括号把指针数组说明 表达式扩起来,根据圆括号和方括号的结合方向,那么 *数组名[]() 说明的是什么呢?是元素返回值类型为指针的函数数组。有这样的函数数祖吗?不知道。所以必须括起来,以保证数组的每一个元素是指针。
  }
  第二种,蒙骗法:
  尽管函数不是变量,但它在内存中仍有其物理地址,该地址能够赋给指针变量。获取函数方法是:用不带有括号和参数的函数名得到。
  函数名相当于一个指向其函数入口指针常量。 那么既然函数名是一个指针常量,那么就可以对其进行一些相应的处理,如强制类型转换。
  那么我们就可以把这个地址放在一个整形指针数组中,然后作为函数指针调用即可。
  完整例子:
  #include "stdio.h"
  int add1(int a1,int b1);
  int add2(int a2,int b2);
  void main()
  {
  int numa1=1,numb1=2;
  int numa2=2,numb2=3;
  int (*op[2])(int a,int b);
  op[0]=add1;
  op[1]=add2;
  printf("%d %d/n",op[0](numa1,numb1),op[1](numa2,numb2));
  }
  int add1(int a1,int b1)
  {
  return a1+b1;
  }
  int add2(int a2,int b2)
  {
  return a2+b2;
  }
   再给出常用的C变量的定义方式:
  a) 一个整型数(An integer)
  b) 一个指向整型数的指针(A pointer to an integer)
  c) 一个指向指针的的指针,它指向的指针是指向一个整型数(A pointer to a pointer to an integer)
  d) 一个有10个整型数的数组(An array of 10 integers)
  e) 一个有10个指针的数组,该指针是指向一个整型数的(An array of 10 pointers to integers)
  f) 一个指向有10个整型数数组的指针(A pointer to an array of 10 integers)
  g) 一个指向函数的指针,该函数有一个整型参数并返回一个整型数(A pointer to a function that takes an integer as an argument and returns an integer)
  h) 一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数( An array of ten pointers to functions that take an integer argument and return an integer )
  答案是:
  a) int a; // An integer
  b) int *a; // A pointer to an integer
  c) int **a; // A pointer to a pointer to an integer
  d) int a[10]; // An array of 10 integers
  e) int *a[10]; // An array of 10 pointers to integers
  f) int (*a)[10]; // A pointer to an array of 10 integers
  g) int (*a)(int); // A pointer to a function a that takes an integer argument and returns an integer
  h) int (*a[10])(int); // An array of 10 pointers to functions that take an integer argument and return an integer
 
 
  
需要注意:
1)函数指针所指向的函数,必须为全局函数或类的静态函数。
 
代码:
#include  " stdafx.h "
#include 
< iostream >

class  A
{
    typedef 
int (*fun)(void);

public:    
     
void  Test(fun fun1)
     
{
         (
*fun1)();
     }
;

    
static int add(void)
    
{
        std::cout
<<"A::add()"<<std::endl;
        
return 2;        
    }

    
}
;

int  add2( void )
{
    std::cout
<<"add2()"<<std::endl;
    
return 2;
    
}

static   int  add3( void )
 
{
     std::cout
<<"add3()"<<std::endl;
     
return 3;
 }



int  _tmain( int  argc, _TCHAR *  argv[])
{
    A a;    
    a.Test(A::add);
    a.Test(add2);
    a.Test(add3);
    
return 0;
}

// output
// A::add()
// add2()
// add3()
// Press any key to continue . . .

成员函数指针:
#include  " stdafx.h "  

class  CMemFuncPtr; 

typedef 
int  (CMemFuncPtr:: * MemFuncPtr)( int int ); 

class  CMemFuncPtr 

public
int Add(int iFirst, int iSecond) 

return iFirst + iSecond; 
}
 
}


int  _tmain( int  argc, _TCHAR *  argv[]) 

MemFuncPtr pfnMemFunc 
= &CMemFuncPtr::Add; 
CMemFuncPtr test; 
(test.
*pfnMemFunc)(13); 

return 0
}
 

other samples:
#include  " stdafx.h "  

class  Object; 
typedef 
int  (Object:: * MemFuncPtr)( int int ); 
typedef 
int  ( * StaticMemFuncPtr)( int int ); 

class  Object 

public
// non-static member 
int Add(int iFirst, int iSecond) 

return iFirst + iSecond; 
}
 
// static member 
static int Sub(int iFirst, int iSecond) 

return iFirst - iSecond; 
}
 

// 
typedef int (Object::*InClassMemFuncPtr)(intint); 
typedef 
int (*InClassStaticMemFuncPtr)(intint); 
}


int  _tmain( int  argc, _TCHAR *  argv[]) 

// Test non-static Add 
// 
MemFuncPtr pfnMemFunc = &Object::Add; 
// Need to bind the member function to a instance (need a this pointer) 
Object test; 
int res = (test.*pfnMemFunc)(13); 

// function call missing argument list; use '&Object::Add' to create a pointer to member 
//MemFuncPtr pfnMemFunc1 = test.Add; 
//res = (test.*pfnMemFunc1)(1, 3); 

Object::InClassMemFuncPtr pfnMemFunc2 
= &Object::Add; 
res 
= (test.*pfnMemFunc2)(13); 

// Test Static Sub 
// 
// cannot convert from 'int (__cdecl *)(int,int)' to 'MemFuncPtr' 
// Object::Sub Calling convention is __cdecl, but Object::*MemFuncPtr should be thisCall 
// function signature include calling convention, parameter list and return value.. 

//pfnMemFunc = Object::Sub; 
//res = (test.*pfnMemFunc)(1, 3); 

StaticMemFuncPtr pfnStaticMemFunc 
= Object::Sub; 
res 
= (*pfnStaticMemFunc)(13); 

StaticMemFuncPtr pfnStaticMemFunc1 
= &Object::Sub; 
res 
= (*pfnStaticMemFunc1)(13); 

Object::InClassStaticMemFuncPtr pfnStaticMemFunc2 
= &Object::Sub; 
res 
= (*pfnStaticMemFunc2)(13); 


// stl ? how to use this “function pointer”??? 
//std::mem_fun<int, Object>(&Object::Add); 
//std::mem_fun<int, Object>(&Object::Sub); 

return 0
}
 

总结:
1)一般的全局的函数指针,只可以使用静态,全局的函数。
2)类的成员函数指针,可以使用类的成员函数。可以使用类的public成员函数指针。
 
 
静态成员变量
 
作者:HolyFire
我们学习C++的时候知道静态变量的特性,他不是临时变量,在编译期间就已经产成。用一个例子就能说明问题。
#include <iostream>
using namespace std;
class A{
public:
        A(){ cout << "Can you see me Now!" << endl; }
        ~A(){ cout << "I'm Go Away!" << endl; }
};
void TestStatic( void )
{
        static A a;
}
void main()
{
        cout << "Program Start!" << endl;
        TestStatic();
        TestStatic();
        TestStatic();
        cout << "Program End!" << endl;
}
结果是:
Program Start!
Can you see me Now!
Program End!
I'm Go Away!
A a只被定义了一次,而且析构是在主程序退出以后才进行的。这说明了什么呢,A a在void TestStatic( void )是同一个实例,他存在于整个程序但是只有在void TestStatic( void )中能访问他。
不相信?那我们来试试看。
#include <iostream>
using namespace std;
class A{
private:
        int count;
public:
        A():count(0){ cout << "Can you see me Now!" << endl; }
        ~A(){ cout << "I'm Go Away!" << endl; }
        void Inc(){ count++; }
        int Count(){ return count; }
};
void TestStatic( void )
{
        static A a;
        a.Inc();
        cout << "count's value is : " << a.Count() << endl;
}
void main()
{
        cout << "Program Start!" << endl;
        TestStatic();
        TestStatic();
        TestStatic();
        cout << "Program End!" << endl;
}
结果是:
Program Start!
Can you see me Now!
count's value is : 1   //初始化count为0,Inc导致count自加值应该为1
count's value is : 2   //没有初始化,Inc导致count自加值应该为2
count's value is : 3   //没有初始化,Inc导致count自加值应该为3
Program End!
I'm Go Away!
事实说明了一切,那么他是如何实现的呢,C++编译器里,他被创建在一个内存区域里,这块区域不是堆也不是栈,编译器在编译阶段就将他们记住,并为他们做好分配工作,如此一来就可以实现这个特性。
看起来他的作用有些象全局变量,但是我们知道,使用全局变量会整加模块的耦合性,降低代码的通用性,所以静态成员变量的出现为我们编程带来的灵活性。
如何将这个小东西用在我们的面向对象编程中呢。他扮演一个什么样的角色呢,这正是我要说的。
我们知道,类的成员变量表示了一个类的属性,对应着对象的物质特性,他们在类的某个实例创建的时候创建,消亡的时候消亡。但是上面说到,静态变量在编译期间就已经存在了,也就是并不随着实例创建的时候创建,消亡的时候消亡。是这样吗。看事实说话。
#include <iostream>
using namespace std;
class A{
public:
        A() { cout << "A is On!" << endl; }
        ~A() { cout << "A is OFF!" << endl; }
};
class B{
public:
        B() { cout << "B is On!" << endl; }
        ~B() { cout << "B is OFF!" << endl; }
private:
        static A a;
};
A B::a;
void main()
{
        cout << "Program Start!" << endl;
        B b1,b2,b3;
        cout << "Program End!" << endl;
}
结果是:
A is On!  //瞧我又说中了,主程序还没有运行,构造函数就开始工作了,这时B的实例还没有登场
Program Start!
B is On!  //b1创建了,但是b1.a并没有创建
B is On!
B is On!
Program End!
B is OFF!  //B的实例销毁了,但是成员变量a没有销毁
B is OFF!
B is OFF!
A is OFF!  //看吧,这才是A的析构函数
注意一个约定:
A B::a;
静态成员变量的初始化一定要在主函数外面,而且静态成员变量一定要初始化。
事实上B::a并不属于B,将他作为B的成员只是为了确定访问的权限
private:
        static A a;
因为这样设定以后只有B才能访问B::a,当然设计的时候要考虑清楚,如果a与B并没有关系,那么这样的设计就没有什么实际的意义。
那么如何设计才能运用这个特性呢。
我们来举个例子。
我们有时候想知道类的实例有几个,也就是被实例化了几次。比如
class A;
A a1,a2,a3;
那么就应该被实例化了3次。能知道这个信息应该很不错。如果用一个全局变量,那么要考虑的问题很多,全局变量会被人任意修改,全局变量定义在那里以及全局变量初始化在那里也会带来困惑,全局变量会不会跟别的全局变量重名等等。
既然静态变量可以实现一些全局变量的功能,何不牛刀小试,看看效果如何。首先静态成员变量只有一个,而且不是类的真实属性,实际上只有一个变量,不会增加类的负担;第二可以给他加上访问限制,只要不是public那么就不能随意修改。既然好处多多,那么就开工。
#include <iostream>
using namespace std;
class A{
public:
       A(){ count++; };  //当产生一个实例的时候计数器加一
    ~A(){ count--; }  //当销毁一个实例的时候计数器减一
       int GetInstanceCount(){ return count; }
private:
       static int count;
};
int A::count = 0;
void main()
{
       cout << "Program Start! " << endl << endl;
       A a1,a2,a3;
       {
              A a4,a5,*pa;
              cout << "Now, Have a1 ,a2 ,a3 , a4 ,a5 Instances!" << endl;
              cout << "Number of class A's Instance is : " << a1.GetInstanceCount() << endl << endl;
              pa = new A;
              cout << "Now Creat a class A's Instance!" << endl;
              cout << "Number of class A's Instance is : " << a2.GetInstanceCount() << endl << endl;
              delete pa;
       }
       cout << "While class's Instances a4 , a5 , pa destroy!" << endl;
       cout << "Only a1 , a2 , a3 Left , is the Count of Instance is 3 ?" << endl;
       cout << "Number of class A's Instance is : " << a3.GetInstanceCount() << endl << endl ;
}
结果是:
Program Start!
Now, Have a1 ,a2 ,a3 , a4 ,a5 Instances!  //有a1 ,a2 ,a3 ,a4 ,a5五个实例
Number of class A's Instance is : 5       //没错,正是五个
Now Creat a class A's Instance!        //在堆里创建了一个,使用pa得到他的引用
Number of class A's Instance is : 6      //跟想的一样,数目增加了
While class's Instances a4 , a5 , pa destroy!  //在堆里释放一个,栈里释放2个
Only a1 , a2 , a3 Left , is the Count of Instance is 3 ? //6 – 1 –2 当然等于3啦
Number of class A's Instance is : 3  我说的没错吧。
因为在构造函数里操作的是同一个变量,所以才能得到正确的结果。这个技术在很多方面得到应用,比如,互斥信号,象动态连接库一样的引用计数器等等。
这里要记住的是,类的静态成员变量实际上只有一个,它不随着类的实例的创建/销毁而增加/减少。它不是类的真正成员,并不是类的一部分。
  
  
纯虚函数和抽象类定义
   
   上面我们已经看到:基类Vehicle包含有自己的、具体的message虚函数的实现。在C++中,也可以在基类中仅定义虚函数的信号:函数的名字、返回类型和参数,而没有实现,但在派生类中必须有该虚函数实现。
   仅定义了函数的信号,而没有函数实现的虚函数称之为纯虚函数。定义纯虚函数的方法是在虚函数参数表右边的括号后加一个"=0"的后缀,例如:
   class vehicle
   {
    …
    virtual void message(void) = 0;
   };
   上面这段代码中,我们便把vehicle的message成员函数定义为纯虚函数。含有纯虚函数的类,我们称之为抽象类,所以,抽象类也称之为抽象的基类。
   下面,我们再看一个抽象类的例子:
   class CPolygon
   {
    protected:
    int width, height;
   public:
    void set_values (int a, int b)
     { width=a; height=b; }
    virtual int area (void) =0;
};
   Cpolygon类的成员函数area被定义为纯虚函数,它返回int变量,没有参数。因为Cpolygon类包含了纯虚函数area,所以,它是一个抽象类,下面的定义:
   CPolygon poly;
是错误的。我们虽然不能定义抽象类对象,但可以定义抽象类指针,例如:
   CPolygon * ppoly1;
   CPolygon * ppoly2
是完全正确的,这是因为基类的指针可以指向派生类的对象
对于抽象类的使用有以下几点规定
  (1) 抽象类只能用作其它类的基类,不能建立抽象类实例。
  (2) 抽象类不能用作参数类型、函数返回类型或显式类型转换。
  (3) 可以声明抽象类的指针或引用,此指针或引用可以指向它的派生类,进而实现多态性。 可以通过如下两种方法间接定义抽象类:
  一种方法是将该类中的所有构造函数都声明为私有类型或受保护类型,这样该类就不能有自己的实例;另一种方法是在该类中定义"纯虚函数"。
  定义纯虚函数的一般格式为:virtual <类型> 成员函数名 (<参数表>)=0;
有关纯虚函数的定义和使用,须说明以下几点:
  (1) 定义纯虚函数时,不能定义其实现部分。因此在对纯虚函数重新定义之前不能调用该函数。
  (2) 虚函数函数名赋值0并没有特别的含义,只是表明该虚函数为纯虚函数。
  (3) 在定义具有纯虚函数的类(抽象类)的派生类时,必须对纯虚函数重新定义,否则该派生类还是抽象类,不能有自己的实例。
  (4) 具有纯虚函数的抽象类的对象指针不能调用抽象类中的纯虚函数(见1),但可以调用抽象类中的非纯虚函数。
 
 
  基类析构函数声明为virtual的作用
 
 
基类析构函数声明为virtual是很重要的~

比如:有一class B 继承自 上边的 class A;
如果我们这样 A pClassA = new B(pClassA这里是个指针); 即new 一个派生类(B)的对象,并且有基类的指针来指向它,这样没问题。但是如果我们要释放刚才的空间delete pClassA 这个就会有麻烦,因为C++标准中对这一行为没有明确定义,即通过基类指针来销毁派生类对象这个行为的结果是无法预期的,其中可能直接导致,delete pClassA 这个行为 根本不能使派生类的(B)的析构函数被调用到,如果B的析构函数中有一些必须要做的事 (比如释放资源什么的)那么将无法进行了;

如果将基类(A)的析构函数声明为virtual就不会这样了,这是因为虚函数会由类的虚函数表(vtalbe)来维护,每当通过指针或引用来调用虚函数,就会到这个vtable中去找相应的虚函数指针;

每个有virtual函数的类都有一份vtable,派生类将自动继承基类的vtable,vtable中实际存储的是virtual函数的指针~
每当派生类改写了基类虚函数,vtable的相应指针项就会更改了而指向新的(派生类override的)虚函数地址。
这样 我们上边的delete pClassA这个行为 就会引发 B类的析构函数了。

转载于:https://www.cnblogs.com/kanego/articles/2269361.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值