第十七章 特殊类成员

第十七章  特殊类成员

1.1  静态成员变量

假如我们要在一个类中的所有对象间共享某种数据,那不妨将其设置为静态成员变量/函数;

static x

1.2  静态成员变量

静态成员变量与成员变量有4点不同:

①前者属于类②前者必须在全局定义③前者被调用只用说明那个类即可④前者在没有创建对象之前就已经存在

#include <iostream>
using namespace std;
class A
{
public:
    static int n;
};
int A::n=0;    //要使用类成员限定来访问静态成员
void show(){cout<<A::n<<"a \n";}
int main()
{
    int i;
    for(i=0;i<5;i++)
    {
        A::n++;            //访问静态成员变量n并对其进行自加操作............公有的可以直接访问①
        show();                //调用全局函数show,该函数输出静态成员变量n的值
    }
    return 0;
}

2.  私有静态成员变量

#include <iostream>
using namespace std;
class A
{
public:
    void func(){cout<<A::x<<endl;;}     //定义公有成员函数func(),用该函数访问私有静态成员变量x
private:
    static int x;                 //将静态成员变量x声明为私有
};
int A::x=1000;                 //定义并初始化静态成员变量
int main()
{
    A a;                          //创建一个对象
    a.func();                         //用该对象访问公有成员函数func()
    //cout << A::x <<endl;        //注意不能直接访问...............私有的不可以直接访问①
    return 0;
}

3.  静态成员函数

静态成员函数在未创建对象时也可以使用,它的使用和静态成员变量一样 ①

/*
#include <iostream>
using namespace std;
class A
{
public:
    void static show(){cout<<A::n;n++;}     //注意:静态成员函数不能访问某个对象的成员变量,因为他没有指向该对象的this指针。不过它可以访问该类的静态成员变量 ②
private:
    static int n;                      //声明私有静态成员变量n
};
int A::n=0;                             //定义私有静态成员变量n
int main()
{
    int i;
    for(i=0;i<5;i++)
    {
        A::show();                         //循环调用公有静态成员函数show()
        cout<<endl;
    }
    return 0;
}
*/

/*
//另外我们也可以通过对象来访问静态成员函数,如:
#include <iostream>
using namespace std;
class A
{
public:
    void static show(){cout<<A::n;n++;}
private:
    static int n;
};
int A::n=0;
int main()
{
    A a,b,c;
    a.show();
    b.show();
    c.show();
    return 0;
}
*/

#include <iostream>
using namespace std;
class A
{
public:
    void show(int i){x=i;cout<<x;}
    //void static show1(int j){x=j;cout<<x;}
    //静态成员函数没有指向对象的this指针,所以不能访问对象的成员数据
private:
    int x;
};
int main()
{
    A a;
    a.show(1);
    //a.show1(2);
    //不能通过静态成员函数访问自己的成员变量
    return 0;
}

4.  静态成员的使用

静态成员可以被继承。

类中任何成员函数都可以访问静态成员,但是静态成员函数不能直接访问非静态成员,静态成员函数不能为说明为虚函数

