C++课程学习笔记第三周:类和对象提高
前言:本文主要是根据MOOC网北大课程——《程序设计与算法(三):C++面向对象程序设计》内容整理归纳而来,整理的课程大纲详见 https://www.cnblogs.com/inchbyinch/p/12398921.html
本文进一步介绍了类和对象的知识,包括this关键字、静态成员、成员对象、友元和常量成员函数。
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,不能在外部调用。(见示例)
- 静态成员变量和函数在类的内部声明时用static关键字,后续初始化或实现时不再加static。
//示例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;
}