C++面向对象程序设计(三)——3.类和对象提高

C++面向对象程序设计(三)——3.类和对象提高

本文是中国大学MOOC,北京大学程序设计与算法(三)C++面向对象程序设计第三周笔记。本课程学习的github仓库欢迎Fork

一 this 指针

C++ 到 C程序的翻译

//C++
class CCar{
  public:
    	int price;
    	void SetPtice(int p);
};
void CCar::SetPrice( int p ){
    price = p;
}
int main(){
    CCar car;
    car.SetPrice( 20000 );
    return 0;
}
//C
struct CCar{
    int price;
};
void SetPrice( struct CCar * this, int p)
{
    this> price = p;
}
int main(){
    struct CCar car;
    SetPrice( & car, 20000 );
    return 0;
}

对比一下这两个程序,我们可以看到:

每个类的非静态成员函数中都隐含包含一个this指针,类型为当前类类型的指针类型

this 作用就是指向成员函数所作用的对象。在非静态成员函数中可以直接使用this来代表指向该函数作用的对象的指针。

我们可以看一个案例:

class Complex{
    public:
    	double real,imag;
    	void Print(){
            cout << real << "," << imag;
        }
    Complex( double r, double i ):real( r ),imag( i )
    {}
    Complex AddOne(){
        this -> real ++;		//等价于 real++
        this -> Print();		//等于于 Print
        return * this;
    }
};

int main(){
    Complex c1(1,1),c2(0,0);
    c2 = c1.AddOne();
    return 0;
}//输出 2,1

需要注意的是:

静态成员函数中不能使用this指针,因为静态成员函数并不具体作用于某个对象。所以,静态成员函数的真实参数的个数,就是程序中写出的参数个数。

然而,类的非静态成员函数,真实的参数比所写的参数多1,多的这个就是this指针。

二 静态成员

基本概念

在定义前面加了static关键字的成员

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

静态成员变量为所有对象共享,且sizeof运算符不会计算静态成员变量

class CMyclass{
    int n;
    static int s;
};
//sizeof(CMyclass) 等于 4

普通成员变量每个对象有各自的一份,而静态成员变量一共就一份,为所有对象共享

普通成员函数必须具体作用于某个对象,而静态成员函数并不具体作用于某个对象。所以实际上静态成员不需要通过对象就能访问。

如何访问静态成员

1.类型::成员名

CRectangle::PrintTotal();

2.对象名.成员名

CRectangle r;
r.PrintTotal();

3.指针->成员名

CRectangle * p = &r;
p -> PrintTotal();

4.引用.成员名

CRectangle & ref = r;
int n = ref.nTotalNumber;

静态成员变量本质上是全局变量,哪怕一个对象都不存在,类的静态成员变量也存在

静态成员函数本质上是全局函数,主要目的是将和某些类紧密相关的全局变量和函数写到类里面,看上去想一个整体,易于维护和理解

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_;
    nTotalNmuber ++;
    nTotalArea += w * h;
}

CRectangle::~CRectangle()
{
    nTotalNumber --;
    nTotalArea -= w * h;
}

void CRectangle::PrintTotal()
{
    cout << nTotalNumber << "," <<nTotalArea <<endl;
}

int CRectangle::nTotalNumber = 0;
int CRectangle::nTotalNumber = 0;
//必须在定义类的文件中对静态函数变量进行以此说明或初始化
//否则编译能通过,链接1不能通过

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

//输出:
//2,13
//2,13

要注意的是:在静态成员函数中,不能访问非静态成员变量,也不能调用非静态成员函数

void CRectangle::PrintTotal()
{
    cout << w << "," << nTotalNumber << "," << nTotalArea << endl;		//error
}
CRectangle::PrintTotal();	//解释不通,w到底属于哪个对象?

三 成员对象和封闭类

基本概念

有成员对象的类叫封闭类

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 yre;
    		CEngine engine;
    public:
    		CCar( int p, int tr, int tw );
};

CCar::CCar( int p, int tr, int w):price(p),tyre(tr,w)
{};


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

上面的例子中,如果CCar类不定义构造函数,那么下面的语句编译会出错

CCar car;

