| 网站首页 | 业界新闻 | 小组 | 威客 | 人才 | 下载频道 | 博客 | 代码贴 | 在线编程 | 编程论坛
欢迎加入我们,一同切磋技术
用户名:   
 
密 码:  
共有 7116 人关注过本帖, 9 人收藏
标题:[分享]c++综合技术应用文章合集^^
取消只看楼主 加入收藏
aipb2007
Rank: 8Rank: 8
来 自:CQU
等 级:贵宾
威 望:40
帖 子:2879
专家分:7
注 册:2007-3-18
收藏
得分:0 

C++中函数指针数组的使用

笔者在开发某软件过程中遇到这样一个问题,前级模块传给我二进制数据,输入参数为 char* buffer和 int length,buffer是数据的首地址,length表示这批数据的长度。数据的特点是:长度不定,类型不定,由第一个字节(buffer[0])标识该数据的类型,共有256(28 )种可能性。

我的任务是必须对每一种可能出现的数据类型都要作处理,并且我的模块包含若干个函数,在每个函数里面都要作类似的处理。若按通常做法,会写出如下代码:

void MyFuntion( char* buffer, int length )

{__int8 nStreamType = buffer[0];

switch( nStreamType )

{case 0:

function1();

 break;

 case 1:

 ......

 case 255:

 function255();

 break;

 }

如果按照这种方法写下去,那么在我的每一个函数里面,都必须作如此多的判断,写出的代码肯定很长,并且每一次处理,都要作许多次判断之后才找到正确的处理函数,代码的执行效率也不高。针对上述问题,我想到了用函数指针数组的方法解决这个问题。

函数指针的概念,在潭浩强先生的C语言程序设计这本经典的教程中提及过,在大多数情况下我们使用不到,也忽略了它的存在。函数名实际上也是一种指针,指向函数的入口地址,但它又不同于普通的如int*、double*指针,看下面的例子来理解函数指针的概念:

1 int funtion( int x, int y );

2 void main ( void )

 {

3  int (*fun) ( int x, int y );

4  int a = 10, b = 20;

5  function( a, b );

6  fun = function;

7  (*fun)( a, b );

8 ……

}

语句1定义了一个函数function,其输入为两个整型数,返回也为一个整型数(输入参数和返回值可为其它任何数据类型);语句3定义了一个函数指针,与int*或double*定义指针不同的是,函数指针的定义必须同时指出输入参数,表明这是一个函数指针,并且*fun也必须用一对括号括起来;语句6将函数指针赋值为funtion,前提条件是*fun和function的输入参数和返回值必须保持一致。语句5直接调用函数function(),语句7是调用函数指针,二者等效。

当然从上述例子看不出函数指针的优点,目的主要是想引出函数指针数组的概念。我们从上面例子可以得知,既然函数名可以通过函数指针加以保存,那们也一定能定义一个数组保存若干个函数名,这就是函数指针数组。正确使用函数指针数组的前提条件是,这若干个需要通过函数指针数组保存的函数必须有相同的输入、输出值。

这样,我工作中所面临的问题可以解决如下:

首先定义256个处理函数(及其实现)。

void funtion0( void );

……..

void funtion255(void );

其次定义函数指针数组,并给数组赋值。

void (*fun[256])(void);

fun[0] = function0;

…….

fun[255] = function();

最后,MyFunction()函数可以修改如下:

void MyFuntion( char* buffer, int length )

{

__int8 nStreamType = buffer[0];

(*fun[nStreamType])();

}

只要2行代码,就完成了256条case语句要做的事,减少了编写代码时工作量,将nStreamType作为数组下标,直接调用函数指针,从代码执行效率上来说,也比case语句高。假如多个函数中均要作如此处理,函数指针数组更能体现出它的优势。


Fight  to win  or  die...
2007-05-05 11:54
aipb2007
Rank: 8Rank: 8
来 自:CQU
等 级:贵宾
威 望:40
帖 子:2879
专家分:7
注 册:2007-3-18
收藏
得分:0 

C++中结构体的的慨念和使用方法
结构体

结构体就是一个可以包含不同数据类型的一个结构,它是一种可以自己定义的数据类型,它的特点和数组主要有两点不同,首先结构体可以在一个结构中声明不同的数据类型,第二相同结构的结构体变量是可以相互赋值的,而数组是做不到的,因为数组是单一数据类型的数据集合,它本身不是数据类型(而结构体是),数组名称是常量指针,所以不可以做为左值进行运算,所以数组之间就不能通过数组名称相互复制了,即使数据类型和数组大小完全相同。

定义结构体使用struct修饰符,例如:

C++ 代码

struct test

{

 float a;

 int b;

};

上面的代码就定义了一个名为test的结构体,它的数据类型就是test,它包含两个成员a和b,成员a的数据类型为浮点型,成员b的数据类型为整型。

由于结构体本身就是自定义的数据类型,定义结构体变量的方法和定义普通变量的方法一样。

test pn1;

这样就定义了一test结构体数据类型的结构体变量pn1,结构体成员的访问通过点操作符进行,

pn1.a=10 就对结构体变量pn1的成员a进行了赋值操作,

注意:结构体生命的时候本身不占用任何内存空间,只有当你用你定义的结构体类型定义结构体变量的时候计算机才会分配内存。

结构体,同样是可以定义指针的,那么结构体指针就叫做结构指针。

结构指针通过->符号来访问成员,下面我们就以上所说的看一个完整的例子:

C++ 代码

//程序作者:管宁

//所有稿件均有版权,如要转载,请务必注明出处和作者

#include<iostream>

#include<string>

usingnamespacestd;

structtest//定义一个名为test的结构体

{

 inta;//定义结构体成员a

 intb;//定义结构体成员b

};

voidmain()

{

 testpn1;//定义结构体变量pn1

 testpn2;//定义结构体变量pn2

 pn2.a=10;//通过成员操作符.给结构体变量pn2中的成员a赋值

 pn2.b=3;//通过成员操作符.给结构体变量pn2中的成员b赋值

 pn1=pn2;//把pn2中所有的成员值复制给具有相同结构的结构体变量pn1

 cout<<pn1.a<<"|"<<pn1.b<<endl;

 cout<<pn2.a<<"|"<<pn2.b<<endl;

 test*point;//定义结构指针

 point=&pn2;//指针指向结构体变量pn2的内存地址

 cout<<pn2.a<<"|"<<pn2.b<<endl;

 point->a=99;//通过结构指针修改结构体变量pn2成员a的值

 cout<<pn2.a<<"|"<<pn2.b<<endl;

 cout<<point->a<<"|"<<point->b<<endl;

 cin.get();

}

总之,结构体可以描述数组不能够清晰描述的结构,它具有数组所不具备的一些功能特性。


Fight  to win  or  die...
2007-05-05 11:55
aipb2007
Rank: 8Rank: 8
来 自:CQU
等 级:贵宾
威 望:40
帖 子:2879
专家分:7
注 册:2007-3-18
收藏
得分:0 

C++中数组和指针类型的关系
一个整数类型数组如下进行定义: C++ 代码 int a[]={1,2,3,4};

如果简单写成: C++ 代码 a;//数组的标识符名称

这将代表的是数组第一个元素的内存地址,a;就相当于&a[0],它的类型是数组元素类型的指针,在这个例子中它的类型就是int*

如果我们想访问第二个元素的地址我们可以写成如下的两种方式!

C++ 代码

&a[1];

a+1//注意这里的表示就是将a数组的起始地址向后进一位,移动到第二个元素的地址上也就是a[0]到a[1]的过程!

数组名称和指针的关系其实很简单,其实数组名称代表的是数组的第一个元素的内存地址,这和指针的道理是相似的!


Fight  to win  or  die...
2007-05-05 11:55
aipb2007
Rank: 8Rank: 8
来 自:CQU
等 级:贵宾
威 望:40
帖 子:2879
专家分:7
注 册:2007-3-18
收藏
得分:0 

C++中用函数模板实现和优化抽象操作
在创建完成抽象操作的函数时,如:拷贝,反转和排序,你必须定义多个版本以便能处理每一种数据类型。以 max() 函数为例,它返回两个参数中的较大者:

double max(double first, double second);

complex max(complex first, complex second);

date max(date first, date second);

//..该函数的其它版本

尽管这个函数针对不同的数据类型其实现都是一样的,但程序员必须为每一种数据类型定义一个单独的版本:

double max(double first, double second)

{

 return first>second? first : second;

}

complex max(complex first, complex second)

{

 return first>second? first : second;

}

date max(date first, date second)

{

 return first>second? first : second;

}

这样不但重复劳动,容易出错,而且还带来很大的维护和调试工作量。更糟的是,即使你在程序中不使用某个版本,其代码仍然增加可执行文件的大小,大多数编译器将不会从可执行文件中删除未引用的函数。

用普通函数来实现抽象操作会迫使你定义多个函数实例,从而招致不小的维护工作和调试开销。解决办法是使用函数模板代替普通函数。

使用函数模板

函数模板解决了上述所有的问题。类型无关并且只在需要时自动实例化。本文下面将展示如何定义函数模板以便抽象通用操作,示范其使用方法并讨论优化技术。

第一步:定义

函数模板的声明是在关键字 template 后跟随一个或多个模板在尖括弧内的参数和原型。与普通函数相对,它通常是在一个转换单元里声明,而在另一个单元中定义,你可以在某个头文件中定义模板。例如:

// file max.h

#ifndef MAX_INCLUDED

#define MAX_INCLUDED

template <class T> T max(T t1, T t2)

{

 return (t1 > t2) ? t1 : t2;

}

#endif

<class T> 定义 T 作为模板参数,或者是占位符,当实例化 max()时,它将替代具体的数据类型。max 是函数名,t1和t2是其参数,返回值的类型为 T。你可以像使用普通的函数那样使用这个 max()。编译器按照所使用的数据类型自动产生相应的模板特化,或者说是实例:

int n=10,m=16;

int highest = max(n,m); // 产生 int 版本

std::complex<double> c1, c2;

//.. 给 c1,c2 赋值

std::complex<double> higher=max(c1,c2); // complex 版本

第二步:改进设计

上述的 max() 的实现还有些土气——参数t1和t2是用值来传递的。对于像 int,float 这样的内建数据类型来说不是什么问题。但是,对于像std::complex 和 std::sting这样的用户定义的数据类型来说,通过引用来传递参数会更有效。此外,因为 max() 会认为其参数是不会被改变的,我们应该将 t1和t2声明为 const (常量)。下面是 max() 的改进版本:

template <class T> T max(const T& t1, const T& t2)

{

 return (t1 > t2) ? t1 : t2;

}

额外的性能问题

很幸运,标准模板库或 STL 已经在 <algorithm> 里定义了一个叫 std::max()的算法。因此,你不必重新发明。让我们考虑更加现实的例子,即字节排序。众所周知,TCP/IP 协议在传输多字节值时,要求使用 big endian 字节次序。因此,big endian 字节次序也被称为网络字节次序(network byte order)。如果目的主机使用 little endian 次序,必须将所有过来的所字节值转换成 little endian 次序。同样,在通过 TCP/IP 传输多字节值之前,主机必须将它们转换成网络字节次序。你的 socket 库声明四个函数,它们负责主机字节次序和网络字节次序之间的转换:

unsigned int htonl (unsigned int hostlong);

unsigned short htons (unsigned short hostshort);

unsigned int ntohl (unsigned int netlong);

unsigned short ntohs (unsigned short netshort);

这些函数实现相同的操作:反转多字节值的字节。其唯一的差别是方向性以及参数的大小。非常适合模板化。使用一个模板函数来替代这四个函数,我们可以定义一个聪明的模板,它会处理所有这四种情况以及更多种情形:

template <class T> T byte_reverse(T val);

为了确定 T 实际的类型,我们使用 sizeof 操作符。此外,我们还使用 STL 的 std::reverse 算法来反转值的字节:

template <class T> T byte_reverse(T val)

{

 // 将 val 作为字节流

 unsigned char *p=reinterpret_cast<unsigned char*> (&val);

 std::reverse(p, p+sizeof(val));

 return val;

}

使用方法

byte_reverse() 模板处理完全适用于所有情况。而且,它还可以不必修改任何代码而灵活地应用到其它原本(例如:64 位和128位)不支持的类型:

int main()

{

 int n=1;

 short k=1;

 __int64 j=2, i;

 int m=byte_reverse(n);// reverse int

 int z=byte_reverse(k);// reverse short

 k=byte_reverse(k); // un-reverse k

 i=byte_reverse(j); // reverse __int64

}

注:模板使用不当会影响.exe 文件的大小,也就是常见的代码浮肿问题。


Fight  to win  or  die...
2007-05-05 12:02
aipb2007
Rank: 8Rank: 8
来 自:CQU
等 级:贵宾
威 望:40
帖 子:2879
专家分:7
注 册:2007-3-18
收藏
得分:0 

浅析c/c++中的指针
在学习c/c+过程中,指针是一个比较让人头痛的问题,稍微不注意将会是程序编译无法

通过,甚至造成死机。在程序设计过程中,指针也往往是产生隐含bug的原因。下面就来

谈谈指针的应用以及需要注意的一些问题,里面也许就有你平时没有注意到的问题,希

望能帮助各位读者理解好指针。

一、我们先来回忆一下指针的概念吧,方便下面的介绍

指针是存放地址值的变量或者常量。例如:int a=1;&a就表示指针常量(“&”表示

取地址运算符,也即引用)。int *b,b表示的是指针变量(注意,是b表示指针变量而

不是*b),*表示要说明的是指针变量。大家注意int *b[2]和int(*b)[2]是不同的,

int *b表示一个指针数组,而int (*b)[2]表示含有两个元素的int指针,这里要注意

运算优先级问题,有助于理解指针问题。

在这里大概介绍基本概念就够了,至于具体使用方法,如赋值等,很多书都有介绍

,我就不多说了。

二、应用以及注意的问题

1、 理解指针的关键所在——对指针类型和指针所指向的类型的理解

①、 指针类型:可以把指针名字去掉,剩下的就是这个指针

例如:int *a;//指针类型为int *

int **a;//指针类型为int **

int *(*a)[8];//指针类型为 int *(*)[8]

②、 指针所指向的类型:是指编译器将把那一片内存所看待成的类型。这里只要把

指针声明语句中的指针名字和名字右边的“*”号去掉就可以了,剩下的就是指针所指向

的类型。

我之所以把他们放在第一位,是因为弄清楚他们是学c/c++指针的重点,正确理解他

们才能使你打好c/c++的编程基础。

2、 指针的应用——传递参数。

其实它可以相当于隐式的返回值,这就比return的方法更加灵活了,可以返回更多

的值,看看下面的例子自然就明白了:

#include "iostream.h"

void example(int *a1,int &b1,int c1)

{

 *a1*=3;

 ++b1;

 ++c1;

}

void main()

{

 int *a;

 int b,c;

 *a=6;

 b=7;c=10;

 example(a,b,c);

 cout <<"*a="<<*a<

 cout <<"b="<

 cout <<"c="<

}

输出:*a=18

b=8

c=10

注意到没有,*a和b的值都改变了,而c没有变。这是由于a1是指向*a(=6)的指针

,也即与a是指向同一个地址,所以当a1指向的值改变了,*a的值也就改变了。在函数中

的参数使用了引用(int &b1),b1是b的别名,也可以把它当作特殊的指针来理解,所

以b的值会改变。函数中的参数int c1只是在函数中起作用,当函数结束时候便消失了,

所以在main()中不起作用。

3、 关于全局变量和局部变量的一个问题

先不废话了,先看看程序:

#include “iostream.h”

int a=5;

int *example1(int b)

{

a+=b;

return &a;

}

int *example2(int b)

{

int c=5;

b+=c;

return &b;

}

void main()

{

int *a1=example1(10);

int *b1=example2(10);

cout <<”a1=”<<*a1<

cout <<”b1=”<<*b1<

}

输出结果:

a1=15

b1=4135

*b1怎么会是4135,而不是15呢?是程序的问题?没错吧?

由于a是全局变量,存放在全局变量的内存区,它一直是存在的;而局部变量则是存

在于函数的栈区,当函数example2()调用结束后便消失,是b指向了一个不确定的区域

,产生指针悬挂。

下面是对example1()和example2()的反汇编(用TC++ 3.0编译):

example1():

push bp;入栈

mov bp,sp

mov ax,[bp+04];传递参数

add [00AA],ax;相加

mov ax,00AA ;返回了结果所在的地址

.

.

.

pop bp;恢复栈,出栈

ret;退出函数

example2():

push bp;入栈

mov bp,sp

sub sp,02

mov word ptr [bp-02],0005

mov ax,[bp-02];传递参数

add [bp+04],ax;相加

lea ax,[bp+04];问题就出在这里

.

.

.

mov sp,bp

pop bp;恢复栈,出栈

ret;退出函数

对比之后看出来了吧?ax应该是存储的是结果的地址。而在example2()中,返回

的却是[bp+04]的内容,因此指针指向了一个不确定的地方,由此产生的指针悬挂。exa

mple1()中,ax返回了正确的结果的地址。

4、 内存问题:使用指针注意内存的分配和边界。

使用指针过程中应该给变量一个适当的空间,以免产生不可见的错误。

请看以下代码:

#include “iostream.h”

void main()

{

char *a1;

char *a2;

cin >>a1;

cin >>a2;

cout <<”a1=”<

cout <<”a2=”<

}

输入:abc

123

输出:

a1=123

a2=

Null pointer assignment

指针指向了“空”。解决办法就是分配适当的内存给这两个字符串。修正后的代码

如下:

#include “iostream.h”

void main()

{

char *a1;

char *a2;

a1=new char [10];

a2=new char [10];

cin >>a1;

cin >>a2;

cout <<”a1=”<

cout <<”a2=”<

delete(a1);注意,别忘了要释放内存空间

delete(a2);

}

到此就能输出正确的结果了。

分配了适当的内存之后要注意释放内参空间,同时还应该注意不要超出所分配的内

存的大小,否则会有溢出现象产生,导致不可预料的结果。

5、 关于特殊的指针——引用

引用有时候应用起来要比指针要灵活,用它做返回的时候是不产生任何变量的副本

的这样减小了内存的占用,提高执行的速度。引用使用起来要比指针好理解,比较直观

。当引用作为参数时,不会改变参数的地址,因此可以作为左值。

下面请看一个例子:

#include “iostream.h”

char ch[5]=”ABCD”;

char &example(int b)

{

return ch;

}

void main()

{

cout <<”ch=”<

example(2)=”c”;

cout<<”ch=”<

}

输出结果:

ch=ABCD

ch=ABcD

在实际编程过程中,可以灵活地引用或指针,尽量提高程序的可读性和执行效率。

三、小结:

指针是学习c/c++的重点难点,主要原因是指针较为抽象,不容易理解。使用指针千

万要明白让指针指向什么地方,如何让指针指向正确的地方。在深入系统底层之中需要

应用到大量的指针,因此需要理解好指针的基本概念,例如:指针类型和指针所指向的

类型。平时应该对留心观察,了解程序的工作过程,必要时候可以对程序进行反汇编,

加深对指针的理解,这种方法同样适合学别的编程方面的知识。

四、结束:

指针的应用是很广泛的,利用指针可以做很多事情,要成为一个好的程序员,必须

对指针有比较深刻的了解。写本文的目的在于让大家对指针有更深一层的了解,提高指

针的应用能力,内容大都是我在实际编程中遇到的问题。相信能给大家一定的帮助。


Fight  to win  or  die...
2007-05-05 12:03
aipb2007
Rank: 8Rank: 8
来 自:CQU
等 级:贵宾
威 望:40
帖 子:2879
专家分:7
注 册:2007-3-18
收藏
得分:0 

教你理解复杂的C/C++声明
介绍

曾经碰到过让你迷惑不解、类似于int * (* (*fp1) (int) ) [10];这样的变量声明吗?本

文将由易到难,一步一步教会你如何理解这种复杂的C/C++声明:我们将从每天都能碰到的

较简单的声明入手,然后逐步加入const修饰符和typedef,还有函数指针,最后介绍一个

能够让你准确地理解任何C/C++声明的“右左法则”。需要强调一下的是,复杂的C/C++声

明并不是好的编程风格;我这里仅仅是教你如何去理解这些声明。注意:为了保证能够在

同一行上显示代码和相关注释,本文最好在至少1024x768分辨率的显示器上阅读。

基础

让我们从一个非常简单的例子开始,如下:

int n;

这个应该被理解为“declare n as an int”(n是一个int型的变量)。

接下去来看一下指针变量,如下:

int *p;

这个应该被理解为“declare p as an int *”(p是一个int *型的变量),或者说p是一

个指向一个int型变量的指针。我想在这里展开讨论一下:我觉得在声明一个指针(或引用

)类型的变量时,最好将*(或&)写在紧靠变量之前,而不是紧跟基本类型之后。这样可

以避免一些理解上的误区,比如:

int* p,q;

第一眼看去,好像是p和q都是int*类型的,但事实上,只有p是一个指针,而q是一个最简

单的int型变量。

我们还是继续我们前面的话题,再来看一个指针的指针的例子:

char **argv;

理论上,对于指针的级数没有限制,你可以定义一个浮点类型变量的指针的指针的指针的

指针...

再来看如下的声明:

int RollNum[30][4];

int (*p)[4]=RollNum;

int *q[5];

这里,p被声明为一个指向一个4元素(int类型)数组的指针,而q被声明为一个包含5个元

素(int类型的指针)的数组。

另外,我们还可以在同一个声明中混合实用*和&,如下:

int **p1; // p1 is a pointer to a pointer to an int.

int *&p2; // p2 is a reference to a pointer to an int.

int &*p3; // ERROR: Pointer to a reference is illegal.

int &&p4; // ERROR: Reference to a reference is illegal.

注:p1是一个int类型的指针的指针;p2是一个int类型的指针的引用;p3是一个int类型引

用的指针(不合法!);p4是一个int类型引用的引用(不合法!)。

const修饰符

当你想阻止一个变量被改变,可能会用到const关键字。在你给一个变量加上const修饰符

的同时,通常需要对它进行初始化,因为以后的任何时候你将没有机会再去改变它。例如

const int n=5;

int const m=10;

上述两个变量n和m其实是同一种类型的--都是const int(整形恒量)。因为C++标准规定

,const关键字放在类型或变量名之前等价的。我个人更喜欢第一种声明方式,因为它更突

出了const修饰符的作用。

当const与指针一起使用时,容易让人感到迷惑。例如,我们来看一下下面的p和q的声明:

const int *p;

int const *q;

他们当中哪一个代表const int类型的指针(const直接修饰int),哪一个代表int类型的

const指针(const直接修饰指针)?实际上,p和q都被声明为const int类型的指针。而i

nt类型的const指针应该这样声明:

int * const r= &n; // n has been declared as an int

这里,p和q都是指向const int类型的指针,也就是说,你在以后的程序里不能改变*p的值

。而r是一个const指针,它在声明的时候被初始化指向变量n(即r=&n;)之后,r的值将不

再允许被改变(但*r的值可以改变)。

组合上述两种const修饰的情况,我们来声明一个指向const int类型的const指针,如下:

const int * const p=&n // n has been declared as const int

下面给出的一些关于const的声明,将帮助你彻底理清const的用法。不过请注意,下面的

一些声明是不能被编译通过的,因为他们需要在声明的同时进行初始化。为了简洁起见,

我忽略了初始化部分;因为加入初始化代码的话,下面每个声明都将增加两行代码。

char ** p1; // pointer to pointer to char

const char **p2; // pointer to pointer to const char

char * const * p3; // pointer to const pointer to char

const char * const * p4; // pointer to const pointer to const char

char ** const p5; // const pointer to pointer to char

const char ** const p6; // const pointer to pointer to const char

char * const * const p7; // const pointer to const pointer to char

const char * const * const p8; // const pointer to const pointer to const char

注:p1是指向char类型的指针的指针;p2是指向const char类型的指针的指针;p3是指向

char类型的const指针;p4是指向const char类型的const指针;p5是指向char类型的指针

的const指针;p6是指向const char类型的指针的const指针;p7是指向char类型const指针

的const指针;p8是指向const char类型的const指针的const指针。

typedef的妙用

typedef给你一种方式来克服“*只适合于变量而不适合于类型”的弊端。你可以如下使用

typedef:

typedef char * PCHAR;

PCHAR p,q;

这里的p和q都被声明为指针。(如果不使用typedef,q将被声明为一个char变量,这跟我

们的第一眼感觉不太一致!)下面有一些使用typedef的声明,并且给出了解释:

typedef char * a; // a is a pointer to a char

typedef a b(); // b is a function that returns

// a pointer to a char

typedef b *c; // c is a pointer to a function

// that returns a pointer to a char

typedef c d(); // d is a function returning

// a pointer to a function

// that returns a pointer to a char

typedef d *e; // e is a pointer to a function

// returning a pointer to a

// function that returns a

// pointer to a char

e var[10]; // var is an array of 10 pointers to

// functions returning pointers to

// functions returning pointers to chars.

typedef经常用在一个结构声明之前,如下。这样,当创建结构变量的时候,允许你不使用

关键字struct(在C中,创建结构变量时要求使用struct关键字,如struct tagPOINT a;

而在C++中,struct可以忽略,如tagPOINT b)。

typedef struct tagPOINT

{

int x;

int y;

}POINT;

POINT p; /* Valid C code */

函数指针

函数指针可能是最容易引起理解上的困惑的声明。函数指针在DOS时代写TSR程序时用得最

多;在Win32和X-Windows时代,他们被用在需要回调函数的场合。当然,还有其它很多地

方需要用到函数指针:虚函数表,STL中的一些模板,Win NT/2K/XP系统服务等。让我们来

看一个函数指针的简单例子:

int (*p)(char);

这里p被声明为一个函数指针,这个函数带一个char类型的参数,并且有一个int类型的返

回值。另外,带有两个float类型参数、返回值是char类型的指针的指针的函数指针可以声

明如下:

char ** (*p)(float, float);

那么,带两个char类型的const指针参数、无返回值的函数指针又该如何声明呢?参考如下

void * (*a[5])(char * const, char * const);

“右左法则”[重要!!!]

The right-left rule: Start reading the declaration from the innermost parenthe

ses, go right, and then go left. When you encounter parentheses, the direction

should be reversed. Once everything in the parentheses has been parsed, jump

out of it. Continue till the whole declaration has been parsed.

这是一个简单的法则,但能让你准确理解所有的声明。这个法则运用如下:从最内部的括

号开始阅读声明,向右看,然后向左看。当你碰到一个括号时就调转阅读的方向。括号内

的所有内容都分析完毕就跳出括号的范围。这样继续,直到整个声明都被分析完毕。

对上述“右左法则”做一个小小的修正:当你第一次开始阅读声明的时候,你必须从变量

名开始,而不是从最内部的括号。

下面结合例子来演示一下“右左法则”的使用。

int * (* (*fp1) (int) ) [10];

阅读步骤:

1. 从变量名开始 -------------------------------------------- fp1

2. 往右看,什么也没有,碰到了),因此往左看,碰到一个* ------ 一个指针

