第十七章 特殊类成员
1.1 静态成员变量
假如我们要在一个类中的所有对象间共享某种数据,那不妨将其设置为静态成员变量/函数;
static x
1.2 静态成员变量
静态成员变量与成员变量有4点不同:
①前者属于类②前者必须在全局定义③前者被调用只用说明那个类即可④前者在没有创建对象之前就已经存在
1 #include <iostream> 2 using namespace std; 3 class A 4 { 5 public: 6 static int n; 7 }; 8 int A::n=0; //要使用类成员限定来访问静态成员 9 void show(){cout<<A::n<<"a ";} 10 int main() 11 { 12 int i; 13 for(i=0;i<5;i++) 14 { 15 A::n++; //访问静态成员变量n并对其进行自加操作............公有的可以直接访问① 16 show(); //调用全局函数show,该函数输出静态成员变量n的值 17 } 18 return 0; 19 }
2. 私有静态成员变量
1 #include <iostream> 2 using namespace std; 3 class A 4 { 5 public: 6 void func(){cout<<A::x<<endl;;} //定义公有成员函数func(),用该函数访问私有静态成员变量x 7 private: 8 static int x; //将静态成员变量x声明为私有 9 }; 10 int A::x=1000; //定义并初始化静态成员变量 11 int main() 12 { 13 A a; //创建一个对象 14 a.func(); //用该对象访问公有成员函数func() 15 //cout << A::x <<endl; //注意不能直接访问...............私有的不可以直接访问① 16 return 0; 17 }
3. 静态成员函数
静态成员函数在未创建对象时也可以使用,它的使用和静态成员变量一样 ①
1 /* 2 #include <iostream> 3 using namespace std; 4 class A 5 { 6 public: 7 void static show(){cout<<A::n;n++;} //注意:静态成员函数不能访问某个对象的成员变量,因为他没有指向该对象的this指针。不过它可以访问该类的静态成员变量 ② 8 private: 9 static int n; //声明私有静态成员变量n 10 }; 11 int A::n=0; //定义私有静态成员变量n 12 int main() 13 { 14 int i; 15 for(i=0;i<5;i++) 16 { 17 A::show(); //循环调用公有静态成员函数show() 18 cout<<endl; 19 } 20 return 0; 21 } 22 */ 23 24 /* 25 //另外我们也可以通过对象来访问静态成员函数,如: 26 #include <iostream> 27 using namespace std; 28 class A 29 { 30 public: 31 void static show(){cout<<A::n;n++;} 32 private: 33 static int n; 34 }; 35 int A::n=0; 36 int main() 37 { 38 A a,b,c; 39 a.show(); 40 b.show(); 41 c.show(); 42 return 0; 43 } 44 */ 45 46 47 48 #include <iostream> 49 using namespace std; 50 class A 51 { 52 public: 53 void show(int i){x=i;cout<<x;} 54 //void static show1(int j){x=j;cout<<x;} 55 //静态成员函数没有指向对象的this指针,所以不能访问对象的成员数据 56 private: 57 int x; 58 }; 59 int main() 60 { 61 A a; 62 a.show(1); 63 //a.show1(2); 64 //不能通过静态成员函数访问自己的成员变量 65 return 0; 66 }
4. 静态成员的使用
静态成员可以被继承。
类中任何成员函数都可以访问静态成员,但是静态成员函数不能直接访问非静态成员,静态成员函数不能说明为虚函数
1 #include <iostream> 2 using namespace std; 3 class aspl //将阿司匹林声明为一个aspl类,那么每箱阿司匹林就是该类的一个对象 4 { 5 public: 6 aspl(float p){price=p;TotalPrice=p+TotalPrice;} //在构造函数中实例化该对象的私有成员变量price,这样就得到了一箱阿司匹林并且有了它的初始价格 7 ~aspl(){TotalPrice=TotalPrice-price;} //析构函数销毁该对象并且将总价格减去该箱的价格,这样账面上就少了一箱阿司匹林,并且总价格也减去了该箱的价格 8 static float get(){return TotalPrice;} 9 private: 10 float price; //由于每箱阿司匹林都有价格,因此必须得有个成员变量来表示价格,这里在aspl这个类中声明一个私有成员变量price 11 static float TotalPrice; //由于阿司匹林的总价格属于类的总价格,而不是某一箱阿司匹林的价格,因此我们要将总价格声明为静态成员变量,这里声明为TotalPrice 12 }; 13 float aspl::TotalPrice=0; //静态成员变量必须初始化 14 void main() 15 { 16 float f; 17 cout<<"阿司匹林的库存总价格为:"; 18 cout<<aspl::get()<<endl; //必须用类名限定符来调用静态成员函数 19 int i=0; 20 cout<<"请输入第"<<i+1<<"次购进的阿司匹林的单箱价格:"; 21 cin>>f; 22 aspl *p[5]; //定义了5个指向aspl类的数组指针p 23 p[i]=new aspl(f); //购进一箱阿司匹林 24 cout<<"阿司匹林的库存总价格为:"; 25 cout<<aspl::get()<<endl; //输出总价格 26 i++; //i代表购进的次数,i++表示将要进行i+1次购进 27 cout<<"请输入第"<<i+1<<"次购进的阿司匹林的单箱价格:"; //提示用户输入i次购进 28 cin>>f; 29 p[i]=new aspl(f); //输入的数值保存在i次购进的对象的成员变量中 30 cout<<"阿司匹林的库存总价格为:"; 31 cout<<aspl::get()<<endl; //输出当前的库存总价格 32 cout<<"请输入卖出的阿司匹林的编号,编号即第几次购进:";//提示用户要删除哪次购进 33 cin>>i; //将输入值保存在i变量中 34 delete p[i]; //删除第i次创建的对象 35 cout<<"阿司匹林的库存总价格为:"; 36 cout<<aspl::get()<<endl; //再次输出销售一箱阿司匹林后的库存总价格 37 }
5. 函数指针
long(*func1)(int); //声明了一个函数指针
long* func2(int); //声明了一个返回指针的函数
第一种定义了一个叫func1的函数指针,该指针指向一个含有int型参数并且返回值为long型的函数
函数指针名可以看作函数名的代号,我们可以通过它来直接调用函数,所以函数指针经常会在条件或者判断语句里出现,以便于用户选择调用不同名字但又类型和参数相同的函数。
另外要注意的是:函数指针可以指向某个函数,但是前提是被指向的函数的参数和返回至都与该函数指针被声明时的返回值和参数相吻合
1 #include <iostream> 2 #include <string> 3 using namespace std; 4 bool check(string str) //检测是否是数字的函数,要注意该函数一定要放在调用函数的上面 5 { 6 for(int i = 0;i<str.length();i++) 7 if((str[i]>'9' || str[i]<'0')&&(str[i]!='.')) 8 return false; 9 return true; 10 } 11 float triangle(float &x,float &y) 12 { 13 return x*y*0.5; 14 } 15 float rectangle(float &x,float &y) 16 { 17 return x*y; 18 } 19 void Swap(float &x,float &y) 20 { 21 float n; 22 n=x; 23 x=y; 24 y=n; 25 } 26 void print(float &x,float &y) 27 { 28 cout<<"长为:"<<x<<" "<<"宽为:"<<y<<endl; 29 } 30 void get(float &a ,float &b) 31 { 32 cout<<"请输入x的新值:"; 33 string str1;cin>>str1; 34 while(!check(str1)) //调用检测数字函数,如果返回值为假,执行该循环,为真退出 35 { 36 cout<<"输入的不是数字,请重新输入!!!"<<endl; 37 cin>>str1; 38 } 39 a = atof(str1.c_str()); //将字符串转换为浮点数 40 cout<<"请输入y的新值:"; 41 string str2;cin>>str2; 42 while(!check(str2)){ 43 cout<<"输入的不是数字,请重新输入!!!"<<endl; 44 cin>>str2; 45 } 46 b = atof(str2.c_str()); 47 } 48 int main() 49 { 50 void(*p)(float &,float &); //声明一个函数指针p,该指针指向一个返回void值并且带有两个float参数的函数 51 float(*fp)(float &, float &); //声明一个函数指针fp,该指针指向一个返回float值并且带有两个float参数的函数 52 bool quit=false; 53 float a=2,b=3; //定义两个参数a和b的值 54 int choice; //声明选择参数choice 55 while(quit==false) 56 { 57 cout<<"(0)退出(1)设定长宽(2)三角形(3)矩形(4)交换长宽:"; 58 cin>>choice; 59 switch(choice) //条件判断语句 60 { 61 case 1: 62 p=get; //用指针p来指向函数名get,该函数带有两个float参数并返回一个void值,与函数指针p的参数和类型相吻合 63 break; 64 case 2: 65 fp=triangle; //用指针fp来指向函数名triangle,该函数带有两个float参数并返回一个float值,与函数指针fp的参数和类型相吻合 66 break; 67 case 3: 68 fp=rectangle; //用指针fp来指向函数名rectangle,该函数带有两个float参数并返回一个float值,与函数指针fp的参数和类型相吻合 69 break; 70 case 4: 71 p=Swap; //用指针p来指向函数名swap,该函数带有两个float参数并返回一个void值,与函数指针p的参数和类型相吻合 72 break; 73 default: 74 quit=true; 75 break; 76 } 77 if(quit)break; 78 if(choice==1||choice==4) //假如选择了第1或者第4项 79 { 80 print(a,b); 81 p(a,b); //调用函数指针p所指向的函数,该指针指向的是一个返回值为void的函数,由于不同的选项中将不同的函数名赋给了指针p,因此选择不同则调用的函数也不同 82 print(a,b); 83 } 84 else if(choice==2||choice==3) //假如选择了第2和第3项 85 { 86 print(a,b); 87 cout<<"面积为:"<<fp(a,b)<<endl; //调用函数指针fp所指向的函数,该指针指向的是一个返回值为float的函数,由于不同的选项中将不同的函数名赋给了指针fp,因此选择不同则调用的函数也不同 88 } 89 } 90 return 0; 91 }
6. 函数指针数组
void(*p[5])(float &,float &);
7. 函数指针也可以作为函数的参数
1 #include <iostream> 2 using namespace std; 3 void square(int &x,int &y) 4 { 5 x=x*x; 6 y=y*y; 7 } 8 void cube(int &x,int &y) 9 { 10 x=x*x*x; 11 y=y*y*y; 12 } 13 void Swap(int &x,int &y) 14 { 15 int z; 16 z=x; 17 x=y; 18 y=z; 19 } 20 void print(void(*p)(int &x,int &y),int &x,int &y) //该函数有3个参数,第1个是一个函数指针p,它指向的函数带有两个参数, 21 //并返回一个void值,另外还有两个int型引用x和y 22 { 23 cout<<"执行函数前 "; 24 cout<<"x:"<<x<<" "<<"y:"<<y<<endl; 25 p(x,y); 26 cout<<"执行函数后 "; 27 cout<<"x:"<<x<<" "<<"y:"<<y<<endl; 28 } 29 int main() 30 { 31 int a=2,b=3; 32 char choice; 33 bool quit=false; 34 void (*p)(int &,int &); 35 //声明的p为一个函数指针,它所指向的函数带有两个参数并返回 一个void值 36 while(quit==false) 37 { 38 cout<<"(0)退出(1)平方(2)立方(3)交换参数:"; 39 cin>>choice; 40 switch(choice) 41 { 42 case '0':quit=true; 43 case '1':p=square;break; //输入1,将函数名square的地址赋给p 44 case '2':p=cube;break; //输入2,将函数名cube的地址赋给p 45 case '3':p=Swap;break; //输入3,将函数名Swap的地址赋给p 46 default:p=0;break; 47 } 48 if(quit==true)break; 49 if(p==0) 50 { 51 cout<<"请输入0到3之间的数字 "; 52 continue; 53 } 54 print(p,a,b); //调用将函数指针作为参数的函数print 55 } 56 return 0; 57 }
8. 使用typedef简化函数指针的声明
1 //typedef 可以简化代码 可以为现有类型创建一个新的名字,或者声明一个对象为某个新类型typedef void(*vp)(int &,int &); 2 //#define f(x) x*x ≠ #define f(x) (x*x) typedef要比#define要好,特别是在有指针的场合 再比如 3 // typedef char* pStr1; //后面代替前面 4 // #define pStr2 char* //没有分号,前面代替后面 5 // pStr1 s1, s2; //定义了两个char* 6 // pStr2 s3, s4; //定义了一个char* s3 一个char s4 7 // 8 9 #include <iostream> 10 using namespace std; 11 typedef void(*vp)(int &,int &);//typedef 将vp声明为一个函数指针类型,该类型的指针指向一个带有两个int型引用参数并返回void的函数 12 void square(int &x,int &y) 13 { 14 x=x*x; 15 y=y*y; 16 } 17 void cube(int &x,int &y) 18 { 19 x=x*x*x; 20 y=y*y*y; 21 } 22 void Swap(int &x,int &y) 23 { 24 int z; 25 z=x; 26 x=y; 27 y=z; 28 } 29 void print(vp,int &,int &); //print函数的声明部分,该函数有三个参数,一个vp类型的函数指针,两个int型引用。 30 int main() 31 { 32 vp p; 33 int a=2,b=3; 34 char choice; 35 bool quit=false; 36 while(quit==false) 37 { 38 cout<<"(0)退出(1)平方(2)立方(3)交换参数:"; 39 cin>>choice; 40 switch(choice) 41 { 42 case '0':quit=true; 43 case '1':p=square;break; //输入,将函数名square的地址赋给p 44 case '2':p=cube;break; //输入,将函数名cube的地址赋给p 45 case '3':p=Swap;break; //输入,将函数名Swap的地址赋给p 46 default:p=0;break; 47 } 48 if(quit==true)break; 49 if(p==0) 50 { 51 cout<<"请输入0到3之间的数字 "; 52 continue; 53 } 54 print(p,a,b); //调用这个将函数指针作为参数的函数 55 } 56 return 0; 57 } 58 void print(vp p,int &x,int &y) //print函数的定义部分,函数头声明了3个接收参数,第1个是vp类型的函数指针p,它指向的函数带有两个参数并返回一个void值,另外还有两个int型引用x和y 59 { 60 cout<<"执行函数前 "; 61 cout<<"x:"<<x<<" "<<"y:"<<y<<endl; 62 p(x,y); 63 cout<<"执行函数后 "; 64 cout<<"x:"<<x<<" "<<"y:"<<y<<endl; 65 }
9. 类的函数指针
1 #include <iostream> 2 using namespace std; 3 class human //抽象类human 4 { 5 public: 6 virtual void run()=0; //纯虚函数run 7 virtual void eat()=0; //纯虚函数eat 8 }; 9 class mother:public human //派生类mother从抽象类human继承 10 { 11 public: 12 void run(){cout<<"母亲跑百米要花二十秒 ";} //覆盖纯虚函数run 13 void eat(){cout<<"母亲喜欢吃零食 ";} //覆盖纯虚函数eat 14 }; 15 class father: public human //派生类father从抽象类human继承 16 { 17 public: 18 void run(){cout<<"父亲跑百米要花十秒 ";} //覆盖纯虚函数run 19 void eat(){cout<<"父亲不喜欢吃零食 ";} //覆盖纯虚函数eat 20 }; 21 class uncle:public human //派生类uncle从抽象类human继承 22 { 23 public: 24 void run(){cout<<"舅舅跑百米要花十一秒 ";} //覆盖纯虚函数run 25 void eat(){cout<<"舅舅喜欢偷吃零食 ";} //覆盖纯虚函数eat 26 }; 27 int main() 28 { 29 void(human::*pf)()=0; //声明一个成员函数指针pf,该指针属于抽象类human 30 human* p=0; //声明一个指向抽象类human的指针p,并将它的内存地址赋为0 31 char choice1,choice2; //声明两个字符变量,用来保存两次用户输入的字符 32 bool quit=false; //声明一个布尔变量quit作为while循环的条件 33 while(quit==false) //当quit为真时退出循环 34 { 35 cout<<"(0)退出(1)母亲(2)父亲(3)舅舅:"; //选择菜单 36 cin>>choice1; //将用户的第1次选择保存在choice1中 37 switch(choice1) //将该选择作为判断的依据 38 { 39 case '0':quit=true;break; //假如输入了字符0,那么将quit的值赋为真,然后退出switch循环 40 case '1':p=new mother;break; //假如输入了字符1,那么创建mother类的新对象,并将p指向它,然后退出switch循环 41 case '2':p=new father;break; //假如输入了字符2,那么创建father类的新对象,并将p指向它,然后退出switch循环 42 case '3':p=new uncle;break; //假如输入了字符3,那么创建uncle类的新对象,并将p指向它,然后退出switch循环 43 default:choice1='q';break; //将字符q赋给choice1,然后退出switch循环 44 } 45 if(quit) //假如quit的值为真 46 break; //退出while循环 47 if(choice1=='q') //假如choice1的值为字符q 48 { cout<<"请输入0到3之间的数字 "; 49 continue; 50 } //输出警告并跳转到while循环的开始处继续执行 51 cout<<"(1)跑步(2)进食 "; //输出选择菜单 52 cin>>choice2; //将第2次用户的选择保存在choice2中 53 switch(choice2) //将用户的第2次选择作为判断的依据 54 { 55 case '1':pf=&human::run;break; //假如输入了字符1,那么将基类human的虚函数run的内存地址赋给成员函数指针,然后退出switch循环。注意,这里的&号是取human类成员函数run的地址 56 case '2':pf=&human::eat;break; //假如输入了字符2,那么将基类human的虚函数eat的内存地址赋给成员函数指针,然后退出switch循环 57 default:break; //退出switch循环 58 } 59 (p->*pf)(); //通过指针p来访问对象,通过*pf来访问该对象的成员函数 60 delete p; //删除p指针,因为*pf指向的不是对象而是该对象的成员函数,所以没有必要删除pf 61 } 62 return 0; 63 }
10. 成员函数指针数组
1 #include <iostream> 2 using namespace std; 3 class paper 4 { 5 public: 6 void read(){cout<<"纸上面的字可以读 ";} 7 void write(){cout<<"纸可以用来写字 ";} 8 void burn(){cout<<"纸可以用来点火 ";} 9 }; 10 typedef void(paper::*p)(); //利用typedef声明一个成员函数指针类型p,该类型的指针指向paper类的成员函数,该函数不具返回值且没有参数 11 int main() 12 { 13 //成员函数指针指向三个成员函数 14 p func[3]={&paper::read,&paper::write,&paper::burn};//用类型p来声明一个func成员函数指针数组,并将它的成员函数指针初始化为它们所指向的函数的内存地址。 15 //与普通数组元素一样,成员函数指针数组的每个成员函数指针也会拥有一个编号,该编号从0开始 16 paper* pp=0; //声明一个指向paper类的普通指针 17 char choice[1]; //声明一个只保存一个字符的char型数组choice 18 bool quit=false; //声明一个布尔变量quit并将它的值赋为false 19 while(quit==false) //当quit的值为false时 20 { 21 cout<<"(0)退出(1)读(2)写(3)点火:"; //输出选择菜单 22 cin>>choice[0]; //将用户的选择保存在字符数组choice中 23 if(choice[0]>'3' || choice[0]<'0') //判断该字符是否在0到3之间,假如不是 24 { 25 cout<<"请输入从0~3之间的数字 "; //提示用户输入 26 } 27 //否则,假如输入的字符在0~3之间 28 else if (choice[0]=='0') //再判断该字符是否等于'0' 29 { 30 quit=true; //等于的话将quit赋为true,那么while条件不成立,退出循环 31 } 32 else 33 { 34 int n; //定义一个整型变量用来接收被转换为整型的字符串 35 pp=new paper; //新构造一个paper类对象,用pp来指向它 36 n=atoi(choice); //将choice转换为整型后再赋给n 37 (pp->*func[n-1])(); // pp指针访问新对象的成员函数,由该对象调用func 指针数组中下标为n-1的指针指向的成员函数,这里要注意数组的下标,n是用户输入的选项值,由于数组元素从0开始,所以要n减1,后面的()表示函数无参数 38 delete pp; //删除pp指针指向的新对象 39 } 40 } 41 return 0; 42 }