因为编译器不明白car.type,该如何初始化。car.engine的初始化没问题,用默认构造函数就可以了。

任何生成封闭类对象的语句,都要使得编译器明白,对象中的成员对象,是如何初始化的。完成这一任务的方法是通过封闭类的构造函数的初始化列表,成员对象初始化列表中的参数可以是任意复杂的表达式,可以包括函数,变量,只要表达式中的函数或变量有定义就行。

封闭类构造函数和析构函数的执行顺序

  1. 封闭类对象生成时,先所有成员对象的构造函数,然后才执行封闭类的构造函数
  2. 对象成员的构造函数调用次序和对象成员在类中的说明次序一致,与成员初始化列表次序无关
  3. 封闭类对象消亡时,先执行封闭类析构函数,再执行成员对象的析构函数。次序和构造函数的调用次序相反
class CTyre{
    public:
    		CTyre(){cout << " Ctyre contructor "<< endl; }
    		~CTyre(){cout << " Ctyre destructor "<< endl; }
}

class CEngine{
    public:
    		CEngine(){cout << "CEngine contructor" << 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;
    return 0;
}


//输出结果
//CEngine contructor
//Ctyre contructor
//CCar constructor
//CCar destructor
//Ctyre destructor
//CEngine destructor
  1. 封闭类的对象如果用默认复制构造函数初始化,那么它包含的成员对象也会用复制构造函数初始化
class A{
    public:
    		A(){cout << "default" << endl;}
    		A(A & a){ cout << "Copy" << endl;}
};

class B {A a;};

int main(){
    B b1, b2(b1);
    return 0;
}

//输出:
//default
//Copy
//b2.a是用类A的复制构造函数初始化的。
//调用复制构造函数的实参是b1.a

四 友元

友元函数

一个类的友元函数可以访问该类的私有成员

class CCar;
class CDriver{
    public:
    	void ModifyCar(CCar * pCar);
};
class CCar
{
    private:
    		int price;
    friend int MostExpensiveCar( CCar cars[], int total );	//声明友元
    friend void CDriver::ModifyCar( CCar * pCar );	//声明友元
}

void CDriver::ModifyCar( CCar * pCar )
{
    pCar->price += 1000 ;	//改装后变贵了
}

int MostExpensiveCar( CCar cars[],int total )	//最贵汽车价格
{
    int tmpMax = -1;
    for( int i = 0; i < total ; ++ i )
    {
        if(car[i].price > tmpMax)
            tmpMax = cars[i].price;
    }
    return tmpMax;
}

int main(){
    return 0;
}

当然我们还可以把一个类的成员函数(包括析构,构造等)说明为另一个类的友元

class B{
    public:
    		void function();
};

class A{
    friend void B::function();
}

友元类

如果AB的友元类,那么A的成员函数可以访问B的私有成员

class CCar{
    private:
    		int price;
    friend class CDriver;	//声明CDriver为友元类
};

class CDriver{
		public:
    		CCar myCar;
    			void ModifyCar(){	//改装汽车
                    myCar.price += 1000;	//CDriver是CCar的友元类,所以可以访问CCar的私有成员
                }
};

int main(){
    return 0;
}

友元类之间的关系不能传递,不能继承

五 常量成员函数

如果不希望某个对象的值被改变,那么可以在对象const关键字

class Sample{
    private:
    int value;
    public:
    Sample(){}
    void SetValue(){}
};

const Sample Obj;	//常量对象
Obj.SetValue ();	//错误,常量对象只能使用构造函数,析构函数,有const说明的函数

类的成员函数说明后面可以加const关键字,该成员函数成为常量成员函数

常量成员函数内部不能改变属性的值,也不能调用非常量成员函数

在定义和声明成员函数时都应该用const关键字

如果一个成员函数中没有调用非常量成员函数,也没有修改成员函数变量的值,那么最好将其写成常量成员函数

如果两个函数,名字参数表都一样,但是一个有const,一个没有,算重载

六 mutable成员变量

可以在const成员函数中修改的成员变量

class CTest{
public:
    	bool GetData()	const
        {
            m_n1++;
            return m_b2;
	}
private:
    mutale int m_n1;
    bool m_b2;
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章