#include <iostream>
using namespace std;
class aspl         //将阿司匹林声明为一个aspl类,那么每箱阿司匹林就是该类的一个对象
{
public:
    aspl(float p){price=p;TotalPrice=p+TotalPrice;}    //在构造函数中实例化该对象的私有成员变量price,这样就得到了一箱阿司匹林并且有了它的初始价格
    ~aspl(){TotalPrice=TotalPrice-price;}     //析构函数销毁该对象并且将总价格减去该箱的价格,这样账面上就少了一箱阿司匹林,并且总价格也减去了该箱的价格
    static float get(){return TotalPrice;}
private:
    float price;                //由于每箱阿司匹林都有价格,因此必须得有个成员变量来表示价格,这里在aspl这个类中声明一个私有成员变量price
    static float TotalPrice;      //由于阿司匹林的总价格属于类的总价格,而不是某一箱阿司匹林的价格,因此我们要将总价格声明为静态成员变量,这里声明为TotalPrice
};
float aspl::TotalPrice=0;      //静态成员变量必须初始化
void main()
{
    float f;
    cout<<"阿司匹林的库存总价格为:";
    cout<<aspl::get()<<endl;      //必须用类名限定符来调用静态成员函数
    int i=0;
    cout<<"请输入第"<<i+1<<"次购进的阿司匹林的单箱价格:";
    cin>>f;
    aspl *p[5];                  //定义了5个指向aspl类的数组指针p
    p[i]=new aspl(f);           //购进一箱阿司匹林
    cout<<"阿司匹林的库存总价格为:";
    cout<<aspl::get()<<endl;      //输出总价格
    i++;                          //i代表购进的次数,i++表示将要进行i+1次购进
    cout<<"请输入第"<<i+1<<"次购进的阿司匹林的单箱价格:";  //提示用户输入i次购进
    cin>>f;
    p[i]=new aspl(f);          //输入的数值保存在i次购进的对象的成员变量中
    cout<<"阿司匹林的库存总价格为:";
    cout<<aspl::get()<<endl;     //输出当前的库存总价格
    cout<<"请输入卖出的阿司匹林的编号,编号即第几次购进:";//提示用户要删除哪次购进
    cin>>i;                        //将输入值保存在i变量中
    delete p[i];                  //删除第i次创建的对象
    cout<<"阿司匹林的库存总价格为:";
    cout<<aspl::get()<<endl;      //再次输出销售一箱阿司匹林后的库存总价格
}

5.  函数指针

long(*func1)(int);  //声明了一个函数指针

long* func2(int);  //声明了一个返回指针的函数

第一种定义了一个叫func1的函数指针,该指针指向一个含有int型参数并且返回值为long型

函数指针名可以看作函数名的代号,我们可以通过它来直接调用函数,所以函数指针经常会在条件或者判断语句里出现,以便于用户选择调用不同名字但又类型和参数相同的函数。

另外要注意的是:函数指针可以指向某个函数,但是前提是被指向的函数的参数和返回至都与该函数指针被声明时的返回值和参数相吻合

//函数指针使用实例的程序代码如下:
#include <iostream>
#include <string>
using namespace std;
bool check(string str)     //检测是否是数字的函数,要注意该函数一定要放在调用函数的上面
{
   for(int i = 0;i<str.length();i++)
   if((str[i]>‘9‘ || str[i]<‘0‘)&&(str[i]!=‘.‘))
   return false;
   return true;
}
float triangle(float &x,float &y)
{
   return x*y*0.5;
}
float rectangle(float &x,float &y)
{
   return x*y;
}
void Swap(float &x,float &y)
{
   float n;
   n=x;
   x=y;
   y=n;
}
void print(float &x,float &y)
{
   cout<<"长为:"<<x<<"  "<<"宽为:"<<y<<endl;
}
void get(float &a ,float &b)
{
   cout<<"请输入x的新值:";
   string str1;cin>>str1;
   while(!check(str1))     //调用检测数字函数,如果返回值为假,执行该循环,为真退出
   {
      cout<<"输入的不是数字,请重新输入!!!"<<endl;
      cin>>str1;
   }
   a = atof(str1.c_str());         //将字符串转换为浮点数
   cout<<"请输入y的新值:";
   string str2;cin>>str2;
   while(!check(str2)){
      cout<<"输入的不是数字,请重新输入!!!"<<endl;
      cin>>str2;
   }
   b = atof(str2.c_str());
}
int main()
{
   void(*p)(float &,float &);         //声明一个函数指针p,该指针指向一个返回void值并且带有两个float参数的函数
   float(*fp)(float &, float &);        //声明一个函数指针fp,该指针指向一个返回float值并且带有两个float参数的函数
   bool quit=false;
   float a=2,b=3;                     //定义两个参数a和b的值
   int choice;                           //声明选择参数choice
   while(quit==false)
   {
      cout<<"(0)退出(1)设定长宽(2)三角形(3)矩形(4)交换长宽:";
      cin>>choice;
      switch(choice)                     //条件判断语句
      {
         case 1:
            p=get;                        //用指针p来指向函数名get,该函数带有两个float参数并返回一个void值,与函数指针p的参数和类型相吻合
            break;
         case 2:
            fp=triangle;                //用指针fp来指向函数名triangle,该函数带有两个float参数并返回一个float值,与函数指针fp的参数和类型相吻合
            break;
         case 3:
            fp=rectangle;                //用指针fp来指向函数名rectangle,该函数带有两个float参数并返回一个float值,与函数指针fp的参数和类型相吻合
            break;
         case 4:
            p=Swap;                       //用指针p来指向函数名swap,该函数带有两个float参数并返回一个void值,与函数指针p的参数和类型相吻合
break;
            default:
            quit=true;
            break;
      }
      if(quit)break;
      if(choice==1||choice==4)            //假如选择了第1或者第4项
      {
         print(a,b);
         p(a,b);                             //调用函数指针p所指向的函数,该指针指向的是一个返回值为void的函数,由于不同的选项中将不同的函数名赋给了指针p,因此选择不同则调用的函数也不同
         print(a,b);
      }
      else if(choice==2||choice==3)        //假如选择了第2和第3项
      {
         print(a,b);
         cout<<"面积为:"<<fp(a,b)<<endl;    //调用函数指针fp所指向的函数,该指针指向的是一个返回值为float的函数,由于不同的选项中将不同的函数名赋给了指针fp,因此选择不同则调用的函数也不同
      }
   }
   return 0;
}

