C++ 类与对象

对象

对象的定义

  • 像结构体一样定义和使用对象及其公开的成员
  • 私有成员不可在对象外部直接访问

对象的构造

对象构造的意义
-构造就是初始化,在定义对象时初始化其数据成员

对象构造的技术手段:使用构造函数:

  • 与类类型同名,没有返回值类型(包括void类型)
  • 构造函数允许重载
  • 构造函数可以带缺省参数,但是不建
  • 至少公开一个构造函数
  • 只能由系统在创建对象时自动调用,程序其他部分不能直接调用

缺省构造函数

类没有明确的构造函数

  • 系统自动产生一个缺省构造函数,自动调用
  • 缺省构造函数无参数,且函数体中没有任何语句
  • 如果定义了任意一个构造函数,则不再生成缺省构造函数

拷贝构造函数

  • 拷贝构造函数用于构造已有对象的副本
  • 拷贝构造函数单参数,为本类的常对象的引用
  • 如未定义,系统自动产生一个缺省拷贝构造函数
  • 缺省拷贝构造函数为位拷贝(浅拷贝),如需深拷贝(例如成员为指针),需自行定义
/*  圆类库接口“circle.h”*/
class Circle
{
public:
  Circle( const Circle & that );
  ……
private:
  double r, x, y;
};

/*  圆类库实现“circle.cpp”*/
Circle::Circle( const Circle & that )
{
  this->r = that.r;
  this->x = that.x;
  this->y = that.y;
};

构造函数的初始化列表 P258

初始化列表的目的与意义

  • 在构造对象时,同步构造内部对象
  • 部分成员(常量与引用)只能初始化,不能赋值
  • 部分成员(类的对象)如果赋值,将导致两次构造
    • 在分配内存时,调用缺省构造函数构造,然后执行构造函数体内
        的赋值语句再次构造,效率不佳
    • 若类没有缺省构造函数,则会导致问题

注意事项

  • 成员初始化按照成员定义顺序,而不是初始化列表顺序
  • 必须保持初始化列表和成员定义的顺序一致性,但允许跳过部分成员;否则后续成员可能不会正确初始化

对象的析构

对象析构的意义

  • 析构就是终止化,在对象生命期结束时清除它

对象析构的技术手段:使用析构函数

  • 与类类型同名,前有“~”记号,无返回值类型(包括void类型),无参数
  • 析构函数必须是公开的
  • 可以由系统在销毁对象时自动调用,也可以由程序其他部分直接调用,但两者工作原理不同
  • 每个类只能有一个析构函数
  • 若未定义,系统会自动产生一个缺省析构函数,该函数无代码

对象数组

像普通数组一样定义和使用

对象数组的初始化

  • 当构造函数单参数时,像普通数组一样构造所有元素
Circle circles[2] = { Circle(1.0, 0.0, 0.0), Circle(2.0, 1.0, 1.0) };

类与对象的成员

内联函数 P213

目的:
程序优化,展开函数代码而不是调用

内联函数使用的注意事项

  • 在函数定义前添加inline关键字,仅在函数原型前使用此关键字无效
  • 编译器必须能看见内联函数的代码才能在编译期展开,因而内联函数必须实现在头文件中
  • 在类定义中给出函数体的成员函数自动成为内联函数
  • 函数体代码量较大,或包含循环,不要使用内联
  • 构造函数和析构函数有可能隐含附加操作,慎用内联
  • 内联函数仅是建议,编译器会自主选择是否内联

常数据成员

常数据成员:值在程序运行期间不可变

  • 定义格式:const 类型 数据成员名称;
  • 初始化:只能通过构造函数中的初始化列表进行

使用示例

 class A
   {
 public:
   A( int a );
 private:
   const int num;
 };

 A::A( int a ) : num(a) { …… };

常成员函数

常成员函数:不能修改对象成员值的函数

  • 定义格式:类型 成员函数名称(参数列表) const;
  • 常成员函数不能调用类中非常成员函数
  • 静态成员函数不能定义为常成员函数

使用示例

 class Circle{
 public:
   double GetArea() const;
   ……
 };
 double Circle::GetArea() const{ …… }

静态数据成员

静态数据成员只有一份,由该类所有对象共享

  • 声明格式:static 类型 静态数据成员名称;
  • 仅声明,不在对象上分配空间
  • 定义格式:类型 类名称::静态数据成员名称 = 初始值;
  • 必须在外部初始化,初始化动作与访问控制无关

