C++面相对象三大特性

封装

【封装复杂,对外留出简单接口】
封装是在设计类的一个基本原理,是将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体,也就是将数据与对数据进行的操作进行有机的结合,形成“类”,其中数据和函数都是类的成员。
封装是一个抽象的模型,该模型对外提供服务,而任何使用该模型的用户不需要知道模型是如何运作的。


那么设计这个类的时候需要考虑的事情就是:
1、 这个类是哪个对象的抽象
2、 这个类需要使用那些数据【数据】
3、 需要怎么来操作这些数据【属性方法】
4、 数据和属性方法是否可由外界访问【访问权限】


C++类中成员变量初始化
对于普通变量 ,有5中方法
第1种,在无参数的构造函数中初始化;
【构造函数中赋值】
第2种,带参数的构造函数中初始化;
【构造函数中赋值】
第3种,直接给成员变量赋值;
【使用类对象时,用对象名.变量名=*** 赋值】
第4种,调用成员函数来初始化成员变量;
【在类中某个方法中赋值,实质同方法一、二】
第5种,用this指针;
【当类中方法定义在类外面时,在变量名前用this->来确定这个变量是类中的】

对于非普通变量
static 静态变量:
static变量属于类所有,而不属于类的对象,因此不管类被实例化了多少个对象,该变量都只有一个。在这种性质上理解,有点类似于全局变量的唯一性。
【类外进行初始化】
const常量
【需要在声明的时候即初始化。因此需要在变量创建的时候进行初始化。一般采用在构造函数的初始化列表中进行。】
Reference 引用型变量
【引用型变量和const变量类似。需要在创建的时候即进行初始化。也是在初始化列表中进行。但需要注意用Reference类型。】

继承

访问权限
public: 所有都能访问
protected:成员可以被派生类对象访问但不能被该类型的普通用户访问
pravate:只能由基类的成员和友元访问

protected成员:可以看成是private和public的混合
像private成员一样,protected成员不能被类的用户访问
像public成员一样,protected成员可被该类的派生类访问

基类的protected可以被其派生类访问,派生类protected不能被其基类访问

没有继承的时候,类只有两种用户:类本身和该类的用户
Public和private就是为了区别这两种用户的权限

有了继承之后,有了第三种用户,用类派生新类的程序员,这时候,如果基类中需要让派生类访问,但不让外界访问的这种权限,就叫protected

经过继承后权限的变化

C++面相对象三大特性


派生类定义方法:

Class classname: ccess-lable base-class

每个派生类对象都有基类的部分。

如果要声明一个派生类,则声明包含类名但不包含派生列表,例如:下面的前向声明会导致编译错误:
Class Bulk_item: public item_base;
正确的前向声明应该是:
Class bulk_item;
Class item_base;

友元关系不能继承,基类的友元对派生类的成没有特殊访问权限,如果基类被授予友元关系,则只有基类具有特殊访问权限,该基类的派生类不能访问授予友元关系的类。【友元可以访问private 和protected】
一个类A中对另一个类B或一个函数b声明友元,那么B或者b就可以访问A中的私有成员和保护成员。



在继承的过程中需要遇到几个概念:重载(overload),重写(override,也称覆盖), 重定义(redefine,也称隐藏)
一、 重载(overload)【根据实参的不同,调用不同的参数】
指函数名相同,但是它的参数表列个数或顺序,类型不同。但是不能靠返回类型来判断。
(1)相同的范围(在同一个作用域中) ;
(2)函数名字相同;
(3)参数不同;
(4)virtual 关键字可有可无。
(5)返回值可以不同;
二、重写(也称为覆盖 override)【基类和子类对于同一个方法有不同的实现】
是指派生类重新定义基类的虚函数,特征是:
(1)不在同一个作用域(分别位于派生类与基类) ;
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有 virtual 关键字,不能有 static 。
(5)返回值相同(或是协变),否则报错;<—-协变这个概念我也是第一次才知道…
(6)重写函数的访问修饰符可以不同。尽管 virtual 是 private 的,派生类中重写改写为 public,protected 也是可以的
三、重定义(也成隐藏)【用的比较少】
(1)不在同一个作用域(分别位于派生类与基类) ;
(2)函数名字相同;
(3)返回值可以不同;
(4)参数不同。此时,不论有无 virtual 关键字,基类的函数将被隐藏(注意别与重载以及覆盖混淆) 。
(5)参数相同,但是基类函数没有 virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆) 。

