C++:探究纯虚析构函数以及实现数组的高速排序与链表的归并排序

标签: 数据结构 数组 链表 高速排序 归并排序 抽象类 虚继承

by 小威威


1.介绍

本篇博文将通过课后作业的(15 C++ Homework) D&A 5 Collection with Inheritance来解说一些重要的排序与零散的知识。并且,本人以科学严谨的态度,对抽象类中析构函数的调用情况进行了分类讨论并一一试验,终于得出了“抽象类最好要定义纯虚析构函数”的结论,并非凭空捏造,或者是从网上拷贝而来。可是。这仍然代表本人观点。不具有权威性,如有错误,欢迎大家批评指出。

2.题目背景

事实上出题人的意思非常easy。就是希望你用代码实现这张图中部分的内容(有方框的)。

如今我来简单的分析一下这张图(仅仅介绍带有慷慨框的)。

1.直线加圆圈相应的方框是指提供接口的类,换句话说就是抽象类。



由图可知,Collection 与 List 是抽象类。

2.直线加空心三角形表达的是继承关系。

由图可知,List继承Collection,ArrayList与Linked List继承List。

3.直线加实心菱形代表的是被包括于。

由图可知,结构体node在Linked List中定义。

本题考查的知识点清单例如以下:

继承、成员函数的纯虚函数、析构函数的纯虚函数、抽象类、接口、预处理指令在头文件里的应用、数据结构(数组与链表)的各种操作、高速排序、归并排序、动态内存的分配与释放、多态等。

这里再反复一下C++中非常重要的三个部分:

1.encapsulation(封装)。2.inheritance(继承)。 3.polymorphism(多态)。

3.这道题的坑:

(1)接口:

接口方便了基类的指针訪问派生类的成员函数。

成员函数一般都要定义成纯虚函数(除了构造函数)。因此它们一般都是抽象类。

并且接口类一般仅仅提供接口,不提供实现,所以定义纯虚析构函数时不能用virtual 函数原型 = 0;的格式,而要用virtual ~类名 {}。但这并不意味着析构函数就不能用virtual 函数原型 = 0;的格式。当类包括实现时。如若在类的定义里用virtual ~类名 = 0;的格式。那么在实现中要又一次定义析构函数。

对于其它成员函数(除构造函数),用virtual 函数原型 = 0;的格式即可了。由于这些成员函数仅仅提供接口,并不会调用,而抽象类尽管没有实例化对象。可是仍然会在删除子对象时调用抽象类的析构函数。

(2)纯虚析构函数

注:正如上面所说。抽象类的析构函数一定须要实现。

所以我觉得虚析构函数与纯虚析构函数应该是同一个概念,没什么差别。

对于一个抽象类。最好要定义纯虚析构函数(这个观点网上有争议,只是我个人更倾向于定义纯虚析构函数)。尽管并非必要的,但定义了肯定是最好的了。我的理由例如以下:

定义一个抽象类,通常是用于提供接口,以方便基类指针訪问派生类中的成员。好,如今我分成两种情况来生成派生类对象,并用一些小代码来展现它们各自的析构函数的调用情况。

I.通过基类指针动态分配内存生成派生类对象。

———————————————-实例一————————————————–

# include <iostream>

using namespace std;

class Base {
public:
virtual void funA() = 0;
~Base() {
cout << "Calling for Base's destruction!\n";
}
}; class Derived : public Base {
public:
void funA() {}
~Derived() {
cout << "Calling for Derived's destruction\n";
}
}; int main(void) {
Base *p1 = new Derived;
delete p1;
// Derived A;
return 0;
} 输出结果:
Calling for Base's destruction!

从代码的输出结果不难发现。假设通过基类指针动态分配内存生成派生类对象,当删去对象时,仅仅会调用基类也就是抽象类的析构函数。

假设将析构函数变成纯虚析构函数呢?

———————————————-实例二————————————————–

# include <iostream>

using namespace std;

class Base {
public:
virtual void funA() = 0;
virtual ~Base() {
cout << "Calling for Base's destruction!\n";
}
}; class Derived : public Base {
public:
void funA() {}
~Derived() {
cout << "Calling for Derived's destruction\n";
}
}; int main(void) {
Base *p1 = new Derived;
delete p1;
// Derived A;
return 0;
} 输出结果:
Calling for Derived's destruction
Calling for Base's destruction!

