在学习C语言和C++的过程中,我们知道C语言是面向过程的编程语言,而C++是面向对象的编程语言,在学习面向对象的编程语言的时候,我们必须要学习的就是面向对象的三大特征点:封装、继承、多态。
1、封装
对象:凡是占据一定空间的事物,都称为对象,实物都可以称为对象,占据空间,想象的实物不能算作对象,因
为是我们想象的虚拟事物,不在显示世界占据空间。
一切皆对象. 这个世界是因为对象相互交互而运行的,对象与对象需要交流、通信、相互配合。
世界是面向对象的,编程要实现一个复杂的系统,最好是模拟现实世界的.
类(class): 现实世界中动物可以算作很多类,比如猫类、犬类等等, 但是有一类东西是不占据空间的,
类是一个总结,概念,抽象的,不存在现实世界的.
类不存在的,只是一个统称。所以我们具体到类中的某个对象,比如猫类中的橘猫就是存在的,可以称
为一个对象干活,对象是存在的。
类,是一类(对象)统称.
如何描述对象:
class 橘猫{
静态的属性
(姓名、年龄、体重等等)
动态的方法/行为/函数
(会爬树、会咬人、会抓老鼠)
}
我们将橘猫拥有的特性写入一个类里面装起来,之后只有橘猫才可以使用这些属性和
行为,这个过程我们就叫封装,这个类与结构体相似,区别在于可以写入函数等等。
封装的过程中有很多的知识点需要我们学习:
(1)类的定义和使用
#include <iostream> // std::cout std::cin
using namespace std;
/*
类中有很多函数,每个函数都很长,那么class会很难看.
怎么办
1.函数的实现部分写在类外
2.类内声明有该函数
*/
class cat {
public:
//静态的属性
string name;
string sex;
int age;
float weight;
//动态的方法
int doCatchMouse(int catchType);
int doClimbTree(void);
//类函数声明
void init(string xname,string xsex,int xage,float xweight);
void show(void);
};
int cat::doCatchMouse(int catchType)
{
cout<<"catchType="<<catchType<<endl;
return 1;
}
int cat::doClimbTree(void)
{
cout<<"climbTree"<<endl;
}
void cat::init(string xname,string xsex,int xage,float xweight)
{
name=xname;
sex=xsex;
age=xage;
weight=xweight;
}
void cat::show(void)
{
cout<<"name="<<name<<" sex="<<sex<<" age="<<age<<" weight="<<weight<<endl;
}
int main()
{
cat xiaohua;
xiaohua.name="xioahua";
xiaohua.sex="man";
xiaohua.age=4;
xiaohua.weight=3.4;
int d = xiaohua.doCatchMouse(66);
d = xiaohua.doClimbTree();
xiaohua.show();
cout<<"####### dahua############"<<endl;
cat dahua;
dahua.name="dahua";
dahua.sex="woman";
dahua.age=3;
dahua.weight=4;
dahua.show();
cout<<"####### laohuahua############"<<endl;
cat laohua;
laohua.init("laohua","man",9,6.8);
laohua.show();
cout<<"###################### 构造函数 <<<<<<<<<<"<<endl;
return 0;
}
(2)构造函数与析构函数
需求: 每当我定义一个新的对象,我都希望对象中 成员被初始化一次,有一下方法:
1) 自己写函数init----
#include<iostream>
using namespace std;
class cat{
public:
string name;
string sex;
int age;
double weight;
void show()
{
cout<<"name="<<name<<" sex="<<sex<<" age="<<age<<" weight="<<weight<<endl;
}
cat(): name("noname"),sex("man"),age(1),weight(0.1) //函数 参数初始化列表
{
//等同于 name="noname";sex="man";age=0;weight=0.1;
}
/*
初始化列表:
1.只有构造函数可以,紧跟函数名后面 fun() : 变量名(值),变量名(值)..
2.初始化列表先于{}执行.
*/
cat(string xname,string xsex,int xage): name( xname),sex(xsex),age(xage)
{
//等同于
//name=xname;
//sex=xsex;age=xage;
}
};
int main()
{
cat xiaohua;
xiaohua.show();
cat dahua ( "dahua","man",3);
dahua.show();
}
2) 类/对象 提供了一个 专门的函数 来完成对象的初始化.
称为----构造函数, 构建制造---对象产生的时候自动执行,我们也可以对这个函数进行重构 可以传递参数进去赋值,构造函数可以不止一个。
#include<iostream>
#include <cstring>
using namespace std;
class cat {
public:
string name;
string sex;
int age;
char *desbuf;
cat() { name="noname";sex="unkown";age=0;desbuf=new char[32]; }
cat(string xname,string xsex,int xage)
{ name=xname; sex=xsex; age=xage; desbuf=new char[32];}
cat(string xname,string xsex,int xage,char *des)
{ name=xname; sex=xsex; age=xage; desbuf=new char[32];strcpy(desbuf,des);}
//拷贝构造函数, 参数是另一个对象,触发条件使用旧对象初始化新对象
cat( cat &oldobj) { cout<<"in copy structure"<<endl;
name="noname"; sex=oldobj.sex;age=oldobj.age;
desbuf=new char[32]; strcpy(desbuf,oldobj.desbuf);
}
void show(){ cout<<"name="<<name<<" sex="<<sex<<" age="<<age<<" des:"<<desbuf<<endl;}
};
int main()
{
cat xiaohua ("xiaohua","man",3,"des,bo si mao");
//cat dahua = xiaohua; //使用另外一个对象来初始化 新对象,效果等同于下一行
cat dahua (xiaohua);
/* 他调用的是 拷贝构造函数
cat dahua = xiaohua; ---g++翻译为--> cat dahua (cat &obj);
如果你不写,系统帮你制定一个, 这个函数使用简单的覆盖机制 -----浅拷贝
这种 简单拷贝方法,有问题:
strcpy(dahua.desbuf,"this is dahua");
xiaohua.show();
dahua.show(); 发现两个对象使用的 同一片空间,不行
我们要重写############################## 完成自己的拷贝 -----深拷贝
*/
dahua.name="dahua";
strcpy(dahua.desbuf,"this is dahua");
xiaohua.show();
}
构造函数 对成员进行初始化
1.和类名一致,没有返回值-不是void, 用于和普通函数区分
2.当对象被构成/产生的时候,函数自动执行--系统调用的
3.构造可以有多个---函数重载 还可以有默认值
注:当你不写构造,编译器帮你指定一个 默认构造 对象名(){ }
析构函数:析构函数与构造函数不同,在一个函数中,析构函数有且只有一个,析构函数在程序或这个类的作用域结束的时候自动执行。在对数的情况下,析构函数都是用来释放空间的。
#include <iostream>
#include <unistd.h>
using namespace std;
class cat {
public:
//静态的成员
string name;
string sex;
int age;
char*buf;
/*构造函数 对成员进行初始化
1.和类名一致,没有返回值-不是void, 用于和普通函数区分
2.当对象被构成/产生的时候,函数自动执行--系统调用的
3.构造可以有多个---函数重载 还可以有默认值
当你不写构造,编译器帮你指定一个 默认构造 cat(){ 啥都不干 }
*/
cat() {cout<<"in structure cat()"<<endl; name="huahua";sex="woman";age=1; buf=new char [128]; } //默认构造
cat(string xname,string xsex) {cout<<"in structure cat(xname,xsex)"<<endl;name=xname;sex=xsex; buf=new char [128];};
cat(string xname,string xsex,int xage ){cout<<"in structure cat(xname,xsex,xage)"<<endl;name=xname;sex=xsex; age=xage; buf=new char [128];}
//void deinit() {delete []buf;}
/*
析构: 当对象消失的时候,自动调用,用来释放一些空间
1) delete p; 2)main函数退出--所有对象都析构 3)局部对象释放
1.析构函数没有返回值,和类名一致,前面加了~
2.析构函数只有一个
*/
~cat() { delete []buf; cout<<"in ~cat();"<<"name="<<name<<endl; };
//动态的方法
void show(void){cout<<"name="<<name<<" sex="<<sex<<" age="<<age<<endl;}
//void init(string name,string sex .....)
};
void do_show(void)
{
cat xiaohua ("laolaohua","man"); //当函数退出的时候,析构被执行
}
int main()
{
cat xiaohua; //注意,当对象产生,系统帮你自动调用构造函数, 默认构造
cat dahua ("dahua","man");
cat laohua ("laohua","woman",9);
//指针访问
cat *pt = &xiaohua;
pt->show(); //效果等于 xiaohua.show();
cout<<"##########new obj ###########"<<endl;
//######### new object-对象 ################
cat *pt0 = new cat ; pt0->show(); // malloc(sizeof(struct cat)); 默认构造
cat *pt1 = new cat ("xiaohua1","woman") ; //指定构造
cat *pt2 = new cat ("xiaohua2","man", 3);
delete pt0; // 释放整个对象 但是如果对象指针指向的空间,没有释放
delete pt1; // 一般在delete之前释放
delete pt2;
{
int a=7;
cat zuhua ("zuhua","man",5); //当{}结束的时候,对象析构执行
}
//a=9; 非法,a的生命周期已经结束
//zuhua.show();
do_show();
while(1){ sleep(1); };
return 0;
}
(3)this 指针(本体指针)
每一个对象中都隐藏了一个指针,this ,指向对象自己
用途:
1)类函数成员-可以获取对象本身 sizeof(*this)
2)区分类成员和传参
#include<iostream>
using namespace std;
class cat{
public:
string name,sex;
int age,weight;
void show() {cout<<"name "<<name<<" this ptr="<<this<<"obj sz="<<sizeof( *this)<<endl;}
cat(string name,string sex,int age,int weight){ this->name=name; this->sex=sex;this->age=age;this->weight=weight; }
private:
//cat *this = currrent object; 隐藏的指针
};
int main()
{
cat xiaohua ("xiaohua","woman",2,4);
xiaohua.name = "xiaohua";
xiaohua.show(); cout<<" &xiaohua="<<&xiaohua<<endl;
}
(4)static和const用法
static 修饰变量,只能在本文件中使用,修饰局部变量的时候可以延长生命周期。
static 修饰函数,限制函数只能在本文件中使用。
在上面修饰的类型不同,所以我们声明的时候也是不一样的。
修饰变量的时候我们需要在程序执行之前,提前声明初始化里面的静态变量。
但是在修饰的是函数的时候不需要声明,对象在没有声明的时候我们也可以调用该函数。
static修饰的成员函数, 不依赖于对象而存在,一直都在, 所有的对象共享它.
注意: static成员函数只能访问 static变量.
#include<iostream>
using namespace std;
class cat {
public:
static int objcnt; // 静态成员变量, 它只有一份,所有对象共享它
//它不依赖于对象而存在
string name,sex;
int age;
cat(){ objcnt++;}
cat(string name,int age){this->name=name;this->age=age; objcnt++;};
//static void show() {cout<<"name="<<name<<"sex="<<sex<<" staticcnt="<<objcnt<<endl;}
//静态成员函数,只能访问静态成员变量 ----因为对象可能不存在
static int getCnt(){ return objcnt;} //静态成员函数: 类成员可以直接访问
}; //静态函数,不依赖于对象而存在,所有对象共享它
int cat::objcnt = 0; //定义并初始化---这个时候才分配了空间
int main()
{
cat laohua("laohua",4);
laohua.show();
cout<<"static fun = "<< cat::getCnt() <<endl; //静态函数访问方法1: 不依赖于对象而是用
cat xiaohua ;
cat xiaoqiang("xiaoqiang",3);
/*如何访问静态成员呢
1.所有对象共享它,可以使用 对象.objcnt
2. 类名::静态成员 方式;
3.类函数可以直接访问
*/
cout<<"obj cnt:"<<xiaoqiang.objcnt<<endl;
cout<<"obj cnt:"<<xiaoqiang.getCnt()<<endl; 静态函数访问方法2: 对象共享它,则对象也可以访问
cout<<"objcnt="<<cat::objcnt<<endl; //不依赖对象
}
const在前面我们讲过一些C++的const与C语言的const的区别,直接看一下用法吧
#include<iostream>
using namespace std;
class person {
public:
const int id; //只允许初始化一次,以后再也不会改动了
const string sex; //在哪里初始化--####只能在构造 ###
string name;
int age;
person(string xsex,int xid,string xname) : id(xid),sex(xsex)
{
cout<<"in person(sex,id,name)"<<endl;
//this->sex=sex; this->id=id; 报错,因为程序不允许改动const常量
// const int aa=4; 以前 定义的时候赋值,编译器搞定的
// aa=8; 程序动态改变,不允许的
//初始化const成员只能在 构造函数的 初始化列表中实现,而且要求 每个构造必须初始化所有的const
this->name=xname;
}
//person() {} 非法,编译器要求所有的构造 必须初始化所有 const成员
#if 0
void set_sex(string sex)
{
this->sex=sex; //编译器报错,不允许的操作
}
#endif
void show() const
{
cout<<"id="<<id <<" sex="<<sex<<" name="<<name<<endl;
//age = 2; 非法,const函数不能试图修改任何成员
}
void set_name(string name){ this->name=name; }
/*修饰函数
有时候我们期望 某个函数不要改动任何东西,为了彻底的保证 他不改动任何成员
我们使用const修改该函数. 编译器一旦发现const函数试图改变任何成员,就报错.
返回值 函数名(参数) const { 函数体 ;}
*/
};
int main()
{
person xiaowang("man",22070401,"xiaowang");
xiaowang.show();
}
(5)public 和 private
在类的定义中主要的作用是一个限制权限的功能。
#include<iostream>
using namespace std;
class house {
public:
string chair;
string pc;
string book;
void speak(void){ }
private:
string bed;
string wazi;
void toliet(){ cout<<wazi<<endl; }
void sleep() { cout<<bed<<endl; cout<<book<<endl; }
};
int main()
{
house xiaomingjia;
cout<<xiaomingjia.chair <<endl; //公有成员,可以在类外使用
xiaomingjia.speak();
cout<<xiaomingjia.wazi<<endl; //私有成员,不允许在类外使用
}
//类的公有成员 可以在类内部使用,也可以在类外使用.
//类的私有成员 只能在类内使用
2、继承
#include<iostream>
using namespace std;
class animal{ //父类
public:
string kind;
int weight;
animal(){ cout<<"in animal( )"<<endl; }
animal(string kind,int weight,string sex,int age)
{ cout<<"in animal( ...)"<<endl;
this->kind=kind; this->weight=weight;this->sex=sex;this->age=age;
}
~animal() {cout<<"~animal()"<<endl;}
void eat(void){cout<<"animal eat()"<<endl;}
void run(void){cout<<"animal run()"<<endl; }
void set_sex(string sex) {this->sex=sex;}
string get_sex() {return sex;}
void set_age(int age) {this->age=age;}
int get_age(void){return age;}
private: //限制类外访问,包括限制 子对象的直接访问
string sex;
int age;
void la(void){cout<<"animal la()"<<endl;}
};
/*
class 子类:父类 {
父类的东西
子类的新东西
}
*/
class cat: public animal{
//已经拥有了animal的##所有东西##,只是父亲的private部分不可以直接访问
//然后我们只需要添加子类特有的即可
public:
string name;
/*
####构造函数:
子类的构造函数在执行的时候,首先去寻找并执行父类的构造函数 (初始化父亲的那一部分)
之后,再执行子类的部分(初始化子类的部分)
子类构造,如何执行调用父亲的哪一个构造....... 如果不指定,调用父类的默认构造
子类构造(参数1,参数2,参数3....):父亲构造(arg1,arg2,....)
{
只用来初始化子类特有的
}
*/
cat() {} //会call father's default structure
cat(string name,int age,string sex) : animal("cat type" ,5,sex,age)
{
cout<<"in cat(...)"<<endl;
this->name=name;
}
/*
析构函数: 当子对象被销毁,执行子对象的析构, 在子对象析构执行完毕后,会自动调用父对象的析构
*/
~cat()
{
cout<<"~cat()"<<endl;
//调用父亲的析构,不需要写,g++自动帮你添加代码
}
void show(){ cout<<"name="<<name<<" sex="<<get_sex()<<" age="<<get_age()<<endl;}
void catchMouse(void){cout<<"cat catchmouse" <<endl;}
void climbTree(void){cout<<"cat climbtree"<<endl;}
private:
void jump(void){cout<<"cat jump"<<endl;}
string color;
};
int main()
{
//cat xiaohua;
cat xiaohua("xiaoxiaohua",3,"woman"); /* 当子类对象产生,立即触发子类构造函数,
子对象构造函数,首先寻找父类构造函数并执行,之后在执行自己的构造函数*/
xiaohua.show();
cout<<"####### father class private show##########"<<endl;
/* xiaohua.sex = "man"; 非法的操作
父类有私有成员, 父类希望这些private都只能在父类中使用.
子类是不能够直接访问的( 子类中有这些东西 ),如果子类想访问,
只能通过父类提供的public 方法
*/
xiaohua.set_sex("man");
cout<<"xiaohua sex="<<xiaohua.get_sex()<<endl;
cout<<"########## public member show########"<<endl;
xiaohua.kind = "cat type";
xiaohua.weight=3;
xiaohua.name="xiaohua";
xiaohua.eat();
xiaohua.catchMouse();
//main退出之后,进程结束,释放所有空间(包括 xiaohua, ),执行xiaohua的析构函数( ~cat() )
}
继承是指子类可以继承父类的公共特性和方法,在子类继承了父类之后,子类同样可以在类中添加一些自己特有的东西。继承可分为公共继承和私有继承以及保护继承三种方式。
3、多态
多态即有那种状态,这里指的是一个函数在继承的类中可以被改写,在父类中存在一种实现的方法,同样在子类中也可以重新编写该函数,改变此函数的实现方法。但是当我们在我们称此函数为虚函数。
#include<iostream>
using namespace std;
class animal{
public:
/*父类的run函数要允许子类覆盖它: 即子类也要实现run
如何允许子类覆盖掉该函数呢?
使用virtual修饰函数, 该函数为一个 虚函数,允许子类覆盖.
*/
virtual void run() {cout<<"animal run"<<endl;}
void bark(){cout<<"animal bark"<<endl;}
private:
string kind;
};
class cat:public animal {
public:
void run() {cout<<"cat run"<<endl;} //会覆盖掉 父类的 虚函数
void catchMouse(){cout<<"cat catchMouse"<<endl;}
private:
string name;
};
class dog:public animal {
public:
void run(){cout<<"dog run"<<endl;}
void watchdog(){ cout<<"watch dog"<<endl;}
private:
void goupao(){ }
};
int main()
{
dog xiaowang;
xiaowang.run(); // dog run
cat xiaohua;
xiaohua.run(); // 打印cat run;
animal *pt = &xiaowang;
pt->run(); //打印结果是 子类的run
/*
原理是:
子类的 run函数覆盖了父类的run(父类的run"消失")了.
*/
}
我们在继承的时候可能会遇到子类函数名与父类函数名相同的情况:
#include<iostream>
using namespace std;
/*
定义一个人类为 基类, 包含姓名,年龄,性别ID,工作 娱乐方法; 定义子类,
实现律师(增加属性方法) 完成工作 娱乐等方法
码农子类(增加属性方法), 完成工作 娱乐等方法
完成构造和析构,至少三种(父类构造)
*/
class person{
public:
virtual void work() =0; //我们发现父类的虚函数 几乎不会被使用,可以不写,直接给0
virtual void funy() =0; //这样的函数,称为 纯虚函数:虚函数没有函数体/实现
//拥有纯虚函数的类,是不完整的( 因为函数没有完成),这种类 称为 虚基类
person() { } // person xiaowang; 非法,因为虚基类 不完整,大小都确定的.
// person * p; 合法的. 虚基类可以定义指针,因为 指针大小固定
person(string xname,string xsex,int xage):name(xname),age(xage),sex(xsex)
{ }
virtual ~person() { cout<<"~person()"<<endl;} /*
虚析构: 如果基类某个函数是虚函数,那么他的子孙的该函数都是虚函数---虚函数继承性
同样,如果 析构 是虚函数,那么 子孙的析构 都是虚函数
*/
private:
string name;
int age;
string sex;
};
class lawer:public person {
public:
void work() { cout<<" da guan si "<<endl;}
void funy() { cout<<" bei law book"<<endl;}
lawer(string name,int workid,int age,string sex):person(name,sex,age )
{
this->workid=workid;
}
~lawer() { cout<<"~lawer()"<<endl; /*隐藏代码:调用父类的析构 去初始化父类部分*/ }
private:
int workid;
};
class coder :public person {
public:
void work() { cout<<" coding "<<endl;}
void funy() { cout<<" shua github "<<endl;}
~coder() { cout<<"~coder()"<<endl; /*隐藏代码:调用父类的析构 去初始化父类部分*/ }
private:
string workage;
};
int main()
{
person *p = new lawer("wanglushi",123789,39,"man");
p->work();
p->funy();
delete p; /* 问题: p是父类指针,虽然指向子类对象,但是只能访问父类的部分,所以这里调用的是父类的析构
子类部分没法释放,怎么办
基类的析构改为 虚函数
这是一个很常用的方法:
如果可以,所有的析构 都要写成虚函数.
*/
p = new coder ;
p->work();
p->funy();
delete p;
}