6.  函数指针数组

void(*p[5])(float &,float &);

7.  函数指针也可以作为函数的参数

#include <iostream>
using namespace std;
void square(int &x,int &y)
{
    x=x*x;
    y=y*y;
}
void cube(int &x,int &y)
{
    x=x*x*x;
    y=y*y*y;
}
void Swap(int &x,int &y)
{
    int z;
    z=x;
    x=y;
    y=z;
}
void print(void(*p)(int &x,int &y),int &x,int &y)    //该函数有3个参数,第1个是一个函数指针p,它指向的函数带有两个参数,
                                                    //并返回一个void值,另外还有两个int型引用x和y
{
    cout<<"执行函数前\n";
    cout<<"x:"<<x<<"\t"<<"y:"<<y<<endl;
    p(x,y);
    cout<<"执行函数后\n";
    cout<<"x:"<<x<<"\t"<<"y:"<<y<<endl;
}
int main()
{
    int a=2,b=3;
    char choice;
    bool quit=false;
    void (*p)(int &,int &);
    //声明的p为一个函数指针,它所指向的函数带有两个参数并返回 一个void值
    while(quit==false)
    {
        cout<<"(0)退出(1)平方(2)立方(3)交换参数:";
        cin>>choice;
        switch(choice)
        {
        case ‘0‘:quit=true;
        case ‘1‘:p=square;break;         //输入1,将函数名square的地址赋给p
        case ‘2‘:p=cube;break;        //输入2,将函数名cube的地址赋给p
        case ‘3‘:p=Swap;break;        //输入3,将函数名Swap的地址赋给p
        default:p=0;break;
        }
        if(quit==true)break;
        if(p==0)
        {
            cout<<"请输入0到3之间的数字\n";
            continue;
        }
        print(p,a,b);                    //调用将函数指针作为参数的函数print
    }
    return 0;
}

8.  使用typedef简化函数指针的声明

//typedef 可以简化代码 可以为现有类型创建一个新的名字,或者声明一个对象为某个新类型typedef void(*vp)(int &,int &);
//#define f(x) x*x ≠ #define f(x) (x*x)  typedef要比#define要好,特别是在有指针的场合 再比如
//      typedef char* pStr1;    //后面代替前面
//        #define pStr2 char*        //没有分号,前面代替后面
//      pStr1 s1, s2;        //定义了两个char*
//    pStr2 s3, s4;        //定义了一个char* s3 一个char s4
//