从输出结果能够得出结论:

通过基类指针动态分配内存生成派生类对象时。如若未定义纯虚析构函数。那么仅仅会调用基类的析构函数;如若定义了纯虚析构函数。不仅会调用派生类的析构函数,还会调用基类的析构函数(所以析构函数须要实现。否则会编译出错)。

那么对于积累是非抽象类的呢?情况怎样?读者能够试一下~

而我实验的结果是无论基类是抽象类还是普通类,都满足上面的结论。

II.通过数据类型+变量名的方式生成派生类对象。

———————————————-实例三————————————————–

# include <iostream>

using namespace std;

class Base {
public:
virtual void funA() = 0;
~Base() {
cout << "Calling for Base's destruction!\n";
}
}; class Derived : public Base {
public:
void funA() {}
~Derived() {
cout << "Calling for Derived's destruction\n";
}
}; int main(void) {
// Base *p1 = new Derived;
// delete p1;
Derived A;
return 0;
} 输出结果:
Calling for Derived's destruction
Calling for Base's destruction!

改成纯虚析构函数试试~

———————————————-实例四————————————————–

# include <iostream>

using namespace std;

class Base {
public:
virtual void funA() = 0;
virtual ~Base() {
cout << "Calling for Base's destruction!\n";
}
}; class Derived : public Base {
public:
void funA() {}
~Derived() {
cout << "Calling for Derived's destruction\n";
}
}; int main(void) {
// Base *p1 = new Derived;
// delete p1;
Derived A;
return 0;
} 输出结果:
Calling for Derived's destruction
Calling for Base's destruction!

从输出结果能够得出结论:

假设通过数据类型+变量名的方式生成派生类对象,那么此时有无定义纯虚析构函数就没有太大的关系了(或许这就是网上的人觉得抽象类不用定义纯虚析构函数的原因)。

总而言之。由上面的试验可知,定义纯虚构造函数还是比較好的,比較适用于两种生成对象的方法。

(3)重定义问题

在一项比較大的project内,或者是比較复杂的代码题内,可能会出现多个文件调用同一个头文件的情况,这便会导致代码重定义问题。为了解决这一问题,我们须要在头文件里增加一些预编译指令来防止此类情况的发生。

如头文件List.hpp:

#ifndef LIST_H_
#define LIST_H_
class List : public Collection {
(content)
};
#endif

在该头文件里,我增加了以下语句:

#ifndef LIST_H_
#define LIST_H_
#endif

假设这个头文件还没有被include,那么也就未定义LIST_H_常量。

于是便会运行#ifndef与#endif之间的代码。当中便定义了LIST_H_常量。

假如这个这个头文件已经被include了。那么就一定定义了LIST_H_ 常量。那么也就不会运行#ifndef与#endif之间的代码,也就不会造成重定义的情况。

于是。为保险起见,最好头文件都加上这三句预编译指令。

(4)要自己实现快排,链表,数组,不能调用STL。

4.这道代码题的核心—->快排的实现以及链表的实现。

(1)对于快排的实现。我就不细讲了,挺简单的。

能够看我曾经的一篇文章学习:高速排序

(2)对于归并。仅仅要了解了它的算法。我们自己就能实现!

推荐一篇博文:

递归算法学习系列二:归并排序

这篇博文我仅仅推荐他对归并排序算法的解说。但我并不推荐他的代码。毕竟他用的是数组的归并排序,显然就没快排快了。我们所要做的就是依据归并排序的算法用链表加以实现!

用链表实现归并排序的代码我会在后文给出。

5.代码实现

题目已给出main.cpp, ArrayList.hpp,LinkedList.hpp,要求我们实现Collection.hpp, List.hpp, ArrayList.cpp, LinkedList.cpp。

// main.cpp

