1.虚函数
1.1 不含有任何数据成员或者虚函数的class或者struct大小为1,含有虚函数的对象在基地址部分有一个vptr,指向虚函数表,因此大小为4个字节。
1.2 动态绑定的原理:假设派生类和基类存在覆盖的关系(基类中定义了虚函数),那么派生类在虚函数表中,会覆盖掉基类相应的虚函数。当程序执行的时候,根据基类指针找到vptr,根据vptr找到vtable,然后找到相应的版本去执行。所以执行的是覆盖的版本,而具体被哪个版本覆盖是由具体的对象类型所决定的,所以才实现了根据对象的具体类型去调用相应的函数(参考资料:http://blog.csdn.net/haoel/article/details/1948051/)。
1.3 当类中含有虚函数的时候(不全面),需要把析构函数设为virtual析构函数。
1.3.1 如下例所示,当用基类的指针指向一个派生类的对象时,基类指针会把该内存空间当做是一个基类的对象,对后面派生类的空间不可见,因此,当析构的时候只析构了基类大小的空间,这就造成了资源释放不完全。
#include <iostream> using namespace std; /* * 基类指针指向子类对象空间 * 释放基类指针时会释放不完全 */ class Base{ public: Base(){ cout << "Base..." << endl; } ~Base(){ cout << "~Base..." << endl; } }; class Derived : public Base{ public: Derived(){ cout << "Derived..." << endl; } ~Derived(){ cout << "~Derived..." << endl; } }; int main(int argc, const char *argv[]) { Base *bp = new Derived(); delete bp; //这里只调用了基类的析构函数 派生类的地址空间没有被释放 return 0; }
1.3.2 当把基类的析构函数设为为虚函数时,在回收资源的时候会根据实际的类型动态绑定,将资源全部回收。
1.4 函数声明为virtual,意味着需要由用户去继承,以重新实现。
1.5 不要试图重定义基类的非virtual函数。
1.6 虚函数尤其是纯虚函数,相当于制定了一种约定、契约,凡是继承并改写(或者实现纯虚函数)的子类,都必须遵守这一约定。
#ifndef __THREAD_H__ #define __THREAD_H__ #include <pthread.h> class Thread{ public: Thread(); virtual ~Thread(){} void start(); static void* thread_func(void *); virtual void run() = 0; void join(); private: pthread_t tid; }; #endif #include "thread.h" #include <iostream> Thread::Thread() :tid(-1) { } void Thread::start(){ pthread_create(&tid, NULL, thread_func, this); } void *Thread::thread_func(void *arg){ Thread *pt = static_cast<Thread *>(arg); pt->run(); //动态绑定 } void Thread::join(){ pthread_join(tid, NULL); } #include "thread.h" #include <iostream> #include <unistd.h> using namespace std; /* * 定义线程类 将执行的函数run定义为纯虚函数 * 由派生类实现不同的操作 */ class TestThread : public Thread{ public: void run(){ while(1){ sleep(1); cout << "hello ..." << endl; } } }; int main(int argc, const char *argv[]) { TestThread th; th.start(); th.join(); return 0; }
2.模板
2.1 模板的几个要点:
a)泛型和模板:泛型就是通用的技术。泛型编程(以独立于任何特定类型的的方式编写代码)不等于模板,模板只是泛型的其中一种手段。
b)模板就是一种生产函数或者类的模型,以 T add(const T &a, const T &b)为例,当使用 add(1,2)时,编译器会进行模板实参推断,产生一个int add(int, int)的模板,当使用 add(string("hello"), string("world"))时,编译器产生string add(const string &, const string&)的版本。
c)vector不是一个类,而是一个类模板,以它为范本,根据实际调用产生的 vector<int>/vector<string> 才是真正的类。
d)模板就是一种代码产生器,或者是一个代码输出函数,输入的为类型,输出的是符合该类型运算的类或者函数。
e)模板的这种代码产生功能,成为一种编译期多态。
f)模板代码在编译时,只编译那些需要的部分,所以当声明map<Test, int> m;而不做其他使用时,,即使Test不支持 < 操作,仍然没有错误, 如下例。
#include <iostream> #include <map> using namespace std; class Test{ public: int a_; }; int main(int argc, const char *argv[]) { map<Test, int> m; //正确 尽管Test不支持< 编译器没有检测 Test t; m[t] = 1; //错误 此时编译器会检测是否支持< return 0; }
2.2 几个简单的模板
2.1.1 程序,这里要注意 模板使用template关键字,尖括号内的类型定义可以使用typename,也可以定义为常量,即为非类型模板形参。。
#include <iostream> #include <string> #include <vector> using namespace std; /* * 两个参数的类型可以不同 */ template <typename T1, typename T2> T1 add(const T1 &a, const T2 &b){ return a + b; } int main(int argc, const char *argv[]) { cout << add(2, 2.3) << endl; return 0; }
2.1.2 程序2。这里T::size_type *p这句话在模板中存在歧义,可以把T::size_type解释成一种类型,所以这里定义了一个指针p,还可以把T::size_type解释成一个变量,所以这样可以看做乘法。解决方案就是在前面加上typename,来说明这是一个定义,而不是乘法。typename T::size_type *p。
#include <iostream> #include <string> #include <vector> using namespace std; template <typename T, typename V> void test(T a, V b){ //这里存在歧义 //T::size_type *p; typename T::size_type *p; } int main(int argc, const char *argv[]) { test(string("hello"), 8); return 0; }
2.3 类模板
2.3.1 编写类模板的注意事项:
a)类的声明和实现放到同一个hpp文件中;
b)所有的函数均为 inline;
c)每个函数在类外实现都要加上模板参数类表;
d)类名要写完整,例如SmartPtr<T>,不能漏掉尖括号,因为SmartPtr不是完整的类。
2.3.2 智能指针类模板
#ifndef __SMART_HPP__ #define __SMART_HPP__ #include <stddef.h> template <typename T> class Smartptr{ public: Smartptr(); Smartptr(T *); ~Smartptr(); void resetPtr(T *); //这里T不能为const const T *getPtr() const; T &operator*(); const T &operator*()const; T *operator->(); const T *operator->()const; operator bool() const; private: Smartptr(const T&); Smartptr operator= (const T&); T *ptr_; }; //智能指针模板类的实现 //每个函数在类外的实现都要加上模板参数列表 template <typename T> inline Smartptr<T>::Smartptr() //类名包括参数 :ptr_(NULL) { } template <typename T> inline Smartptr<T>::Smartptr(T* ptr) :ptr_(ptr) { } template <typename T> inline Smartptr<T>::~Smartptr(){ delete ptr_; } template <typename T> inline void Smartptr<T>::resetPtr(T *ptr){ if(ptr_ != ptr){ delete ptr_; ptr_ = ptr; } } template <typename T> inline const T *Smartptr<T>::getPtr()const{ return ptr_; } template <typename T> inline T &Smartptr<T>::operator*(){ return *ptr_; } template <typename T> inline const T &Smartptr<T>::operator*()const{ return *ptr_; } template <typename T> inline T *Smartptr<T>::operator->(){ return ptr_; } template <typename T> inline const T *Smartptr<T>::operator->() const{ return ptr_; } template <typename T> inline Smartptr<T>::operator bool() const{ return ptr_; } #endif #include "smartptr.hpp" #include <iostream> #include <assert.h> using namespace std; class Animal{ public: Animal(){ cout << "Animal ..." << endl; } ~Animal(){ cout << "~Animal..." << endl; } void display(){ cout << "in Animal..." << endl; } }; int main(int argc, const char *argv[]) { Smartptr<Animal> pt(new Animal); assert(pt); // 这里重载了bool类型 cout << pt.getPtr() << endl; pt.resetPtr(NULL); assert(pt == 0); cout << "------------" << endl; pt.resetPtr(new Animal); pt->display(); return 0; }
3. 队列类模板
3.1 编写队列类模板要注意的几点:
a)typedef 不能写成全局,因为没有使用的时机。以typedef Node<T> *NodePtr 为例,与模板相关的名字都是不完整的,所以直接使用 NodePtr 会找不到符号(编译器不会反向推断 Node<T> 中 T 的类型),使用 Node<T> 这种typedef 是没有意义的。
b)在Queue的编写中,如果:
template <typename T> class Node{ friend class Queue; private: T data_; Node* next_; };
这表明Node<T>的友元类是Queue,这个Queue是一个普通类,与模板无关。如果:
template <typename T> class Node{ friend class Queue<T>; private: T data_; Node* next_; };
表明此时的Queue不是一个模板类,无法这样使用,这里需要告诉编译器,Queue是一个模板类,因此正确的代码如下:
template <typename T> class Queue; //前向声明 用于 friend class template <typename T> class Node{ friend class Queue<T>; private: T data_; Node* next_; };
这两行的作用是告诉编译器,Queue不是一个普通的类,而是一个模板类,所以下面的friend才能使用Queue<T>。
c)模板之间互相声明为友元的要点:被声明为 friend 的类,首先应该告知编译器,这是一个模板类,这需要带模板参数类表的前向声明;其次声明 friend 的时候,应该制定具体的模板参数。
3.2 源程序。
#ifndef __QUEUE_H__ #define __QUEUE_H__ #include <stddef.h> #include <assert.h>
template <typename T> class Queue; //前向声明 用于 friend class template <typename T> class Node{ friend class Queue<T>; private: T data_; Node* next_; }; template <typename T> class Queue{ public: typedef Node<T> *NodePtr; // Queue(); Queue(const Queue &queue); Queue &operator=(const Queue &queue); ~Queue(); void push(const T &data); void pop(); void clear(); const T &top() const; bool isEmpty()const; size_t getSize()const; private: void copyElements(const Queue &queue); NodePtr head_; NodePtr tail_; size_t size_; }; template <typename T> inline Queue<T>::Queue() :head_(NULL), tail_(NULL), size_(0){ } template <typename T> inline void Queue<T>::copyElements(const Queue &queue){ NodePtr pCur = queue.head_; while(pCur != queue.tail_){ push(pCur->data_); pCur = pCur->next_; } } template <typename T> inline Queue<T> &Queue<T>::operator=(const Queue &queue){ clear(); copyElements(queue); } template <typename T> inline Queue<T>::Queue(const Queue &queue) :head_(NULL), tail_(NULL), size_(0) { copyElements(queue); } template <typename T> inline Queue<T>::~Queue(){ clear(); } template <typename T> inline void Queue<T>::push(const T &data){ NodePtr pt = new Node<T>; pt->data_ = data; pt->next_ = NULL; if(isEmpty()){ head_ = tail_ = pt; } else{ tail_->next_ = pt; tail_ = pt; } size_++; } template <typename T> inline void Queue<T>::pop(){ assert(!isEmpty()); NodePtr pt = head_; head_ = head_->next_; delete pt; } template <typename T> inline void Queue<T>::clear(){ while(!isEmpty()){ pop(); } } template <typename T> inline const T &Queue<T>::top() const{ assert(!isEmpty()); return head_->data_; } template <typename T> inline bool Queue<T>::isEmpty() const{ return head_ == NULL; } template <typename T> inline size_t Queue<T>::getSize()const{ return size_; } #endif #include "queue.hpp" #include <iostream> #include <assert.h> using namespace std; int main(int argc, const char *argv[]) { Queue<int> Q; Q.push(1); Q.push(2); while(!Q.isEmpty()){ cout << Q.top() << endl; Q.pop(); } return 0; }