定义:
class Shape
{
public:
//...
void moveTo(Point newLocation);
bool validate()const;
vritual bool draw() const=0;
//...
}:
class Circle: public Shape
{
//...
bool draw() const;
//...
};
void (Shape::*mf1)(Point ) = &Shape::moveTo;//指向类的成员函数的指针。
ReturnType (ClassName::*pFuncName)(paramList);
pFuncName定义了指向ClassName中的一组函数。这组函数的形式是返回值为ReturnType,函数列表为paramList.
ClassName的限定使得只有ClassName或派生自它的类的对象才能解引用这个函数指针。
1 #include"stdafx.h"
2 #include<stdio.h>
3 #include<stdlib.h>
4
5 class Base;
6 class Derived;
7 typedef long (Base::*PFunc)(long,long);
8
9 enum FuncType
10 {
11 ADD=1,
12 SUB,
13 DIV,
14 MUL,
15 };
16
17 typedef struct tagCallBackFunc
18 {
19 long funcType;
20 PFunc proc;
21 }COMMAND,*PCOMMAND;
22
23 class Base
24 {
25 public:
26
27 virtual PCOMMAND GetCommands(void);
28 static COMMAND command[];
29
30 long base_add(long a,long b){printf("in base_add()\n");return a+b;}
31 long base_sub(long a,long b){printf("in base_sub()\n");return a-b;}
32 long base_div(long a,long b){printf("in base_div()\n");return a/b;}
33 long base_mul(long a,long b){printf("in base_mul()\n");return a*b;}
34 };
35
36 COMMAND Base::command[]={
37 {ADD,(PFunc)&Base::base_add},
38 {SUB,(PFunc)&Base::base_sub},
39 {MUL,(PFunc)&Base::base_mul},
40 {DIV,(PFunc)&Base::base_div},
41 };
42 PCOMMAND Base::GetCommands()
43 {
44 return command;
45 }
46
47 class Derived:public Base
48 {
49 public:
50 Derived(void){}
51 ~Derived(void){}
52 virtual PCOMMAND GetCommands(void){return command;}
53
54 long add(long a,long b){printf("in add()\n");return a+b;}
55 long sub(long a,long b){printf("in sub()\n");return a-b;}
56 long div(long a,long b){printf("in div()\n");return a/b;}
57 long mul(long a,long b){printf("in mul()\n");return a*b;}
58
59 static COMMAND command[];
60 };
61
62 COMMAND Derived::command[]=
63 {
64 {ADD,(PFunc)&Derived::add},
65 {SUB,(PFunc)&Derived::sub},
66 {MUL,(PFunc)&Derived::mul},
67 {DIV,(PFunc)&Derived::div},
68 {0}
69 };
70
71
72
73 void test(Base *control,FuncType funcType,long operand1,long operand2)
74 {
75 PCOMMAND pCommand = control->GetCommands();
76 PCOMMAND pNowCommand=NULL;
77
78 for(long i=0;pCommand[i].funcType;++i)
79 {
80 if(funcType == pCommand[i].funcType)
81 {
82 pNowCommand = &pCommand[i];
83 break;
84 }
85 }
86
87 if(pNowCommand)
88 {
89 long res = (control->*pNowCommand->proc)(operand1,operand2);
90 printf("res=%d\n",res);
91 }
92 }
93
94 int main()
95 {
96 Derived *d =new Derived();
97 Base *b = (Base*)d;
98 test(b,ADD,1,2);
99
100 Base *bb = new Base;
101 test(bb,MUL,1,2);
102 delete bb;
103 delete d;
104 return 0;
105 }
第89行是对指向类成员函数的指针的解析。
首先说一下数组指针和函数指针的定义问题。
一条重要的规则:函数和数组修饰符的优先级比指针修饰符的优先级高。函数的修饰符为()、数组的修饰符为[]、指针的修饰符为*。
eg:
int *A[N]: 等价于(int*)(A[N]):A[N]表示有N个元素的数组。元素类型为int*
int (*A)[N]:因为有括号提高了*的优先级。所以等价于int ((*A)[N])。表示A是一个指针。A指向的是含有N个元素的数组。数组中存放的元素为int型。
对于指向函数的指针。
int (*afp2[N])(); afp2[N]表示afp2是个含有N个元素的数组。数组中的元素为指向返回值为int、无参数的函数指针。
因为函数的声明较为复杂。可以用typedef来使代码更加明确。
我们用typedef来改写上面的声明。
typedef int (*FP)();//定义FP为返回值为int、参数列表为空的函数的类型。
FP afp2[N];//含有N个元素的数组,返回值为FP。
将一个函数的地址初始化或赋值给一个指向函数的指针时,无需显示的取其地址。编译器知道隐式的取其地址。
void*可以指向任意类型的数据。
不存在可以指向任意类型函数的通用指针。
下面的代码片段中红色标出的部分等价于绿色标出的部分:
红色的部分更简洁明了。很适合链表操作中的添加操作。
zwl_srv_control *__list__,*__end__;
void add_job(zwl_srv_control*pcontrol)
{
pcontrol->_pnext = NULL;
EnterCriticalSection(&__cs__);
(__end__?__end__->_pnext:__list__) = pcontrol;
/*
if(!__end__)
__list__ = pcontrol;
else
__end__->next = pcontrol;
*/
__end__ = pcontrol;
LeaveCriticalSection(&__cs__);
Sleep(0);
}
对于任意含有n个节点的有根树仅用O(n)的空间。
是一种左孩子、有兄弟的表示。
每一个节点包含下列属性:
1.指向父节点的指针
2.left-child[x]指向节点x的最左孩子
3.right-sibling[x]指向节点x紧右边的兄弟。
在O(3n/2)比较后求出最大值和最小值
思想:成对的处理元素,两者较大的和max比较,两者较小的和min比较。
GetMaxAndMin(A,n)
if n为奇数
max = min = A[0];
i = 1;
else
if A[0]>A[1] then max = A[0]; min = A[1]
else max = A[1]; min = A[0];
i =2;
for i to n-1
if(A[i]>A[i+1])
{
max = max>A[i] ? max: A[i];
min = A[i+1]<min ? A[i+1]:min;
}
else
{
max = A[i+1]>max ? A[i+1]:max;
min = A[i]<min ? A[i] : min;
}
i+=2;
下面介绍一种在O(n)的时间内找出第k个最大值(最小值)的方法
该方法和快速排序相似。不同在于每次只出理一边。
伪代码如下:
Random-select(A,p,r,i)//找到A中的第i个最小值
if p==r
then return a[p]
q = random-partition(A,p,r)
k = p-q+1
if(i==k)
then return A[q]
else if i<k
then return random-select(A,p,q-1,i)
else return random-select(A,q+1,r,i-k)
这个算法很不错。
最近在看《算法导论》,首先在这推荐一下这本书,写的确实很精彩
。
《算法导论》的第二部分分析了多种排序算法。包括插入排序、归并排序、堆排序、快速排序以及线性排序的几个算法。
下面简要总结一下。
对于输入规模为n的数组。插入排序的复杂度为O(n^2)。
归并排序、堆排序、快速排序的复杂度为O(n㏒n);
线性排序的复杂度为O(n)
1.插入排序的性能和输入元素的的序列有很大的关系,如果输入已排序的序列,则复杂度是线性的,若输入是逆序的则是O(n^2)的。
2.堆排序用到了优先队列(优先队列是一种用来维护由一组元素构成的集合S的数据结构)。
3.快速排序的关键是主元的选取(pivot)。在排序过程中元素被分成四部分:小于等于主元的序列、大于主元的序列、未比较的序列、主元。
当未比较的序列未空时,再分别对小于等于主元的序列进行排序、对大于主元的序列进行排序。
以上三种排序都是原地排序。所谓的原地排序(in-place)是指这些元素是在原数组中重排序的,在任何时刻,至多其中的常数个数字是存储在数组之外的。
4.归并排序是将原数组划分成子序列,对子序列进行排序,然后将排序好的子序列合并到一起从而使得原序列重排。
归并排序不是原地排序,它需要额外的内存资源。
以上四种是比较常用的排序,对于输入序列没有特殊的要求,并且都是比较排序,也就是说通过比较各个元素而进行的排序。它们的时间复杂度最好为O(n㏒n);
下面介绍能在时间内完成的排序。
1.计数排序
适用条件:输入序列中的元素取值在一个范围之内0-k
基本思想:对于每一个元素确定比它小的的元素的个数。
排序A序列,将结果放入B中,元素的取值范围为0-k
伪代码如下:
Count-sort(A,B,k)
for i=0 to k
do C[i]=0
for j=1 to length(A)
do C[A[j]] = C[A[j]]+1;计算A[j]的个数。
for i=1 to k
do C[i] = C[i-1]+C[i];计算小于和等于i的元素个数
for j=length(A) downto 1
do B[C[A[j]]] = A[j];
C[A[j]] = C[A[j]]-1;
我们可以明显看到计数排序不是原地排序。同时计数排序是稳定的(即相同的元素输入和输出的相对位置不变。)
c语言之typedef 的用法 |
用途一: 定义一种类型的别名,而不只是简单的宏替换。可以用作同时声明指针型的多个对象。比如: char* pa, pb; // 这多数不符合我们的意图,它只声明了一个指向字符变量的指针, // 和一个字符变量; 以下则可行: typedef char* PCHAR; // 一般用大写 PCHAR pa, pb; // 可行,同时声明了两个指向字符变量的指针 虽然: char *pa, *pb; 也可行,但相对来说没有用typedef的形式直观,尤其在需要大量指针的地方,typedef的方式更省事。
用途二: 用在旧的C代码中(具体多旧没有查),帮助struct。以前的代码中,声明struct新对象时,必须要带上struct,即形式为: struct 结构名 对象名,如: struct tagPOINT1 { int x; int y; }; struct tagPOINT1 p1;
而在C++中,则可以直接写:结构名 对象名,即: tagPOINT1 p1;
估计某人觉得经常多写一个struct太麻烦了,于是就发明了: typedef struct tagPOINT { int x; int y; }POINT;
POINT p1; // 这样就比原来的方式少写了一个struct,比较省事,尤其在大量使用的时候
或许,在C++中,typedef的这种用途二不是很大,但是理解了它,对掌握以前的旧代码还是有帮助的,毕竟我们在项目中有可能会遇到较早些年代遗留下来的代码。
用途三: 用typedef来定义与平台无关的类型。 比如定义一个叫 REAL 的浮点类型,在目标平台一上,让它表示最高精度的类型为: typedef long double REAL; 在不支持 long double 的平台二上,改为: typedef double REAL; 在连 double 都不支持的平台三上,改为: typedef float REAL; 也就是说,当跨平台时,只要改下 typedef 本身就行,不用对其他源码做任何修改。 标准库就广泛使用了这个技巧,比如size_t。 另外,因为typedef是定义了一种类型的新别名,不是简单的字符串替换,所以它比宏来得稳健(虽然用宏有时也可以完成以上的用途)。
用途四: 为复杂的声明定义一个新的简单的别名。方法是:在原来的声明里逐步用别名替换一部分复杂声明,如此循环,把带变量名的部分留到最后替换,得到的就是原声明的最简化版。举例:
1. 原声明:int *(*a[5])(int, char*); 变量名为a,直接用一个新别名pFun替换a就可以了: typedef int *(*pFun)(int, char*); 原声明的最简化版: pFun a[5];
2. 原声明:void (*b[10]) (void (*)()); 变量名为b,先替换右边部分括号里的,pFunParam为别名一: typedef void (*pFunParam)(); 再替换左边的变量b,pFunx为别名二: typedef void (*pFunx)(pFunParam); 原声明的最简化版: pFunx b[10];
3. 原声明:doube(*)() (*e)[9]; 变量名为e,先替换左边部分,pFuny为别名一: typedef double(*pFuny)(); 再替换右边的变量e,pFunParamy为别名二 typedef pFuny (*pFunParamy)[9]; 原声明的最简化版: pFunParamy e;
理解复杂声明可用的“右左法则”:从变量名看起,先往右,再往左,碰到一个圆括号就调转阅读的方向;括号内分析完就跳出括号,还是按先右后左的顺序,如此循环,直到整个声明分析完。举例: int (*func)(int *p); 首先找到变量名func,外面有一对圆括号,而且左边是一个*号,这说明func是一个指针;然后跳出这个圆括号,先看右边,又遇到圆括号,这说明(*func)是一个函数,所以func是一个指向这类函数的指针,即函数指针,这类函数具有int*类型的形参,返回值类型是int。 int (*func[5])(int *); func右边是一个[]运算符,说明func是具有5个元素的数组;func的左边有一个*,说明func的元素是指针(注意这里的*不是修饰func,而是修饰func[5]的,原因是[]运算符优先级比*高,func先跟[]结合)。跳出这个括号,看右边,又遇到圆括号,说明func数组的元素是函数类型的指针,它指向的函数具有int*类型的形参,返回值类型为int。
也可以记住2个模式: type (*)(....)函数指针 type (*)[]数组指针 ---------------------------------
陷阱一: 记住,typedef是定义了一种类型的新别名,不同于宏,它不是简单的字符串替换。比如: 先定义: typedef char* PSTR; 然后: int mystrcmp(const PSTR, const PSTR);
const PSTR实际上相当于const char*吗?不是的,它实际上相当于char* const。 原因在于const给予了整个指针本身以常量性,也就是形成了常量指针char* const。 简单来说,记住当const和typedef一起出现时,typedef不会是简单的字符串替换就行。
陷阱二: typedef在语法上是一个存储类的关键字(如auto、extern、mutable、static、register等一样),虽然它并不真正影响对象的存储特性,如: typedef static int INT2; //不可行 编译将失败,会提示“指定了一个以上的存储类”。
|
|
|
|
在C++中,有三种类型的循环语句:for, while, 和do...while, 但是在一般应用中作循环时,我们可能用for和while要多一些,do...while相对不受重视。
但是,最近在读我们项目的代码时,却发现了do...while的一些十分聪明的用法,不是用来做循环,而是用作其他来提高代码的健壮性。
1. do...while(0)消除goto语句。
通常,如果在一个函数中开始要分配一些资源,然后在中途执行过程中如果遇到错误则退出函数,当然,退出前先释放资源,我们的代码可能是这样:
version 1
bool Execute()
{
// 分配资源
int *p = new int;
bool bOk(true);
// 执行并进行错误处理
bOk = func1();
if(!bOk)
{
delete p;
p = NULL;
return false;
}
bOk = func2();
if(!bOk)
{
delete p;
p = NULL;
return false;
}
bOk = func3();
if(!bOk)
{
delete p;
p = NULL;
return false;
}
// ..........
// 执行成功,释放资源并返回
delete p;
p = NULL;
return true;
}
这里一个最大的问题就是代码的冗余,而且我每增加一个操作,就需要做相应的错误处理,非常不灵活。于是我们想到了goto:
version 2
bool Execute()
{
// 分配资源
int *p = new int;
bool bOk(true);
// 执行并进行错误处理
bOk = func1();
if(!bOk) goto errorhandle;
bOk = func2();
if(!bOk) goto errorhandle;
bOk = func3();
if(!bOk) goto errorhandle;
// ..........
// 执行成功,释放资源并返回
delete p;
p = NULL;
return true;
errorhandle:
delete p;
p = NULL;
return false;
}
代码冗余是消除了,但是我们引入了C++中身份比较微妙的goto语句,虽然正确的使用goto可以大大提高程序的灵活性与简洁性,但太灵活的东西往往是很危险的,它会让我们的程序捉摸不定,那么怎么才能避免使用goto语句,又能消除代码冗余呢,请看do...while(0)循环:
version3
bool Execute()
{
// 分配资源
int *p = new int;
bool bOk(true);
do
{
// 执行并进行错误处理
bOk = func1();
if(!bOk) break;
bOk = func2();
if(!bOk) break;
bOk = func3();
if(!bOk) break;
// ..........
}while(0);
// 释放资源
delete p;
p = NULL;
return bOk;
}
“漂亮!”, 看代码就行了,啥都不用说了...
2 宏定义中的do...while(0)
如果你是C++程序员,我有理由相信你用过,或者接触过,至少听说过MFC, 在MFC的afx.h文件里面,你会发现很多宏定义都是用了do...while(0)或do...while(false), 比如说:
#define AFXASSUME(cond) do { bool __afx_condVal=!!(cond); ASSERT(__afx_condVal); __analysis_assume(__afx_condVal); } while(0)
粗看我们就会觉得很奇怪,既然循环里面只执行了一次,我要这个看似多余的do...while(0)有什么意义呢?
当然有!
为了看起来更清晰,这里用一个简单点的宏来演示:
#define SAFE_DELETE(p) do{ delete p; p = NULL} while(0)
假设这里去掉do...while(0),
#define SAFE_DELETE(p) delete p; p = NULL;
那么以下代码:
if(NULL != p) SAFE_DELETE(p)
else ...do sth...
就有两个问题,
1) 因为if分支后有两个语句,else分支没有对应的if,编译失败
2) 假设没有else, SAFE_DELETE中的第二个语句无论if测试是否通过,会永远执行。
你可能发现,为了避免这两个问题,我不一定要用这个令人费解的do...while, 我直接用{}括起来就可以了
#define SAFE_DELETE(p) { delete p; p = NULL;}
的确,这样的话上面的问题是不存在了,但是我想对于C++程序员来讲,在每个语句后面加分号是一种约定俗成的习惯,这样的话,以下代码:
if(NULL != p) SAFE_DELETE(p);
else ...do sth...
其else分支就无法通过编译了(原因同上),所以采用do...while(0)是做好的选择了。
也许你会说,我们代码的习惯是在每个判断后面加上{}, 就不会有这种问题了,也就不需要do...while了,如:
if(...)
{
}
else
{
}
诚然,这是一个好的,应该提倡的编程习惯,但一般这样的宏都是作为library的一部分出现的,而对于一个library的作者,他所要做的就是让其库具有通用性,强壮性,因此他不能有任何对库的使用者的假设,如其编码规范,技术水平等。