20150507
所谓继承,就是从父辈得到的属性和行为。是通过继承对父辈的属性和行为进行进一步的细化或者扩充来形成新的类。
在c++中,我们把旧有的类称为基类或者父类,而把从基类继承产生的新类则称为派生类,也称为子类。
派生类(子类)的声明方式
class 派生类名:继承方式 基类名1,继承方式 基类名2...
{
//派生类新增加的属性;
//派生类新增加的行为;
}
;
单继承,这个派生类只有一个基类
多继承,这个派生类可以有多个基类
20150508
派生方式有3种
publi,protected,private
不同的派生方式决定了派生类如何访问从基类继承下来的成员变量和成员函数。
public方式继承,表示派生类是基类的一个子类型,在基类所有成员的访问级别在派生类中不改变。大多数情况都采用public方式继承。
20150511
private派生方式,把基类的所有公有(public)成员变成自己的私有(private)成员。这种继承方式的派生类不再直接支持基类的公有接口。
protected派生方式把基类的公有成员变成protected类型,保护基类的所有接口不被外界访问,只能由自身及自身的派生类访问。
class Human
{//人类共有的行为
public:
void walk();
void talk()
protected:
string m_strname;
int m_nage;
bool m_bmale;
private:
};
//下面的都是派生类
//public继承方式继承human类
class teacher:public human
{//在子类中添加老师特有的行为,老师是human的子类
public:
void preparelesson();
void teachlesson();
void reviewhomework();
//在子类中添加老师特有的属性,上面的是行为,这里的是属性
protected:
int m_nduty;
private:
};//上面的都是teacher自己独自的东西,还没继承派生human中的行为和属性
class student:public human
{//在子类中添加学生特有的行为
public:
void attendclass();
void dohomework();
protected:
int m_nscore;
private:
};
class pupil:public student
{//在子类中添加小学生特有的行为
public:
void attendclass();
void dohomework();
protected:
private:
};
teacher mrchen;
//我们在teacher类中并没有定义walk()成员函数,
//这里是通过继承从基类human中得到的成员函数
mrchen.walk();
mrchen.teachlesson();
继承使用规范:
1、拥有的派生关系的两个类必须相关。
2、不能把组合当成继承
一台电脑拥有键盘,鼠标,显示器的功能,但电脑不能由键盘,鼠标,显示器派生,而应该由他们组合
//组合的例子
class keyboard
{
public:
void input();
};
class mouse
{
public:
void click();
};
class monitor
{
public:
void display();
};
class computer
{
//电脑的行为
//电脑的具体行为都有其各个组成部分来负责完成
public:
computer (keyboard* pkeyboard,
mouse* pmouse,
monitor* pmonitor)
{
m_pkeyboard=pkeyboard;
m_pmouse=pmouse;
m_pmonitor=pmonitor;
}
void input()
{
m_pkeyboard->input();
}
//鼠标负责用户单机
void click()
{
m_pmouse->click();
}
void display()
{
m_pmonitor->display();
}
private:
keyboard* m_pkeyboard;
mouse* m_pmouse;
monitor* m_pmonitor;
};
//注意:生命周期相同则采用对象;生命周期不同,则采用指针。
基类指针可以指代派生类的对象,而派生类的对象也可以当成基类对象使用。
20150512
//定义human类,这个类有一个成员函数buyticket()表示买票的动作
class human
{
public:
void buyticket()
{
cout<<"人买票"<<endl;
}
private:
};
class teacher:public human
{
};
class student:public human
{
};
int _tmain(int argc,_tchar* argv[])
{
human* ppassenger=null‘
ppassenger=new teacher();
ppassenger->buyticket();
delete ppassenger;
ppassenger=new student();
ppassenger->buyticket();
delete ppassenger;
ppassenger=null;
return 0;
}
虚函数的产生
为了满足各个派生类对类的行为进行自定义的需要,c++提供了虚函数的机制,在基类的函数声明前加上virtual关键字,这个函数就成为虚函数。
工资管理系统开发:
经理和普通员工同属于员工,都是从员工派生出来的。
找到对象之后,就可以分析这些对象所拥有的属性和行为。
如果你是架构师,只要你完成了类的设计,那么你的工作就到此结束了。
如果你是程序员,你还需要将类的设计用c++语言来实现。
//salarysystem.h 头文件
#pragma once
#include<string>
#include<iostream>
using namespace std;
//员工类,下面是员工类的构造函数
class emplyee
{
public:
emplyee (string strname ,int nyears):
m_strname (strname),m_nyears(nyears) //利用构造函数参数对属性进行初始化
{};
//员工类的行为,上面是员工的构造函数,下面是员工的行为
public:
//提供一个纯虚函数作为公有接口,供外界获得员工的工资
//因为经理类和普通员工这两个派生类对工资的计算方式不同,所以这里必须提供纯虚函数供派生类对其进行自定义
virtual int getsalary()=0;
//因为这个函数只是简单地返回一个属性值,所以把它定义在类的声明中,使其成为一个内联函数,提高性能
string getname()
{
return m_strname;
};
//员工类的属性,上面是员工的行为
//因为这些属性需要遗传给它的派生类,所以这里将其访问级别设置为protected:
protected:
//入职时间
int m_nyears;
string m_strname;
};
//下面的都是派生类
//经理类,因为经理是员工中的一种,所以它从员工类emplyee派生
class manager : public emplyee
{
public:
//使用基类的构造函数,完成堆属性的初始化工作
manager(string strname,int nyears):emplyee(strname,nyears)
{};
public:
//根据需求描述我们使用了不同的工资计算方式,利用面向队形的堕胎机制,使得同名的函数在不同的派生类中可以有不同的实现。
virtual int getsalary()
{
return 5000 * m_nyears + 10000;
}
};
class worker: public emplyee
{
public:
work(string strname,int nyears):emplyee(strname,nyears)
{};
public:
virtual int getsalary()
{
return 200 * m_nyears +2000;
}
};
完成员工类及其派生类之后,就可以在工资管理系统类salarysystem中创建对象并使用这些对象来代表员工,对工资进行管理。salarysystem实现如下:
const int max_count=1000;
class salarysystem
{
public:
salarysystem(void);
~salarysystem(void);
public:
void inputemplyee(void);
void displaysalary(void);
double getaversalary(void);
//因为这些属性仅仅供工资管理系统系统自己使用,所以我们将其访问级别设置为private
private:
int m_count;
//这里将员工对象的指针保存到数组中,通过指针访问员工对象
emplyee* m_arremplyee[max_count];
};
#include "stdafx.h"
#include"salarysystem.h"
salarysystem::salarysystem(void) //构造函数,对类的属性进行初始化
{
m_ncount=0; //将员工总数初始化为0
}
salarysystem::~salarysystem(void) // 析构函数,清理资源,释放内存
{
for (int i=0;i<m_ncount;++i) //循环遍历保存员工对象指针的数组,清理资源
{
emplyee* pemplyee=m_arremplyee[i];
delete pemplyee; //销毁对象,释放内存
m_arremplyee[i]=null; //将相应指针设置为null,不可访问
}
}
void salarysystem::inputemplyee(void) //获取用户输入
{
cout<<"请输入员工信息\n"<<
"格式:员工姓名 入职时间 是否为经理级别\n"<<
"例如:chenliangqiao 4 0\n"<<
"输入end表示输入结束"<<endl;
string strname=""; //局部变量,用于接收用户输入
int nyears =0;
bool bmanager=false;
int nindex=0;
while(nindex<max_count)
{
cin.clear();//清空输入流
cin>>strname>>nyears>>bmanager;//从输入流读取用户输入的数据
if ("end"==strname)
break;
emplyee* pemplyee=null;//根据用户输入创建相应的员工对象
if (bmanager)
{
pemplyee=new manager(strname,nyears);//如果用户输入的是一个经理,则创建manager对象
}
else
{
pemplyee=new worker(strname,nyears);//如果用户输入的是一个普通员工,则创建worker对象
}
m_arremplyee[nindex]=pemplyee;//将创建对象指针保存到数组中
++nindex;//索引值递增,开始下一次录入循环
}
m_ncount=nindex;//保存输入的员工总数
}
void salarysystem::displaysalary(void)//显示输出工资信息
{
cout<<"工资管理系统"<<endl;//显示工资信息
cout<<"当前员工总数:"<<m_ncount<<
"\n平均工资是:"<<getaversalary()<<endl;
cout<<"员工具体工资信息如下:"<<endl;
//循环遍历保存员工对象指针的数组,输出每个员工对象的具体信息,这里使用的是基类指针调用基类的共有接口函数,但是如果这个接口函数是虚函数,那么他们将调用这个指针所只想的实际对象的相应函数。例如,这里对gesalary()函数的调用,编译器将根据pemplyee这个基类指针所指向的具体对象是manager还是worker来决定到底是调用manager类中的getsalry()实现还是worker类中的getsalary()实现。这就是面向对象中多态的体现。
for (int i=0;i<m_ncount;++i)
{
emplyee* pemplyee=m_arremplyee[i];
cout<<pemplyee->getname()<<"\t"<<
pemplyee->getsalary()<<endl;
}
}
double salarysystem::getaversalary()
{
int ntotal=0;
for(int i =0;i<m_ncount;++i)
{
emplyee* pemplyee=m_arremplyee[i];
ntotal += pemplyee->getsalary();
}
return (double)ntotal/(m_ncount);
}
完成工资管理系统类salarysystem之后,我们只需在主函数中简单地使用这个类就完成了整个系统。
#include "stdafx.h"
#include "salarysystem.h"//引入salarysystem声明所在的头文件
int _tmain(int argc, _tchar* argv[])//在主函数中使用salarysystem对象
{
salarysystem nsalarysys;//创建salarysystem对象
nsalarysys.inputemplyee();//获取用户输入
nsalarysys.displaysalary();//显示具体的工资信息
return 0;
}
高手是这样炼成的。
c++类对象的内存模型
类是对属性和行为的封装,在类的对象中也应该有属相(成员变量)和行为(成员函数)
也就是在内存中也应该有对象的成员变量和成员函数
this指针是c++中的一个关键字,它代表只想当前对象的指针,成员函数中的成员变量就是通过this指针找到自己所属的对象。
利用该指针,可以访问到该对象的所有成员变量和成员函数。
这是一个成员变量通过this指针找所属对象的过程。
class base
{
public:
int changevalue(int nval)//成员函数访问成员变量
{
this->setvalue(nval)//通过this指针访问成员函数
return this->m_nval;//通过this指针访问成员变量
}
};
class base//base类
{
public:
void setvalue(int nval)//成员函数访问成员变量,setvalue是base类的成员函数
{
m_nval=nval;
}
private://类的成员变量,m_nval是base类的成员变量
int m_nval;
};
base abase;//创建对象调用其成员函数,abase是一个对象
abase.setvalue(1);调用成员函数对成员变量进行赋值
this指针帮助指向当前对象,上面abase是对象
class base
{
public:
void setvalue(int nval)//成员函数setvalue访问成员变量nval
{
this->m_nval=nval;//显示使用this指针访问成员变量
}
};
当通过某个对象调用它的成员函数时,系统会隐式地传递给成员函数一个指向这个对象的指针。
所以对成员变量的访问,就变成对这个对象所属成员变量的访问。
例如:通过abase对象调用setvalue()成员函数,那么在setvalue()成员函数中,隐藏的this指针就指向abase这个对象。
this->m_nval=nvla语句是对abase的m_nval成员变量赋值。
this指针---成员变量---成员函数---对象
利用指针,可以访问到该对象所有成员变量和变量函数
class base
{
public:
int changevalue(int nval)//成员函数访问成员变量
{
this->setvalue(nval)//通过this指针访问成员函数
return this->m_nval;//通过this指针访问成员变量
}
};
更多的情况下,this指针用来返回指向对象本身的指针以实现对象的链式引用,或者避免对同一对象进行赋值操作。
class point //描述一个点位置的类
{
public:
point(int x,int y):m_nx(x),m_ny(y)
{};
void operator=(point& pt)//重载赋值操作符"=",进行赋值操作
{
if (this !=&pt)//判断传递过来的参数是否是这个对象本身,如果是同一个对象,则不进行赋值操作
{
m_nx=pt.m_nx;
m_ny=pt.m_ny;
}
}
point& move(int x,int y)//移动点的位置
{
m_nx +=x;
m_ny +=y;
return *this;//返回对象本身,这样可以利用函数返回值进行链式引用
}
private:
int m_nx;
int m_ny;
};
c++中指针最难,指针也是最厉害的。
20150513
指针的运算
指针是一种基本数据类型,可参与算术运算,关系运算,赋值运算
最常用的指针运算时加减运算
例子:使用指针遍历整个数组
int narray[3]={1,2,3};//定义一个数组
int* pindex=narray;//将数组的起始地址复制给指针pindex
cout<<"指针指向的地址是:"<<pindex<<endl;//输出指针指向的地址
cout<<"指针指向的数据的值是:"<<*pindex<<endl;//输出值
pindex++;
cout<<"指针指向的地址是:"<<pindex<<endl;//输出指针指向的地址
cout<<"指针所指向的数据的值是:"<<*pindex<<endl;//输出值
int型指针加1,地址增加4个单位
字符型指针加1,地址增加1个单位
双精度型指针加2,地址增加16个单位。
将null跟指针进行比较,是判断一个指针是否可用的常用手段,可以有效避免指针的非法访问
int * pint;//定义一个指针,这时指针是一个随机值,指向随机的一个内存地址
pint=null;//将指针赋值为null,表示指针还没有合适的值,不可用
int narray[10];
pint=narray;//将数组首地址赋值给指针,这里赋值的是数值的首地址
if (null !=pint)//判断指针是否初始化完成
{
//指针有效,可以开始使用指针
}
null的讲解
是一个宏
#define null 0
实际上就是整数0,常用null值表示一个指针变量是空指针,它没有指向一个正确的内存地址,而是一个无效的不可访问的指针。
除了用null宏,还可以用nullptr表示一个空指针:
int* pint=nullptr;//定义一个指针,并将它赋值为空指针,表示此时指针不可用
if(nullprt==pint)//判断指针是否为空指针,如果是空指针,就对其进行赋值
{
pint=&narray;//将数组的首地址复制给指针
}
void函数的介绍
void体现的是一种抽象,更多用来修饰和限制一个函数。
如果一个函数没有返回值,则可用void作为这个函数的返回值类型,表示这个函数没有返回值;如果一个函数没有形式参数列表,也可以用void作为其形式参数,表示这个函数不需要任何参数。
void作用:
void类型的修饰作用
void类型指针作为指向抽象数据的指针,可以作为两个具有特定类型指针之间相互转换的桥梁。
如果两个指针的类型相同,可以直接在两个指针之间互相赋值;如果两个指针指向不同的数据类型,则不许使用强制类型转换运算符,把赋值操作符右边的指针类型转换为左边的指针类型
int* pint; //指向整型数的指针
float* pfloat;//指向浮点数的指针
pint=pfloat; //直接赋值会产生编译错误
pint=(int*)pfloat; //必须强制类型转换后进行赋值
当使用void类型指针时,就没有类型转换的麻烦,
void* pvoid; //void类型指针
int* pint; //int类型指针
pvoid=pint; //任何其他类型的指针都可以直接赋值给void类型指针
int8 pint;
float* pfloat=(float*)pint;//这种强制类型转换方式非常粗鲁,所以在c++中引入新的类型转换操作符static_cast
格式为:
static_cast<类型说明符>(表达式)
c语言类型转换和c++类型转换比较
c:
int nval1,nval2;
double fresult=((double)navl1)/nval2;
c++:
couble frelult=static_cast<double>(nval1)/nval2;
int n=2;
int* pn=&n;
int** ppn=&pn;
**定义了一个指向pn指针的指针
指向指针的指针的语法格式:
数据类型标识符** 指针变量名
20150514
使用指针作为函数参数,
使用指针作为函数返回值
引用
引用的本质就是变量的别名,就是变量的错号。对变量的引用的任何操作,就是对变量本省的操作
语法格式为:
数据类型& 引用名=变量名
//数据类型要跟引用的变量的数据类型相同,"&"符号表示声明的是一个引用,
int nintvalue=1;//首先定义一个整型变量
int& rintvalue=nintvalue;//定义一个整型引用并将它跟整型变量关联起来
指针和引用的区别:
引用在声明的时候必须初始化,指针在任何时候都可以完成初始化
就是说引用必须与某个合法的事先存在的变量关联,而指针可以为空指针(null),可以不与任何变量建立关联。
引用的主要应用就是传递函数的参数和返回值
void increase(int& nval)//给整型数加1的函数increase
{
nval+=1;
}
int nint =1;
increase(nint);//变量nint的值为2,这里调用了incarease函数,nint时参数,
三种传递函数参数和返回值的方法:
- 传值,传值是指直接将实际参数的值复制给形式参数,完成参数的传递
- 传指针,传指针是指将需要传递的数据的指针作为参数进行传递
- 传引用,传引用是指将需要传递的数据的引用作为参数进行传递
三种比较:
//传值,通过传值传入参数和传出返回值
int funcbyvalue(int x)
{
x=x+1;
return x;
}
void funcbypointer(int* p)
{
*p=*p +1;
}
void funcbyreference(int& r)
{
r=r+1;
}
int _tmain(int argc,_tchar* argv[])
{
int n =0;
cout<<"n的初始值,n="<<n<<endl;
funcbyvalue(n);
cout<<"传值,n="<<n<<endl;//其内部的形式参数x指示外部实际参数n的一份拷贝,当对x进行运算时不能改变n的值,所以输出结果为0
funcbypointer(&n);
cout<<"传指针,n="<<n<<endl;//指针p是指向外部变量n的指针,改变指针p所指向的数据的值实际上就是改变n的值,所以在函数内部对指针p所指向的数据的改变,就直接反映到n的数值的修改,输出结果为1,因为刚开始p=0,所以p=0+1=1
funcbyreference(n);
cout<<"传引用,n="<<n<<endl;//引用r就是外部变量n的引用,引用r和变量n都是同一个数据,在函数内部改变r同样也会修改n,所以最终输出结果为2.因为n=r,刚开始就是1+1,所以结果为2
return 0;
}
输出结果
n的初始值,n=0
传值,n=0
传指针,n=1
传引用,n=2
在可以的情况下,尽量使用传引用来传递函数参数,尽量少用传指针(指针使用具有一定的危险性),也少用传值(效率低下)
传指针:效率高,可以同时传入传出参数
传值:形式简单自然,便于理解,代码可读性高
传引用:效率高,可以同时传入传出参数,形式自然
c++中的亡羊补牢---异常处理
程序报错可能导致:算法失效,程序运行时无故停止,程序崩溃
最简单的就是使用return语句
异常处理机制
异常处理语法格式:
try
{
}
catch(异常类型名[形参名]) //捕获特定类型的异常
{
//对异常进行处理
}
catch(...)//如果省略具体的异常类型用"..."表示,则表示捕获所有类型的异常
{
//对所有类型的异常进行处理
}
finally:
{
//对异常的最终处理,对所有异常的处理都会执行这些语句
}
throw关键字语法结构:
throw异常表达式;
double divide(int a,int b)
{
if (0==b)
throw "不能使用0作为除数";
return(double)a/b;
}
处理异常的3个步骤:
1、抛出异常2、捕获异常3、处理异常
double divide(int a,int b)
{
if(0==b)
throw"不能使用0作为除数";//抛出异常
return (double)a/b;
}
try
{
double fresult=divide(3,0);//捕获异常
}
catch(char* pmsg)
{
cout<<"程序运行发生异常:"<<pmsg<<endl;//处理异常
}
根据函数接口声明抛出正确类型的异常对异常得到正确的处理很重要。
如果在函数的声明中没有包括异常的接口声明,则此函数可以抛出任何类型的异常
如:double divide(int a,int b);
如果将throw关键字之后的异常类型列表留空,则表示这个函数不会抛出任何类型的异常。
double divide(int a,int b) throw();
异常使用注意的地方:
1、不要用异常替代可以处理的分支结构;异常只能用来代表”异常“,是非预期的错误状态,不能用异常来完全代替返回值
bool bresult =isfinished();
if (true==besult)
{
return true;
}
else
{
return false;//不要用抛出异常来代替可以明确表示状态的返回值
}
2、不要再循环中使用异常处理
如果要对循环中发生的错误进行处理,使用返回值是一个好的选择
3、如果函数的参数是指针,则需要在函数入口处对这个指针的有效性进行检查
学习编写更加复杂的c++程序
一个程序的所有代码都包含在它的源文件或头文件中
源文件:用于实现程序的各种功能,将程序划分为多个子模块,每个模块单独放在一个源文件中进行管理。.cpp结尾