#include <iostream>
#include <cstdlib>
#include "Collection.hpp"
#include "List.hpp"
#include "LinkedList.hpp"
#include "ArrayList.hpp"
#include <exception> using std::cin;
using std::cout;
using std::endl;
using std::exception; class AlgorithmnForbidden : public exception {
virtual const char *what() const throw() {
return "Please do not use std::sort or std::list or std::vector .....";
}
}; class TEST {
private:
int *testData;
int data_size; public:
TEST() {
#if defined(_GLIBCXX_ALGORITHM) || defined(_GLIBCXX_LIST) || \
defined(_GLIBCXX_VECTOR)
//throw AlgorithmnForbidden();
cout << "please do not use algorithm" << endl;
#endif
cin >> data_size;
cout << "test data size:" << data_size << endl;
testData = new int[data_size];
for (int i = 0; i < data_size; i++) {
cin >> testData[i];
}
} ~TEST() { delete[] testData; } void test_List(Collection *c) {
cout << (c->isEmpty() ? "true" : "false") << endl; int n = data_size; for (int i = 0; i < n; i++) {
c->add(testData[i]);
} reinterpret_cast<List *>(c)->sort(); for (int i = 0; i < n; i++) {
cout << (*reinterpret_cast<List *>(c))[i] << " ";
} cout << endl; // not empty
cout << (c->isEmpty() ? "true" : "false") << endl; for (int i = 0; i < n / 2; i++) {
cout << "(" << (c->contain(i) ? "true" : "false");
cout << ","
<< (reinterpret_cast<List *>(c)->indexOf(i) != -1 ? "true" : "false")
<< ") ";
c->remove(i);
} cout << endl; for (int i = 0; i < c->size(); i++) {
cout << (*reinterpret_cast<List *>(c))[i] << " ";
}
cout << endl;
} void test_ArrayList() {
Collection *c = new ArrayList();
test_List(c);
delete c;
} void test_LinkedList() {
Collection *c = new LinkedList();
test_List(c);
delete c;
} void runAllTests() {
cout << "Testing ArrayList:" << endl;
test_ArrayList();
cout << endl;
cout << "Testing LinkedList:" << endl;
test_LinkedList();
}
}; int main() {
TEST t;
t.runAllTests();
return 0;
} // ArrayList.hpp
#ifndef ARRAYLIST_H_
#define ARRAYLIST_H_ #include "List.hpp" class ArrayList : public List {
public:
ArrayList();
~ArrayList();
virtual void add(E e);
virtual void clear(void);
virtual bool contain(E e);
virtual bool isEmpty(void);
virtual void remove(E e);
virtual E& operator[](int index);
virtual E& get(int index);
virtual int indexOf(E element);
virtual void sort(void);
virtual int size(void); private:
E* storage;
int _size;
int _maxsize;
static const int extend_factor = 2;
void extend(void);
}; #endif // LinkList.hpp
#ifndef LINKEDLIST_H_
#define LINKEDLIST_H_ #include "List.hpp"
#include <iostream> class LinkedList : public List {
public:
typedef struct node {
E data;
struct node* next;
struct node* prev;
node(E data, struct node* next = NULL, struct node* prev = NULL)
: data(data), next(next), prev(prev) {}
} node;
LinkedList();
~LinkedList();
virtual void add(E e);
virtual void clear(void);
virtual bool contain(E e);
virtual bool isEmpty(void);
virtual void remove(E e);
virtual E& operator[](int index);
virtual E& get(int index);
virtual int indexOf(E element);
virtual void sort(void);
virtual int size(void); private:
node* head;
node* tail;
int _size;
}; #endif

对于Collection.hpp。依据最開始给出的那个图中相应方框内的内容进行定义。

#ifndef COLLECTION_H_
#define COLLECTION_H_
class Collection {
protected:
typedef int E;
public:
virtual ~Collection() {} // 一定要加上virtual
virtual void add(E e) = 0;
virtual void clear(void) = 0;
virtual bool contain(E e) = 0;
virtual bool isEmpty(void) = 0;
virtual void remove(E e) = 0;
virtual int size(void) = 0;
};
#endif

对于List.hpp。也是如图中相应方框的内容进行定义。

#ifndef LIST_H_
#define LIST_H_
# include "Collection.hpp"
class List : public Collection {
public:
virtual ~List() {} // 一定要加上virtual
virtual E& operator[](int index) = 0;
virtual E& get(int index) = 0;
virtual int indexOf(E element) = 0;
virtual void sort(void) = 0;
};
#endif

