听课笔记---程序设计与算法(三)C++面向对象程序设计(郭伟老师)---第三周
Week3
目录
this
指针
静态成员函数和静态成员变量
成员对象和封闭类
常量对象和常量函数
友元
内容与思考
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 CCaar {
int price; // 成员变量->域
}
void SetPrice(struct CCar * this, int p) // 成员函数->全局函数+this指针
{
this->price = p;
}
int main()
{
struct CCar car;
SetPrice(&car, 20000); // 加入一个参数
return 0;
}
可以理解为C++语言的机器级别代码(真正运行的代码)事实上就加入了
this
指针理解含有
this
指针的代码可以尝试将其翻译为等价的C语言代码
故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;} // 函数执行和对象没有任何关系
};
int main()
{
A * p = NULL;
p->Hello(); // 等价于 Hello(p), 空指针也可调用成员函数
return 0;
}
输出:
hello
但如下代码是错误的,其成员函数中使用了对象,可以通过编译,但是执行时会非正常终止。
#include<iostream>
using namespace std;
class A {
int i;
public:
void Hello() { cout << i << "hello" << endl;} // 函数执行使用了对象
};
int main()
{
A * p = NULL;
p->Hello(); // 空指针不可调用成员函数
return 0;
}
静态成员函数和静态成员变量
定义方式
在声明前加入static
关键字
实例
class Crectangle {
private:
int w, h;
static int nTotalArea; // 静态成员变量
static int nTotalNumber; // 静态成员变量
public:
Crectangle(int w_, int h_);
~Crectangle();
static void PrintTotal(); // 静态成员函数
};
静态成员的特征与本质
- 静态成员变量被所有对象共享。
sizeof
不计算静态成员变量的空间 - 静态成员函数被所有对象共享,不具体作用于某一个对象。
- 不需要通过对象就可以访问,但可以通过对象访问
- 静态成员变量必须在使用前进行声明或初始化,否则编译通过,链接不通过
- 静态成员变量本质上是全局变量,没有对象也会存在
- 静态成员函数本质上是全局函数
- 这种机制使得和某些类相关的全局变量和全局函数写到类里面,易于理解和维护
静态成员的访问方式
-
类名::成员名
Crectangle::PrintTotal()
-
对象名.成员名
Crectangle r; r.PrintTotal(); // 不作用在r上
-
对象指针->成员名
Crectangle * p = &r; p->PrintTotal(); // 不作用在r上
-
引用.成员名
Crectangle & ref = r; int n = ref.nTotalNumber; // 不属于 r
静态成员实例
#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;
}
注意事项
- 静态成员函数不可以使用非静态成员变量,包括
this
指针 - 静态成员函数不可以调用非静态成员函数
成员对象和封闭类
定义
- 是另一个类的成员变量的对象为成员对象
- 有成员对象的类为封闭类
实例
#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
没有无参构造函数,初始化需要参数-
封闭类中的成员对象初始化必须使用构造函数的初始化列表(拥有无参构造函数的成员对象除外),不能使用构造函数体,列表中的参数可以是任意有定义的表达式
如:
CCar::CCar(int p, int tr, int w):tyre(tr, w) { price = p; }
这段代码仍然正确,因为
price
是普通成员变量而非成员对象。但是,这段代码出错
CCar::CCar(int p, int tr, int w): { tyre.r = tr; // 出错 tyre.w = w; // 出错 price = p; }
封闭类的构造函数和析构函数的执行顺序
先构造成员对象,后构造封闭类对象(构造封闭类对象可能使用成员对象)
先析构封闭类对象,后析构成员对象(封闭类析构函数可能使用成员对象)
-
实例
#include<iostream> using namespace std; class CTyre { public: CTyre() { cout << "CTyre Constructor called." << endl;} ~CTyre() { cout << "CTyre Destructor called." << endl;} }; class CEngine { public: CEngine() {cout << "CEngine Constructor called." << endl;} ~CEngine() { cout << "CEngine Destructor called." << endl;} }; class CCar { CEngine engine; CTyre tyre; public: CCar() {cout << "CCar Constructor called." << endl;} ~CCar() { cout << "CCar Destructor called." << endl;} }; int main() { CCar car; return 0; }
输出:
CEngine Constructor called.
CTyre Constructor called.
CCar Constructor called.
CCar Destructor called.
CTyre Destructor called.
CEngine Destructor called.
封闭类的复制构造函数
会使得封闭类的成员对象使用复制构造函数而非普通构造函数初始化,成员对象的复制构造函数的实参即为封闭类复制构造函数的实参的成员对象。
实例
#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
.
常量对象和常量成员函数
定义方式
-
常量对象定义方式
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() { return 0; }
常量成员函数调用规则
常量对象只能调用常量成员函数
非常量对象可以调用常量成员函数和非常量成员函数
-
同样参数表,同样返回值类型的成员函数,一个是常量成员函数,另一个不是,这两个函数就形成重载关系,在调用时,非常量对象虽然可以调用任何一个,但默认调用非常量成员函数。
实例:
#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; }
输出:
1
2
友元
定义
- 友元函数:一个类的友元函数(非成员函数)可以访问该类的私有成员
- 友元类:若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;
}
友元类:
#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;
}
注意事项
- 友元关系不能传递,不能继承
练习题源码与分析
BigAndBase
封闭类与成员对象的构造函数与复制构造函数
Code:
#include<iostream>
using namespace std;
class Base {
public:
int k;
Base(int a):k(a) {}
};
class Big {
public:
int v;
Base b;
Big(int i):v(i),b(i) {}
Big(const Big & a):b(a.b.k) // b can only be initialized by the list
{
v = a.v;
}
};
int main()
{
int n;
while (cin >> n) {
Big a1(n);
Big a2 = a1;
cout << a1.v << "," << a1.b.k << endl;
cout << a2.v << "," << a2.b.k << endl;
}
return 0;
}
Pointer
常量对象与常量成员函数,常量成员函数的调用规则,this
指针
Code:
#include<iostream>
using namespace std;
struct A {
int v;
A(int vv):v(vv){}
const A * getPointer() const
{
return this;
}
};
int main()
{
const A a(10);
const A * p = a.getPointer();
cout << p->v << endl;
return 0;
}
ProgramCompletion
引用,this
指针
#include<iostream>
using namespace std;
class A {
public:
int val;
A(int i = 123)
{
val = i;
}
A & GetObj()
{
return *this;
}
};
int main()
{
int m, n;
A a;
cout << a.val << endl;
while (cin >> m >> n) {
a.GetObj() = m;
cout << a.val << endl;
a.GetObj() = A(n);
cout << a.val << endl;
}
return 0;
}
Zoo
多态与继承,暂且不表,来日再说
Code:
#include<iostream>
using namespace std;
class Animal {
public:
static int number;
virtual ~Animal() {}
};
class Dog :public Animal
{
public:
static int number;
Dog()
{
Dog::number++;
Animal::number++;
}
~Dog()
{
Dog::number--;
Animal::number--;
}
};
class Cat :public Animal
{
public:
static int number;
Cat()
{
Cat::number++;
Animal::number++;
}
virtual ~Cat()
{
Cat::number--;
Animal::number--;
}
};
int Cat::number = 0;
int Dog::number = 0;
int Animal::number = 0;
void print()
{
cout << Animal::number << " animals in the zoo, " << Dog::number << " of them are dogs, " << Cat::number << "of them are cats" << endl;
}
int main()
{
print();
Dog d1, d2;
Cat c1;
print();
Dog *d3 = new Dog();
Animal* c2 = new Cat;
Cat * c3 = new Cat;
print();
delete c2;
delete c3;
delete d3;
print();
return 0;
}
WarCraft1
综合测试,使用了友元,静态成员等等
Code:
#include<iostream>
#include<cstring>
#include<iomanip>
#define TYPENUM 5
using namespace std;
class Headquater;
class Warrior {
private:
char Type[10]; // Name
int strength;
int number;
friend Headquater;
};
class Headquater {
private:
static Warrior Types[TYPENUM + 1]; // 五种战士,所有基地共享
int WarriorSeq[TYPENUM + 1]; // 出兵顺序
int WarriorNum[TYPENUM + 1]; // 各兵种数量,与WarriorSeq对应
char Color[10];
int WarriorTotal; // 兵力总数,即当前兵编号
int NexttoPro; // 下一个训练兵种的下标
int LifeTotal; // 生命元
bool Stop; // 是否已停止制造兵
public:
Headquater(const char Color[], int LifeTotal); // 构造函数,确定颜色,生命元
void SetWarSeq(int a1, int a2, int a3, int a4, int a5); // 设置出兵顺序
static void WarTypeInit(int strength[]); // 各兵种strength值设置
void Event(int time); // 某一时间点的动作
bool StopOrNot() {return Stop;}
};
Warrior Headquater::Types[6];
void Headquater::WarTypeInit(int strength[]) // 设置strength值
{
for (int i = 0; i < TYPENUM; i++) {
Types[i].strength = strength[i];
}
strcpy(Headquater::Types[0].Type, "dragon");
strcpy(Headquater::Types[1].Type, "ninja");
strcpy(Headquater::Types[2].Type, "iceman");
strcpy(Headquater::Types[3].Type, "lion");
strcpy(Headquater::Types[4].Type, "wolf");
}
Headquater::Headquater(const char Col[], int Life) // 构造函数
{
strcpy(Color, Col);
LifeTotal = Life;
Stop = false;
NexttoPro = 0;
WarriorTotal = 0;
for (int i = 0; i < TYPENUM; i++) {
WarriorNum[i] = 0;
}
}
void Headquater::SetWarSeq(int a1, int a2, int a3, int a4, int a5) // 设置出兵顺序
{
WarriorSeq[0] = a1;
WarriorSeq[1] = a2;
WarriorSeq[2] = a3;
WarriorSeq[3] = a4;
WarriorSeq[4] = a5;
}
void Headquater::Event(int time) // 某一时刻动作
{
if (Stop) { // 已停止出兵
return;
}
cout << setw(3) << setfill('0') << time << " " << Color << " "; // 输出时间
if (LifeTotal < Types[WarriorSeq[NexttoPro]].strength) { // 无法制造当前应制造兵种
int index = (NexttoPro + 1) % TYPENUM;
while (LifeTotal < Types[WarriorSeq[index]].strength && index != NexttoPro) { // 查找下一可制造兵种
index = (index + 1) % TYPENUM;
}
if (index == NexttoPro) { // 无可制造兵种,停止造兵
cout << "headquarter stops making warriors" << endl;
Stop = true;
return;
}
else { // 找到可制造兵种
NexttoPro = index;
}
}
cout << Types[WarriorSeq[NexttoPro]].Type << " " << ++WarriorTotal << " born with strength " << Types[WarriorSeq[NexttoPro]].strength
<< "," << ++WarriorNum[NexttoPro] << " " << Types[WarriorSeq[NexttoPro]].Type << " in " << Color << " headquarter" << endl; // 输出造兵行为
LifeTotal -= Types[WarriorSeq[NexttoPro]].strength; // 减少生命元
NexttoPro = (NexttoPro + 1) % TYPENUM; // 指向下一应制造兵种
}
int main()
{
int strength[TYPENUM + 1];
int N, LifeUnit;
cin >> N;
for (int i = 1; i <= N; i++) {
cin >> LifeUnit; // 读入生命元
Headquater Blue("blue", LifeUnit);
Headquater Red("red", LifeUnit);
Blue.SetWarSeq(3, 0, 1, 2, 4);
Red.SetWarSeq(2, 3, 4, 1, 0); // 初始化两大基地
for (int j = 0; j < TYPENUM; j++) { // 读入力量值
cin >> strength[j];
}
Headquater::WarTypeInit(strength); // 设置力量值
cout << "Case:" << i << endl;
int time = 0;
while (!Blue.StopOrNot() || !Red.StopOrNot()) { // 有一方未停止造兵,即继续行为
Red.Event(time);
Blue.Event(time);
time++;
}
}
return 0;
}