一、类的静态成员
1.目的
静态成员(关键字:static)是解决同一个类的不同对象之间数据和函数共享问题。区分全局变量,全局变量也能实现数据共享,但安全性和封装性被破坏了。
2.静态数据成员
(1) 使用关键字static在类内声明,类外定义和初始化,用::
指明所属的类。
(2) 静态数据成员属于整个类,每个对象均可引用。静态数据成员的值对所有对象来说都是一样的。如果改变它的值,则各个对象中这个数据成员的值都同时改变。
(3) 静态数据成员在内存中只占一份空间,是在所有对象之外单独开辟的空空间。也就是说为对象分配的空间不包括静态数据成员占的空间。静态数据成员在程序编译时被分配空间,结束时释放空间。
(4) 只要在类中定义了静态数据成员,即使不定义对象,也要为静态数据成员分配空间,它可以被引用。
(5) 静态数据成员可以通过对象名引用,也可以过类名引用。
//1.通过类名访问
Student :: m_tatal = 10;
//2.通过对象名来访问
Student stu("小明", 15, 88);
stu.m_total = 20;//注意他们受访问权限的控制
//3.通过对象指针来访问
Student *pstu = new Student("小芳", 19, 96);
pstu->m_total = 20;//静态变量可以访问,可以修改
//以上三种方式等效
(6) 如果未对其赋初值,自动赋初值为0.
int Student::m_total = 0; 等价于 int Student::m_total;
3.静态成员函数
(1) 类外代码可以使用类名和作用域操作符来调用静态成员函数。公有的静态成员函数可以通过类名或对象名调用,而一般的非静态成员函数只能通过对象名调用。
(2) 静态成员函数可以直接访问该类的静态数据和静态函数。作用不是为了对象间的沟通,而是为了能处理静态数据成员。
(3) 静态成员函数不属于某一对象,为各个对象共有,并且可以通过类来直接调用,所有没有this指针,无法对一个对象中的非静态成员进行默认访问。但可以通过参数传递方式得到对象名,通过对象名访问。
例1:
#include <iostream>
using namespace std;
class Point {
public:
Point(int xx=0, int yy=0) {X=xx; Y=yy; countP++; } //每创建一个对象,对静态数据成员有影响,所以更新coutP
Point(Point &p){X=p.X;Y=p.Y; countP++; } //所有对象共同维护同一个countP
~Point(){countP--; } //每调用一次释放一个对象,与之相关的静态数据成员也要进行更新
int GetX() {return X;}
int GetY() {return Y;}
static void ShowCount(){cout<<", Object id="<<countP<<endl;} //静态成员函数
private:
int X,Y;
static int countP; //类内静态数据成员声明
};
int Point::countP=0; //类外静态数据成员定义和初始化,使用类名限制
void main() {
Point A(4,5);
cout<<"Point A:"<<A.GetX()<<","<<A.GetY();
A.ShowCount(); //用对象名调用用静态成员函数
Point B(A);
cout<<"Point B:"<<B.GetX()<<","<<B.GetY();
Point.ShowCount(); //用类名调用静态成员函数
}
上述例子注意是静态数据成员的值与对象相关时,在构造函数里要进行相应的操作更新。静态成员函数可以使用对象名或类名调用。
例2:
class A{
public:
static void f(A a);
private:
int x;
};
void A::f(A a){
//cout<<x;
cout<<a.x; //静态成员函数访问非静态数据成员必须通过参数传递方式得到对象名,然后通过对象名访问非静态数据成员
}
附:函数访问数据成员表
静态成员函数与普通成员函数的根本区别在于:普通成员函数有 this 指针,可以访问类中的任意成员;而静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数)
二、友元
1.友元介绍
(1) 友元提供了不同类或对象的成员函数之间、类的成员函数与一般函数之间进行数据共享的机制。(破坏数据封装和数据隐藏)
(2) 使用友元函数和友元类,使用关键字friend。(友元类的所有成员函数都自动成为友元函数)
(3) 为了确保数据的完整性,及数据封装与隐藏的原则,建议尽量不使用或少使用友元。
2.友元函数
(1) 在类声明中由关键字friend修饰的非本类的成员函数,在它的函数体中能够通过对象名访问private 和protected成员。
(2) 访问对象中的成员必须通过对象名。
<1>普通函数声明为友元函数
#include<iostream>
using namespace std;
class Date;
class Time {
public:
Time(int h, int m, int s);
friend void display(Time &t);
private:
int hour, minute, second;
};
Time::Time(int h,int m,int s):hour(h),minute(m),second(s){}
void Time::display(Time &t) { //在友元函数体中能够通过对象名访问某类的private 和protected成员。
cout << t.hour << ":" << t.minute << ":" << t.second << endl;
}
int main() {
Time t1(20, 8, 30);
display(t1);
return 0;
}
<2>友元成员函数
#include<iostream>
using namespace std;
class Date;
class Time {
public:
Time(int h, int m, int s);
void display(Date &d);
private:
int hour, minute, second;
};
Time::Time(int h,int m,int s):hour(h),minute(m),second(s){}
class Date {
public:
Date(int m, int d, int y);
friend void Time::display(Date &d);
//来自Time类的函数是Date类的友元函数,因此在Date类中声明friend,
//并将Date类对象传参以便函数体访问Date类私有数据。
private:
int month, day, year;
};
Date::Date(int m,int d,int y):month(m),day(d),year(y){}
void Time::display(Date &d) {
cout << d.year << "-" << d.month << "-" << d.day << endl; //display通过对象名访问Date类的私有数据
cout << hour << ":" << minute << ":" << second << endl;
}
int main() {
Date d1(4, 10, 2020);
Time t1(20, 13, 14);
t1.display(d1);
return 0;
}
<3>一个函数可以被多个类声明为“朋友”,引用多个类中的私有数据
#include <iostream>
using namespace std;
class Date;
class Time{
public:
Time(int h,int m,int s){hour=h;minute=m;sec=s;}
friend void display(const Date &d,const Time &t);
private:
int hour,minute,int sec;
};
class Date {
public:
Date(int m,int d,int y){month=m;day=d;year=y;}
friend void display(const Date &d,const Time &t);
private:
int month,day,year;
};
void display(const Date &d,const Time &t){
cout<<d.month<<"/"<<d.day<<"/"<<d.year<<endl;
cout<<t.hour<<":"<<t.minute<<":"<<t.sec<<endl;
}
int main(){
Time t1(10,13,56);
Date d1(4,9,2020);
display(d1,t1);
return 0;
}
3.友元类
若B类为A类的友元类,则B类的所有成员函数都是A类的友元函数,都能访问对方类的private 和protected成员。(反之不成立)
#include <iostream>
using namespace std;
class Date;
class Time{
public:
Time(int h,int m,int s){hour=h;minute=m;sec=s;}
friend Date; //Date类为Time类的友元类,Date对象能访问Time所有成员
private:
int hour,minute,int sec;
};
class Date {
public:
Date(int m,int d,int y){month=m;day=d;year=y;}
void display(const Time &t);
private:
int month,day,year;
};
void display(cconst Time &t){
cout<<month<<"/"<<day<<"/"<<year<<endl;
cout<<t.hour<<":"<<t.minute<<":"<<t.sec<<endl;
}
int main(){
Time t1(10,13,56);
Date d1(4,9,2020);
d1.display(t1);
return 0;
}
注意:
(1) 友元关系是不能传递的,是单向的,是不被继承的。
(2) 一般并不把整个类声明为友元类,只将需要的成员函数声明为友元函数.
(3) 友元破坏封装原则,但有助于数据共享。不要过多使用友元,只有在使用它能使程序精炼,并大大提高程序的效率时才用友元。
三、用const修饰的类成员
1.常引用和常对象
常引用:被引用的对象不能被更新。
const 类型说明符 &引用名
常对象:对象必须进行初始化, 不能被更新, 不能调用普通成员函数
const 类名 对象名 或者 类名 const 对象名
例:常引用作形参
#include<iostream>
using namespace std;
void display(const double& r);
int main(){
double d(9.5);
display(d);
return 0;
}
void display(const double& r) {
//r=0; //报错,因为常引用做形参,在函数中不能更新r所引用的对象。
cout<<r<<endl;
}
例:常对象
#include <iostream>
using namespace std;
class Point {
public:
Point(int xx=0, int yy=0) {X=xx;Y=yy;cout<<"构造函数被调用"<<endl;}
void Setpoint(int x,int y){X=x;Y=y;}
int GetX() const {return X;}
int GetY() {return Y;}
private:
int X,Y;
};
void main(){
const Point A(4,5); //A 常对象
A.Setpoint(1,2); //非法调用普通成员函数和更新对象
cout<<A.GetY()<<endl; //非法调用普通成员函数
}
2.常数据成员
(1) 使用const说明的数据成员。
(2) 常数据成员只能用初始化列表的形式赋初值。
(3) 可以被const成员函数引用,也可以被非const成员函数引用
(4) 常对象的数据成员都是常数据成员,因此常对象构造函数只能用参数初始化表对常数据成员进行初始化。
#include<iostream>
using namespace std;
class A{
public:
A(int i);
void print();
const int &r; //常引用,不能被更新
private:
const int a; //常数据成员
static const int b; //静态常数据成员
};
const int A::b=10; //静态常数据成员类定义初始化
A::A(int i):a(i),r(a) {} //构造函数里用初始化列表给常数据成员a和r赋初值
void A::print()
{ cout<<a<<":"<<b<<":"<<r<<endl;
}
void main(){
A a1(100),a2(0);
a1.print(); // 100:10:100
a2.print(); // 0:10:0
}
2.常成员函数
(1) 类型说明符 函数名(参数表)const;
(2) 常成员函数只能引用本类的所有数据成员(包括const数据成员,也可引用非const数据成员),不能修改它们,也不能调用该类中没有用const修饰的成员函数。
(3) 常对象只能调用它的常成员函数。
(4) const关键字可以被用于参与对重载函数的区分。
(5) const成员函数声明和定义处都要加上const
(6) 函数开头的const用来修饰函数的返回值,表示返回值是const类型,是不可以被修改的一块内存.
例如:const get_char();
#include<iostream>
using namespace std;
class R{
public:
R(int r1, int r2){R1=r1;R2=r2;}
void print();
void print() const; //常成员函数
private:
int R1,R2;
};
void R::print(){
cout<<R1<<":"<<R2<<endl;
}
void R::print() const{
cout<<R1<<";"<<R2<<endl;
}
void main(){
R a(5,4);
a.print(); // 5:3
const R b(20,52); //常对象
b.print(); // 20:52 函数重载
}
注意:
- 如果一个类的有些数据成员值允许改变,另一些数据成员的值不允许改变,则可以将一部分数据成员声明为const。可以用非const的成员函数引用这些数据成员的值,修改非const数据成员的值。
- 如果要求所有的数据成员的值都不允许改变,则可以将对象声明为const,再用const成员函数引用数据成员,起到“双保险”的作用。
- 常对象只能调用其中const成员函数。如果需要访问常对象中的数据成员,可将常对象中所有成员函数都声明为const成员函数,但必须确保在函数中不修改对象中的数据成员。