友元、this指针、常量

友元

友元函数

一个类的友元函数可以访问该类的私有成员

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();
}

这种方式使得在class A中也可以通过class B的function访问到B的私有成员。

友元类

类A如果是B的友元类,那么A的成员函数就可以访问B的私有成员

class CCar{
private:
int price;
friend class CDiver; // 声明CDiver为友元类
};
class CDiver{
public:
CCar myCar;
void ModifyCar(){
myCar.price += 1000; // CDriver是CCar的友元类,可以访问其私有成员
}
}

友元类之间的关系不能传递,也无法继承,也就是如果A是B的友元,B是C的友元,那么不能说A是C的友元。


this指针

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指针作用于当前指向的对象
this->real++; // 等价于 real++
this->Print(); // 等价于 Print()
return *this;
}
};

使用时:

int main(){
Complex c1(1, 1), c2(0, 0);
c2 = c1.AddOne();
return 0;
}

c++编译时相当于需要把它转换成c语言,转换后的代码为:

struct Complex{
double real;
double imag;
}
void Print(struct Complex *this){
cout<<this->real<<","<<this->imag;
}
void Complex(struct Complex *this, double r, double, i){
this->real = r;
this->imag = i;
}
struct Complex AddOne(struct Complex *this){
this->real++;
this->Print(this);
return *this;
}
int main(){
struct Complex complex1;
Complex(& complex1, 1, 1);
struct Complex complex2;
Complex(& complex2, 0, 0);

complex2 = AddOne(& complex1);
Print(& complex2);
return 0;
}

在程序中c2的实部为2,虚部为1,输出结果为

2,1

this指针作用

在编译时,如果对象是空,即this指针作用的对象为NULL,

class A{
int i;
public:
void Hello(){
cout<<"Hello"<<endl;
}
};
int main(){
A *p = NULL;
p->Hello();
}

在编译时,如果对象是空,即this指针作用的对象为NULL,程序仍然会执行第10行代码p->Hello()程序,因为在编译时,翻译为:

void Hello(A * this){
cout<<"Hello"<<end
}

int main(){
A *p = NULL;
p->Hello(p);
}

在这种情况下程序不会产生错误。

在下面的情况下,程序会出错:

class A{
int i;
public:
void Hello(){
cout<<i<<"Hello"<<endl;
}
};
int main(){
A *p = NULL;
p->Hello();
}

此时,编译翻译为c语言时,变为:

void Hello(A * this){
cout<<this->i<<"Hello"<<endl;
}
int main(){
A *p = NULL;
p->Hello(p);
}

这里this是个空指针,因此this->i这句会编译错误。

this指针和静态成员函数

静态成员函数中不能使用this指针,因为静态成员函数不具体作用于某个对象,因此在翻译时,静态成员函数的真实的参数个数就是程序中写出的参数个数。


常量及常引用

常量对象

如果不希望某个对象的值被改变,则定义该对象的时候可以在前面加const关键字。

如:

class Demo{
private:
int value;
public:
void SetValue(){}
};
const Demo Obj; // 常量对象

obj初始化完成之后就无法再修改其值。

常量成员函数

在类的成员函数说明后面可以加const关键字,则该成员函数成为常量成员函数。

如:

class Sample{
public:
int value;
void GetValue() const;
void func() { };
Sample() { }
};
void Sample::GetValue() const{
value = 0; // wrong
func(); //wrong
}

常量成员函数执行期间不应修改其所作用的对象。因此,在常量成员函数中不能修改成员变量的值(静态成员变量除外),也不能调用同类的非常量成员函数(静态成员函数除外)。

在main函数中,常量对象用法:

int main(){
const Sample sample;
sample.value = 100; // erro常量对象不可被修改
sample.func(); // erro常量对象不可调用非常量函数
sample.GetValue(); // ok常量对象可以执行常量成员函数
return 0;
}

两个成员函数,名字和参数表都一样,但是一个是const,一个不是,算重载。

如:

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();
return 0;
}

输出结果为:

1,2

常引用

引用前面可以加const关键字,称为常引用。不能通过常引用,修改其引用的变量。