示例
 

class A
 {
 private:
   static int count;
 };

 int A::count = 0;

静态成员函数

  • 在类而不是对象上调用
  • 目的:访问类的静态数据成员;若要访问类的非静态数据成员,必须指定对象或者使用指向对象的指针

示例:

class A
 {
 public:
   static int f();
   static int g( const A & a );
 private:
   static int count;
   int num;
 };

int A::count = 0;
int A::f()
{
  return count;
}
int A::g( const A & a )
{
  return a.num;
}

静态常数据成员

静态常数据成员:值在程序运行期间不可变,且只有唯一副本

  • 定义格式:static const 类型 数据成员名称
  • 初始化:只能在类的外部初始化

使用示例

 class A
 {
 private:
   static const int count;
 };

 const int A::count = 10;

友元函数与友元类

友元:慎用!破坏类数据封装与信息隐藏

  • 类的友元可以访问该类对象的私有与保护成员
  • 友元可以是函数、其他类成员函数,也可以是类
  • 定义格式:friend 函数或类声明;
  • 两个类的友元关系不可逆,除非互为友元

使用示例

 class Circle
 {
   friend double Get_Radius();
   friend class Globe;  // 将Globe类所有成员函数声明为友元
 private:
   double radius;
 };

继承

继承的意义

  • 派生类拥有基类的全部属性与行为
  • 派生类可以增加新的属性与行为

单继承

单继承的基本语法格式

  • class 派生类名称 : 派生类型保留字 基类名称 { … };