#include <iostream>
using namespace std;
typedef void(*vp)(int &,int &);//typedef 将vp声明为一个函数指针类型,该类型的指针指向一个带有两个int型引用参数并返回void的函数
void square(int &x,int &y)
{
    x=x*x;
    y=y*y;
}
void cube(int &x,int &y)
{
    x=x*x*x;
    y=y*y*y;
}
void Swap(int &x,int &y)
{
    int z;
    z=x;
    x=y;
    y=z;
}
void print(vp,int &,int &);            //print函数的声明部分,该函数有三个参数,一个vp类型的函数指针,两个int型引用。
int main()
{
    vp p;
    int a=2,b=3;
    char choice;
    bool quit=false;
    while(quit==false)
    {
        cout<<"(0)退出(1)平方(2)立方(3)交换参数:";
        cin>>choice;
        switch(choice)
        {
        case ‘0‘:quit=true;
        case ‘1‘:p=square;break;     //输入,将函数名square的地址赋给p
        case ‘2‘:p=cube;break;      //输入,将函数名cube的地址赋给p
        case ‘3‘:p=Swap;break;      //输入,将函数名Swap的地址赋给p
        default:p=0;break;
        }
        if(quit==true)break;
        if(p==0)
        {
            cout<<"请输入0到3之间的数字\n";
            continue;
        }
        print(p,a,b);                    //调用这个将函数指针作为参数的函数
    }
    return 0;
}
void print(vp p,int &x,int &y)    //print函数的定义部分,函数头声明了3个接收参数,第1个是vp类型的函数指针p,它指向的函数带有两个参数并返回一个void值,另外还有两个int型引用x和y
{
    cout<<"执行函数前\n";
    cout<<"x:"<<x<<"\t"<<"y:"<<y<<endl;
    p(x,y);
    cout<<"执行函数后\n";
    cout<<"x:"<<x<<"\t"<<"y:"<<y<<endl;
}

9.  类的函数指针

#include <iostream>
using namespace std;
class human                                 //抽象类human
{
public:
    virtual void run()=0;                    //纯虚函数run
    virtual void eat()=0;                    //纯虚函数eat
};
class mother:public human                 //派生类mother从抽象类human继承
{
public:
    void run(){cout<<"母亲跑百米要花二十秒\n";}    //覆盖纯虚函数run
    void eat(){cout<<"母亲喜欢吃零食\n";}    //覆盖纯虚函数eat
};
class father: public human                 //派生类father从抽象类human继承
{
public:
    void run(){cout<<"父亲跑百米要花十秒\n";}    //覆盖纯虚函数run
    void eat(){cout<<"父亲不喜欢吃零食\n";}    //覆盖纯虚函数eat
};
class uncle:public human                 //派生类uncle从抽象类human继承
{
public:
    void run(){cout<<"舅舅跑百米要花十一秒\n";}    //覆盖纯虚函数run
    void eat(){cout<<"舅舅喜欢偷吃零食\n";}        //覆盖纯虚函数eat
};
int main()
{
    void(human::*pf)()=0;                    //声明一个成员函数指针pf,该指针属于抽象类human
    human* p=0;                             //声明一个指向抽象类human的指针p,并将它的内存地址赋为0
    char choice1,choice2;                    //声明两个字符变量,用来保存两次用户输入的字符
    bool quit=false;                         //声明一个布尔变量quit作为while循环的条件
    while(quit==false)                        //当quit为真时退出循环
    {
        cout<<"(0)退出(1)母亲(2)父亲(3)舅舅:";    //选择菜单
        cin>>choice1;                        //将用户的第1次选择保存在choice1中
        switch(choice1)                        //将该选择作为判断的依据
        {
            case ‘0‘:quit=true;break;            //假如输入了字符0,那么将quit的值赋为真,然后退出switch循环
            case ‘1‘:p=new mother;break;        //假如输入了字符1,那么创建mother类的新对象,并将p指向它,然后退出switch循环
            case ‘2‘:p=new father;break;        //假如输入了字符2,那么创建father类的新对象,并将p指向它,然后退出switch循环
            case ‘3‘:p=new uncle;break;        //假如输入了字符3,那么创建uncle类的新对象,并将p指向它,然后退出switch循环
            default:choice1=‘q‘;break;        //将字符q赋给choice1,然后退出switch循环
        }
        if(quit)                                //假如quit的值为真
            break;                                //退出while循环
        if(choice1==‘q‘)                        //假如choice1的值为字符q
        {    cout<<"请输入0到3之间的数字\n";
            continue;
        } //输出警告并跳转到while循环的开始处继续执行
        cout<<"(1)跑步(2)进食\n";                //输出选择菜单
        cin>>choice2;                                //将第2次用户的选择保存在choice2中
        switch(choice2)                        //将用户的第2次选择作为判断的依据
        {
            case ‘1‘:pf=&human::run;break;        //假如输入了字符1,那么将基类human的虚函数run的内存地址赋给成员函数指针,然后退出switch循环。注意,这里的&号是取human类成员函数run的地址
            case ‘2‘:pf=&human::eat;break;        //假如输入了字符2,那么将基类human的虚函数eat的内存地址赋给成员函数指针,然后退出switch循环
            default:break;                        //退出switch循环
        }
        (p->*pf)();                            //通过指针p来访问对象,通过*pf来访问该对象的成员函数
        delete p;                                //删除p指针,因为*pf指向的不是对象而是该对象的成员函数,所以没有必要删除pf
    }
    return 0;
}