如:

const int & r = n;
r = 6; // error
n = 4; // ok

常引用经常被用来作为函数的参数,因为在写函数的时候,若把对象作为函数参数,生成这个参数对象需要调用复制构造函数,增加了时间和空间的开销。因此,使用对象的引用作为参数,节省开销。

对象引用作为函数参数有一定的风险性,若函数中不小心修改了形参的值,那么实参也会发生改变,如果不想让实参发生变化,可以将形参写为const Object & o可以确保不会出现无意中更改o值的语句。


测验

  1. 以下关于 this 指针的说法中不正确的是:
  • 在构造函数内部可以使用this指针
  • 成员函数内的this 指针,指向成员函数所作用的对象
  • const成员函数内部不可以使用this 指针
  • 在析构函数内部可以使用 this 指针
  1. 以下程序,哪个是不正确的?
  • A
int main(){
class A { int v; };
A a;
a.v = 3;
return 0;
}
  • B
int main() {
class A { public: int v; A * p; };
A a;
a.p = & a;
return 0;
}
  • C
int main() {
class A { public: int v; };
A * p = new A;
p->v = 4;
delete p;
return 0;
}
  • D
int main() {
class A { public: int v; A * p; };
A a;
a.p = new A;
delete a.p;
return 0;
}

编程作业

程序填空

  1. 下面程序输出结果是:
0
5

填空:

#include <iostream>
using namespace std;
class A{
public:
int val;
// 此处代码填空
A(int n=0){val = n;}
A & GetObj(){return *this;}
// 填空完成
};
int main(){
A a;
cout<<a.val<<endl;
a.GetObj() = 5;
cout<<a.val<<endl;
}

空缺处代码为:

A(int n=0){
val = n;
}
A & GetObj(){
return *this;
}
  1. 下面程序的输出是:
10

请补足Sample类的成员函数。不能增加成员变量。

#include <iostream>
using namespace std;
class Sample{
public:
int v;
Sample(int n):v(n) { }
// 在此处补充你的代码
};
int main() {
Sample a(5);
Sample b = a; // 初始化b,调用复制构造函数
cout << b.v;
return 0;
}

空缺处代码为:

Sample(Sample & o){
v = o.v * 2;
}
  1. 下面程序的输出结果是:
5,5
5,5

请填空:

#include <iostream>
using namespace std;
class Base {
public:
int k;
Base(int n):k(n) { }
};
class Big {
public:
int v;
Base b;
// 在此处补充你的代码
};
int main() {
Big a1(5);
Big a2 = a1; // 初始化a2,调用复制构造函数
cout << a1.v << "," << a1.b.k << endl;
cout << a2.v << "," << a2.b.k << endl;
return 0;
}

空缺处代码为:

Big(int n):v(n), b(n){}      // 初始化列表,对v,b初始化
// 下面这种方法会编译报错,编译器不知道如何对封闭类Big的Base对象b进行初始化
//Big(Big & big){
// v = big.v;
// b = big.b;
//}

程序设计

魔兽世界之一:备战

描述

魔兽世界的西面是红魔军的司令部,东面是蓝魔军的司令部。两个司令部之间是依次排列的若干城市。

红司令部,City 1,City 2,……,City n,蓝司令部

两军的司令部都会制造武士。武士一共有 dragon 、ninja、iceman、lion、wolf 五种。每种武士都有编号、生命值、攻击力这三种属性。

双方的武士编号都是从1开始计算。红方制造出来的第n个武士,编号就是n。同样,蓝方制造出来的第n个武士,编号也是n。武士在刚降生的时候有一个生命值。在每个整点,双方的司令部中各有一个武士降生。

红方司令部按照iceman、lion、wolf、ninja、dragon的顺序循环制造武士。

蓝方司令部按照lion、dragon、ninja、iceman、wolf的顺序循环制造武士。

制造武士需要生命元。

制造一个初始生命值为m的武士,司令部中的生命元就要减少m个。

如果司令部中的生命元不足以制造某个按顺序应该制造的武士,那么司令部就试图制造下一个。如果所有武士都不能制造了,则司令部停止制造武士。