3. 跳出括号,碰到了(int) ----------------------------------- 一个带一个int参数

的函数

4. 向左看,发现一个* --------------------------------------- (函数)返回一个指

5. 跳出括号,向右看,碰到[10] ------------------------------ 一个10元素的数组

6. 向左看,发现一个* --------------------------------------- 指针

7. 向左看,发现int ----------------------------------------- int类型

总结:fp1被声明成为一个函数的指针,该函数返回指向指针数组的指针.




Fight  to win  or  die...
2007-05-05 12:04
aipb2007
Rank: 8Rank: 8
来 自:CQU
等 级:贵宾
威 望:40
帖 子:2879
专家分:7
注 册:2007-3-18
收藏
得分:0 

////////////////////////////////////////原文////////////////////////////////////////////////////////////
Q: 我能防止别人从我的类继承吗?
A: 可以的,但何必呢?好吧,也许有两个理由:

出于效率考虑——不希望我的函数调用是虚的
出于安全考虑——确保我的类不被用作基类(这样我拷贝对象时就不用担心对象被切割(slicing)了)[译注:“对象切割”指,将派生类对象赋给基类变量时,根据C++的类型转换机制,只有包括在派生类中的基类部分被拷贝,其余部分被“切割”掉了。]
根据我的经验,“效率考虑”常常纯属多余。在C++中,虚函数调用如此之快,和普通函数调用并没有太多的区别。请注意,只有通过指针或者引用调用时才会启用虚拟机制;如果你指名道姓地调用一个对象,C++编译器会自动优化,去除任何的额外开销。
如果为了和“虚函数调用”说byebye,那么确实有给类继承体系“封顶”的需要。在设计前,不访先问问自己,这些函数为何要被设计成虚的。我确实见过这样的例子:性能要求苛刻的函数被设计成虚的,仅仅因为“我们习惯这样做”!