(1)ArrayList.cpp

对于ArrayList.cpp的实现,事实上相似于实现vector类,由于这个array有extend的功能。

除了排序,我觉得须要有点小思维的就是remove函数。

不难想到最普通的方法就是从头到尾遍历数组,除去同样元素,并且实现同样元素后面元素的前移。

可是我有一个更快,更巧的方法。由main.cpp可知, 在调用remove()函数之前。数组内的元素已经排完序。并且remove的数字是从小到大的。不难想到,它仅仅会在数组的一端删去同样元素。

所以我先将整个数组给倒过来,然后从后往前遍历。

假设遍历到要删除的元素。直接_size–即可。由于仅仅会从尾端删除元素,故不用考虑删除中间元素的情况。我的ArrayList::remove()是这样实现的。事实上我的代码还能够改进,先推断最后一个元素是否是我们要删除的元素,假设不是,就不运行遍历,假设是,就运行遍历。

还有再缩短时间的方法。就是加一个循环终止调节,这个条件能够是推断相邻元素是否都是我们要删除的元素。最后要将数组倒回来!!

不必调用quick_sort()函数。

extend()函数也是挺有意思。只是要注意分情况。假设storage为NULL,那就直接分配内存给它。假设不为空。要在分配内存之后将原数组中的数据拷贝到新内存中,然后把原数组的空间delete,以避免内存泄漏。

// ArrayList.cpp

# include "ArrayList.hpp"
# include <iostream> static int a = 0;
void quick_sort(int *pArr, int pbig, int psmall) {
if (pbig >= psmall) return;
int key = pbig;
int len = psmall + 1;
while (pbig != psmall) {
for (; psmall > pbig; psmall--) {
if (pArr[key] > pArr[psmall]) break;
}
for (; pbig < len; pbig++) {
if (pbig == psmall) {
int temp = pArr[key];
pArr[key] = pArr[psmall];
pArr[psmall] = temp;
break;
}
if (pArr[key] < pArr[pbig]) break;
}
if (pbig != psmall) {
int temp = pArr[psmall];
pArr[psmall] = pArr[pbig];
pArr[pbig] = temp;
}
}
quick_sort(pArr, key, pbig-1);
quick_sort(pArr, pbig+1, len-1);
}
ArrayList :: ArrayList() : _size(0), _maxsize(0), storage(NULL) {}
ArrayList :: ~ArrayList() { clear(); }
void ArrayList :: add(ArrayList::E e) {
if (_size == _maxsize) extend();
storage[_size] = e;
_size++;
}
void ArrayList :: clear(void) {
if (storage != NULL) delete[] storage;
storage = NULL;
_size = 0;
_maxsize = 0;
}
bool ArrayList :: contain(ArrayList::E e) {
for (int i = 0; i < _size; i++) {
if (storage[i] == e) return true;
}
return false;
}
bool ArrayList :: isEmpty(void) {
if (_size == 0) return true;
return false;
}
void ArrayList :: remove(ArrayList::E e) {
static int b = _size;
a++;
if (a == 1) {
for (int i = 0, j = _size-1; i < j; i++, j--) {
int temp = storage[i];
storage[i] = storage[j];
storage[j] = temp;
}
}
for (int i = _size-1; i >= 0; i--) {
if (storage[i] == e) _size--;
}
if (a == b/2) {
for (int i = 0, j = _size-1; i < j; i++, j--) {
int temp = storage[i];
storage[i] = storage[j];
storage[j] = temp;
}
}
} ArrayList::E& ArrayList :: operator[](int index) { return storage[index]; }
ArrayList::E& ArrayList :: get(int index) { return storage[index]; }
int ArrayList :: indexOf(ArrayList::E element) {
for (int i = 0; i < _size; i++) {
if (storage[i] == element) return i;
}
return -1;
}
void ArrayList :: sort(void) {
if (storage != NULL) {
quick_sort(storage, 0, _size-1);
}
}
int ArrayList :: size(void) { return _size;}
void ArrayList :: extend(void) {
_maxsize += extend_factor;
if (storage == NULL) {
storage = new E[_maxsize];
} else {
E* pArr = new E[_maxsize];
pArr[_size] = pArr[_size+1] = 0;
for (int i = 0; i < _size; i++) {
pArr[i] = storage[i];
}
delete[] storage;
storage = pArr;
}
}