给定一个时间,和双方司令部的初始生命元数目,要求你将从0点0分开始到双方司令部停止制造武士为止的所有事件按顺序输出。

一共有两种事件,其对应的输出样例如下:

  1. 武士降生

输出样例: 004 blue lion 5 born with strength 5,2 lion in blue headquarter

表示在4点整,编号为5的蓝魔lion武士降生,它降生时生命值为5,降生后蓝魔司令部里共有2个lion武士。(为简单起见,不考虑单词的复数形式)注意,每制造出一个新的武士,都要输出此时司令部里共有多少个该种武士。

  1. 司令部停止制造武士

输出样例: 010 red headquarter stops making warriors

表示在10点整,红方司令部停止制造武士

输出事件时:

首先按时间顺序输出;

同一时间发生的事件,先输出红司令部的,再输出蓝司令部的。

输入

第一行是一个整数,代表测试数据组数。

每组测试数据共两行。

第一行:一个整数M。其含义为, 每个司令部一开始都有M个生命元( 1 <= M <= 10000)。

第二行:五个整数,依次是 dragon 、ninja、iceman、lion、wolf 的初始生命值。它们都大于0小于等于10000。

例:

1
20
3 4 5 6 7

输出

对每组测试数据,要求输出从0时0分开始,到双方司令部都停止制造武士为止的所有事件。

对每组测试数据,首先输出"Case:n" n是测试数据的编号,从1开始 。

接下来按恰当的顺序和格式输出所有事件。每个事件都以事件发生的时间开头,时间以小时为单位,有三位。

例:

Case:1
000 red iceman 1 born with strength 5,1 iceman in red headquarter
000 blue lion 1 born with strength 6,1 lion in blue headquarter
001 red lion 2 born with strength 6,1 lion in red headquarter
001 blue dragon 2 born with strength 3,1 dragon in blue headquarter
002 red wolf 3 born with strength 7,1 wolf in red headquarter
002 blue ninja 3 born with strength 4,1 ninja in blue headquarter
003 red headquarter stops making warriors
003 blue iceman 4 born with strength 5,1 iceman in blue headquarter
004 blue headquarter stops making warriors

代码

class Warrior{
public:
string color;
int HP;
Warrior(string c, int i){
color = c;
HP = HPs[i]
}
};

class Dragon: public Warrior{
public:
Dragon(string color):warrior(color, 0){}

};

class Ninja: public Warrior{
public:
Ninja(string color):warrior(color, 1){}

};

class Iceman: public Warrior{
public:
Iceman(string color):warrior(color, 2){}

};

class Lion: public Warrior{
public:
Lion(string color):warrior(color, 3){}

};

class Wolf: public Warrior{
public:
Wolf(string color):warrior(color, 4){}

};

class Headquart{
private:
int power;
string color;
int numberOfWarriors;
bool carryOn;
int each[5];
int current;
int selectType;
public:
Headquart(int p, string c){
color = c;
power = p;
carrayOn = true;
numberOfWarriors = 0;
current = 0;
for(int i=0; i<5; i++){
each[i] = 0;
}
}
bool getCarrayOn(){
return carrayOn;
}
bool create(string c){
cout<<c;
int HPOrder[5];
for(int i=0; i<5; i++){
if( c=="red" )
HPOrder[i] = HPs[redOrder[i]];
if (c=="blue")
HPOrder[i] = HPs[blueOrder[i]];
};
if(power < minHP){
cout<<"headquarter stops making warriors"<<endl;
carryOn = false;
return false;
}
while(power >= minHP){
if(power >= HPOrder[current])
break;
if(current == 4)
current = 0;
else
current++;
}

if(c == "red")
select = redOrder[current];
if(c == "blue")
select = blueOrder[current];

string warriors[5] = { " dragon "," ninja "," iceman "," lion "," wolf " };
switch(select){
case 0:
new Dragon(c);
each[0]++;
power = power - HPs[0];
// TODO 这里优化

}
return true;
}
}
Author: NYY
Link: http://yoursite.com/2018/08/22/c++/c-2-2/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.