派生类型保留字

  • public:基类的public、protected成员在派生类中保持,private成员在派生类中不可见(属于基类隐私
  • protected:基类的private成员在派生类中不可见,public、protected成员在派生类中变为protected成
  • private:基类的private成员在派生类中不可见,public、protected成员在派生类中变为private成员

设计类时若需要使用继承机制,建议将派生类需要频繁使用的基类数据成员设为protected

示例:

// “Point.h”

#include <iostream>
using namespace std;

class Point
{
public: 
  Point(int x = 0, int y = 0) : _x(x), _y(y) { }
  Point(const Point& pt) : _x(pt._x), _y(pt._y) { }
  int GetX() const {  return _x;  }
  void SetX(int x) {  _x = x;  }
  int GetY() const {  return _y;  }
  void SetY(int y) {  _y = y;  }
  friend ostream& operator<<( ostream& os, const Point& pt );
protected:
  int _x, _y;
}; 

class Point3D: public Point
{
public:
  Point3D(int x = 0, int y = 0, int z = 0) : Point(x,y), _z(z) { }
  Point3D(const Point3D& pt3d) : Point(pt3d._x, pt3d._y), _z(pt3d._z) { }
  int GetZ() const {  return _z;  }
  void SetZ(int z) {  _z = z;  }
  friend ostream& operator<<( ostream& os, const Point3D& pt3d );
protected:
  int _z;
};
//“point.cpp”
#include "point.h"

ostream& operator<<( ostream& os, const Point& pt )
{
  os << "(" << pt._x << ", " << pt._y << ")";
  return os;
}

ostream& operator<<( ostream& os, const Point3D& pt3d )
{
  os << "(" << pt3d._x << ", " << pt3d._y << ", " << pt3d._z << " )";
  return os;
}

多继承

多继承的基本语法格式
 class 派生类名称: 派生类型保留字 基类名称1, 派生类型保留字 基类名称2, … { … };
 
多继承示例

class A { … };  class B { … };
class C: public A, protected B { … };

多继承可能导致的问题:派生类中可能包含多个基类副本,慎用!

class A { … };  class B: public A { … };
class C: public A, protected B { … };

函数覆盖与二义性

派生类成员函数名称与基类相同
 

class Point {  void Print();  };
class Point3D: public Point { void Print();  };
Point pt( 1, 2 );
Point3D pt3d( 1, 2, 3 );

调用示例

  • pt.Print():调用Point类的Print成员函
  • pt3d.Print():调用Point3D类的Print成员函数
  • Point类的Print成员函数在Point3D类中仍存在,但被新类中的同名函数覆盖
  • 访问规则(解析):pt3d.Point::Print()

派生类成员函数名称与基类相同

class A {  public: void f();  };
class B {  public: void f();  };
class C: public A, public B {  public: void f();  };
C c;

调用示例

  • c.f():调用C类的成员函数
  • c.A::f():调用C类继承自A类的函数
  • c.B::f():调用C类继承自B类的函数

虚基类

虚拟继承的目的

  • 取消多继承时派生类中公共基类的多个副本,只保留一份
  • 格式:派生时使用关键字virtual

使用示例:D中只有A的一份副本

class A {  public: void f();  };
class B: virtual public A {  public: void f();  };
class C: virtual public A {  public: void f();  };
class D: public B, public C {  public: void f();  };

派生类的构造函数与析构函数

构造函数的执行顺序

  • 调用基类的构造函数,调用顺序与基类在派生类中的继承顺序相同
  • 调用派生类新增对象成员的构造函数,调用顺序与其在派生类中的定义顺序相同
  • 调用派生类的构造函数

析构函数的执行顺序

  • 调用派生类的析构函数
  • 调用派生类新增对象成员的析构函数,调用顺序与其在派生类中的定义顺序相反
  • 调用基类的析构函数,调用顺序与基类在派生类中的继承顺序相反

类的赋值兼容性

公有派生时,任何基类对象可以出现的位置都可以使用派生类对象代替

  • 将派生类对象赋值给基类对象,仅赋值基类部分
  • 用派生类对象初始化基类对象引用,仅操作基类部分
  • 使指向基类的指针指向派生类对象,仅引领基类部分

保护派生与私有派生不可以直接赋值

  • 尽量不要使用保护派生与私有派生
#include <iostream>  
#include <string>  
using namespace std;  
class Base  
{  
public:  
  Base(string s) : str_a(s) { }  
  Base(const Base & that) { str_a = that.str_a; }  
  void Print() const { cout << "In base: " << str_a << endl; }  
protected:  
    string str_a;  
}; 
class Derived : public Base
{  
public:  
    Derived(string s1,string s2) : Base(s1), str_b(s2) { }  // 调用基类构造函数初始化
    void Print() const { cout << "In derived: " << str_a + " " + str_b << endl; }  
protected:  
    string str_b;  
}; 
int main()  
{  
  Derived d1( "Hello", "World" );  

  Base b1( d1 );   // 拷贝构造,派生类至基类,仅复制基类部分
  d1.Print();  // Hello World
  b1.Print();  // Hello

  Base & b2 = d1;  // 引用,不调用拷贝构造函数,仅访问基类部分
  d1.Print();  
  b2.Print();  

  Base * b3 = &d1;  // 指针,不调用拷贝构造函数,仅引领基类部分
  d1.Print();
  b3->Print();  
  return 1;  
}

多态

P536

  • 当我们使用基类的指针引用调用一个虚成员函数时会执行动态绑定
  • 引用或指针的静态类型与动态类型不同这一事实是C++语言支持多态性的根本所在
  • 当我们使用基类的引用或指针调用基类中定义的一个函数时,我们不知道该函数真正作用的对象是什么类型,因为它可能是基类的对象,也可能是派生类的对象。如果该函数是虚函数,则直到运行时才会决定到底执行哪个版本,判断的依据是引用或指针所绑定的对象的真实类型
  • 在C++中 ,会为每一个类的对象维持一个虚拟表,用虚拟表的指针指向这个虚表,每一个虚表中会记录这个类所实现的所有虚函数的入口地址。当是基类时,写入基类虚函数地址;当构造的是派生类对象时时,写入派生类虚函数地址;所以即使使用一个指向基类的指针指向这个派生类的对象,当它调用虚函数时,一查虚拟表,查到的依然是派生类的虚函数的入口地址;所以那个指向基类的指针,才能够调用派生类的虚函数;若是非虚函数,则做不到,是哪个类,就调用哪个类对应的函数;

多态性

  • 目的:不同对象在接收到相同消息时做不同响应
  • 现象:对应同样成员函数名称,执行不同函数体

多态性的实现**

  • 虚函数:使用virtual关键字声明成员函数
  • 声明格式:virtual 函数返回值 函数名称(参数列表);

非虚函数示例:

// 头文件 
#include <iostream>
using namespace std;

class Account
{
public:
  Account( double d ) : _balance(d) { }
  double GetBalance() const;
  void PrintBalance() const;
private:
    double _balance;
};

inline double Account::GetBalance() const
{
  return _balance;
}

class CheckingAccount : public Account
{
public:
   CheckingAccount(double d) : Account(d) { }
   void PrintBalance() const;
};

class SavingsAccount : public Account
{
public:
   SavingsAccount(double d) : Account(d) { }
   void PrintBalance() const;
};
// 源文件

void Account::PrintBalance() const
{
   cerr << "Error. Balance not available for base type." << endl; 
}

void CheckingAccount::PrintBalance() const
{
  cout << "Checking account balance: " << GetBalance() << endl;
}

void SavingsAccount::PrintBalance() const
{
  cout << "Savings account balance: " << GetBalance() << endl;
}

int main()
{
   CheckingAccount * checking = new CheckingAccount( 100.00 ) ;
   SavingsAccount  * savings  = new SavingsAccount( 1000.00 );

   Account * account = checking;
   account->PrintBalance();

   account = savings;
   account->PrintBalance();

  delete checking;
  delete savings;  
  return 0; 
}

虚函数示例

// 头文件
#include <iostream>
using namespace std;

class Account
{
public:
  Account( double d ) : _balance(d) { }
  double GetBalance() const;
  virtual void PrintBalance() const;
private:
    double _balance;
};

inline double Account::GetBalance() const
{
  return _balance;
}

class CheckingAccount : public Account
{
public:
   CheckingAccount(double d) : Account(d) { }
   virtual void PrintBalance() const;
};

class SavingsAccount : public Account
{
public:
   SavingsAccount(double d) : Account(d) { }
   virtual void PrintBalance() const;
};
// 源文件

void Account::PrintBalance() const
{
   cerr << "Error. Balance not available for base type." << endl; 
}

void CheckingAccount::PrintBalance() const
{
  cout << "Checking account balance: " << GetBalance() << endl;
}

void SavingsAccount::PrintBalance() const
{
  cout << "Savings account balance: " << GetBalance() << endl;
}

int main()
{
   CheckingAccount * checking = new CheckingAccount( 100.00 ) ;
   SavingsAccount  * savings  = new SavingsAccount( 1000.00 );

   Account * account = checking;
   account->PrintBalance();

   account = savings;
   account->PrintBalance();

  delete checking;
  delete savings;
  return 0;
}

纯虚函数

  • 充当占位函数,没有任何实现
  • 派生类负责实现其具体功能
  • 声明格式:virtual void f( int x ) = 0;

抽象类

  • 带有纯虚函数的类
  • 作为类继承层次的上层

虚析构函数

  • 保持多态性需要虚析构函数,以保证能够正确释放对象
  • 若是非虚函数,则和特定的类相关;当在一个指向基类的指针上去销毁它的派生类的对象的时候,是没有办法正确销毁的

抽象类与虚函数示例

// “Point.h”

#include <iostream>
using namespace std;

//纯虚类
class Point
{
public: 
  Point() { }
  virtual void Print() const = 0;
//  virtual ~Point();
}; 

class Point2D : virtual public Point
{
public: 
  Point2D( int x = 0, int y = 0 ) : _x(x), _y(y) { }
  Point2D( const Point2D& pt2d ) : _x(pt2d._x), _y(pt2d._y) { }
  int GetX() const {  return _x;  }
  void SetX( int x ) {  _x = x;  }
  int GetY() const {  return _y;  }
  void SetY( int y ) {  _y = y;  }
  virtual void Print() const;
protected:
  int _x, _y;
}; 

class Point3D: virtual public Point2D
{
public:
  Point3D( int x = 0, int y = 0, int z = 0 ) : Point2D(x,y), _z(z) { }
  Point3D( const Point3D& pt3d ) : Point2D(pt3d._x, pt3d._y), _z(pt3d._z) {  }
  int GetZ() const {  return _z;  }
  void SetZ( int z ) {  _z = z;  }
  virtual void Print() const;
protected:
  int _z;
};
// “point.cpp”
void Point2D::Print()
{
  cout << "( " << _x << ", " << _y << " )";
}
void Point3D::Print()
{
  cout << "( " << _x << ", " << _y << ", " << _z << " )";
}
// “main.cpp”
int main()
{
//pt1和pt2是指向基类的指针;我们构造的是派生类的对象,让基类的指针指向派生类,
//然后在基类的指针上调用Print(),没问题,它调用的都将是派生类的那个虚函数
   Point * pt1 = new Point2D( 1, 2 );
   Point * pt2 = new Point3D( 1, 2, 3 );
   pt1->Print();
   pt2->Print();
}
//若写了虚析构函数,则在pt1和pt2上删除,则删除的都是二维点和三维点的那个对象,
//绝不会删除抽象的那个点的对象,
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值