(2) Linkedlist.cpp

除了排序,我觉得remove()函数的实现须要点小思维。

只是链表的remove()函数实现比数组简单多了。由于这个链表是双向链表。操作起来较为方便。(双向链表的具体实现详见:实现双向链表, 单向链表的具体实现相见:16.03.11实验课总结, 单向链表入门:入门:链表的基本操作)

由于仅仅会从一端删除元素,所以我们仅仅需对链表进行遍历。循环中仅仅需推断第一个结点中的数据是否等于我们要删除的,假设是,删除第一个结点。假设不是,直接break来终止循环。此处已经有了循环终止条件,就没有必要再设置一个新的了。

以下上LinkedList.cpp,重点在归并排序

// LinkedList.cpp

# include "LinkedList.hpp"
static int a = 0;
void merge_sort(LinkedList::node** head, int _size) {
if (*head == NULL || (*head)->next == NULL) return;
int count = 1;
LinkedList::node *p1 = *head;
while (p1 != NULL) {
if (count == _size/2) break;
p1 = p1->next;
count++;
}
LinkedList::node *p2 = p1->next;
p1->next = NULL;
p2->prev = NULL;
merge_sort(head, count);
merge_sort(&p2, _size-count);
LinkedList::node *p3 = *head;
LinkedList::node *p4 = p2;
LinkedList::node *merge_list = new LinkedList::node(0);
LinkedList::node *p5 = merge_list;
int size = 1;
while (size < _size) {
p5->next = new LinkedList::node(0);
p5->next->prev = p5;
size++;
p5 = p5->next;
}
p5 = merge_list;
while (p3 != NULL && p4 != NULL) {
if (p3->data > p4->data) {
p5->data = p4->data;
p4 = p4->next;
} else {
p5->data = p3->data;
p3 = p3->next;
}
p5 = p5->next;
}
if (p3 == NULL) {
while (p5 != NULL) {
p5->data = p4->data;
p5 = p5->next;
p4 = p4->next;
}
} else if (p4 == NULL) {
while (p5 != NULL) {
p5->data = p3->data;
p5 = p5->next;
p3 = p3->next;
}
}
while (*head != NULL) {
LinkedList::node *p6 = (*head)->next;
delete (*head);
*head = p6;
}
while (p2 != NULL) {
LinkedList::node *p7 = p2->next;
delete p2;
p2 = p7;
}
*head = merge_list;
}
LinkedList :: LinkedList() : head(NULL), tail(NULL), _size(0) {}
LinkedList :: ~LinkedList() { clear(); }
void LinkedList :: add(LinkedList::E e) {
node *p1 = new node(e);
if (head == NULL) {
head = tail = p1;
} else {
tail->next = p1;
p1->prev = tail;
tail = p1;
}
_size++;
}
void LinkedList :: clear(void) {
if (head != NULL) {
node *p1;
while (head != NULL) {
p1 = head->next;
delete head;
head = p1;
}
}
_size = 0;
head = tail = NULL;
}
bool LinkedList :: contain(LinkedList::E e) {
node *p1 = head;
while (p1 != NULL) {
if (e == p1->data) return true;
p1 = p1->next;
}
return false;
}
bool LinkedList :: isEmpty(void) {
if (_size == 0) return true;
return false;
}
void LinkedList :: remove(LinkedList::E e) {
while (head != NULL) {
if (head->data == e) {
node *temp = head;
head = head->next;
head->prev = NULL;
delete temp;
_size--;
} else {
break;
}
}
}
LinkedList::E& LinkedList :: operator[](int index) {
node *p1 = head;
while (index--) {
p1 = p1->next;
}
return p1->data;
}
LinkedList::E& LinkedList :: get(int index) {
return (*this)[index];
}
int LinkedList :: indexOf(LinkedList::E element) {
node *p1 = head;
int count = 0;
int flag = 0;
while (p1 != NULL) {
if (p1->data == element) {
flag = 1;
break;
}
count++;
p1 = p1->next;
}
if (flag == 1) return count;
else return -1;
}
void LinkedList :: sort(void) {
if (head == NULL) return;
merge_sort(&head, _size);
int count = 1;
node *p1 = head;
while (count < _size) {
p1 = p1->next;
count++;
}
tail = p1;
}
int LinkedList :: size(void) { return _size; }

