1.1学会面向对象的编程思想
面向对象的英文缩写是OO,它是一种设计思想。
面向对象有3大特点:封装、继承和多态。
1.封装
封装有两个作用,一个是将不同的小对象封装成一个大对象;另外一个是把一部分内部属性和功能对外界屏蔽。在设计时可以先对小对象进行设计,然后对小对象之间相互联系确定各自大小等方面的属性。
2.继承
继承是和类密切相关的概念。继承性是子类自动共享父类数据结构和方法的机制,这是类之间的一种关系。在定义和实现一个类的时候,可以在一个已经存在的类的基础上进行,把这个已经存在的类所定义的内容作为自己的内容,并加入若干新的内容。
在类层次中,子类只继承一个父亲的数据结构和方法,称为单重继承,子类继承了多个父类的数据结构和方法,称为多重继承。
在软件开发中,类的继承使所建立的软件具有开放性、可扩充性,这是信息组织与分类的行之有效的方法,它简化了对象、类的创建工作量,增加了代码的可重性。
继承性是面向对象程序设计语言不同于其他语言的最重要的特点,是其他语言所没有的。采用继承性,使公共的特性能够共享,提高软件的重用性。
3.多态性
多态性是指相同的行为可作用于多种类型的对象上并获得不同的结果。不同的对象,收到同一个消息可以产生不同的结果,这种现象就称为多态性。多态性允许每个对象以适合自身的方式去响应共同的消息。
1.2面向对象与面向过程编程
面向过程编程的主要思想是先做什么后做什么,在一个过程中实现特定功能。一个大的实现过程还可以分成各个模块,各个模块可以按功能进行划分,然后组合在一起实现特定功能。在面向编程中,程序模块可以是一个函数,也可以是整个源文件。
面向对象编程主要以数据为中心,传统的面向过程的功能分解法属于结构化分析方法。分析者将对象系统的现实世界看成一个大的处理系统,然后将其分解为若干个子处理过程,解决系统的总体控制问题。在分析过程中,用数据描述各子处理过程之间的联系,整理各子处理额过程的执行顺序。
面向过程编程的一般流程是:
现实世界->面向过程建模(流程图、变量、函数)->面向过程语言->执行求解
面向过程编程的稳定性、可修改性和可重用性都比较差。
1.软件重用性差
重用性是指同一事件不经过修改或稍加修改就可多次使用的性质。软件重用性是软件工程追求的目标之一。处理不同的过程都有不同的结构,当过程改变时,结构也需要改变,前期开发的代码无法得到充分的利用。
2.软件可维护性差
软件工程枪带哦软件的可维护性,强调文档资料的重要性,规定最终的软件产品应该由完整、一致的配置成分组成。在软件开发过程中,始终强调软件的可读性、可修改性和可测试性事软件的重要质量指标。面向过程编程由于软件的重用性差,造成维护时费用和成本也很高,而且大量修改代码存在许多未知的漏洞。
3.开发软件不能满足用户需要
大型软件系统一般设计各种不同领域的知识,面向过程编程往往描述软件的各个最低层的、针对不同领域设计不同的结构及处理机制,当用户需求发生变化时,就要修改最低层的结构。当处理用户需求变化较大时,面向过程编程将无法修改,可能导致软件的重新开发。
1.2.2面向对象编程
面向对象编程有费解的数据结构、复杂的组合逻辑、详细的过程和数据之间的关系、高深的算法,面向过程开发的程序可能描述成算法加数据结构。面向过程考法是分析过程与数据之间的边界在哪里,进而解决问题。面向对象则是从另一个角度思考,将编程思维设计成符合人的思维逻辑。
面向对象程序设计者的任务包括两个方面:意识设计所需的各种类和对象,即决定把哪些数据和操作封装在一起;二是考虑怎样向有关对象发送消息,以完成所需的任务,这是它如同一个总调度,不断地向各个对象发出消息,让这些对象活动起来(或者说激活这些对象),完成自己职责范围内的工作。
各个对象的操作完成了,整体任务也就完成了。显然,对一个大型任务来说,面向对象程序设计方法是十分有效的,它能大大降低程序设计人员的工作难度,减少出错几率。
面向对象开发的程序可以描述成“对象+消息”。面向对象编程一般过程:
现实世界->面向对象建模(类图,对象,方法)->面向对象语言->执行求解
1.2.3 面向对象特点
面向对象技术充分体现分解、抽象、模块化、信息隐藏等思想,有效提高软件生产率、编程软件开发时间、提高软件质量,是控制复杂度的有效途径。
面向对象不仅适合普通人员,也适合经理人员。降低维护开销的技术是可以释放管理者的资源,将其投入到待处理的应用中。在经理们看来,面向对象不是纯技术的,它既能给企业的组织也能给经理的工作带来变化。
当一个企业采纳了面向对象,其组织将发生变化。类的重用重要类库和类库管理人员,每个程序员都要加入到两个组中的一个:一个是设计和编写新类组;另一个是应用类创建新应用程序组。面向对象不太强调编程,需求分析相对地将变得更加重要。
面向对象编程主要有代码容易修改、代码复用性高、满足用户需求三个特点。
1.代码容易修改
面向对象编程的代码都是封装在类里面,如果类的某个属性发生变化,只需修改类中成员函数的实现即可,其他的程序函数不发生改变。如果类中属性变化较大,则使用继承的方法重新派生新类。
2.代码复用性高
面向对象编程的类都是具有特定功能的封装,需要使用类中特定的功能,只需要声明该类并调用其成员函数即可。如果需要的功能在不同类,还可以进行多重继承,将不同类的成员封装到一个类中。功能的实现可以像积木一样随意组合,大大提高了代码的复用性。
3.满足用户需求
由于面向对象编程的代码复用性高,用户的要求发生变化时,只需修改发生变化的类。如果用户的要求变化较大时,就对类进行重新组装,将变化大的类重新开发,功能没有发生变化的类可以直接拿来使用。面向对象编程可以及时地想要用户需求的变化。
1.3类和对象
面向对象中的对象需要通过定义类来声明,对象依次是一种形象的说法,在编写代码过程中则是通过定义一个类来实现。
C++类不同于汉语中的类、分类、类型,它是一个特殊的概念,可以是对统一类型事物进行抽象处理,也可以是一个层次结构中的不同层次结点。例如:将客观世界看成一个Object类,动物是客观世界中的一小部分,定义为Animal类,狗是一种哺乳动物,是动物的一种,定义为Dog类,鱼也是一种动物,定义为Fish类,则类的层次关系是:
类是一个新的数据类型,它和结构体有些相似,是由不同数据类型组合成的集合体,但类要比结构体增加了操作数据的行为,这个行为就是函数。
1.3.1类的声明和定义
前面已经对类的概念进行说明,可以看出类是用户自己指定的类型。如果程序中要用到类这种类型,就必须自己根据需要进行说明,或者使用别人设计好的类。下面来看一下如何设计一个类。
class 类名标识符{[public:][数据成员声明][成员函数声明][private:][数据成员声明][成员函数声明][protected:][数据成员声明][成员函数声明]}; //注意这里需要加分号;
类的声明格式的说明如下:
- class是定义类结构体的关键字,大括号内被称为类体或类空间。
- 类名标识符指定的就是类名,类名就是一个新的数据类型,通过类名可以声明对象。
- 类的成员有函数和数据两个类型。
- 大括号内是定义和声明类成员的地方,关键字public、private、protected是类成员访问的修饰符。
类中的数据成员的类型可以是任意的,包括整型、浮点型、字符型、数组、指针和引用等,也可以是对象。另一个类的对象可以作为该类的成员,但自身类的对象不可以作为该类的成员,而自身类的指针或引用则可以作为该类的成员。
例如,给出一个员工信息类声明:
class CPerson{/*数据成员*/int m_iIndex;//声明数据成员char m_cName[25];//声明数据成员short m_shAge;//声明数据成员double m_dSalary;//声明数据成员/*成员函数*/short getAge();//声明成员函数int setAge(short sAge);//声明成员函数int getIndex();//声明成员函数int setIndex(int iIndex);//声明成员函数char* getName();//声明成员函数int steName(char cName);//声明成员函数double getSalary();//声明成员函数int setSalary(double m_dSalary);//声明成员函数};
在代码中,class关键字是用来定义类这种类型的,Cperson是定义的原根信息类的名称。在大括号中包含了4个数据成员分别表示CPerson类的属性,包含了8个成员函数表示Cperson类的行为。
1.3.2头文件与源文件
在一个源文件中使用#include指令,可以将头文件的全部内容包含进来,也就是将另外的文件包含进本文件之中。#include指令使编译程序将另一个源文件嵌入带有#include的源文件,被读入的源文件必须使用双引号或尖括号括起来。
#include"stdio.h"
#include<stdio.h>
上面的给出双引号和尖括号的形式,这两者的区别:用尖括号时,系统到存放C++库函数头文件所在的目录中寻找要包含的文件,称为标准形式;用双引号时,系统先在用户当前目录寻找要包含的文件,若找不到,再到存放C++库函数头文件所在的目录中寻找要包含的文件。通常情况下,如果为了调用库函数用#include命令来包含相关的头文件,则用尖括号,可以节省查找的时间。如果要包含的时用户自己编写的文件,一般用双引号,用户自己编写的文件通常是在当前目录中。如果该文件不在当前目录中,双引号可给出文件路径。
1.3.3类的实现
前面只是再CPerson类中声明了类的成员。然而要使用这个类中的方法,即成员函数,还要对其进行定义具体操作。
第一种方法是将类的成员函数都定义在类体内。
以下代码都在person.h头文件中,类的成员函数都定义在类体内。
#include <stdio.h>
#include<string.h>
class CPerson
{//数据成员int m_iIndex;char m_cName[25];short m_shAge;double m_dSalary;//成员函数short getAge(){return m_shAge;}int setAge(short sAge){m_shAge = sAge;return 0;//执行成功返回0}int getIndex(){return m_iIndex;}int setIndex(int iIndex){m_iIndex = iIndex;return 0;//执行成功返回0}char* getName(){return m_cName;}int setName(char cName[25]){strcpy(m_cName, cName);return 0;//执行成功返回0}double getSalary(){return m_dSalary;}int setSalsry(double dSalary){m_dSalary = dSalary;return 0;//执行成功返回返回0}
};
第二种方法,也可以将类体内的成员函数的实现放在类体外,但如果类成员定义在类体外,需要用到与运算符“::”,放在类体内和类体外的效果是一样的。
#include <stdio.h>
#include<string.h>
class CPerson
{//数据成员int m_iIndex;char m_cName[25];short m_shAge;double m_dSalary;//成员函数short getAge();int setAge(short sAge);int getIndex();int setIndex(int iIndex);char* getName();int setName(char cName[25]);double getSalary();int setSalsry(double dSalary);
};
//类成员函数的实现部分
short CPerson::getAge()
{return m_shAge;
}
int CPerson::setAge(short sAge)
{m_shAge = sAge;return 0;//执行成功返回0
}
int CPerson::getIndex()
{return m_iIndex;
}
int CPerson::setIndex(int iIndex){m_iIndex = iIndex;return 0;//执行成功返0
}
char* CPerson::getName()
{return m_cName;
}
int CPerson::setName(char cName[25])
{strcpy(m_cName, cName);return 0;//执行成功返回0
}
double CPerson::getSalary()
{return m_dSalary;
}
int CPerson::setSalsry(double dSalary)
{m_dSalary = dSalary;return 0;//执行成功返回返回0
}
前面两种方式都是将代码存储在同一个文件内。C++语言可以实现将函数的声明和定义放在不同的文件中,一般在头文件放入函数的声明,在实现文件放入函数的实现。同样可以将类的定义放在头文件中,将类成员函数的实现放在实现文件内。存放类的头文件和实现文件最好和类名相似或相同。
将CPerson类的声明部分放在person.h文件内,代码如下:
#include <stdio.h>#include<string.h>class CPerson{//数据成员int m_iIndex;char m_cName[25];short m_shAge;double m_dSalary;//成员函数short getAge();int setAge(short sAge);int getIndex();int setIndex(int iIndex);char* getName();int setName(char cName[25]);double getSalary();int setSalsry(double dSalary);};
说明:代码中出现的关键字public表示成员变量可以被类外部调用。
将Cperson类的实现部分放在person.cpp文件内,代码如下:
#include“stdafx.h”#include<iostream>#include“person.h”//类成员函数的实现部分short CPerson::getAge(){return m_shAge;}int CPerson::setAge(short sAge){m_shAge = sAge;return 0;//执行成功返回0}int CPerson::getIndex(){return m_iIndex;}int CPerson::setIndex(int iIndex){m_iIndex = iIndex;return 0;//执行成功返回0}char* CPerson::getName(){return m_cName;}int CPerson::setName(char cName[25]){strcpy(m_cName, cName);return 0;//执行成功返回0}double CPerson::getSalary(){return m_dSalary;}int CPerson::setSalsry(double dSalary){m_dSalary = dSalary;return 0;//执行成功返回返回0}
关于类的实现有两点说明:
- 类的数据成员需要初始化,成员函数还要添加实现代码。类的数据成员不可以在类的声明中初始化。例如:
class Cperson{//数据成员int m_iIndex=1;char m_cName[25]="Mary";short m_shAge=22;double m_dSalar=1700.00;//成员函数short getAge();int setAge(short sAge);int getIndex();int setIndex(int iIndex);char* getName();int setName(char cName[25]);double getSalary();int setSalsry(double dSalary);};
上面代码是不能通过编译的。
- 空类是C++中最简单的类,其声明方式如下:
class CPerson{};
空类只是起占位的作用,在需要时再定义类成员及实现。
-
-
- 对象
-
对象也是称为类的事例化。以上面的person类为例,它定义了人所具有的属性、特征等,类内部的数据都是为描述每一个具体的人而准备的。这个具体的人指的就是类的实例化——对象。
定义一个新类后,就可以通过类名来声明一个对象。声明的形式如下:
类名 对象列表
CPerson jack;
声明多个对象如下:
CPerson LiMing, Tony;
从程序的角度来看,对象j与其他数据类型一样,具有类型CPerson这个自定义类型。从抽象上理解,jack是定义的CPerson人类的一个例子,一个典范。
-
-
- 访问类的对象
-
访问类中的对象,形式如下:
CPerson jack;jack.age = 25;//jack的年龄是25jack.setName("jack");//通过成员函数设定jack对象的name为jack
1.4 类的构造函数
1.4.1 构造函数的概念
在类的实例进入其作用域时,也就是建立一个对象,构造函数就会被调用,那么构造函数的作用是什么呢?当建立一个对象时,常常需要做某些初始化的工作,如对数据成员进行赋值设置类的属性,而这些操作刚好放在构造函数中完成在前面使用各种成员函数来初始化对象的属性,从代码的整洁度和效率来讲都是不令人满意的。在构造函数中,可以完成初始化工作。
1.4.2 构造函数的定义和使用
在类中声明一个和类相同名字的函数,以CPerson类为例:
class CPerson{CPerson();};
可以注意到的是,此函数的功能是在类构造时完成初始化任务,此函数无定义返回值类型。实现此函数可以通过内部实现方法也可以通过外部实现。
CPerson::CPerson(){int m_iIndex = 1;string m_sName = "Mary";short m_shAge = 22;double m_dSalary = 1700.00;}
构造函数中可以有参数,通过外部数据完成对象内部的初始化。
CPerson::CPerson(int index,string name,short age,double salary){int m_iIndex = index;string m_sName = name;short m_shAge = age;double m_dSalary = salary;}
当创建对象时,程序自动调用构造函数。同一个类中可以具有多个构造函数,通过这样的形式创建一个CPerson对象:
CPerson p1(0, "jack", 22, 7000);//调用带参数的构造函数CPerson p2 = Cperson(1, "tony", 25, 8000);//调用带参数的构造函数CPerson p;//调用不带参数的构造函数
依照重载函数的特性,C++将找到对象创建时所调用的构造函数。
如果程序员在编写程序时,没有写构造函数,编译器执行过程中会分配一个什么内容没有的默认构造函数。
默认构造函数也称为无参构造函数。默认构造函数可以被改写。若类中没有声明无参构造函数,只声明了带参的构造函数,此时类没有默认构造函数。
使用构造函数初始化类成员的代码还允许写成如下形式:
CPerson(int index,string name):m_index(index),m_name(name)
它的作用时使用参数初始化自身的两个成员变量。
1.5类的析构函数
当类的对象销毁时,编译器会调用类的析构函数。构造函数主要是用来在创建对象时,给对象中的一些数据成员赋值,主要目的是来初始化对象。析构函数的功能是用来释放一个对象的,在对象删除前,用它来做一些清理工作,它与构造函数正好相反。构造函数名标识符和类名标识符相同,析构函数名表示符就是在类名标识符前面加“~”符号。
~CPerson();
1.6 类的静态成员
静态数据在程序开始时就获得空间,直到程序结束后才会被收回。静态数据可以声明在函数体内,也可以声明在函数体外。那么,类中也是可以有静态成员的。
类的静态成员与非静态成员有很大的区别。从使用上来讲,调用静态成员不需要实例化对象,而是以如下方式调用:
类名::静态成员
从设计类的思想来看,静态成员应该时类共用的。以人类作为例子,人有很多属性:姓名,年龄,身高等。显然这些不是共用的,一个具体人的名字,年龄不能交给所有人使用。在人类中添加一个属性:生存环境。具体以何种数据类型来表示它视情况而定:好坏,又或质量等级(int),但重要的是它一定是一个静态变量。因为对人类来讲,生存环境是共用的。类中的静态函数无法调非静态成员成员,非静态成员变量在类为实例化时无法在内存中一直存在。若想在静态函数中使用某些成员变量,可以在形参列表事例话本类的对象,这样在函数中可以使用该对象的成员。
例:我们共有一个地球。
human.h声明了human类:
#include<string.h>using namespace std;class human {public:string m_name;human();human(string name);static int nTheEarth;static void GetFeel(human h);void Protect();void Destory();};Human.cpp实现了human类:#include"human.h"#include<iostream>#include<string>using namespace std;int human::nTheEarth = 101;//静态变量初始化!!!human::human(){m_name = "佚名";}human::human(string name){m_name = name;}void human::Destory(){human::nTheEarth -= 20;cout << m_name << "破坏了环境"<<endl;}void human::Protect(){human::nTheEarth += 6;cout<< m_name << "劲微薄之力保护了环境" << endl;}void human::GetFeel(human h){cout << "环境现在的情况:";if (nTheEarth > 100){cout << "世界真美好" << endl;}else if (nTheEarth > 80){cout << "空气还算新鲜,但总觉得还是差了一些" << endl;}else if (nTheEarth > 60){cout << "天不蓝,水不清,勉强能忍受" << endl;}else if (nTheEarth > 40){cout << "数目稀少,黄沙漫天" << endl;}else if (nTheEarth > 20){cout << "呼吸困难,水源稀缺" << endl;}else if (nTheEarth > 0){cout << "难道世界末日到了么?" << endl;}if (nTheEarth < 50){cout << "感到环境变的糟糕,";h.Protect();}}程序入口;int main(){human h1("雷锋");human h2("某工厂老板");human h3("小明");human::GetFeel(h3);for (int day = 0; day < 4; day++){h1.Protect();h2.Destory();if (day % 2 == 0){h3.Protect();}else{h3.Destory();}}cout << "现在的环境指数:" << human::nTheEarth << ",看来人类需要行动起来了..." << endl;for (int day = 0; day < 3; day++){h1.Protect();human::GetFeel(h2);h3.Protect();}cout << "现在的环境指数:" << human::nTheEarth << endl;return 0;}
human实现的Protect和Destroy方法都对静态成员成员nTheEarth进行了操作。可以看到的是每个human实例执行这两个函数后,静态成员变量nTheEarth都会变化,这个值是所有对象共用的。主函数使用区域调用了human类的静态方法GetFeel()。环境的变化,每个人看到的都是一样的,所以没有必要申请非静态成员函数来表示这一过程。
注意:和其他静态变量一样,类的静态成员变量也需要初始化。初始化时必须在外部定义,而且要表明静态变量的类型。
1.7 对象的指针
指向相应类对象的指针,就是对象的指针。它的声明方法和其他类型一样:
类名* p;
类的指针可以调用它所指对象的成员。形式如下:
p->类成员
1.8 this指针
对于类的非静态成员,每一个对象都要自己的一份拷贝,即每个对象都有自己的数据成员,不过成员函数却是每个对象共用的。那么调用共享的成员函数是如何找到自己的数据成员,就是提供类中的隐藏的this指针。
例:同一类的不同对象数据。
class CBook//定义一个CBook类{public:int m_Pages;//定义一个数据成员void OutputPages()//定义一个成员函数{cout << m_Pages << endl;//输出信息}};int main(){CBook vbBook, vcBook;//定义两个CBook类对象vbBook.m_Pages = 512;//设置vbBook对象的成员函数vcBook.m_Pages = 570;//设置vcBook对象的成员函数vbBook.OutputPages();//调用OutputPages方法输出vbBook对象的数据成员vcBook.OutputPages();//调用OutputPages方法输出vcBook对象的数据成员return 0;}
每个对象在调用OutPutPages方法时是如何区分自己的手机号聚聚成员,是通过this指针。在每个类的成员函数(非静态成员函数)中都隐含包含this指针,指向被调用对象的指针。其类型为当前类类型的指针类型,在const方法中,为当前类类型的const指针类型。
void OutputPages()//定义一个成员函数{cout << this->m_Pages << endl;//使用this指针访问数据成员}
实际上,编译器为了实现this指针,在成员函数中自动添加了this指针对数据成员的方法,类似于上面的OutputPages方法。此外,为了将this指针指向当前调用对象,并在成员函数中能使用,在每个成员函数中都隐含包含一个this指针作为函数参数,并在函数调用时将对象自身的地址隐含作为实际参数传递。
void OutputPages(CBook *this)//隐含添加this指针{cout << this->m_Pages << endl;//输出信息}
1.9 对象与复制
当函数以相应的类作为形参列表时,对象可以作为函数的参数传入。值传递是先复制实参副本。
复制构造函数时指类的对象被复制时,所调用的函数。有两种情况对象都会调用复制构造函数:
- 将一个对象复制给另一个对象时:
对象1 = 对象2;//对象1与对象2所属同类
对象1(对象2);
对象2的复制构造函数会被调用
- 作为值传递的实参
function(对象1);
在function函数体内,使用的是对象1的副本。所以之前会调用对象1复制构造函数。
和构造函数一样,C++在未发现自定义的复制构造函数之前都会创建一个默认的构造函数,其作用如上所示。
自定义的复制构造函数的声明格式为:
类名(类名&形参)
值得注意的是,赋值构造函数是引用传递的函数。既然默认赋值构造函数已经完成拷贝工作,何时需要重新定义它呢?例如:一个类具有指针类型的数据,默认赋值构造函数执行之后,原对象和副本的指针成员指向的是同一片内存空间。通过指针改变该内存,就会改变两个对象实际应用的数据(也就是这块内存的内容)。这时可以自定义赋值的构造函数,将两个指针的内存分离开。
例:菌类的繁殖
germ.h声明一个菌类
#pragma once#include<iostream>#include<string.h>using namespace std;class germ{public:int m_age;string m_name;germ(germ& g);germ(string s);~germ();};germ.cpp实现了菌类#include"germ.h"using namespace std;germ::germ(string s){m_name = s;m_age = 1;cout << "发现了" << m_name << endl;}germ::germ(germ& g){g.m_age += 1;this->m_age = 1;this->m_name = g.m_name + "复制体";cout << "产生了" << g.m_name << "的复制体" << endl;}germ::~germ(){cout << this->m_name << "被消灭了" << endl;}germ& copyGerm(germ gc){return gc;}int main(){germ g1("有氧菌");germ g2(g1);germ g3("无氧菌");germ g4 = g3;germ g5 = copyGerm(g4);return 0;}
1.10 const对象
当一个对象创建之后,若不希望它的任何数据发生变化,则可以将其直接声明为const对象:
const 类名 对象
值得注意的是,const对象必须初始化。依照前面的一贯说法,这是一个只读对象。可以调用它的数据和函数,但不能对它进行修改。除此之外,const对象的this指针也是常量。在前面说过,成员函数子啊自己的函数体内自动为成员变量加上this指针。函数声明形式如下:
返回类型 函数名(参数列表)const;
即在函数头结尾加上const。只能对类中的函数体做如此声明,对外部函数无效。
1.11 对象数组
在数组中,数组是通过指针分配的一段额定大小的内存空间。同样的,数组也可以包含对象。声明对象数组的形式如下:
box boxArray[5];box boxArray2[2] = { box(1,1,1),box(2,2,2) };box boxArray3[3] = (3, styleBox);
值得注意的是,第一种申请对象数组的方法必须保证类中含有默认的构造函数,否则编译器将会报错。同样的,可以通过对象指针申请动态对象数组。
box* pbox;pbox = new box[n];//n为整数
同时需要确认box中含有默认(无参)构造函数。
例:批量化生产
box.h代码如下:class box {public://类成员变量float m_lengh;///长float m_width;//宽float m_higth;//高int Number;//流水线编号//类成员函数box(float length, float width, float hight);box();bool Compare(const box b)const;void ToCheck();//显示当前盒子的规格void Rebuld(float lenth, float width, float hight);//重新定义长 宽 高};box.cpp代码如下:#include<iostream>#include"box.h"using namespace std;box::box(){m_lenth = 1.000f;m_width = 1.000f;m_hight = 1.000f;cout << "制作的盒子长:" << m_lenth << "宽:" << m_width << "高:" << m_hight << endl;}box::box(float lenth, float width, float hight){m_lenth = lenth;m_width = width;m_hight = hight;cout << "制作的盒子长:" << m_lenth << "宽:" << m_width << "高:" << m_hight << endl;}bool box::Compare(const box b)const{return (m_lenth == b.m_lenth) & (m_width == b.m_width) & (m_hight == b.m_hight);}void box::ToCheck(){cout << "本盒子现在长:" << m_lenth << "宽:" << m_width << "高:" << m_hight << endl;}void box::Rebuild(float lenth, float width, float hight){m_lenth = lenth;m_width = width;m_hight = hight;}程序入口main.cpp:bool check(float a, float b, float c){return (a > 0) & (b > 0) & (c > 0) & (a < 100) & (b < 100) & (c < 100);}int main(){float lenth;float hight;float width;cout << "请输入您所需要盒子,长、宽、高" << endl;while (cin >> lenth, cin >> hight, cin >> width, !check(lenth, width, hight)){cout << "抱歉,您所输入的规格超出我们的制作水平,请重新输入" << endl;}const box styleBox(lenth, width, hight);cout << "请输入您的订单个数:" << endl;int count;while (cin >> count, !((count > 0) & (count < 6)))//数学检查{if (count > 5){cout << "抱歉,订单数额超过生产水平,请重新输入" << endl;}else{cout << "请确认输入的数值为正数,请重新输入" << endl;}}box* boxArray;boxArray = new box[count];//动态对象数组bool bOK = false;for (int i = 0; i < count; i++){boxArray[i].Rebuild(lenth, width, hight);boxArray[i].ToCheck();if (styleBox.Compare(boxArray[i])){cout << "此产品符合规格" << endl;}}delete[]boxArray;return 0;}
1.12 重载运算符
运算符重载概念:对已有的运算符重新定义,赋予其另外一种功能,以适应不同的数据类型。
1.12.1 加号运算符的重载
作用:实现两个自定义数据类型相加的运算。
#include<iostream>using namespace std;//加号运算符重载class Person{public://1、成员函数重载加号//Person operator+(Person& p)//{// Person temp;// temp.m_A = this->m_A + p.m_A;// temp.m_B = this->m_B + p.m_B;// return temp;//}int m_A;int m_B;};//2、全局函数重载+号Person operator+(Person& p1, Person& p2){Person temp;temp.m_A = p1.m_A + p2.m_A;temp.m_B = p1.m_B + p2.m_B;return temp;}//函数重载的版本Person operator+(Person& p1, int num){Person temp;temp.m_A = p1.m_A + num;temp.m_B = p1.m_B + num;return temp;}void test01(){Person p1;p1.m_A = 10;p1.m_B = 10;Person p2;p2.m_A = 10;p2.m_B = 10;//成员函数重载本质调用//Person p3 = p1.operator+(p2);//全局函数重载本质调用//Person p3 = operator+(p1, p2);Person p3 = p1 + p2;//运算符重载也可以发生函数重载Person p4 = p1 + 10;//Person + intcout << "p3.m_A=" << p3.m_A << endl;cout << "p3.m_B=" << p3.m_B << endl;cout << "p4.m_A=" << p4.m_A << endl;cout << "p4.m_B=" << p4.m_B << endl;}int main(){test01();system("pause");return 0;}
总结1:对于内置的数据类型的表达式的运算符是不可能改变的
总结2:不要滥用运算符重载
1.12.2 左移运算符重载
作用:可以输出自定义数据类型
#include<iostream>using namespace std;//左移运算符重载class Person{public://利用成员函数实现运算符重载//不会利用成员函数重载<<运算符,因为无法实现 cout在左侧//Person operator<<(Person& p)int m_A;int m_B;};//只能利用全局函数重载左移运算符(链式编程)ostream& operator<<(ostream& cout, Person& p){cout << "m_A= " << p.m_A << " m_B=" << p.m_B;return cout;}void test01(){Person p;p.m_A = 10;p.m_B = 10;cout << p << endl;}int main(){test01();system("pause");return 0;}
1.12.3 递增运算符重载
作用:通过重载递增运算符,实现自己的整型数据
#include<iostream>using namespace std;//重载递增运算符重载//自定义整型class MyInteger{friend ostream& operator<<(ostream& cout, MyInteger myint);public:MyInteger(){m_Num = 0;}//重载前置++运算符MyInteger& operator++(){//先进行++的运算m_Num++;return *this;}//重载后置++运算符//int表示占位参数,可以区分前置和后置递增MyInteger operator++(int){//先记录结果MyInteger temp = *this;//后递增m_Num++;return temp;}private:int m_Num;};//重载左移运算符ostream& operator<<(ostream& cout, MyInteger myint){cout << myint.m_Num;return cout;}void test01(){MyInteger myint;cout << ++myint << endl;}void test02(){MyInteger myint;cout << myint++ << endl;}int main(){test01();system("pause");return 0;}
总结:前置递增返回引用,后置递增返回值。
1.12.4 赋值运算符重载
C++编译器至少给一个类型添加4个函数:
- 默认构造参数(无参,函数体为空);
- 默认析构参数(无参,函数体为空);
- 默认拷贝构造参数,对属性进行值拷贝;
- 赋值运算符operator=,对属性进行指拷贝。
如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝的问题。
#include<iostream>using namespace std;class Person{public:Person(int age){m_Age=new int(age);}~Person(){if (m_Age != NULL){delete m_Age;m_Age = NULL;}}//重载 赋值运算符Person& operator=(Person& p){//编译器是提供浅拷贝//先判断是否有属性在堆区,如果有先释放干净,然后再进行深拷贝if (m_Age != NULL){delete m_Age;m_Age = NULL;}//深拷贝m_Age = new int(*p.m_Age);//放回对象本身return *this;}int* m_Age;};void test01(){Person p1(18);Person p2(20);Person p3(30);p3 = p2 = p1;//赋值操作p2 = p1;cout << "p1的年龄为:" << *p1.m_Age << endl;cout << "p2的年龄为:" << *p2.m_Age << endl;cout << "p3的年龄为:" << *p3.m_Age << endl;}int main(){test01();system("pause");return 0;}
1.12.5 关系运算符重载
作用:重载关系运算符,可以让两个自定义类型对象进行对比操作。
#include<iostream>using namespace std;class Person{public:Person(string name, int age){m_Name = name;m_Age = age;}string m_Name;int m_Age;//重载==号bool operator==(Person& p){if (this->m_Name == p.m_Name && this->m_Age == p.m_Age){return true;}else{return false;}}//重载!=号bool operator!=(Person& p){if (this->m_Name != p.m_Name && this->m_Age != p.m_Age){return true;}else{return false;}}};void test01(){Person p1("Tom",18);Person p2("Tom", 18);if (p1 == p2){cout << "p1和p2是相等的" << endl;}}int main(){test01();system("pause");return 0;}1.11.6 函数调用运算符重载函数调用运算符()也可以重载由于重载后使用的方法非常像函数的调用,因此称为仿函数仿函数没有固定写法,非常灵活。#include<iostream>using namespace std;//打印输出类class MyPrint{public://重载函数调用运算符void operator()(string test){cout << test << endl;}};//仿函数非常灵活,没有固定的写法//加法类class MyAdd{public:int operator()(int num1, int num2){return num1 + num2;}};void MyPrint02(string test){cout << test << endl;}void test02(){MyAdd myadd;int ret = myadd(100, 100);cout << ret << endl;//匿名函数对象cout << MyAdd()(100, 100) << endl;}void test01(){MyPrint myPrint;myPrint("Hello World");//仿函数MyPrint02("hello world");}int main(){test02();test01();system("pause");return 0;}