#include <iostream>
#include <complex>
using namespace std;

class Base
{
public:
    virtual void a(int x)    {    cout << "Base::a(int)" << endl;      }
    // overload the Base::a(int) function
    virtual void a(double x) {    cout << "Base::a(double)" << endl;   }
    virtual void b(int x)    {    cout << "Base::b(int)" << endl;      }
    void c(int x)            {    cout << "Base::c(int)" << endl;      }
};

class Derived : public Base
{
public:
    // redefine the Base::a() function
    void a(complex<double> x)   {    cout << "Derived::a(complex)" << endl;      }
    // override the Base::b(int) function
    void b(int x)               {    cout << "Derived::b(int)" << endl;          }
    // redefine the Base::c() function
    void c(int x)               {    cout << "Derived::c(int)" << endl;          }
};

注意:

1、重载也可以出现在基类和子类之间,需要子类显示注明基类的函数

2、重写是针对基类中有virtual关键字的函数,子类可以不加virtual关键字,但是这个重写的函数仍然是虚函数。

3、派生类中虚函数的声明必须与机类中的定义方式完全匹配。但有一个例外,返回对基类的引用(或指针)的虚函数。派生类中的虚函数可以返回 基类函数所返回类型的派生类的引用(或指针)。

多态

基类指针(或引用)指向派生类对象,是动态绑定的关键。
而动态绑定是多态的主要实现方式
动态绑定两个条件:1、虚函数。2、通过基类类型的引用或指针进行函数调用

定义为virtual的函数是基类期待派生类对象重新定义的,基类希望派生类继承的函数不能定义为虚函数。

存在派生类型引用到基类类型引用的自动转换,反之则不存在

关于virtual关键字

要注意,继承层次的根类一般都要定义析构函数
保留virtual的目的是启用动态绑定,除构造函数外,任意非static成员函数都可以是虚函数,保留字virtual只在类内部的成员函数中声明出现,不能用在类定义体外部出现的函数定义上。

子类中强制覆盖虚函数机制

在成员函数代码中可以使用该作用域操作符覆盖虚函数机制。
Item_base *basep=&derived
Double d =naseP->Item_base::net_price(42)
强制调用基类中的版本
派生类虚函数调用基类版本时,必须显示使用作用域操作符。

引用转换不同于对象转换

把派生类对象传给希望接受基类引用的函数时,引用直接绑定该对象,实参是对该对象的引用,对象本身并为被复制,该对象仍然是派生类的对象。
将派生类对象传给希望接受基类对象的函数时,情况则不通,派生类对象会被“截取”,派生类对象的基类部分被复制到形参。
构造函数和复制控制函数

基类构造函数:

派生类构造函数:

首先初始化基类,然后根据声明次序初始化派生类的成员。(只能初始化直接基类)

派生类调用基类的构造函数和

 #include <iostream.h>
  class animal
  {
  public:
    animal(int height, int weight)
    {
      cout<<"animal construct"<<endl;
    }
    …
  };
  class fish:public animal
  {
  public:
    fish():animal(400,300)
    {
      cout<<"fish construct"<<endl;
    }
    …
  };
  void main()
  {
    fish fh;
  }

一旦类定义了自己的接口,与该类对象所有交互都应该通过该接口。

复制控制函数
如果派生类定义了自己的赋值操作,则改操作必须对象基类部分进行显示的赋值

析构函数
派生类析构函数不负责撤销基类对象的成员。基类中的析构函数必须为虚函数。

即使析构函数没有工作要做,继承层次的根类也应该定义一个虚析构函数。

作用域:
在继承的情况下:派生类的作用域嵌套在基类作用域中,如果不能在派生类作用域中确定名字,就在外围基类作用域中查找该名字的定义。

与基类成员同名的派生类成员将屏蔽对基类成员的直接访问。


在基类和派生类中使用同一名字的成员函数,其行为与数据成员一样,在派生类作用域中派生类成员将屏蔽基类成员。即使函数原型不同。基类成员也会被屏蔽。
在这种屏蔽的情况下:在派生类中可以使用using base::函数名
这样的方式来显示的使用基类中的同名函数

调用函数时,确定函数 遵循以下四个步骤:

1、 首先确定进行函数调用对象,指针,引用的静态类型
2、 在该类中查找函数-直接基类中找—>循环递归向上找
3、 一旦找到该名字,就进行常规类型检查,查看如果给定找到的定义,该函数调用是否合法。
4、 合法编译器就生成代码。如果是虚函数且通过引用或者指针调用。则编译器生成代码以确定对象的动态类型运行那个版本。否则,编译器直接生成代码直接调用函数



一些问题:
一 构造函数为什么不能是虚函数
1,从存储空间角度
虚函数对应一个vtable,这大家都知道,可是这个vtable其实是存储在对象的内存空间的。问题出来了,如果构造函数是虚的,就需要通过 vtable来调用,可是对象还没有实例化,也就是内存空间还没有,怎么找vtable呢?所以构造函数不能是虚函数。
2,从使用角度

虚函数主要用于在信息不全的情况下,能使重载的函数得到对应的调用。构造函数本身就是要初始化实例,那使用虚函数也没有实际意义呀。所以构造函数没有必要是虚函数。

二、构造函数中赋值和初始化列表

class CExample
{
public:
	int a;
	float b;
	//构造函数初始化列表
	CExample(): a(0),b(8.8)
	{}
	//构造函数内部赋值
	CExample()
	{
		a=0;
		b=8.8;
	}
};
现在我们来看构造函数中冒号初始化和函数初始化的问题,类构造函数的作用是创建一个类的对象时,调用它来构造这个类对象的数据成员,一要给出此数据成员分配内存空间,二是要给函数数据成员初始化,构造数据成员是按数据成员在类中声明的顺序进行构造。
   冒号初始化与函数体初始化的区别在于:
冒号初始化是给数据成员分配内存空间时就进行初始化,就是说分配一个数据成员只要冒号后有此数据成员的赋值表达式(此表达式必须是括号赋值表达式),那么分配了内存空间后在进入函数体之前给数据成员赋值,就是说初始化这个数据成员此时函数体还未执行。
   对于在函数中初始化,是在所有的数据成员被分配内存空间后才进行的。
这样是有好处的,有的数据成员需要在构造函数调入之后函数体执行之前就进行初始化如引用数据成员,常量数据成员和对象数据成员,看下面的一段程序:

class student
{
 public :
    student ()
 protected:
    const int a;
    int &b; 
}
student ::student (int i,int j)
{
   a=i;
   b=j;
}
在Student类中有两个数据成员,一个是常量数据成员,一个是引用数据成员,并且在构造函数中初始化了这两个数据成员,但是这并不能通过编译,因为常量初始化时必须赋值它的值是不能再改变的,与常量一样引用初始化也需要赋值,定义了引用后,它就和引用的目标维系在了一起,也是不能再被赋值的。所以C ++":"后初始化的机制,使引用和常量数据成员变为可能的,Student类的构造函数应为:
   student ::student(int i,int j):a(i),b(j){} 

class teach
{ 
 public :
    teach(char *p="name",int a=0)
                   
 protected:
    char name[30];
    int age;
}
teach::teach(char*p,int a)
{
    strcopy(name ,p);
    age=a;    
    cout>>name>>endl;
}
class student
{
public:
   student (char *p="name");                                   
protected;
    char name[30];
    teach teacher;
};
student::student(char *p)
{
    strcopy(name,p);
    cont>>name>>endl;
} 
在上面的程序中通不过编译,编译系统会告诉你teacher这个类对象缺默认构造函数,因为在teach 类中没有定义默认的构造函数。那么带参数的构造函数怎么进行构造呢?通过我们前面提到的冒号赋值。那它的构造函数应该是:
  
  student::student(char *p,char *pl,int ag):teacher(pl,ag)
  {
    strcopy(name,p);
    cont>>name>>endl;
  }
就是说在没有默认构造函数的时候,如果一个类对象是另一个类的数据成员,那么初始化这个数据成员,就应该放到冒号后面。这样可以带参数。在类的定义中,如:
    protected;
    char name[30];
    teach teacher
类对象是不能带参数的,因为它只是声明。

参考的资料:
1、 C++ premier
2、 http://www.wutianqi.com/?p=3171

3、 http://jodonchu.blog.51cto.com/3822410/1085831

4、http://www.cnblogs.com/BlueTzar/articles/1223169.html

5、http://www.cppblog.com/luyulaile/archive/2011/02/14/140059.html



C++面相对象三大特性,布布扣,bubuko.com

C++面相对象三大特性

上一篇:Java并发编程之线程管理(高级线程同步9)


下一篇:转移python