类成员变量

静态成员变量

静态成员:在说明前面加了static关键字的成员。

class CRectangle
{
private:
int w, h;
static int nTotalArea; //静态成员变量
static int nTotalNumber;
public:
CRectangle(int w_,int h_);
~CRectangle();
static void PrintTotal(); //静态成员函数
};

静态成员和普通成员区别

  • 普通成员变量每个对象有各自的一份
  • 静态成员变量一共就一份,为所有对象共享
  • 静态成员不需要通过对象就可以访问

当使用sizeof运算时,不会计算静态成员变量的size,如:

class CMyclass{
int n;
static int s;
};
sizeof(CMclass); // 等于4,不计算静态成员变量的值
  • 普通成员函数必须具体作用于某个对象
  • 静态成员函数并不具体作用于某个对象

静态成员变量本质上是全局变量,哪怕一个对象都不存在,类的静态成员变量也存在。相同,静态成员函数本质上是全局函数。设置静态成员这种机制的目的是将和某些类紧密相关的全局变量和函数写到类里面,看上去像一个整体,易于维护和理解。

例:

考虑一个需要随时知道矩形总数和总面积的图形处理程序,可以用全局变量来记录总数和总面积,但此时的弊端就是无法直观看出两个全局变量跟矩形类之间的关系,且其他类的全局函数也可以访问这个变量,不安全。

因此,用静态成员将这两个变量封装进类中,更容易 理解和维护。

class CRectangle{
private:
int w, h;
static int nTotalArea;
static int nTotalNumber;
public:
CRectangle(int w_, int h_);
~CRectangle();
static void PrintTotal();
};

CRectangle::CRectangle(int w_, int h_){
w = w_;
h = h_;
nTotalNumber++;
nTotalArea += w * h;
}
CRectangle::~CRectangle(){
nTotalNumber--;
nTotalArea -= w * h;
}
void CRectangle::PrintTotal(){
cout<<nTotalNumber<<","<<nTotalArea<<endl;
}

在使用的时候,必须在定义类的文件中对静态成员变量进行声明或初始化,否则编译通过但链接不通过。

int CRectangle::nTotalNumber = 0;
int CRectangle::nTotalArea = 0;

int main(){
CRectangle r1(3, 3), r2(2, 2);
// cout<<CRectangle::nTotalNumber;
CRectangle::PrintTotal();
r1.PrintTotal();
return 0;
}

第三行代码在外部直接访问类的私有变量,无法通过编译。

  • 在静态成员函数中,不能访问非静态成员变量,也不能调用非静态成员函数 如果在PrintTotal()静态成员函数中cout<<w<<endle时编译错误

其输出为:

2,13
2,13
1,9
0,0

上面的CRectangle类写法有很大的缺陷,即忽略了复制构造函数,因为在使用CRectangle类时,有时会调用复制构造函数,生成临时的隐藏CRectangle对象,有两种情况:

  • 调用一个以CRectangle类对象作为参数的函数时
  • 调用一个以CRectangle类对象作为返回值的函数时

临时对象在消亡时会调用析构函数,减少nTotalNumber 和nTotalArea的值,可是这些临时对象在生成时却没有增加nTotalNumber 和 nTotalArea的值。

解决办法:为CRectangle类写一个复制构造函数。

CRectangle::CRectangle(CRectangle & r ){
w = r.w;
h = r.h;
nTotalNumber ++;
nTotalArea += w * h;
}

成员对象和封闭类

成员对象:一个类的成员变量是另一个类的对象

封闭类(Enclosing):包含成员对象的类

如:

class CTyre{                  // 轮胎类
private:
int radius;
int width;
public:
CTyre(int r, int w):radius(r), width(w){}
};
class CEngine{ // 引擎类

};

class CCar{ // 封闭类
private:
int price;
CTyre tyre; // 成员对象轮胎
CEngine engine; // 成员对象引擎
public:
CCar(int p, int tr, int tw);
};
// 初始化列表,初始化tyre
CCar::CCar(int p, int tr, int tw):price(p), tyre(tr, tw){

};
int main(){
CCar car(20000, 17, 225);
return 0;
}

CTyre类中,编写构造函数时,使用初始化列表的方式对radiuswidth进行初始化。使用这样的方式的好处是进行复制的风格更好。

如果CCar类不定义构造函数,则在执行CCar car时会编译错误,因为编译器不知道car.tyre该如何初始化,但car.engine的初始化没有问题。

封闭类构造函数

定义封闭类的构造函数时,添加初始化列表的方法为:

类名::构造函数(参数表):成员变量1(参数表),成员变量2(参数表),...{

}

成员对象初始化列表中的参数可以是任意复杂的表达式,也可以使函数、变量、表达式中的函数

构造函数调用顺序

当封闭类对象生成的时候,类的构造函数的调用顺序为:

  1. 执行所有成员对象的构造函数
  2. 执行封闭类的构造函数

成员对象的构造函数调用顺序和成员对象在封闭类中的声明顺序一致,和在成员初始化列表中的出现顺序无关。

当封闭类对象消亡时,类的析构函数调用顺序为:

  1. 执行封闭类的析构函数
  2. 执行成员对象的析构函数

构造函数和析构函数的调用顺序刚好相反。

例:

class CTyre {
public:
CTyre() {
cout << "CTyre constructor" << endl;
}
~CTyre() {
cout << "CTyre destructor" << endl;
}
};
class CEngine {
public:
CEngine() {
cout << "CEngine constructor" << endl;
}
~CEngine() {
cout << "CEngine destructor" << endl;
}
};

class CCar{
private:
CEngine engine;
CTyre tyre;
public:
CCar(){
cout << "CCar constructor" << endl;
}
~CCar(){
cout << "CCar destructor" << endl;
}
};

int main(){
CCar car;
reurn 0;
}

首先执行成员对象的构造函数,根据声明的顺序,分别调用CEngineCTyre的构造函数,最后执行封闭类CCar封闭类自身的构造函数。析构函数的调用顺序与构造函数调用顺序相反,因此输出为:

CEngine constructor
CTyre constructor
CCar constructor
CCar destructor
CTyre destructor
CEngine destructor

测验

  1. 如果某函数的返回值是个对象,则该函数被调用时,返回的对象
  • 是通过复制构造函数初始化的
  • 不需要初始化
  • 是通过无参数的构造函数初始化的
  • 用哪个构造函数初始化取决于函数中 return 语句是怎么写的
  1. 以下说法正确的是:
  • const成员函数不能作用于非 const 对象
  • 静态成员变量每个对象有各自的一份
  • 在静态成员函数中不能使用 this 指针
  • 在静态成员函数中可以调用同类的其他任何成员函数const成员函数不能作用于非 const 对象
Author: NYY
Link: http://yoursite.com/2018/08/21/c++/c-2-1/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.