好了,无论如何,说了那么多,毕竟你只是想知道,为了某种合理的理由,你能不能防止别人继承你的类。答案是可以的。可惜,这里给出的解决之道不够干净利落。你不得不在在你的“封顶类”中虚拟继承一个无法构造的辅助基类。还是让例子来告诉我们一切吧:

class Usable;
class Usable_lock {
friend class Usable;
private:
Usable_lock() {}
Usable_lock(const Usable_lock&) {}
};
class Usable : public virtual Usable_lock {
// ...
public:
Usable();
Usable(char*);
// ...
};
Usable a;
class DD : public Usable { };
DD dd; // error: DD::DD() cannot access
// Usable_lock::Usable_lock(): private member

//////////////////////////////////////////////////////////////////////////////////////////////////////////

如果把
class Usable : public /*virtual*/ Usable_lock //虚继承Usable_lock类

这样,代码就可以通过,就没有达到防止Usable 被继承的作用。

////////////////////////////////////////////////////////////////////////////////
先说说上面的代码,包括继承原理:

首先,把我们不想被继承的类作为一个派生类去继承一个没哟意义的Usable_lock类,注意,这个类显示的把构造函数
声明为私有,然后把Usable作为友员类,这就是关键。
再来,继承中派生类创建的过程:首先要调用父类的构造函数,即先建立一个父类的对象。
这里Usable类是公有继承,所以是访问不到私有的构造函数,但是作为友员是可以的,所以消除了这个限制。由此,我们的Usable类是可以被创建的。

