声明类的一般形式:
class 类名 [:基类1[,基类2]]
{
private :
私有的数据和成员函数;
protected
保护的数据和成员函数;
public :
公用的数据和成员函数;
};
private: 私有的。仅类内可见。子类及外部都不见。
public: 公共的。类外也可见。
protected: 受保护的。类及派生的子类可见。
使用类时注意的小问题:
使用类时,类在声明时,会直接创建类的实体。如果不想在声明时创建,一般使用指针方式声明类。如:
CTest A(x, y); // 声明时直接创建了 A类,并执行A的构造函数 CTest(x, y); CTest *B; // 先声明一个类的指针 B = new CTest(x, y); // 在需要时,再创建类的实体。 delete B; // 释放 B 的实体。如不执行delete释放。*B所指向的实体会一直存在。 // 注意: A不用释放,他会在作用域结束时,自动释放,也就是函数执行完后。会自动释放A。
构造函数
类的构造函数与类同名。
无参构造函数在类对象进入其作用域时调用构造函数。即类被创建时,调用构造函数。
构造函数没有返回值,因此也不需要在定义构造函数时声明类型,这是它和一般函数的一个重要的不同之点。
默认无参构造函数不需用户调用,也不能被用户调用。
在构造函数的函数体中不仅可以对数据成员赋初值,而且可以包含其他语句。但是一般不提倡在构造函数中加入与初始化无关的内容,以保持程序的清晰。
尽管在一个类中可以包含多个构造函数,但是对于每一个对象来说,建立对象时只执行其中一个构造函数,并非每个构造函数都被执行。
如果用户自己没有定义构造函数,则C++系统会自动生成一个构造函数,只是这个构造函数的函数体是空的,也没有参数,不执行初始化操作。
派生类的构造函数不会继承父类的构造函数。需要显式执行。执行方法是在派生类的构造函数后加冒号:加父类的构造函数(可带参数)
如:
class CTest1: CTest{ public: CTest1(int x, int y):CTest(x) //CTest()是 CTest1的父类的构造函数, 假设其有参数。 { ….. //语句。 }; }
释构函数
类的释造函数与类同名,并在前面加上~。
当类对象的生命期结束时,会自动执行析构函数。
具体地说如果出现以下几种情况,程序就会执行析构函数:
如果在一个函数中定义了一个对象(它是自动局部对象),当这个函数被调用结束时,对象应该释放,在对象释放前自动执行析构函数。
static局部对象在函数调用结束时对象并不释放,因此也不调用析构函数,只在main函数结束或调用exit函数结束程序时,才调用static局部对象的析构函数。
如果定义了一个全局对象,则在程序的流程离开其作用域时(如main函数结束或调用exit函数) 时,调用该全局对象的析构函数。
如果用new运算符动态地建立了一个对象,当用delete运算符释放该对象时,先调用该对象的析构函数。
析构函数不返回任何值,没有函数类型,也没有函数参数。因此它不能被重载。一个类可以有多个构造函数,但只能有一个析构函数。
析构函数的作用并不是删除对象,而是在对象被册除(释放)时被调用的一个过程,以便在撤销对象占用的内存之前完成一些清理工作,使这部分内存可以被程序分配给新对象使用。
派生类是不能继承基类的析构函数的,也需要通过派生类的析构函数去调用基类的析构函数。
构造与释构函数执行顺序:
类在创建时:先依次执行上一父类的构造函数,再执行当前类构造函数。即类本身的构造函数是最后执行的。
基类构造-->父1构造-->…….-->最近的父类构造-->本类(当前类)构造
类在释放时:先执行本类释构函数。再依次执行父类的释构函数。如果有多层继承的,最后执行是最基类的释构。
本类(当前类)释构-->最近的父类释构-->…….--> 父1释构-->基类构造
类的函数成员的一些说明:
static 静态函数:
在类的函数声明前加上 static关键字。即该函数就成了类的静态函数。他会在程序运行时,就为该函数声明内存。
静态函数,是类本身的函数。直接用类名加作用域符::就可以调用,如: CTest::myFunc();
不需在类的实创建后才能调用。这跟类的一般函数不同。他也可以在类的实体创建后,像使用一般函数一样使用,如 A.myFunc();
class CTest { public: static void fn() { puts("TEST"); } static void fn(int x) // 像普通函数一样, 虚函数可重载。 { cout << "TEST:" << x << endl; } }; void main() { CTest::fn(); // 直接引用静态函数 CTest::fn(100); CTest A; // 类的实体 A.fn(); // 实体引用静态函数 A.fn(200); }
调用父类虚函数
调用父类的虚函数,在派生类作用域中可以用:this-><类名>::<函数名>();或 :: <类名>::<函数名>();在实体中可以用实例名加成员符 (.)或(->)<类名>::<函数名>
如示例:
class CFather { public: virtual void Say() // 父类的虚拟函数 { puts("father say"); } }; class CChild : public CFather { public: void Say() // 函数被重载 { ::CFather::Say(); //也可写成 this->CFather::Say(); puts("Child say"); } }; int _tmain(int argc, _TCHAR* argv[]) { CChild C; C.Say(); C.CFather::Say(); //实体引用父类虚函数 system("pause"); }
纯虚函数
纯虚函数是在声明虚函数时被“初始化”为0的函数。声明纯虚函数的一般形式是
virtual 函数类型 函数名 (参数表列) = 0;
如:
class CTest { public: CTest(); // 构造函数 protected: virtual float myFunc( )const =0; //纯虚函数 }
纯虚函数只有函数的名字而不具备函数的功能,不能被调用。它只是通知编译系统:“在这里声明一个虚函数,留待派生类中定义”。在派生类中对此函数提供定义后,它才能具备函数的功能,可被调用。纯虚函数只是了实现多态性。
关于纯虚函数需要注意的几点:
纯虚函数没有函数体;
最后面的“=0”并不表示函数返回值为0,它只起形式上的作用,告诉编译系统“这是纯虚函数”;
这是一个声明语句,最后应有分号。
类的模板函数
模板 Template 经常在C++中使用。由于模板都是编辑器预先根据函数调用情况。再将模板化的函数生成实际的地址。因此,不能实现分离编译。也就是说,如果函数声明在H头文件,函数实现在CPP文件时,通常情况是会出现编译错误的。
为了解决这个问题。一般是将模板函数的实现放在H头文件。但还有一个办法。就是给模板函数生成一个虚拟的调用函数。即在CPP文件中,编写一个专门的函数。带各种模板参数,调用一下模板函数。这样,就能让编译器为CPP文件生成的OBJ文件产生这个模板函数的地址。
从而就不会出现调用错误了。
关于限定模板函数的类型参数。一般有以下这种方式。
template <typename valueType> INT64 ReadData(valueType &outValue) { static_assert(!is_pointer<valueType>::value, "pointer type unsupported."); //指针型 static_assert(is_literal_type<valueType>::value, "base type unsupported. in: (int, double, char...)"); //基本类型 也可以写专门的某一类,如:is_floating_type return Read(&outValue, sizeof(valueType)); }
__super关键字
__super是VC中定义的关键字。是指代当前类的上一级类。在派生类中当要一直回调上一级的成员时。这个关键字很有用。