内容简单介绍
1、第二部分第一课:面向对象初探。string的惊天内幕
2、第二部分第二课预告:掀起了"类"的盖头来(一)
面向对象初探,string的惊天内幕
上一课《【C++探索之旅】第一部分第十二课:指针一出。谁与争锋》中。大家辛苦了。
诚然,指针是不easy啃的硬骨头。只是,假以时日,小火慢炖,能够成为一碗上好的骨头汤,对你的C++水平那可是大补。
好了,口水擦一擦,我们正式进入C++探索之旅的第二部分啦,激动不?刚擦完的哈喇子可不要继续流啊。
这一部分的课程称为:C++之面向对象
由于我们要探索"面向对象编程"(OOP: Object-Oriented Programming) 。
这是一种不同于以往的编程模式。
这样的方法不会马上使你的程序发生革命性的改变。并且刚開始接触的时候你甚至会认为这样的模式没什么用。
眼下为止我们的编程模式一直是:面向过程。也就是:我想要实现什么功能,就写什么函数。声明相应变量。等等。比方我要显示一段信息到屏幕上。我就创建一个函数:
void showMessage(string message);
可是,相信我:慢慢地。你会发现面向对象的编程模式比面向过程更符合人类的思维。你将能更好地组织你的代码。
注意:这一课接下来提到的"对象"。事实上有时是指对象。有时是指类。由于要到下一课我们才学习"类"的概念。临时先不细分。希望通过这一课让大家对面向对象有大致了解。
对象...有啥用啊?
听到面向对象编程,你最好奇的词应该是"对象"(英语是object)吧。
你会想:这不会是又一种神奇的设计吧?难道是一个宿醉后的疯狂程序猿想出来的东西?
事实上不然。
我们身边充满着对象:你的车是一个对象,你的电脑是一个对象。你的手机是一个对象,等等。
事实上,我们所知道的全部东西都能够被看作对象。面向对象编程。就是在代码中操作这些被称为"对象"的元素。
以下列举几种平时我们在编程中常见的对象:
-
一个窗口:比如QQ的一个聊天对话框。绘图软件的主界面。
-
一个button:比方安装软件时那些"下一步"的button。
-
游戏里的一个人物。
-
一首歌。一个视频
说了这么多。对象究竟是啥呀?是一种新的变量吗,还是一种新的函数类型?
不不不,都不是。
对象是一种新的编程元素!
或者说得更详细一些,对象里面混合了变量和函数。
可不要被这句话吓跑了。我们一点点来探索。
想象一下... 一个对象。
少年,不是让你想象结婚对象好嘛~
还是来点实际的图片好了,能帮助我们理解。
设想:
有一天,一个程序猿决定要写一个图形界面的程序,这个程序能够在屏幕上显示窗口,调整窗口大小。移动窗口,删除窗口。在窗口里能够画各种图形。等等。
要实现这些功能,代码是比較复杂的:须要不少函数。这些函数之间互相调用。并且还须要非常多变量。比如窗口位置(x坐标和y坐标),窗口的宽,高,等等。
这个程序猿花了不少的时间来写这个程序。
程序有点复杂。可是他完毕了。终于。他实现的代码包括了非常多的函数和变量。
当我们第一次看到他的程序时,就好像看一个化学家的一个实验环境一样,啥也看不懂。
例如以下图:
只是,这个程序猿对自己的程序还是非常惬意的。他甚至要把程序公布到网上,让大家能够用他的程序来创建窗口,直接拿来用就能够了,而不须要从零開始写代码。
只是,有一个问题。假设你不是化学专家,你可能得花不少时间搞懂这一堆东西究竟是怎么运作的:哪一个函数最先被调用呢?为了改变窗口的大小,哪个变量要被传递到哪个函数里呢?总之。我们非常害怕把整个实验环境给弄炸了。
在接到一些用户的建议和抱怨后,这个程序猿决定替用户着想。
他又一次设计了他的代码。从面向过程的模式改为面向对象的模式。
这就好比他把全部和这个化学实验有关的东西都放到一个慷慨盒子里。
这个慷慨盒子就是我们所说的"对象"。例如以下图:
在上图中,慷慨盒子的一部分被设为透明,是有益的,使你能够看到里面的景象。
是的,我们的化学实验的全部设备都在慷慨盒子里。可是。实际当中。慷慨盒子是全然不透明的,用户从外面看不到里面究竟有什么。
例如以下图:
这个慷慨盒子里存放着函数和变量(那些化学仪器,试管,烧杯,等等),但这些元素对于用户却是不可见的。
这样,用户看到的就不再是成堆的试管,烧杯,等等让其抓狂的东西了。在慷慨盒子外面,我们呈现给用户的就仅仅有一些button:一个button用于"打开窗口"。一个button用于"改变窗口大小"。一个button用于"关闭窗口"。等等。用户全然不须要理解慷慨盒子里面的运作原理。
因此。以面向对象的模式来编程,就是:
程序猿编写代码(可能非常复杂)。然后将这些复杂的代码都装到一个慷慨盒子(对象)里。用户从外面不能看到里面的实现细节。
因此,对于使用这个对象的用户来说,操作起来就easy多了:仅仅需按下相应的button。不须要精通化学也能够使用整个实验环境提供的功能了。
当然了。上面的比喻仅仅是一个大致概念。
这一课我们临时还不学习广义上怎么创建对象。我们反其道而行之,先来使用一个对象。事实上这个对象我们之前已经接触过好多次了,就是string。
好几课之前。我们已经说过。string这样的类型和普通的int,bool,float。double等不一样。后面的这些是C++的基础数据类型,用于存放简单的数据。
但string却不是这样。事实上它是一个对象。string这个类型背后隐藏了非常多细节。
揭开string背后的神奇内幕
别看string宝宝非常乖的,白天好像非常正经的大公司老板Bruce Wayne(布鲁斯·韦恩),事实上他是蝙蝠侠。背后秘密可多了。
多亏了面向对象的编程模式,我们在第一部分课程里就已经開始使用string了。当时我们还不知道string背后的秘密。以下我们就来揭开string神奇的面纱。看看里面的机制大概是什么样。
对于电脑来说,字符事实上并不存在
为什么说string内部的机制事实上比較复杂呢?
首先,我们之前把string称为"字符串",但事实上电脑并不认识string当中的那些字符。
还记得吗?我们说电脑是一台仅仅知道计算的大计算器而已(英语computer就是"计算机"的意思),它仅仅认识数字。
可是,假设电脑仅仅知道操作数字,那么它又是怎样在屏幕上显示那么多字符的呢?比如你如今就用屏幕在看小编写的文章。
信息技术的先驱们早就想到了好办法。或许你听说过ASCII(发音是[aski])表。
ASCII是American Standard Code for Information Interchange的缩写,表示"美国信息交换标准码"。
ASCII表就是为数字和字符的相应转换服务的一个表格。以下给出ASCII表的一部分:
数字 |
字符 |
数字 |
字符 |
---|---|---|---|
64 |
@ |
96 |
‘ |
65 |
A |
97 |
a |
66 |
B |
98 |
b |
67 |
C |
99 |
c |
68 |
D |
100 |
d |
69 |
E |
101 |
e |
70 |
F |
102 |
f |
71 |
G |
103 |
g |
72 |
H |
104 |
h |
73 |
I |
105 |
i |
74 |
J |
106 |
j |
75 |
K |
107 |
k |
76 |
L |
108 |
l |
77 |
M |
109 |
m |
如我们所见。大写字母A相应了65,小写字母a相应了97。
全部的英文字母都包括在这个表中。
那么,就是说:每次电脑仅仅要看到数字65。就会把它当作大写字母A来处理咯?
不是的。仅仅有我们要求电脑翻译一个数字到字符的时候,它才会照做(果然非常呆萌)。实际当中。电脑是依据我们声明的变量的类型来决定怎样"看待"每个存储在变量中的数字的。大致规则例如以下:
-
比如,我们用int型的变量来储存数字65。那么电脑就把它当成一个数字来看待。
-
相反地。假如我们用char型的变量来储存数字65,那么电脑就把它当成一个字符来看待。电脑会说:嗯。这是大写字母A。事实上,char是英语character的缩写,表示"字符"。专门用来储存字符的。
因此,char类型的变量储存数字,可是这个数字会被"解释"成字符。
只是,一个char型变量一次仅仅能储存一个字符,那么我们怎么储存一个句子呢?我们接着学习。
字符串就相似字符数组
既然char仅仅能储存一个字符(由于char的大小是一个字节。也就是8个bit位,正好是一个英文字符的大小,中文字符占2个字节),因此。程序猿们就想到了创建char型数组。
我们已经学过数组了,也就是内存中相邻的相同类型的变量的集合。
因此,用字符数组来存放多个字符的集合(比如:一句话)是非常棒的选择,我们通常也将字符数组称为"字符串",由于它是一串字符么。
因此,我们仅仅要这样声明一个char型数组:
char text[100];
text这个char型数组里能够储存100个字符。
只是这个数组是静态的,大小不可变。
假设你要创建大小能够改变的"动态数组",就能够用vector这样的类型啦。
比如:
vector<char> text;
理论上。我们能够使用静态char型数组或动态char型数组来存放一串字符。
只是这样非常不方便,因此C++的设计者决定将有关字符串的操作都封装到一个对象里。那就是string。
创建并使用string对象
经过刚才那一节,我们了解到:操作字符串事实上并不简单啊。
须要创建一个char型数组,数组中的每个元素是一个字符。并且,我们须要创建足够大的数组。以便装下我们要存放的字符串。
总之,有非常多方面要考虑。
这时,面向对象编程就能够派上用场了。还记得我们上面的图片中那个慷慨盒子吗?string就是这样一个"慷慨盒子"。
创建一个string对象
创建一个对象和我们之前创建一个普通的变量(比方int型变量)是相似的。比如:
#include <iostream> #include <string> // 必须引入string头文件,由于string的原型就定义在里面 using namespace std; int main() {??? string mString; //创建一个string类型的对象mString??? ? return 0; }
上面的程序不复杂,我们主要关注那句创建string对象的指令:
string mString;
故此。创建对象的方法和创建一个变量是一样的咯?
并不尽然,创建一个对象有几种方式。我们方才演示的仅仅是当中最简单的一种。
只是。这样的方式确实和创建一个普通的变量没什么大差别。
这样的话,我们怎样区分对象和普通变量呢?
为了区分变量和对象,能够从命名上来看,我们有规则:
-
变量的类型以小写字母開始。比如普通的变量类型int
-
对象的类型以大写字母開始,比如:Car
我知道你肯定会说:你看string不就是以小写字母開始的吗?它也是对象的类型啊。
我承认,这是一个例外,上面的规则并非强加给我们的。非常显然。实现string的程序猿并没有遵守这个规则。
只是。大部分的情况下,对象的类型是以大写字母開始的。
当声明字符串时对其初始化
为了在创建对象时就对其初始化,有多种方式,我们来看看最简单的一种:
int main() {??? string mString("Hello !");??? //创建一个string类型的对象mString,并初始化它的值 ??? ??? return 0; }
这样的方式的初始化,和C++的基础变量类型,比如int,double等,是一样的。
除了上面的这样的方式,我们也能够用以下方式来初始化我们的string对象:
string mString = "Hello !";
我们既然创建了一个string对象。并为其赋值"Hello !",我们能够来打印这个对象的内容。
int main() {??? string mString("Hello !"); ??? cout << mString << endl;??? ??? //显示string的内容,就好像它是一个字符串???? ??? ??? return 0; }
执行。显示:
Hello !
在字符串初始化后再改变其值
如今我们已经创建了我们的字符串。并且对其赋了初值。我们还能够再改变其值。
int main() { ??? string mString("Hello !"); ??? cout << mString << endl; ?? ? ??? mString = "How are you ?"; ??? cout << mString << endl; ? ??? return 0; }
为了改变string变量的值。我们须要用=号(赋值符号)。
上面演示的方法,事实上和之前我们使用普通变量类型时并没太大差别。
我在这里仅仅是要向你展示面向对象的奇妙之处。
你,也就是用户,刚才就好像按下了一个button,这个button的作用是发出一个命令:我要将字符串的内容从"Hello !"改写为"How are you ?"。
尽管你的指令非常简短,但string对象内部的那些函数接到指令后却開始忙乎起来了(让我想到了小黄人),它们依次做了以下几件事:
-
首先检測当前存放"Hello !"的char型数组是否足够容纳"How are you ?
"这个字符串。
-
答案是否定的。
因此它们创建一个新的char型数组。大小足以容下"How are you ?"这个新的字符串。
-
一旦新的char型数组创建完毕。就把旧的数组销毁。毕竟没用了。
-
然后把"How are you ?"这个字符串的内容复制到新的char型数组里。
看到吧。这就是面向对象编程的一个强大之处:我们全然不知道string对象里面原来发生了这么多事。
字符串的串联
假如我们想要将两个字符串的内容前后相接。合并为一个字符串。理论上说来,这是不easy实现的。可是string对象中早就设计好这样的机制啦:
int main() { ??? string mString1("Hello !"); ??? string mString2("How are you ?"); ??? string mString3; ? ??? mString3 = mString1 + " " + mString2; ??? cout << mString3 << endl; ? ??? return 0; }
非常easy不是吗?仅仅要用加号将两个字符串连起来就好了,至于string对象内部做了多少复杂的操作,我们"VIP用户"根本不在乎,有权就是这么"任性"~
字符串的比較
还要继续学吗?非常不错。就要这样的精神。
我们能够用==和!=符号来比較字符串,分别表示相等和不相等。
int main() { ??? string mString1("Hello !"); ??? string mString2("How are you ?"); ? ??? if (mString1 == mString2) // 显然两个字符串不相等。括号里条件语句为假 ??? { ??????? cout << "两个字符串相等." << endl; ??? } ??? else ??? { ??????? cout << "两个字符串不相等." << endl; ??? } ? ??? return 0; }
事实上。在string对象内部,这个比較的过程是两个字符串的一个字符接一个字符来比較的(借助一个循环)。
可是我们不须要在意这些细节,就是这么潇洒。
string的一些方法
string对象的功能可不至于此。当中还有非常多实用的方法,我们能够来见识几个经常使用的。
属性和方法
我们之前说过。一个对象中包括了变量和函数。
只是,我们既然要学习面向对象编程了,就要用更专业的术语。
在面向对象的领域,对象中的变量被称为"属性",而当中的函数被称为"方法"。仅仅是称呼不同而已。
你能够把对象提供的每个方法想象成我们之前那个慷慨盒子外面对用户可见的那些button。
属性和方法又被称为"成员变量"和"成员函数"。
为了调用一个对象中的方法。我们用一种你已经见识过的方式:
object.method()
我们在对象和其成员函数之间用一个点来连接。
这意味着:对于此对象,我调用它的这种方法。
理论上,我们也能够用相同的方式来訪问成员变量(属性)。然而,在面向对象编程中,我们尽量避免用户直接訪问我们的私人成员变量。
关于这个,有非常多知识点,我们以后的课程会讲到。
调用成员函数和成员变量的方式也是对象独有的,普通的变量类型并不能这样做。
这也是除了名字之外差别对象和变量的好方法。
我们来看几个string提供的成员函数吧。
size方法
size在英语中是"大小。尺寸"的意思。
因此。size方法用于获取string对象的大小,也就是里面包括的字符数目。
size方法的使用方法非常easy。没有參数:
int main() { ??? string mString("Hello !"); ??? cout << "字符串的长度是 " << mString.size(); ? ??? return 0; }
执行,显示:
字符串的长度是 7
erase方法
erase方法用于删去字符串的内容,由于erase在英语中是"删除。清除"的意思。
int main() {??? string mString("Hello !"); ????cout << "字符串的内容是 : " << mString << endl; ???? ??? mString.erase(); ??? ??? cout << "调用erase方法后, 字符串的内容是 : " << mString << endl;???? ??? return 0; }
执行,显示:
字符串的内容是 : Hello !
调用erase方法后, 字符串的内容是 :
正如我们预期的,调用erase方法后。字符串的内容被清空了。
事实上。erase方法的效果和
?
mString = "";
是一样的。
substr方法
substr是sub和string的缩合,sub表示"子的。副的"。string就是"字符串"啦,因此,顾名思义,substr就是取得一个字符串的一部分。
substr方法的原型例如以下:
string substr( size_type index, size_type num = npos );
能够看到。substr的返回值也是string类型。它接收两个參数,一个是index。表示从字符串的第几个字符開始截取。第二个參数是num。表示截取多少个字符。
事实上,更确切地说,substr方法接收两个參数,第一个參数是必须的。第二个參数是非必须的。就是说。第一个參数必须要提供。第二个參数假如没有提供,那么num就会取默认值。默认值是npos。
看到上面的原型中有 num = npos了吗?临时还不须要深究,这个事实上是C++的一种特性,术语称为"默认參数"。就是说,假如函数被调用时。这个參数没有被赋予值。那么这个參数会取等号后面的默认值(相似"备胎"的概念)。
npos这个值表示:取余下的全部字符,直到最后一个。
举个样例吧:
int main() { ??? string mString("Hello !"); ??? cout << mString.substr(3) << endl; ? ??? return 0; }
执行,显示:
lo !
由于我们仅仅给了substr一个參数,就是3,所以substr的第一个參数index就被赋值为3。而第二个參数num没有被赋值。就会取默认值。
因此。表示从mString的第四个字符開始截取,一直截取到最后。
再来试试给第二个參数num赋值的情况。
int main() {??? string mString("Surprise !");??? cout << mString.substr(2, 4) << endl;???? return 0; }
执行,显示:
rpri
也非常好理解。我们给index和num分别赋值2和4。那么就是从mString的第3个字符開始截取,截取4个字符。
我们之前在数组的那一课已经学习过了,我们能够对string类型的对象用相似数组的方式来訪问当中某一个字符,使用中括号[]。比如:
string mString("Surprise!"); cout << mString[3] << endl;? //显示第4个字符,就是 ‘p‘
好了,这一课就结束了。
这一课的主要目的是不想让你对面向对象编程感到恐惧。经过此课的学习,你是否已经对面向对象有点概念了呢?事实上,在这之前。你就已经用过对象了,string和vector事实上都是。
我希望你已经做好准备来创建属于你自己的对象了。都说程序猿没有对象。那么我们自己new一个呗~
这是下一课開始的目标。
总结
-
面向对象编程是设计代码的一种方法。操作的是被称为"对象"的元素。
-
对象里面的实现细节能够非常复杂。可是使用对象的人却并不须要关心这些细节,仅仅须要知道怎样使用就能够了。这是面向对象编程的一大优势。
-
一个对象是由一些属性和方法组成的,也就是变量和函数。
-
我们能够调用对象的方法来实现各种需求。
-
在内存中,字符串的操作事实上是非常复杂的。
为了替我们这些C++的使用者着想,C++的设计者为我们设计了静止的对象:string。
我们仅仅须要用string来创建字符串实例,操作这些字符串,而并不须要关心内存中究竟发生了什么。
第二部分第二课预告
今天的课就到这里。一起加油吧!
下一课我们学习:掀起了"类"的盖头来(一)