6.知识的补充(From wikipedia)

True object-orient programming requires objects to support three qualities: encapsulation, inheritance, and polymorphism.Polymorphism enables one common interface for many implementations, and for objects to act differently under different circumstances.

C++ supports several kinds of static (compile-time) and dynamic (run-time) polymorphisms, supported by the language features described above. Compile-time polymorphism does not allow for certain run-time decisions, while run-time polymorphism typically incurs a performance penalty.

Static polymorphism is not true polymorphism including function overloading, operator overloading and templates which is not what we are going to work on(in this question).

Dynamic polymorphism:

Variable pointers (and references) to a base class type in C++ can refer to objects of any derived classes of that type in addition to objects exactly matching the variable type. This allows arrays and other kinds of containers to hold pointers to objects of differing types. Because assignment of values to variables usually occurs at run-time, this is necessarily a run-time phenomenon.

C++ also provides a dynamic_cast operator, which allows the program to safely attempt conversion of an object into an object of a more specific object type (as opposed to conversion to a more general type, which is always allowed). This feature relies on run-time type information (RTTI). Objects known to be of a certain specific type can also be cast to that type with static_cast, a purely compile-time construct that has no runtime overhead and does not require RTTI.

Ordinarily, when a function in a derived class overrides a function in a base class, the function to call is determined by the type of the object. A given function is overridden when there exists no difference in the number or type of parameters between two or more definitions of that function. Hence, at compile time, it may not be possible to determine the type of the object and therefore the correct function to call, given only a base class pointer; the decision is therefore put off until runtime. This is called dynamic dispatch. Virtual member functions or methods[43] allow the most specific implementation of the function to be called, according to the actual run-time type of the object. In C++ implementations, this is commonly done using virtual function tables. If the object type is known, this may be bypassed by prepending a fully qualified class name before the function call, but in general calls to virtual functions are resolved at run time.

In addition to standard member functions, operator overloads and destructors can be virtual. As a rule of thumb, if any function in the class is virtual, the destructor should be as well. (此处表示应该使用虚析构函数,没有强制)As the type of an object at its creation is known at compile time, constructors, and by extension copy constructors, cannot be virtual. Nonetheless a situation may arise where a copy of an object needs to be created when a pointer to a derived object is passed as a pointer to a base object. In such a case, a common solution is to create a clone() (or similar) virtual function that creates and returns a copy of the derived class when called.

A member function can also be made “pure virtual” by appending it with = 0 after the closing parenthesis and before the semicolon. A class containing a pure virtual function is called an abstract data type. Objects cannot be created from abstract data types; they can only be derived from. Any derived class inherits the virtual function as pure and must provide a non-pure definition of it (and all other pure virtual functions) before objects of the derived class can be created. A program that attempts to create an object of a class with a pure virtual member function or inherited pure virtual member function is ill-formed.


以上内容皆为本人观点。欢迎大家提出批评和指导,我们一起探讨。


