this指针篇
C++程序到C程序的翻译
- C++程序
class CCar {
public:
int price;
void SetPrice(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来代表指向该函数作用的对象的指针
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指针作用
正确代码
class A
{
int i;
public:
void Hello() { cout << "Hello" << endl; }
}; // 翻译成C:void Hello( A * this ){ cout << "Hello" << endl; }
错误代码
class A
{
int i;
public:
void Hello() { cout << i << "Hello" << endl; }
}; // 翻译成C:void Hello( A * this ){ cout << this->i << "Hello" << endl; }
//this若为NULL,则出错!!
int main()
{
A* p = NULL;
p->Hello(); //翻译成C:Hello(p);
return 0;
} //输出:Hello
this指针和静态成员函数
- 静态成员函数中不能使用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;
注:静态成员变量和静态成员函数都不具体属于某个对象也不具体作用于某个对象!!!
- 静态成员变量本质上是全局变量,哪怕一个对象都不存在,类的静态成员变量也存在
- 静态成员函数本质上是全局函数
- 设置静态成员这种机制的目的是将和某些类紧密相关的全局变量与函数写到类里面,看上去像一个整体,易于维护和理解
静态成员示例
- 考虑一个需要随时知道矩形总数和总面积的图形处理程序
- 可以用全局变量来记录总数和总面积
- 用静态成员将这两个变量封装进类中,就更容易理解和维护
#include<iostream>
using namespace std;
class CRectangle {
private:
int w, h;
static int nTotalNumber;
static int nTotalArea;
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;
}
注意事项
在静态成员函数中,不能访问非静态成员变量,也不能调用非静态成员函数
void CRectangle::PrintTotal()
{
cout << w << "," << nTotalNumber << "," << nTotalArea << endl; //wrong
}
CRectangle::PrintTotal(); //解释不通,w到底是属于哪个对象的?
//此类CRectangle类写法,有缺陷
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;
}
缺陷:
1.在使用CRectangle类时,有时会调用复制构造函数生成临时的隐藏的CRectangle对象。
- 调用一个以CRectangle类对象作为参数的函数时
- 调用一个以CRectangle类对象作为返回值的函数时
2.临时对象在消亡时会调用析构函数,减少nTotalNumber和nTotalArea的值,可是这些临时对象在生成时却没有增加nTotalNumber和nTotalArea的值。
解决办法
为CRectangle类写一个复制构造函数
CRectangle::CRectangle(CRectangle& r)
{
w = r.w; h = r.h;
nTotalNumber++;
nTotalArea += w * h;
}
成员对象与封闭类
有成员对象的类叫封闭类
#include<iostream>
using namespace std;
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);
};
CCar::CCar(int p, int tr, int tw) :price(p), tyre(tr, tw)
{
};
int main()
{
CCar car(20000, 17, 225);
return 0;
}
-
上例中,如果CCar类不定义构造函数,则下面的语句会编译出错:
CCar car -
因为编译器不明白car.tyre该如何初始化。car.engine的初始化没问题,用默认构造函数即可。
-
任何生成封闭类对象的语句,都要让编译器明白,对象中的成员对象,是如何初始化的。
-
具体的做法就是:通过封闭类的构造函数的初始化列表。
-
成员对象初始化列表中的参数可以是任意复杂的表达式,可以包括函数,变量,只要表达式中的函数或变量有定义就行。
初始化列表
:radius(r), width(w)
在初始化列表里,我们可以为每一个成员变量指定一个初始值。如:上述的radius(r)就是把radius这个成员变量初始化成 r 的值。
封闭类构造函数和析构函数的执行顺序
- 封闭类对象生成时,先执行所有对象成员的构造函数,然后才执行封闭类的构造函数。
- 对象成员的构造函数调用次序和对象成员在类中说明次序一致,与它们在成员初始化列表中出现的次序无关。
- 当封闭类的对象消亡时,先执行封闭类的析构函数,然后再执行成员对象的析构函数。次序和构造函数的调用次序相反。
封闭类实例
#include<iostream>
using namespace std;
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 contructor" << endl; }
~CCar() { cout << "CCar destructor" << endl; }
};
int main()
{
CCar car;
return 0;
}
/*
输出结果:
CEngine contructor
CTyre contructor
CCar contructor
CCar destructor
CTyre destructor
CEngine destructor
*/
封闭类的复制构造函数
#include<iostream>
using namespace std;
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。
常量对象、常量成员函数
常成员
常量对象
如果不希望某个对象的值被改变,则定义该对象的时候可以在前面加const关键字
class Demo {
private:
int value;
public:
void SetValue(){ }
};
const Demo Obj; //常量对象
常量成员函数
- 在类的成员函数说明后面可以加const关键字,则该成员函数成为常量成员函数。
- 常量成员函数执行期间不应修改其所作用的对象。因此,在常量成员函数中不能修改成员变量的值(静态成员变量除外),也不能调用同类的非常量成员函数(静态成员函数除外)。
class Sample
{
public:
int value;
void GetValue() const;
void func() { };
Sample() { };
};
void Sample::GetValue() const
{
value = 0; //wrong
func(); //wrong
}
int main() {
const Sample o;
o.value = 100; //err.常量对象不可被修改
o.func(); //err.常量对象上面不能执行非常量成员函数
o.GetValue(); //ok,常量对象上可以执行常量成员函数
return 0;
} //在DevC++中,要为Sample类编写不参构造函数才可以,
// Visual Studio 2010(或其上的版本)中不需要。
常量成员函数的重载
两个成员函数,名字和参数表都一样,一个是const,一个不是,算重载
#include<iostream>
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;
std::cout << objTest1.GetValue() << "," << objTest2.GetValue();
return 0;
} //输出:1,2
常引用
引用前面可以加const关键字,成为常引用。不能通过常引用,修改其引用的变量。
const int& r = n;
r = 5; //error
n = 4; //ok
问题:
对象作为函数的参数时,生成该参数需要调用复制构造函数,效率比较低。用指针作参数,代码又不好看。
法一:
可以用对象的引用作为参数,如:
class Sample { };
void PrintfObj(Sample& o) { }
问题:
对象引用作为函数的参数有一定风险性,若函数中不小心修改了形参o,则实参也跟着变。
法二:
可以用对象的常引用作为参数,如:
class Sample { };
void PrintfObj(const Sample& o) { }
这样函数中就能确保不会出现无意中更改o值的语句了。
友元
友元分为友元函数和友元类两种
友元函数
- 友元函数:一个类的友元函数可以访问该类的私有成员
class CCar; //提前声明CCar类,以便后面的CDriver类使用
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 (cars[i].price > tmpMax)
tmpMax = cars[i].price;
return tmpMax;
}
int main()
{
return 0;
}
- 可以将一个类的成员函数(包括构造、析构函数)说明为另一个类的友元
class B {
public:
void function();
};
class A {
friend void B::function();
};
友元类
- 如果A是B的友元类,那么A的成员函数可以访问B的私有成员
class CCar{
private:
int price;
friend class CDriver; //声明CDriver为友元类
};
class CDriver {
public:
CCar myCar;
void ModifyCar() { //改装汽车
myCar.price += 1000; //因CDriver是CCar的友元类,
//故此处可以访问其私有成员
}
};
int main() {
return 0;
}
友元类之间的关系不能传递,也不能继承