10.  成员函数指针数组

#include <iostream>
using namespace std;
class paper
{
public:
    void read(){cout<<"纸上面的字可以读\n";}
    void write(){cout<<"纸可以用来写字\n";}
    void burn(){cout<<"纸可以用来点火\n";}
};
typedef void(paper::*p)(); //利用typedef声明一个成员函数指针类型p,该类型的指针指向paper类的成员函数,该函数不具返回值且没有参数
int main()
{
    //成员函数指针指向三个成员函数
    p func[3]={&paper::read,&paper::write,&paper::burn};//用类型p来声明一个func成员函数指针数组,并将它的成员函数指针初始化为它们所指向的函数的内存地址。
                                                        //与普通数组元素一样,成员函数指针数组的每个成员函数指针也会拥有一个编号,该编号从0开始
    paper* pp=0;                    //声明一个指向paper类的普通指针
    char choice[1];                //声明一个只保存一个字符的char型数组choice
    bool quit=false;                //声明一个布尔变量quit并将它的值赋为false
    while(quit==false)                //当quit的值为false时
    {
        cout<<"(0)退出(1)读(2)写(3)点火:";      //输出选择菜单
        cin>>choice[0];                   //将用户的选择保存在字符数组choice中
        if(choice[0]>‘3‘ || choice[0]<‘0‘) //判断该字符是否在0到3之间,假如不是
        {
            cout<<"请输入从0~3之间的数字\n";     //提示用户输入
        }
        //否则,假如输入的字符在0~3之间
        else if (choice[0]==‘0‘)      //再判断该字符是否等于‘0‘
        {
            quit=true;                    //等于的话将quit赋为true,那么while条件不成立,退出循环
        }
        else
        {
            int n;                       //定义一个整型变量用来接收被转换为整型的字符串
            pp=new paper;                  //新构造一个paper类对象,用pp来指向它
            n=atoi(choice);    //将choice转换为整型后再赋给n
            (pp->*func[n-1])();        // pp指针访问新对象的成员函数,由该对象调用func 指针数组中下标为n-1的指针指向的成员函数,这里要注意数组的下标,n是用户输入的选项值,由于数组元素从0开始,所以要n减1,后面的()表示函数无参数
            delete pp;                //删除pp指针指向的新对象
        }
    }
    return 0;
}

第十七章 特殊类成员

时间: 2024-10-25 12:25:07

第十七章 特殊类成员的相关文章

第17章 特殊类成员

//*******引进静态成员变量的作用就是声明一个属于类而不属于对象的全局变量.意味着它为该类的所有实例所共享, //也就是说当某个类的实例修改了该静态成员变量,其修改值为该类的其它所有实例所见. //在某个类A中声明一个static int number;初始化为0.这个number就能被所有A的实例共用. // 在A的构造函数里加上number++,在A的析构函数里加上number--. // 那么每生成一个A的实例,number就加一,每销毁一个A的实例,number就减一, // 这样

“全栈2019”Java第八十七章:类中嵌套接口的应用场景(拔高题)

难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第八十七章:类中嵌套接口的应用场景(拔高题) 下一章 "全栈2019"Java第八十八章:接口中嵌套接口的应用场景 学习小组 加入同步学习小组,共同交流与进步. 方式一:关注头条号Gorhaf,私信"Java学习小组". 方式二:关注公众号Gorhaf,回复"J

