1. this
指针
由来:C++原来没有编译器,可以将C++程序翻译为C程序然后使用C的编译器,翻译过程中,class转化为struct,成员变量转化为结构的域,成员函数转化为全局函数,但是成员函数需要增加一个 指向作用对象的指针this
作为参数,以方便确定被操作对象并对其进行操作。
比如下面的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;
}
可以被转化为:
struct CCar {
int price; // 成员变量->域
}
void SetPrice(struct CCar * this, int p) // 成员函数->全局函数+this指针
{
this->price = p;
}
int main()
{
struct CCar car;
SetPrice(&car, 20000); // 加入一个参数。对象 car 的地址作为实参
return 0;
}
- this 指针的作用:指向成员函数所作用的对象(注意:this 是一个指针,*this 就是 this 所指向的对象)
this
指针使用实例—返回被作用对象:
#include<iostream>
using namespace std;
class Complex {
public:
double real, imag;
void Print();
Complex(double r, double i):real(r), imag(i) {}
Complex AddOne();
};
Complex Complex::AddOne()
{
this->real++;
this->Print();
return * this; // 返回被作用对象的值
}
void Complex::Print()
{
cout << real << "," << imag;
}
int main()
{
Complex c1(1, 1), c2(0, 0);
c2 = c1.AddOne();
return 0;
}
//输出:2,1
使用空指针调用成员函数:
这样的调用是有条件的,条件就是:成员函数内部不使用被作用对象的成员变量或成员函数
如下代码,是正确的:
#include<iostream>
using namespace std;
class A {
int i;
public:
void Hello() { cout << "hello" << endl;} // 函数执行和对象没有任何关系
//等价于 void Hello(A * this) {cout << "hello" << endl;} (翻译成C语言)
};
int main()
{
A * p = NULL;
p->Hello(); // 等价于 Hello(p), 空指针也可调用成员函数(函数体里并未使用该空指针)
return 0;
}
但如下代码是错误的,其成员函数中使用了对象,可以通过编译,但是执行时会非正常终止。
#include<iostream>
using namespace std;
class A {
int i;
public:
void Hello() { cout << i << "hello" << endl;} // 函数执行使用了对象
////等价于 void Hello(A * this) {cout << this->i << "hello" << endl;}
};
int main()
{
A * p = NULL;
p->Hello(); // 空指针不可调用成员函数 Hello(p) 函数体里调用了空指针
return 0;
}
this 指针和静态成员函数:
静态成员函数中不能使用 this 指针!(静态成员函数并不具体作用于某个对象!)
静态成员函数的真实的参数个数就是程序中写出的参数个数。普通成员函数的真实参数个数比写出的对一个(this 指针)。
2. 静态成员函数和静态成员变量
静态成员:在说明前加了 static 关键字的成员
class Crectangle {
private:
int w, h;
static int nTotalArea; // 静态成员变量
static int nTotalNumber; // 静态成员变量
public:
Crectangle(int w_, int h_);
~Crectangle();
static void PrintTotal(); // 静态成员函数
};
基本概念:
- 普通成员变量每个对象有各自的一份,而静态成员变量一共就一份,为所有对象共享。
- sizeof 运算符不会计算静态成员变量。(计算一个类的字节数时忽略静态成员变量)
- 普通成员变量必须具体作用于某个对象,而静态成员变量并不具体作用于某个对象。(普通成员变量调用时:myclass.a 或 myclass->a 。而静态成员变量调用时不需指定对象)。
- 静态成员不需要通过对象就能访问。
- 静态成员变量本质上是全局变量,没有对象,类的静态成员变量也存在
- 静态成员函数本质上是全局函数
- 这种机制使得和某些类相关的全局变量和全局函数写到类里面,易于理解和维护
静态成员的访问方式:
- 类名::成员名
Crectangle::PrintTotal()
- 对象名.成员名
Crectangle r; r.PrintTotal(); // 只是一种形式,并不作用在对象 r 上
- 对象指针->成员名
Crectangle * p = &r; p->PrintTotal(); // 不作用在对象 r 上
- 引用.成员名
Crectangle & ref = r; int n = ref.nTotalNumber; // 不属于 r。共享的 nTotalNumber
- 静态成员变量必须在使用前在类的定义之外进行声明或初始化,否则编译通过,链接不通过
- 在静态成员函数中,不能访问非静态成员变量,也不能调用非静态成员函数。
静态成员实例:
#include<iostream>
using namespace std;
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(); // 两个PrintTotal等价
return 0;
}
//输出
//2,13
//2,13
实例的改进:
注意到实例中没有对复制构造函数进行重新定义,使用复制构造函数生成的对象没有修改两个静态成员变量,但消亡时调用了析构函数,会导致两个静态成员变量比正确值小(一旦有调用复制构造函数的对象生成就比正确值小。),可以通过自定义复制构造函数来改进。
#include<iostream>
using namespace std;
class Crectangle {
private:
int w, h;
static int nTotalArea; // 静态成员变量
static int nTotalNumber; // 静态成员变量,别的类无法访问
public:
Crectangle(int w_, int h_);
Crectangle(Crectangle & c); // 自定义复制构造函数
~Crectangle();
static void PrintTotal(); // 静态成员函数,别的类无法访问
};
Crectangle::Crectangle(Crectangle & c) //增加自定义的复制构造函数
{
w = c.w;
h = c.h;
nTotalNumber++;
nTotalArea += w * h;
}
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(); // 两个PrintTotal等价
return 0;
}
3. 成员对象和封闭类
- 是另一个类的成员变量的对象为成员对象
- 有成员对象的类为封闭类
#include<iostream>
using namespace std;
class CTyre {
int radius;
int width;
public:
CTyre (int r, int w):radius(r), width(w) {} //初始化列表
};
class CEngine {
};
class CCar { // 封闭类
int price;
CTyre tyre; // 成员对象
CEngine engine; // 成员对象
public:
CCar(int p, int ty, 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 的自定义构造函数是必须的,否则编译器的无参构造函数不知道如何初始化成员对象 tyre 因为 CTyre 没有无参构造函数,初始化需要参数
任何生成封闭类对象的语句,都要让编译器明白,对象中的成员对象是如何初始化的。具体的做法:通过封闭类构造函数的初始化列表
封闭类中的成员对象初始化必须使用构造函数的初始化列表(拥有无参构造函数的成员对象除外),不能使用构造函数体,列表中的参数可以是任意有定义的表达式
封闭类的构造函数和析构函数的执行顺序:
-
先执行所有成员对象的构造函数,然后才执行封闭类对象的构造函数(构造封闭类对象可能使用成员对象)。
-
成员对象的构造函数调用次序和成员对象在类中的说明次序一致,与它们在初始化列表中出现的次序无关。
-
先执行封闭类对象的析构函数,然后执行成员对象的析构函数(封闭类析构函数可能使用成员对象)。次序和构造函数的调用次序相反。
封闭类的复制构造函数:
会使得封闭类的成员对象使用复制构造函数而非普通构造函数初始化,成员对象的复制构造函数的实参即为封闭类复制构造函数的实参的成员对象。
#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
b1
没有自定义构造函数或复制构造函数,均使用缺省的函数。- 缺省的构造函数初始化了
b1
,调用了a
的无参构造函数,输出default
- 缺省的复制构造函数初始化了
b2
, 调用了a
的复制构造函数,其实参为b1.a
,输出copy
.
4. 常量对象和常量成员函数
定义方式:
-
常量对象定义方式
const
类名 对象名 -
常量成员函数定义方式
返回值类型 类名::函数名(参数表)
const
{函数体}
注:定义常量成员函数时把 const 关键字放在后面。
常量成员函数定义规则:
- 常量成员函数执行期间不应修改其所作用的对象。
- 不能修改成员变量的值(静态成员变量除外)。
- 也不能调用同类的非常量成员函数(静态成员函数除外)。
实例:
#include<iostream>
using namespace std;
class Sample {
int value;
public:
void GetValue() const; //常量成员函数
void func() {};
Sample() {};
};
void Sample::GetValue() const
{
value = 0; // 出错。修改了成员变量的值
func(); // 出错。调用了非常量成员函数
}
int main()
{
const Sample o; //定义成员对象
o.value = 100; //err。常量对象不可被修改。
o.func(); //err。常量对象上面不能执行非常量成员函数。
o.GetValue(); //ok。常量对象上可以执行常量成员函数。
return 0;
}
常量成员函数的重载:
- 两个成员函数,名字和参数表都一样。但是一个是 const ,一个不是。这算是成员函数的重载,不是重复定义。
- 在调用时,常量对象只能调用常量成员函数;非常量对象虽然可以调用任何一个,但默认调用非常量成员函数。
#include<iostream>
using namespace std;
class CTest {
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() << endl; // 调用常量成员函数
cout << objTest2.GetValue() << endl; // 调用非常量成员函数
return 0;
}
5. 友元
友元分为友元函数和友元类两种。
定义:
- 友元函数:一个类的友元函数(非成员函数)可以访问该类的私有成员
- 友元类:若A是B的友元类,则类A的成员函数可以访问类B的私有成员
友元函数实例:
#include<iostream>
using namespace std;
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 (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();
};
友元类实例:
#include<iostream>
using namespace std;
class CDriver; // 提前声明,便于使用
class CCar {
private:
int price;
friend CDriver; // 声明友元类
};
class CDriver {
CCar myCar; //定义对象
public:
void ModifyCar(CCar * pCar);
};
void CDriver::ModifyCar(CCar * pCar)
{
pCar->price += 1000; // 操作私有变量
}
int main()
{
return 0;
}
注意事项:
- 友元关系不能传递,不能继承