到这里,看看为什么普通的非虚拟继承,能使dd的创建合法。
首先,按照我们先前说讲,DD类公有继承Usable类,创建dd对象的过程是先调用父类构造函数(Usable类构造函数),这一步合法,因为可以访问到Usable的构造函数。这时就要创建一个Usable对象,要创建Usable 对象,就要调用Usable 的父类构造函数,就来到Usable_lock类,开始我们说了,由Usable访问Usable_lock是没有问题的。所以这样一来,dd对象就被正确的创建了。他间接的访问过Usable_lock。

////////////////////////////////////////////////////////////////////////
下面说说BJ在这个例子中是怎样通过虚拟技术防止Usable类被继承的:
虚拟继承,通过在声明派生类继承父类时加上virtual来指定。
虚拟继承的作用:当系统碰到多重继承的时候保证继承类成员函数的唯一性。
怎么理解他的作用,看下面这个图1:
class base //基类
class derive1 : public base
class derive2 : public base

class derive3 : public derive1,public derive2

这种情况下,derive3就同时拥有了两套base的函数。所以在调用时产生的模糊性会使程序出错。具体我就不谈那么多了。

解决这个问题,就用到了虚拟技术。
修改上面的代码 图2:
class base //基类
class derive1 : virtual public base
class derive2 : virtual public base

