C++课程学习笔记第三周:类和对象提高

1 this指针

this 是 C++ 中的一个关键字,也是一个 const 指针,它指向当前对象,通过它可以访问当前对象的所有成员。this 只能用在类的内部,通过 this 可以访问类的所有成员,包括 private、protected、public 属性的。

  • 其作用就是指向成员函数所作用的对象。
  • 注意在静态成员函数中不能使用this,因为静态成员函数本质是全局函数,不作用于任何对象。
  • 成员函数在编译时,会被偷偷塞进一个this指针,但静态成员函数不会。

this本质:

  • this 实际上是成员函数的一个形参,在调用成员函数时将对象的地址作为实参传递给 this。不过 this 这个形参是隐式的,它并不出现在代码中,而是在编译阶段由编译器默默地将它添加到参数列表中。
  • this 作为隐式形参,本质上是成员函数的局部变量,所以只能用在成员函数的内部,并且只有在通过对象调用成员函数时才给 this 赋值。
  • 在《C++函数编译原理和成员函数的实现》一节中讲到,成员函数最终被编译成与对象无关的普通函数,除了成员变量,会丢失所有信息,所以编译时要在成员函数中添加一个额外的参数,把当前对象的首地址传入,以此来关联成员函数和成员变量。这个额外的参数,实际上就是 this,它是成员函数和成员变量关联的桥梁。
//示例1
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;
        }
};

//示例2:很诡异的一个示例,看起来是错的,但理解了this的本质就知道实际是对的。
class A{
    int i;
    public:
        void Hello() { cout << "hello" << endl; }
};
int main(){
    A * p = NULL;
    p->Hello();
} // 输出:hello  注意:如果在Hello()函数中使用了成员变量i,则会报错

2 静态成员变量和静态成员函数

  • 在定义前面加了static关键字的成员,即为静态成员。
  • 普通成员变量每个对象有各自的一份,而静态成员变量一共就一份,为所有对象共享。故sizeof运算符sizeof(CMyclass)不会计算静态成员变量。
  • 普通成员函数必须具体作用于某个对象,而静态成员函数并不具体作用于某个对象。
  • 静态成员不需要通过对象就能访问。
  • 静态成员变量本质上是全局变量,哪怕一个对象都不存在,类的静态成员变量也存在。静态成员函数本质上是全局函数。设置静态成员这种机制的目的,是将和某些类紧密相关的全局变量和函数写到类里面,看上去像一个整体,易于维护和理解。

访问方法:

  • 类名::成员名,比如 CRectangle::PrintTotal();
  • 对象名.成员名,比如 CRectangle r; r.PrintTotal();
  • 指针->成员名,比如 CRectangle * p = &r; p->PrintTotal();
  • 引用.成员名,比如 CRectangle & ref = r; int n = ref.nTotalNumber;

注意:

  • 在静态成员函数中,不能访问非静态成员变量,也不能调用非静态成员函数。
  • 必须在定义类的文件中对静态成员变量进行一次说明或初始化。否则编译能通过,链接不能通过。
  • 若静态成员变量定义为private,不能在外部调用。(见示例)
//示例1。此例中应再自定义复制构造函数,否则逻辑疏漏。
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; // Wrong ,  私有
    CRectangle::PrintTotal();
    r1.PrintTotal();
    return 0;
}

3 成员对象和封闭类

定义和使用:

  • 有成员对象的类叫 封闭(enclosing)类。
  • 通过封闭类的构造函数的初始化列表,来初始化封闭类和成员变量及成员对象。

构造函数和析构函数执行顺序:

  • 封闭类对象生成时,先执行所有对象成员的构造函数,然后才执行封闭类的构造函数。
  • 对象成员的构造函数调用次序和对象成员在类中的说明次序一致,与它们在成员初始化列表中出现的次序无关。
  • 当封闭类的对象消亡时,先执行封闭类的析构函数,然后再执行成员对象的析构函数。次序和构造函数的调用次序相反。
  • 封闭类的对象,如果是用默认复制构造函数初始化的,那么它里面包含的成员对象,也会用复制构造函数初始化。
class CCar { // 汽车类
    private:
        int price; // 价格
        CTyre tyre;
        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;
}

4 友元

  • 友元分为友元函数和友元类两种,用friend声明一下即可。
  • 可以将一个类的成员函数(包括构造、析构函数)说明为另一个类的友元。
  • 友元函数:一个类的友元函数可以访问该类的私有成员。友元类:如果A是B的友元类,那么A的成员函数可以访问B的私有成员。
  • 友元类之间的关系不能颠倒,不能传递,不能继承。
class CCar{
    private:
        int price;
        friend int MostExpensiveCar( CCar cars[], int total); //声明友元函数
        friend void CDriver::ModifyCar(CCar * pCar); //声明友元函数
        friend class CDriver; //声明CDriver为友元类
};
class CDriver{
    public:
        CCar myCar;
        void ModifyCar() { // 改装汽车
            //因CDriver 是CCar 的友元类,故此处可以访问其私有成员
            myCar.price += 1000;
         }
};
int main(){ return 0; }

5 常量成员函数

  • 常量对象:如果不希望某个对象的值被改变,则定义该对象的时候可以在前面加const关键字。
  • 常量成员函数:在类的成员函数后面加const关键字(包括声明和定义)。
  • 常量成员函数内部不能改变属性的值(mutable成员变量除外),也不能调用非常量成员函数(因为这些函数可能会改变值)。
  • 两个函数,名字和参数表都一样,但是一个是const,一个不是,算重载。
//示例1
class Sample {
    private :
        int value;
    public:
        Sample() { }
        void SetValue() {  }
};
const Sample Obj; //常量对象
Obj.SetValue(); //错误。常量对象只能使用构造函数、析构函数和带const说明的函数


//示例2
class CTest {
    private :
        int n;
    public:
        CTest() { n = 1 ; }
        int GetValue() const { return n ; }
        int GetValue() { return 2 * n ; }
};
int main() {
    const CTest objTest1;
    CTest objTest2;
    cout << objTest1.GetValue() << "," << objTest2.GetValue(); //1,2
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/inchbyinch/p/12185207.html