一、类的继承
对我们来说可以根据物体的形状把物体归为一类,比如矩形是形,椭圆形是形,三角形也是形。人们习惯把相同的性质抽取出来,成立一个类,再根据细节的不同,衍化出不同的小类,这些小类就是派生类。比如:关于形状,可以有下面的类层次结构,其中CShape是一个模糊称呼,涵盖了所有的形状,它可以描述这些形状的颜色,然后再细分成各个子类:
class CEllipse : public CShape //说明CEllipse是从CShape类派生来的 { }; |
关键字public表明派生类共享基类的公有接口。CWage可以看作是CEmployee的扩展,增加了下标范围检查的额外特性
#include <iostream>
using namespace std;
/*****建立一个员工薪资体系,包括职位、名字、薪资记录方法*****/
class CEmployee //职员
{
public:
CEmployee();
CEmployee(const char* nm)
{
strcpy(m_name, nm);
cout << "名字:" << m_name << endl;
}
virtual float computePay()//若要调用后面的该函数,则需要在父类定义并实例化才可以
{
return 0;
};
private:
char m_name[30];
};
//————————————————————————————————————
class CWage : public CEmployee //小时工;需要知道他的名字、工时m_hours,时薪m_wage
{
public:
CWage(const char* nm) : CEmployee(nm)
{
m_wage = 250.0;//时薪
m_hours = 40.0;//工时
cout << "wage: " << m_wage << endl << "hours: " << m_hours << endl;
}
void setWage(float wg) //成员函数
{
m_wage = wg;
}
void setHours(float hrs)
{
m_hours = hrs;
}
virtual float computePay()
{
float sale = m_hours * m_wage;
cout << "BasicMoney: " << sale << endl;
return sale;
}//计薪
private:
float m_wage; //时薪
float m_hours; //工作时间
};
二、类中的成员的访问特性
类成员主要是指类中的数据成员和方法。在定义类时,类成员是具有访问限制的,C++语言提供了3个访问限定符用于标识类成员的访问,分别为public、protected和private。public成员也被称为公有成员,public成员可以在程序的任何地方进行访问。protected成员被称为保护成员,该成员只能在该类和该类的派生类(子类)中访问,除此之外,程序的其他地方不能访问保护成员。private成员也被称为私有成员,该成员只能在该类的成员函数或友元中访问,派生类以及程序的其他地方均不能访问私有成员。如果在定义类时,没有指定访问限定符,默认为private。
与每个类相关的算法称为该类的公有接口(public interface)。数据以私有形式被存储在每个对象中,对数据的访问应与一般的程序代码隔离开来。
通常公有成员提供该类的公有接口——即实现这个类的行为的操作集合。它包括该类的所有的成员函数,或者只包括其中一个子集。私有成员提供私有实现代码——即存储信息的数据。 保护成员(protected)内的数据成员和成员函数不提供给一般的程序,只提供给派生类使用。
注:放在基类的私有区域内的成员只能提供该类自己使用,派生类不能使用。
基于对象的程序设计方法通过内联函数机制帮助用户进行内存的读取,降低了函数调用的大开销。在类定义中被定义的成员函数【如size()】会被自动当做是内联函数。此外,我们也可以用inline关键字显式的要求一个函数被视为内联函数。
面向对象的程序设计方法通过继承机制和动态绑定机制扩展了抽象数据类型:
继承机制是对现有实现代码的重用,动态绑定是指对现有的公有接口的重用。
拿图书馆的例子来说,共享的公有接口和私有的数据都放在一个抽象类(图书馆资料:LibraryMaterial)中,每个特殊的图书馆资料都从LibraryMaterial抽象类继承共享的行为,它们只需要提供与自身行为相关的算法和数据。
2.1 在定义类方法时,即定义函数时如果不需要在方法中修改类的数据成员,建议在方法声明的最后使用const关键字,表示用户不能在该方法中修改类的数据成员。
/*Set Username*/
void SetUsername(const char* pUsername)
{
if(pUsername != NULL)
{
strcpy(m_Username, pUsername);//将pUsername拷贝给m_Username
}
}
char *GetUsername() const //加const表示对GetUsername中的成员变量不做修改
{
return (char*) m_Username;
}
#include <iostream>
#include <string>
using namespace std;
class CScore
{
public:
CScore(string &sub, double &total, double &num):m_sub(sub), m_total(total), m_num(num) //有const的参数定义和无const参数的区别
{
}
CScore(const string &sub, const double &total, const double &num) :m_sub(sub), m_total(total), m_num(num)
{
}
string getSubject()
{
return m_sub;
}
double Total()
{
return m_total;
}
double Number()
{
return m_num;
}
double Ave() const
{
if (m_total)
return m_total / m_num;
else
return 0;
}
bool same_sub(const CScore &aa) const//把成绩单作为参数传入,不修改数据成员所以加const
{
return m_sub == aa.m_sub;
}
void add(const CScore &aa) //将后一个成绩单上的值加到前一个成绩单上,数据成员有变化故不能加const。
{
m_total += aa.m_total;
m_num += aa.m_num;
}
private:
string m_sub; //科目
double m_num; //人数
double m_total;//总分
};
int main()
{
string str = "数学";
double d1 = 255.0;
double d2 = 5.0;
CScore b(str, d1, d2);
cout << b.getSubject() << "," << b.Total() << "," << b.Number() << "," << b.Ave() << endl;
cout << "d2' = " << d2 << endl;
d2 = d1;
cout << "d2 = " << d2 << endl;
CScore c("a", 110.0, 11.0);
cout << "Ave =" << c.Ave() << endl;
//求两成绩单的和,要先检查这是否是同一科
if (b.same_sub(c)) //把c的值加到b上
c.add(b);
cout << "两个成绩单的和" << c.Total() << endl;
getchar();
return 0;
}
类成员的访问是通过对象实现的。对象被称为类的实例化。当程序中定义一个类时,并没有为其分配存储空间,只有当定义类的对象时,才分配存储空间。
int main()
{
CUser user;//声明CUser类的对象
user.SetUsername("hello");//实例化类成员
user.SetUserPassword("123456");
user.Logic();
return 0;
}
2.2 类成员是具有访问权限的,如果在类的外部访问私有或受保护的成员将出现访问错误。
2.3 成员访问操作符
通过使用成员访问操作符可以调用一个有名字的成员函数,比如上面的Logic(),或者普通的变量。如果类对象被定义为指针类型,需要使用“->”操作符来访问;非指针类型的用“.”操作符用来访问类对象。
例如:
class IntArray { public: int size() const { return size; } private: //内部数据 int _size; int *ia; } IntArray array; int array_size = array.size(); //array调用size()函数 |
#include <iostream>
#include <stdio.h>
#include <assert.h>
#include <string.h>
using namespace std;
class CUser
{
private:
char m_Username[120];
char m_Password[120];
public:
bool Logic(); //成员函数
/*Set Username*/
void SetUsername(const char* pUsername)
{
if(pUsername != NULL)
{
strcpy(m_Username, pUsername);//将pUsername拷贝给m_Username
}
}
char *GetUsername() const //加const表示对GetUsername中的成员变量不做修改
{
return (char*) m_Username;
}
/*Set UserPassword*/
void SetUserPassword(const char* pUserPassword)
{
if(pUserPassword != NULL)
{
strcpy(m_Password, pUserPassword);
}
}
char* GetUserPassword() const
{
return(char*) m_Password;
}
};
bool CUser::Logic() //类域操作符,可以指出成员函数属于哪个类
{
if(strcmp(m_Username, "hello") == 0 && strcmp(m_Password, "123456") == 0)
{
cout << "登录成功" << endl;
return true;
}
else
{
cout << "登录faile" << endl;
return false;
}
}
int main()
{
/*CUser user;//声明CUser类的对象user
user.SetUsername("hello");//实例化类成员
user.SetUserPassword("123456");
user.Logic();
return 0;*/
CUser *pUser = new CUser;//定义一个类的指针对象pUser,分配内存空间
pUser -> SetUsername("hello");
pUser ->SetUserPassword("123456");
pUser ->Logic();
delete pUser; //释放内存空间
return 0;
}
三、多重继承
一个类可以从多个基类中派生。在派生类由多个基类派生的多重继承模式中,基类是用基类表语法成分来说明的,多重继承的语法与单一继承很类似,只需要在声明继承的多个类之间加上逗号来分隔。定义形式如下:
class 派生类名:访问权限 基类名称,访问权限 基类名称,访问权限 基类名称 { 。。。 }; |
例如B类是由类C和类D派生的,可按如下方式进行说明:
class B: public C, public D { ... }; |
#include <iostream>
using namespace std;
class CPeople
{
public:
float Weight(float wg)
{
cout << "CPeople weight: " << wg << endl;
return 0;
}
};
class CAnimal
{
public:
float Weight(float wg)
{
cout << "CAnimal weight: " << wg << endl;
return 0;
}
};
class CThings : public CPeople, public CAnimal
{
public:
void show();
};
void CThings::show()
{
cout << "成功啦!" << endl;
}
int main(int argc, char* argv[])
{
CThings ct;
ct.show();
//ct.Weight(3); //错误
ct.CPeople::Weight(15);
ct.CAnimal::Weight(20);
getchar();
return 0;
}
如上例,不能直接用ct.Weight(),会出现错误提示
而是用作用域运算符ct.CPeople::Weight(15);显式指出所要调用的父类的函数,结果如下:
基类的说明顺序一般没有重要的意义,除非在某些情况下需要调用构造函数和析构函数创建清空内存时,在这样的情况下,会有一些影响。
由构造函数引起的初始化发生的顺序。若你的代码依赖于B的D部分要在C部分之前初始化,则此说明顺序将很重要,你可以在继承表中把D类放到C类的前面。初始化是按基类表中的说明顺序进行初始化的。
激活析构函数以做清除工作的顺序。当类的其他部分正在被清除时,如果某些特别的部分要保留,则该顺序也很重要。析构函数的调用是按基类表说明顺序的反向进行调用的。