class derive3 : public derive1,public derive2

采用虚拟继承的时候,在derive3中,一旦一次继承了base类,再次继承base的时候就会忽略重复的base类。所以保证继承类成员函数的唯一性。

///////////////////////////////////////////////////////////////////
在这个问题中,BJ大师用到的不是这个原理,而是由此产生的一个构造函数调用顺序问题。
在非虚拟继承中,比如图1中。在构造derive3时,其父类调用顺序为derive1-derive2-base;
然后在虚拟继承中。比如图2中。在构造derive3时,其父类调用顺序为base-derive1-derive2;

同样,这样的顺序,作用显而易见,是防止base被多次继承。

再回到我们最初的话题,在DD类创建对象dd时,最先被调用的是Usable_lock类的构造函数,但是,由于Usable_lock构造函数声明被私有,就出现不能访问的错误。由此,是不能创建DD对象的。用这个方法,完成了防止了Usable被继承。


[此贴子已经被作者于2007-6-2 0:27:22编辑过]


Fight  to win  or  die...
2007-06-02 00:23
快速回复:[分享]c++综合技术应用文章合集^^
数据加载中...
 
   



关于我们 | 广告合作 | 编程中国 | 清除Cookies | TOP | 手机版

编程中国 版权所有,并保留所有权利。
Powered by Discuz, Processed in 0.031621 second(s), 8 queries.
Copyright©2004-2024, BCCN.NET, All Rights Reserved