1类和对象 (本页包含 1.类和对象 2.继承 3.重载函数与重载运算符)
1.1 C++ 类 & 对象
C++ 在 C 语言的基础上增加了面向对象编程,C++ 支持面向对象程序设计。类是 C++ 的核心特性,通常被称为用户定义的类型。
类用于指定对象的形式,它包含了数据表示法和用于处理数据的方法。类中的数据和方法称为类的成员。函数在一个类被称为类的成员。
类的定义是以关键字class 开头。
类的对象的公共数据成员可以使用直接成员访问预算符(.)访问。私有的成员和受保护的成员不能使用直接成员访问运算符 (.) 来直接访问。
1.2 c++类成员函数
成员函数可以定义在类定义内部,或者单独使用范围解析运算符::来定义。在类定义中定义的成员函数把函数声明为内联的,即便没有使用inline标识符。所以您可以按照如下方式定义:
class Box{
public:
double length;
double breath;
double height;
double getLength(){ //类内部实现成员函数
return length;
}
double getVolume(); //成员函数声明,用于在外部进行实现。
}
也可以在类的外部使用范围解析运算符::定义(类内必须有类方法声明)
double Box::getVolume(void){
return length * breadth * height;
}
1.3 类访问修饰符
一个类可以有多个public、protected、或private标记区域(这四个关键字称为访问说明符)。
class Base {
public:
// public members go here
protected:
// protected members go here
private:
// private members go here
};
私有(private)成员
私有成员变量或者函数在类的外部是不可访问的。只有类和友元函数可以访问私有成员。
默认情况下,类的所有成员都是私有的。
protected保护成员
保护成员函数或者变量与私有成员十分相似,但是有点不同,保护成员在派生类(即子类)中可以访问。
1.4 类的构造函数
构造函数的名字与类的名称是完全相同的,并且不会返回任何类型,也不会返回void。构造函数可用于为某些成员变量设置初始值。
class Line{
public:
void setLength( double len );
double getLength( void );
Line(); // 这是构造函数
Line(double t);
private:
double length;
};
// 成员函数定义,包括构造函数
Line::Line(void){
cout << "Object is being created" << endl;
}
Line::Line(double len){
length=len;
}
int main(){
Line line;
Line line1(10);
return 0;
}
使用初始化列表来初始化字段
使用初始化列表来初始化字段:
Line::Line( double len): length(len){
cout << "Object is being created, length = " << len << endl;
}
上面的语法等同于如下语法:
Line::Line( double len){
cout << "Object is being created, length = " << len << endl;
length = len;
}
假设有一个类 C,具有多个字段 X、Y、Z 等需要进行初始化,同理地,您可以使用上面的语法,只需要在不同的字段使用逗号进行分隔,如下所
C::C( double a, double b, double c): X(a), Y(b), Z(c){
....
}
1.5 类的析构函数
和构造函数一样只不过在函数前面加~
即: ~Line();
1.6 拷贝构造函数
通过使用另一个同类型的对象来初始化新创建的对象。
复制对象把它作为参数传递给函数。
复制对象,并从函数返回这个对象
classname (const classname &obj) {
// 构造函数的主体
}
通过使用已有的同类型的对象来初始化新创建的对象:
class Line{
public:
int getLength( void );
Line( int len ); // 简单的构造函数
Line( const Line &obj); // 拷贝构造函数
~Line(); // 析构函数
private:
int *ptr;
};
// 成员函数定义,包括构造函数
Line::Line(int len){
cout << "Normal constructor allocating ptr" << endl;
// 为指针分配内存
ptr = new int;
*ptr = len;
}
Line::Line(const Line &obj){
cout << "Copy constructor allocating ptr." << endl;
ptr = new int;
*ptr = *obj.ptr; // copy the value
}
Line::~Line(void){
cout << "Freeing memory!" << endl;
delete ptr;
}
int Line::getLength( void ){
return *ptr;
}
void display(Line obj){
cout << "Length of line : " << obj.getLength() <<endl;
}
// 程序的主函数
int main( ){
Line line1(10);
Line line2 = line1; // 这里也调用了拷贝构造函数
display(line1); //在形参赋值的那一刻调用了拷贝构造函数
display(line2);
return 0;
}
Normal constructor allocating ptr
Copy constructor allocating ptr.
Copy constructor allocating ptr.
Length of line : 10
Freeing memory!
Copy constructor allocating ptr.
Length of line : 10
Freeing memory!
Freeing memory!
Freeing memory!
C++友元函数
类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数,所以友元函数没有this指针。
友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。
如果要声明函数为一个类的友元,需要在类定义中该函数原型前使用关键字 friend,如下所示:
class Box
{
double width;
public:
double length;
friend void printWidth( Box box );
void setWidth( double wid );
};
声明类 ClassTwo 的所有成员函数作为类 ClassOne 的友元,需要在类 ClassOne 的定义中放置如下声明:
friend class ClassTwo;
class Box{
double width;
public:
friend void printWidth( Box box );
void setWidth( double wid );
};
// 成员函数定义
void Box::setWidth( double wid ){
width = wid;
}
// 请注意:printWidth() 不是任何类的成员函数
void printWidth( Box box ){
/* 因为 printWidth() 是 Box 的友元,它可以直接访问该类的任何成员 */
cout << "Width of box : " << box.width <<endl;
}
// 程序的主函数
int main( ){
Box box;
// 使用成员函数设置宽度
box.setWidth(10.0);
// 使用友元函数输出宽度
printWidth( box );
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
Width of box : 10
C++ 内联函数
是通常与类一起使用。如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方。inline
C++ 类的静态成员
我们可以使用 static 关键字来把类成员定义为静态的。当我们声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本。静态成员在类的所有对象中是共享的。如果不存在其他的初始化语句,在创建第一个对象时,所有的静态数据都会被初始化为零。我们不能把静态成员放置在类的定义中,但是可以在类的外部通过使用范围解析运算符 :: 来重新声明静态变量从而对它进行初始化,如下面的实例所示。
类内定义:
static int objectCount;//类内不可以初始化
类外初始化:int Box::objectCount =0; //不再加static
cout << "Total objects: " << Box::objectCount << endl;
类内定义:
static int getCount() {
return objectCount;
}
类外访问:Box::getCount();
2.1 继承
已有的类称为基类,新建的类称为派生类。
class Rectangle: public Shape
访问控制和继承
派生类可以访问基类中所有的非私有成员。因此基类成员如果不想被派生类的成员函数访问,则应在基类中声明为 private。
访问 | public | protected | private |
---|---|---|---|
同一个类 | yes | yes | yes |
派生类 | yes | yes | no |
外部的类 | yes | no | no |
一个派生类继承了所有的基类方法,但下列情况除外:
- 基类的构造函数、析构函数和拷贝构造函数。
- 基类的重载运算符。
- 基类的友元函数。
我们几乎不使用 protected 或 private 继承,通常使用 public 继承。当使用不同类型的继承时,遵循以下几个规则:
- 公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问。
- 保护继承(protected): 当一个类派生自保护基类时,基类的公有和保护成员将成为派生类的保护成员。
- 私有继承(private):当一个类派生自私有基类时,基类的公有和保护成员将成为派生类的私有成员。
多继承
多继承即一个子类可以有多个父类,它继承了多个父类的特性。
C++ 类可以从多个类继承成员,语法如下:
3.1 重载运算符和重载函数
重载声明是指一个与之前已经在该作用域内声明过的函数或方法具有相同名称的声明,但是它们的参数列表和定义(实现)不相同。
当您调用一个重载函数或重载运算符时,编译器通过把您所使用的参数类型与定义中的参数类型进行比较,决定选用最合适的定义。选择最合适的重载函数或重载运算符的过程,称为重载决策。
重载函数:
这些同名函数的形式参数(指参数的个数、类型或者顺序)必须不同。您不能仅通过返回类型的不同来重载函数。
重载运算符:
c++允许重载大部分C++内置的运算符。
重载运算符有一个返回类型和一个参数列表。
Box operator+(const Box&);
如果我们定义上面的函数为类的非成员函数,那么我们需要为每次操作传递两个参数,如下所示:
Box operator+(const Box&, const Box&);
一元运算符重载:
// 重载负运算符( - )
Distance operator- ()
{
feet = -feet;
inches = -inches;
return Distance(feet, inches);
}
二元运算符
// 重载 + 运算符,用于把两个 Box 对象相加 (类内定义)
Box operator+(const Box& b){
Box box;
box.length = this->length + b.length;
box.breadth = this->breadth + b.breadth;
box.height = this->height + b.height;
return box;
}
关系运算符重载:
// 重载小于运算符( < )
bool operator <(const Distance& d){
if(feet < d.feet){
return true;
}
if(feet == d.feet && inches < d.inches){
return true;
}
return false;
}
输入/输出运算符重载
C++ 能够使用流提取运算符 >> 和流插入运算符 << 来输入和输出内置的数据类型。您可以重载流提取运算符和流插入运算符来操作对象等用户自定义的数据类型。
我们需要把运算符重载函数声明为类的友元函数,这样我们就能不用创建对象而直接调用函数。
面的实例演示了如何重载提取运算符 >> 和插入运算符 <<。
friend ostream &operator<<( ostream &output, const Distance &D ){
output << "F : " << D.feet << " I : " << D.inches;
return output;
}
friend istream &operator>>( istream &input, Distance &D ){
input >> D.feet >> D.inches;
return input;
}
C++ ++ 和 -- 运算符重载
// 重载前缀递增运算符( ++i )
Time operator++ () {
++minutes; // 对象加 1
if(minutes >= 60)
{
++hours;
minutes -= 60;
}
return Time(hours, minutes);
}
// 重载后缀递增运算符( i++ )
Time operator++( int ) {
// 保存原始值
Time T(hours, minutes);
// 对象加 1
++minutes;
if(minutes >= 60){
++hours;
minutes -= 60;
}
// 返回旧的原始值
return T;
}
赋值运算符重载:
void operator=(const Distance &D )
{
feet = D.feet;
inches = D.inches;
}
C++ 函数调用运算符 () 重载
函数调用运算符 () 可以被重载用于类的对象。当重载 () 时,您不是创造了一种新的调用函数的方式,相反地,这是创建一个可以传递任意数目参数的运算符函数。下面的实例演示了如何重载函数调用运算符 ()。
// 重载函数调用运算符
Distance operator()(int a, int b, int c)
{
Distance D;
// 进行随机计算
D.feet = a + c + 10;
D.inches = b + c + 100 ;
return D;
}
外部调用:D1(1,2,3);//对象名加括号内容
C++ 下标运算符 [] 重载
下标操作符 [] 通常用于访问数组元素。重载该运算符用于增强操作 C++ 数组的功能。
类内定义的一部分:
int& operator[](int i)
{
if( i > SIZE )
{
cout << "索引超过最大值" <<endl;
// 返回第一个元素
return arr[0];
}
return arr[i];
}
int main(){
safearay A;
cout << "A[2] 的值为 : " << A[2] <<endl;
cout << "A[5] 的值为 : " << A[5]<<endl;
cout << "A[12] 的值为 : " << A[12]<<endl;
return 0;
}
C++ 类成员访问运算符 -> 重载
类成员访问运算符( -> )可以被重载,但它较为麻烦。它被定义用于为一个类赋予"指针"行为。运算符 -> 必须是一个成员函数。如果使用了 -> 运算符,返回类型必须是指针或者是类的对象。
运算符 -> 通常与指针引用运算符 * 结合使用,用于实现"智能指针"的功能。这些指针是行为与正常指针相似的对象,唯一不同的是,当您通过指针访问对象时,它们会执行其他的任务。比如,当指针销毁时,或者当指针指向另一个对象时,会自动删除对象。
间接引用运算符 -> 可被定义为一个一元后缀运算符。也就是说,给出一个类:
class Ptr{
//...
X * operator->();
};
类 Ptr 的对象可用于访问类 X 的成员,使用方式与指针的用法十分相似。例如:
void f(Ptr p )
{
p->m = 10 ; // (p.operator->())->m = 10
}
语句 p->m 被解释为 (p.operator->())->m。同样地,下面的实例演示了如何重载类成员访问运算符 ->。
#include <iostream>
#include <vector>
using namespace std;
// 假设一个实际的类
class Obj {
static int i, j;
public:
void f() const { cout << i++ << endl; }
void g() const { cout << j++ << endl; }
};
// 静态成员定义
int Obj::i = 10;
int Obj::j = 12;
// 为上面的类实现一个容器
class ObjContainer {
vector<Obj*> a;
public:
void add(Obj* obj){
a.push_back(obj); // 调用向量的标准方法
}
friend class SmartPointer;
};
// 实现智能指针,用于访问类 Obj 的成员
class SmartPointer {
ObjContainer oc;
int index;
public:
SmartPointer(ObjContainer& objc){
oc = objc;
index = 0;
}
// 返回值表示列表结束
bool operator++() // 前缀版本{
if(index >= oc.a.size()) return false;
if(oc.a[++index] == 0) return false;
return true;
}
bool operator++(int) // 后缀版本{
return operator++();
}
// 重载运算符 ->
Obj* operator->() const {
if(!oc.a[index]){
cout << "Zero value";
return (Obj*)0;
}
return oc.a[index];
}
};
int main() {
const int sz = 10;
Obj o[sz];
ObjContainer oc;
for(int i = 0; i < sz; i++){
oc.add(&o[i]);
}
SmartPointer sp(oc); // 创建一个迭代器
do {
sp->f(); // 智能指针调用
sp->g();
} while(sp++);
return 0;
}