【WPF学习】第四十七章 WriteableBitmap类

WPF允许使用Image元素显示位图.然而,按这种方法显示图片的方法完全是单向的.应用程序使用现成的位图,读取问题,并在窗口中显示位图.就其本身而言,Image元素没有提供创建和编辑位图信息的方法. 这正是WriteableBitmap类的用武之地.该类继承自BitmapSource,BitmapSource类是当设置Image.Source属性时使用的类(不管是在代码中直接设置图像,还是在XAML中隐式地设置图像).但BitmapSource是只读的位图数据映射,而WriteableBitma

第十四——十七章作业

                                                                                                     第十四章 15.3.1 有些成功人士或公司认为不需要独立的测试角色(Test),你怎么看? 在一些软件公司中,QA的工作中包含了Test的角色,负责验证程序是否符合预先设计的功能和特性.但是QA的工作量是很多的,一个好的QA不仅需要对程序架构有着很好的理解,对程序功能和性能都有着较深的理解,并且要

Gradle 1.12用户指南翻译——第四十七章. Build Init 插件

文由CSDN博客貌似掉线翻译,其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Github上的地址: https://github.com/msdx/gradledoc 本文翻译所在分支: https://github.com/msdx/gradledoc/tree/1.12. 直接浏览双语版的文档请访问: http://gradledoc.qiniudn.com/1.12/usergu

读《构建之法》第四章 、第十七章

第四章    两人合作 通过对于<构建之法>第四章的阅读使我对代码规范 . 代码复审 . 以及结对编程有了更加深刻的认识,所谓代码规范可以分为两个部分,代码风格规范和代码设计规范,代码风格规范的原则是:简明 . 易读 . 无二义性,代码书写的形式,变量命名的方法,注释程序如何工作都有详细的介绍,令我受益颇多.代码设计规范不光是程序书写的格式问题,而且牵涉到咸亨需设计 . 模块之间的联系 . 设计模式等方方面面,函数的设计,语句的使用,错误的处理,这些都是我们在进行程序设计的时候需要注意的地方.

阅读《构建之法》第四章、第十七章收获

阅读<构建之法>第四章.第十七章 阅读这一章的时候,我意识到了很多以前写程序没有注意到的地方,以前写程序就只知道能运行就好,根本不管自己写的程序占多少内存,运行的时间,是否有优化的空间,写代码的时候也不注意规范,有时候设计的函数根本用不上,造成代码冗余.同时也认识到结对编程的重要性,没读这本书之前就觉得结对编程就是两个人一人负责一个模块,然后合在一起,调试调试.但实则不然,真正的结对编程应该像书中那样,一个是驾驶人,一个是领航人,两个人有规律的进行编程.期间,一人编程一人复审,极大地提高了效率

《构建之法》读第四、十七章收获

第四章 两人合作 读了第四章,我才意识到代码规范的重要性,代码不仅要自己看懂,也要能让别人看懂,代码规范能使团队合作更好的进行.代码规范分为代码风格规范和代码设计规范.其中代码风格规范要注意缩进.行宽.括号.断行与空白的{}行.分行.命名.下划线.大小写.注释等问题. 问题一.命名法 文中关于命名这一注意事项,作者向我们详细介绍了"匈牙利命名法".基本原则是:变量名=属性+类型+对象描述那还有没有其他的命名方式呢? 1. Java变量的基本命名法则: a)   以下划线.字母.美元符开

第十七章

太上,下知有之:其次,亲誉之:其次,畏之:其下,侮之.信不足焉,有不信.犹呵,其贵言也.成功遂事,而百姓谓我自然. 第十七章1 管理者的四境界之最高境界 各位朋友大家好,今天我们接着来学习<道德经>,这几天我是在新加坡讲课,在跟大家分享<道德经>的同时,白天还要讲课. 新加坡是一个非常安静的.非常有序的国家,现代化程度非常高.因为我会经常来讲课,所以我发现新加坡有很多地方是值得我们学习的,这个有机会跟大家慢慢聊. 今天我们开始讲<道德经>的第十七章.这章有意思了,这章讲