C++:探究纯虚析构函数以及实现数组的高速排序与链表的归并排序的更多相关文章

  1. 虚析构函数(√)、纯虚析构函数(√)、虚构造函数(X)

    from:http://blog.csdn.net/fisher_jiang/article/details/2477577 一. 虚析构函数 我们知道,为了能够正确的调用对象的析构函数,一般要求具有 ...

  2. c++ 纯虚析构函数

    ; 这就是一个纯虚析构函数,这种定义是允许的. 一般纯虚函数都不允许有实体,但是因为析构一个类的过程中会把所有的父类全析构了,所以每个类必有一个析构函数. 所以.纯虚析构函数需要提供函数的实现,而一般 ...

  3. C++中的虚析构函数、纯虚析构函数具体解释

    C++中析构函数能够为纯虚函数吗? 众所周知.在实现多态的过程中,一般将基类的析构函数设为virtual.以便在delete的时候能够多态的链式调用.那么析构函数能否够设为纯虚呢? class CBa ...

  4. 从零开始学C++之虚函数与多态(二):纯虚函数、抽象类、虚析构函数

    一.纯虚函数 虚函数是实现多态性的前提 需要在基类中定义共同的接口 接口要定义为虚函数 如果基类的接口没办法实现怎么办? 如形状类Shape 解决方法 将这些接口定义为纯虚函数 在基类中不能给出有意义 ...

  5. 读书笔记 effective c++ Item 7 在多态基类中将析构函数声明为虚析构函数

    1. 继承体系中关于对象释放遇到的问题描述 1.1 手动释放 关于时间记录有很多种方法,因此为不同的计时方法创建一个TimeKeeper基类和一些派生类就再合理不过了: class TimeKeepe ...

  6. EC笔记,第二部分:7.为多态基类声明虚析构函数

    7.为多态基类声明虚析构函数 1.为多态基类声明虚析构函数 code1: class A{ public: int* a; A():a(new int(5)) {} ~A(){ delete a; } ...

  7. C/C++中的虚析构函数和私有析构函数的使用

    代码: #include <iostream> using namespace std; class A{ public: A(){ cout<<"construct ...

  8. why pure virtual function has definition 为什么可以在基类中实现纯虚函数

    看了会音频,无意搜到一个frameworks/base/include/utils/Flattenable.h : virtual ~Flattenable() = 0; 所以查了下“纯虚函数定义实现 ...

  9. 虚析构函数? vptr? 指针偏移?多态数组? delete 基类指针 内存泄漏?崩溃?

    五条基本规则: 1.如果基类已经插入了vptr, 则派生类将继承和重用该vptr.vptr(一般在对象内存模型的顶部)必须随着对象类型的变化而不断地改变它的指向,以保证其值和当前对象的实际类型是一致的 ...

随机推荐

  1. HBase编程 API入门系列之delete(客户端而言)(3)

    心得,写在前面的话,也许,中间会要多次执行,连接超时,多试试就好了. 前面的基础,如下 HBase编程 API入门系列之put(客户端而言)(1) HBase编程 API入门系列之get(客户端而言) ...

  2. C#之单列双列集合绑定数据

    ---恢复内容开始--- 1.单列集合绑定方式 davList.DataSource=new BindingList<类型名>(集合名); 2.双列集合绑定方式 BindingSource ...

  3. 基于NPOI的扩展

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using NPOI.HSS ...

  4. Java多线程-synchronized关键字

    进程:是一个正在执行中的程序.每一个进程执行都有一个执行顺序.该顺序是一个执行路径,或者叫一个控制单元. 线程:就是进程中的一个独立的控制单元.线程在控制着进程的执行. 一个进程中至少有一个线程 Ja ...

  5. Android MediaRecorder自定义分辨率

    Android MediaRecorder自定义分辨率 工作这么久了,确实积累了不少东西,但都是以文档的形式存在U盘里的,为什么不写博客呢?因为懒啊!!!总感觉博客太难写了(大概是上学时候写作文恐惧症 ...

  6. 解决无法移除tomcat中的项目

    问题:启动myeclipse,tomcat提示报错,blind,但是你移除的时候无法移除,只会显示一个黄色的感叹号,此时你直接在webapp中删除时,也提示呗占用无法删除. 办法:关掉myeclips ...

  7. Excel常用的小技巧

    1.Excel如何实现单元格内轻松换行:按住ALT+enter就可以了. 2.Excel固定表头:在“视图”>冻结窗口>冻结首行. 3.防止电脑突然断电,导致正在编辑的Excel数据丢失, ...

  8. 【python】os.getcwd和getcwdu

    print os.getcwd(), type(os.getcwd()) print os.getcwdu(), type(os.getcwdu()) 结果如下: C:\Users\Administr ...

  9. Table is specified twice, both as a target for 'UPDATE' and as a separate source

    UPDATE Bins b SET b.ShopSn =’111201611111168706’ WHERE b.Id IN (SELECT b.Id FROM Bins b JOIN BinInve ...

  10. CDC之fast->slow (1)

    Sampling slower signals into faster clock domains causes fewer potential problems than sampling fast ...