OOP(Object Oriented Programming,面向对象编程)最重要的特性:
①抽象;
②封装和数据隐藏;
③多态;
④继承;
⑤代码的可重用性。
注:以上不太懂,
再ps:看完全部就懂了
过程性编程:
一般先考虑遵循的步骤,然后考虑如何处理这些数据(可能需要把这些数据放存储在一个文件之中,写入和读取)。
面向对象编程:
①考虑如何表示数据、如何处理数据;
②考虑面向的是什么对象,然后考虑这个对象的各个方面;
③包括基本数据单元,如何计算这些数据,更新和报告这些数据;
④用户和程序交互的方式:初始化、更新和报告——这一条就是用户接口。
总之,采用OOP方法时,首先要从用户角度来考虑对象——描述对象所需的数据以及描述用户与数据交互所需的操作。完成对接口的描述后,需要确定如何实现接口和数据存储。最后,使用新的设计方案创建出程序。
抽象:
处理复杂性的方法之一是简化和抽象。在计算中,为了根据信息和用户之间的接口来表示它,抽象是至关重要的。也就是说,把问题的本质特征抽象出来,并根据特征来描述解决方案。
抽象是通往 用户定义类型 的途径,(但还是不懂抽象是什么。是把复杂的事情具体到某几种行为么?)
在C++中,用户定义类型 指的是 实现抽象接口的类设计。
抽象大概就是指把一个复杂的东西,具体到某几个特征,比如说一个人的身高,体重,三围等。然后分析其体脂比之类。
类型:
指定基本类型完成了三项工作:
①决定数据对象需要的内存数量(比如char是1字节,int一般是4字节);
②决定如何解释内存中的位(long和float在内存中占用的位数相同,但将他们转换为数值的方法不同);
③决定可使用数据对象执行的操作或方法(例如string能加,但char字符串不行);
对于内置数据类型(如char、int之类)来说,有关操作信息被内置到编译器之中。
但C++中定义用户自定义的类型时(即类),必须自己提供这些信息,付出这些劳动换来了根据实际需要制定新数据类型的强大功能和灵活性。
类:
类是一种将抽象转换为用户定义类型的C++工具,它将数据表示和操纵数据的方法组合成一个整洁的包。
也就是说,数据、数据表示、数据操纵的综合体(且这些放在一起了),称为类。
类规范由两个部分组成:
①类声明:以数据成员的方式描述数据部分,以成员函数(被称为方法)的方式,描述公有接口。
②类方法定义:描述如何实现类成员函数。
接口:
接口是一个共享框架,供两个系统(计算机和用户、或计算机和其他计算机)交互时使用。
当我们使用电脑时,不是直接读写内存,而是用程序提供的接口来进行操纵。
程序接口将用户的意图,转换为存储在计算机中的具体信息。
对于类,说的是公共接口。
在类这里,public是使用类的程序,交互系统由类对象组成,而 接口由编写类的人提供的方法组成。
接口能让人编写与类对象交互的代码(例如 类名.类函数名),从而让程序能使用类对象。
开发一个类,并编写一个使用它的程序,需要完成多个步骤。这里将开发过程分成多个阶段,而不是一次性完成。
①通常,C++程序员将接口(类定义)放在头文件之中;
②并将实现(类方法的代码)放在源代码文件中。
为了识别类,有一种常见但不通用的约定——将类首字母大写。
如代码:
//1.h
#pragma once //函数只能被访问一次,类似#ifndef
#include<string>
class Stock //class是关键字,Stock是类名
{
private: //私有成员,属于被数据隐藏部分,无法直接访问
std::string company; //名字
long shuliang; //数量
double danjia; //单价
double zongjia; //总价
void set_zongjia() { zongjia = danjia*shuliang; } //私有函数,总价=单价*数量
public: //公共成员,属于接口,是使用类的程序,是类方法
void get(const std::string&x, long m, double pr); //类方法之一
void buy(long m, double pr); //又之一,以下略
void sell(long m, double pr);
void update(double pr);
void show();
};
注:
①类的关键字是class(注意,不能用typename替代),
②默认为private私有成员,私有成员不能被直接访问;
③注明了public的为接口,接口可以被访问和调用。
访问控制:
在类里面,使用类对象的程序都可以直接访问 公有部分 ,但只能通过 公有成员函数(或 友元函数),来访问对象的私有成员。
公有成员函数 是程序与对象的私有成员之间的桥梁,提供了对象和程序之间的接口。
防止程序直接访问数据(在类是私有对象的数据,因为公有对象的数据可以被访问),被称为 数据隐藏。
C++还提供了第三个访问关键字protected,(第13章)
这里附一个书上的图。
类设计尽可能将公有接口与实现细节分开。
公有接口表示设计的抽象组件。
将实现细节放在一起,并将他们与抽象分开,被称为封装。
①数据隐藏(将数据放在类的私有部分中)是一种封装;
②将实现的细节隐藏在私有部分中,就像Stock类对set_zongjia()所做的那样,也是一种封装。
③将函数定义和类声明放在不同的文件中,也是封装。
按照以上说法,封装的实质是,把数据或实现细节,让用户或其他,无法直接接触,只能通过展现出来的方法,来操纵数据。
OOP和C++
OOP实际上是一种编程风格,从某种程度来讲,可以用于任何一种语言之中(例如java往往要求了解OOP)。
但C++中提供了许多专门用来实现OOP方法的特性,可以使程序员更方便和直观来使用OOP方法编写程序,典型的就是类(私有成员无法直接访问);
数据隐藏:
不仅可以防止直接访问数据(防止数据被任意修改),还让开发者(类的用户)无需了解数据是如何被表示的,只需要知道各种类方法(公有成员函数)的功能即可(比如需要什么参数,返回什么值,显示什么内容之类)。
原则上是将实现细节从接口设计中分离出来。如果以后找到了更好的、实现数据表示或成员函数细节的方法,可以对这些细节进行修改,而无需修改程序接口,这使程序维护起来更容易。
——也就是说,某个类方法,传递固定参数和返回某种类型值。
①在这两个已知的情况下,用户可以任意使用这个类方法。
②如果觉得这个类方法的效率不够高,可以只优化这个类方法内部的代码(在不影响参数、返回值的情况下),无需修改使用这个类方法的代码。
控制对成员的访问:
首先,类分公有部分和私有部分。
公有部分可以访问,私有部分只能通过公有部分访问。
无论是公有还是私有,都可以有数据(例如int a这样)和函数(例如void abc(int a))这样。
由于为了隐藏数据(OOP的特性,封装和数据隐藏),所以数据通常放在私有部分(这样就不能被直接访问了),而函数(组成了类接口的成员)被放在公有部分;否则,我们就无法调用这些数据(因为私有部分只能通过公有部分的接口来访问,除非这个私有部分的函数被放在公有部分函数定义里)。
通常,程序员们用私有成员函数来处理不属于公有接口的实现细节。(这个不太理解)
类和结构的差别:
①类默认是私有成员(除非声明public);
②结构默认是公有成员(但无私有部分);
③结构通常只用来存放纯粹的数据对象;
④类可以存放函数。
实现类成员函数:
所谓实现类成员函数,就是给类的公有部分的函数原型,提供函数定义。
其与普通函数定义大部分相同,但除此之外,他具有两个特征:
①定义成员函数时,使用作用域解析符“::”来标识函数所属的类;
②类方法可以访问函数的private部分(即私有成员)。
以上面那个Stock类为例,其函数定义为:
void Stock::show()
{
using namespace std;
cout << "company: " << company << endl;
cout << "shuliang: " << shuliang << endl;
cout << "danjia: " << danjia << endl;
cout << "zongjia: " << zongjia << endl;
}
因为有作用域运算符,因此可以定义同名但非同一个类的函数。
例如:Stock::show() 和 Still:show()(假如有一个still类)
实现类成员函数的特点:可以使用、操纵类中的私有成员。
如代码:
#include<iostream>
#include<string>
#include<Windows.h> //Sleep()
#include<ctime>
class Player
{
std::string Name;
int Lv = 1; //等级,初始化时为1
int Exp = 0; //经验,初始化为0
double Hp_s; //生命值系数,非生命值
int Hp; //生命值
double Atk_s; //攻击系数,非攻击力
int Atk; //攻击力
void hp_() { Hp = int(Hp_s*Lv*Lv*Lv); }; //生命值是等级的三次方乘以系数,私有函数,只有接口可以访问和使用
void atk_() { Atk = int(Atk_s*Lv*Lv*Lv); }; //攻击力是等级的三次方乘以系数,同上
public:
void start(std::string name, double hps, double atks); //初始化,参数为名字、攻击系数,生命值系数,效果是初始化设定私有成员的数据
void show(); //显示属性,效果是输出私有成员的数据
void exp_up(int); //经验获取,效果是更新私有成员的数据
int atk(); //返回攻击力,效果是输出私有成员的数据
int hp(); //返回生命值,同上
};
void start(Player& p); //初始化新成员的函数
int main()
{
using namespace std;
Player p1;
start(p1); //调用初始化函数
cout << endl << endl;
system("pause");
int exp;
for (int i = 0;i < 5;i++)
{
exp = (clock() % 20)*(i + 1);
cout << "你击杀了第" << i + 1 << "个怪,获取经验" << exp;
p1.exp_up(exp);
system("pause");
}
cout << "这里有一个怪物,生命值有 100 点" << endl;
int dam = int(p1.atk() + p1.hp()*0.2); //伤害为攻击力+生命值*0.2
cout << "你对怪物造成了 " << dam << " 点伤害" << endl;
if (dam >= 100)cout << "怪物死亡!你胜利啦!" << endl;
else cout << "怪物还剩下 " << 100 - dam << " 点生命值" << endl;
cout << "再次显示一次你的属性~~~" << endl;
p1.show();
system("pause");
return 0;
}
void start(Player& p) //一个类对象的引用作为参数,初始化。注意,这个不是类方法,没有作用域解析运算符
{
using namespace std;
string name;
cout << "创建新人物...\n请输入人物姓名:";
getline(cin, name);
cout << "现在生成新人物的攻击力系数";
for (int i = 0;i < 5;i++)
{
Sleep(500);
cout << ".";
}
double a = (clock() % 100 + 1.0) / 100 + 1;
system("pause");
cout << "新人物的攻击力系数为:" << a << endl;
cout << "现在生成新人物的生命值系数";
for (int i = 0;i < 5;i++)
{
Sleep(500);
cout << ".";
}
double b = (clock() % 60 + 1.0) / 30 + 3;
system("pause");
cout << "新人物的生命值系数为:" << b << endl;
p.start(name, b, a);
p.show();
}
void Player::start(std::string name, double hps, double atks) //初始化属性
{
Name = name;
Hp_s = hps;
Atk_s = atks;
hp_();
atk_();
}
void Player::show() //输出属性
{
using namespace std;
cout << "****** " << Name << " 的属性表******" << endl;
cout << "等级: " << Lv << endl;
cout << "经验值: " << (((Exp - Lv*Lv*Lv) < 0) ? 0 : (Exp - Lv*Lv*Lv)) << endl; //升级总经验是等级的三次方,这里显示当前级别的经验
cout << "生命值: " << Hp << endl;
cout << "攻击力: " << Atk << endl;
cout << "******************************" << endl << endl;
}
void Player::exp_up(int a) //经验获取
{
Exp += a;
int m = Lv;
int i;
if (Exp < 8)
{
Lv = 1;
return;
}
for (i = 1;i < 100;i++) //如果经验小于某1级的三次方,则说明还没到这一级
{
if (Exp < (i + 1)*(i + 1)*(i + 1))break;
}
if (i > Lv) Lv = i;
if (Lv > m) //如果升级,属性更新
{
std::cout << "你升级了!新的级别为:" << Lv << std::endl;
hp_();
atk_();
show();
}
}
inline int Player::atk() //返回攻击力
{
return Atk;
}
inline int Player::hp() //返回生命值
{
return Hp;
}
输出:
创建新人物...
请输入人物姓名:勇武 的 王
现在生成新人物的攻击力系数.....请按任意键继续. . .
新人物的攻击力系数为:1.91
现在生成新人物的生命值系数.....请按任意键继续. . .
新人物的生命值系数为:5
****** 勇武 的 王 的属性表******
等级: 1
经验值: 0
生命值: 5
攻击力: 1
******************************
请按任意键继续. . .
你击杀了第1个怪,获取经验9你升级了!新的级别为:2
****** 勇武 的 王 的属性表******
等级: 2
经验值: 1
生命值: 40
攻击力: 15
******************************
请按任意键继续. . .
你击杀了第2个怪,获取经验32你升级了!新的级别为:3
****** 勇武 的 王 的属性表******
等级: 3
经验值: 14
生命值: 135
攻击力: 51
******************************
请按任意键继续. . .
你击杀了第3个怪,获取经验57你升级了!新的级别为:4
****** 勇武 的 王 的属性表******
等级: 4
经验值: 34
生命值: 320
攻击力: 122
******************************
请按任意键继续. . .
你击杀了第4个怪,获取经验12请按任意键继续. . .
你击杀了第5个怪,获取经验95你升级了!新的级别为:5
****** 勇武 的 王 的属性表******
等级: 5
经验值: 80
生命值: 625
攻击力: 238
******************************
请按任意键继续. . .
这里有一个怪物,生命值有 100 点
你对怪物造成了 363 点伤害
怪物死亡!你胜利啦!
再次显示一次你的属性~~~
****** 勇武 的 王 的属性表******
等级: 5
经验值: 80
生命值: 625
攻击力: 238
******************************
请按任意键继续. . .
总结:
①所谓私有成员函数,就是指,只有编写这个类的人,能在类中使用;而使用类的人,是无法使用的。一般是用在公有成员函数之中;
②若要修订代码,私有成员函数在使用起来修改容易(不需要每次都去公有成员函数内修改函数代码);
③私有成员函数还可以减少工作量。例如void hp_() { Hp = int(Hp_s*Lv*Lv*Lv); }; 可以自动根据系数计算当前最大生命值。
注意,这里的void hp_()和void atk_()可以合并在一起,但代码里并没有合并。(我写的时候没注意,事实上这两个函数的作用是一个类型,都是更新数据,只不过更新的数据不同)
④函数的内联:其定义位于类声明中的函数(如void hp_() ),都将自动成为内联函数。而像void Player::exp_up(int a) 这样的函数,就不是内联函数。
如果没有定义在类声明中,也可以在外部定义时,加上关键字inline来声明这个是内联函数,如:inline int Player::atk()
注意,内联函数一般要求是短小的函数,例如void hp_()就符合,而void Player::exp_up(int a)就有点偏长了。
⑤类方法所调用的数据成员:
首先,在方法里,调用的是该类的数据成员,不是其他类的数据成员(例如这里是Player这个类的);
其次,调用的是同一个对象内的数据成员。例如声明2个类对象,对第一个类对象使用类方法赋值,第一个对象的私有成员的值被改变了,然而第二个则依然是初始值;
⑥所创建的每一个新对象,都有其自己的存储空间,用于存储其内部变量和类成员;
但同一个类的所有对象共享一组类方法,即每种方法只有一个副本。例如,假设p1和p2都是Player对象,则p1.Hp和p2.Hp各占用一个内存块,而p1.show()和p2.show()将执行同一个方法,也就是说,他们将执行同一个代码块,只是将这些代码用于不同的数据。
在OOP中,调用成员函数,被称为发送消息,因此,将同样的消息发送给两个不同的对象将调用同一个方法,但该方法被用于两个不同的对象。如教程中附带的图:
类的使用:
①C++的目标是使得使用类和使用基本的内置类型(如int之类)尽可能相同;
②创建类:声明类变量(如Player p1;)、
或使用new运算符来为类对象分配空间(如Player *p1 = new Player;);
③函数相关:可以将类作为参数、也可以作为返回值(如:Player start(Player& p));
④类的赋值:可以将一个对象赋值给另一个,如:
Player *p1 = new Player;
Player p2 = *p1;
⑤C++提供了一些工具,可用于初始化对象、让cin和cout识别对象,甚至在相似的类对象之间进行自动类型转换。(但目前还没接触到)
⑥使用一个新类型,最关键是要了解成员函数的功能,而不必考虑其实现细节。
客户/服务器模型:
OOP程序员常依照客户/服务器模型来讨论程序设计。在这个概念之中,客户是使用类的程序(即客户调用类成员函数,或者获知其返回的内容)。类声明(包括类方法)构成了服务器,他是程序可以使用的资源。客户只能通过以公有方式定义的接口使用服务器,这意味着客户(这里指程序员)唯一的责任是了解接口。服务器(服务器的设计人员)的责任是确保服务器的该接口可靠,且稳定准确的执行。
服务器人员:只能修改类设计的实现细节,不能修改接口。
程序员:只能使用接口。
这样的话,二者的行为将不会互相干扰,也不会带来意外的影响(例如修改服务器的实现细节,优化代码之类,不会影响使用该接口的程序员)。
修改实现:
ostream类包含了一些可用于控制格式的成员函数。(具体要在17章进行讨论)
例如:
std::cout.setf (std::ios_base::fixed, std::ios_base::floatfield);
这设置了cout对象的一个标记,命令cout使用定点计数法。
而在上面的前提下,使用cout.precision(2); 将显示两位小数。
但存在一个问题,那就是这些格式是从使用后一直到最后都有效的。因此,在使用前应储存当前状态,在修改实现后,再恢复。
例如:
ios_base::fmtflags 变量名(例如a) = cout.setf(ios_base::fixed, ios_base::floatfield); //存储该类型格式
std::streamsize 变量名(例如b) = cout.precision(3); //存储
cout.setf(a); //恢复到存储时
cout.precision(b); //恢复到存储时
这种方法可以用在实现之中。