指针问题!!做指针总结时的心得及发现的问题!!各位大师请指点一二!回者有分

monk188 2005-05-11 02:04:56
先说两句题外话.最近,在研读mfc的源代码,研读过程中倍感自己C++的功底不足,既要从mfc的设计上思考,又得从C++的语法上思考,

十分的吃力.虽说是收获不小,但总觉得,不是个办法.因此,现在先放下了mfc来补C++.
  
  关键的问题还是在指针上,自己被感指针功能的强大,并且深刻的意识到学习C/C++不会使用指针是绝对行不通的,因此放下手中的mfc来

做一个指针用法的小结.并希望此次小结能让自己的C++水平上升一个层次.

_____________________________________________
  由于自己还是一个大二的学生(马上大三了),实践经验少之又少,自己的总结多数来自书籍,还有自己少之又少的代码编写过程中.可

能总结了不少错误的原则,还望各位大师帮学生一个忙.指点一二.免的辛辛苦苦总结了半天还把自己误入歧途啊!哈哈哈...

#############################################

小结一.
  把指针变量作为函数参数的用途.
1.用来传递大型对象(避免按值传递时花费在拷贝上的时间).
 这是我的理解:把指向大型对象的指针传入函数体中,意味着函数有了操作该对象的功能.通过该指针操作了一块不属于该函数局部域的内

存块.而花费的开销只是在函数体内分配了该对象指针的空间,传参时只是对对象的指针进行了一下拷贝.因此在时间和空间上都提高了效率


  //该总结来自C++ Primer,

2.用于面向对象编程中,经常把基类指针作为函数的参数,根据不同的对象实例的指针实现多态的操作.
 小生在看mfc原代码的时候,体会相当的深刻,熟悉mfc的类的层次结构的大师应该有很深的体会.整个mfc提供的那么多类,就是通过指针和

继承把一个个不相干的类联系到了一起.最后就出现了Application Framework这样迷人的东西.

3.数组作为函数的参数.
数组作为函数的参数的实质,函数的参数是一个该类型的指针变量。而该指针变量指向数组的首地址。
 所以以下代码的实质是一样的。
 void F( char *pchar );
void F( char buf[] );
这里出现这样的问题。
 在全局域中定义
 main()
{
int a[10];
sizeof(a);//返回的数组单元的长度。
   //所以通过
sizeof(a)/sizeof(a[0]);
//就可以计算出数组的长度。
}

而定义函数
f( int *p )
{
sizeof(p);//计算出的是该指针变量单元的长度
}
因此
main()
{
int a[10];
f(a);//函数内部则不能得到数组的长度,因此难以对数组进行操作
}
因此在编写操作数组的函数中,一般设置两个参数,一般的写发如下.
template<class Type>
void f( Type *p , int size );
 //该终结来自小生的一次实验代码和C++ primer

4.指向函数的指针作为函数的参数
为什么要使用函数指针作为函数的参数。
 我的理解:在一个复杂而又想让他具有通用功能的过程中(函数)中,对参数数据的操作在功能具有一定的相似性,但又有明显的差异和不同
!因此需要使用不同的函数来处理不同的数据,或者处理相同的数据来达到不同的效果!从而支持某函数(过程)的实现。
一个经典的用法是:
编写遍历二叉树的函数。
 遍历只是一个手段,而最终的目的是为了对树内的数据进性行操作!
 所以函数的定义写成这样
 void BinaryTree::inorder( void(*pfn)( node& item ) )
这样只需要编写返回值为void 参数类型为 node&类型的函数就可以对选择遍历操作时对树内结点的操作。方法是把函数指针传入遍历的函数
就可以啦!

5.指向指针的指针作为函数的参数。
这种用法到现在为止小生还没有见过。只是在"谭浩强"的C语言书中见过。但觉得其价值自己提炼不出来。
 这里向各位大师请教,如果有写过这样的函数,请忙小生总结一下。这种类型的函数的用法!

注意:
这里我觉得有一个问题值得注意!
 指针作为函数的参数不一定指向切实的对象。
 因此在函数体内部做这样的检查是必要的,避免发生,难以发现的错误。
 if( p!=NULL )
{
...
}
else
{
//发出警告
}

以上是小生对指针做为函数参数的总结
——————————————————————————————————————————————————————————————

二。返回值是函数指针的函数。
 返回值是函数指针的函数,涉及到复杂的内存管理问题。
 自己更是头大的要命。所以我把这个问题避开。以后做内存管理的总结时在深入。
 在这里我只谈一个mfc框架中普遍的用法。
 通过函数返回的某个类型的指针,当然在mfc初始化的时候这个指针已经指向确切的对象。通过该指针就可以为别的类提供必要的服务。
 例如:mfc中经典的view/document类中
CMyView::OnDraw( CDC* pDC )
{
CMyDoc *pDoc=GetDocument();//这个地方就获得了CMyDocument对象使用权限,这样就可以在OnDraw函数中为之提供服务了。
ASSET_VALID(pDC);
//// TODO: add draw code for native data here
}
 通过这样的方法,就在没有定义在CMyView类内部定义CMyDocument对象的情况下使用了CMyDocument的对象。
 避免了使用组合的方法。当然,通过这样的方法,切实的把两个类的使用融合到了一起,堪称经典。
 在mfc的整个框架中有好多类都是这样指针联系起来协同工作的。
 
 这也是我看mfc代码看不下去的巨大原因!设计太过巧妙。自己指针的功底又不足啊。

——————————————————————————————————————————————————————————————
这就是自己目前阶段的总结。肯定有好多漏洞吧!
还望各位大师给小生一些指点。
小生在这里先谢过了!!
...全文
381 34 打赏 收藏 转发到动态 举报
写回复
用AI写文章
34 条回复
切换为时间正序
请发表友善的回复…
发表回复
monk188 2005-05-13
  • 打赏
  • 举报
回复
啊。。。。。!
看了前辈的解释真是毛色顿开!
现在已经理解了!
因为
**ptr=0;
所以*ptr就是在取一个空地址上的内容。
还要把某地址的址赋值给它,当然是不和逻辑的事情。
明白,明白。
————————————————————————————————————————————
拜谢!
whg1016 2005-05-12
  • 打赏
  • 举报
回复
august1983 2005-05-12
  • 打赏
  • 举报
回复
我觉得指针搞得太复杂很容易造成数据上传递失误,不知道各位遇到过这些问题没有.

我遇到过不少,找错误花了很多时间
monk188 2005-05-12
  • 打赏
  • 举报
回复
回:
————————————————————————————-————————————————
func1(char **p)
{
*p = new char[1024];
}

func2(char *p)
{
p = new char[1024];
}

func2会产生内存泄露,func1表现正确。
————————————————————————————————————————————-
我想解释一下我理解func2内存泄露的原因,看是否正确。
调用func2的时候如:
func2( pstr );
回产生这一系列的操作。
系统在栈空间上分配 char *p;
并进行操作 p=pstr;
然后是函数内部的工作,p=new char[1024];
然后函数体结束,p的栈空间被回收,则开辟的空间成了无法操作的空间,因为没有指针指想它。
原因是分配空间是给pstr的拷贝分配的。new的时候p已经指向另一快空间。
不知道理解是否正确。
所以在进行这样的函数时用指针的指针就比较合适?
————————————————————————————————————————————
还有一个问题,就是关于该函数的内存管理。
new 和 delete不是一定要成对出现的吗?
楼主的函数func1中分配的空间如何回收呢?
难道是在编写一个
func1_release( char **p )
{
delete *p;
}
并提醒用户,这个函数需要与func1成对使用?

————————————————————————————————————————————
这便是我一直不知道怎么做返回值是 指针类型的 函数的总结。
关键是不明白,在函数体内部分配了堆空间的函数,要如何设计?
因为总得想办法delete;
————————————————————————————————————————————

还请指点!
拜谢!!
nicknide 2005-05-12
  • 打赏
  • 举报
回复
Q:但是奇怪的问题是该代码,在运行的时候出错!!!
不知道是怎么回事!
还请前辈指点!!!!

A:注意这里,
char **pstr=NULL;
以后指针这里直接写0,NULL是不好的习惯和标准,这样一来就可以看明白了:
char **ptr = 0;
-> char **ptr;ptr = 0;
然后这样:
*ptr = other_ptr;

崩溃是自然的,因为你写了0地址;
因该这样:
char **ptr = new char*;
*ptr = other_ptr;

monk188 2005-05-12
  • 打赏
  • 举报
回复
看了大家的热心回贴真的好感动!
先谢谢大家!
————————————————————————————————————————————
回:nicknide(封月翔天) 的例子!
看了你的例子,我立刻做了相关的实验。结果正确。
细细分析起来,马上又产生了疑惑。

static 在局部域分配的栈空间(非堆空间)在程序执行完毕是如何回收的
还是系统会帮你把这件事情做好。

上边的例子感觉在一个void的函数中出现了return的效果
————————————————————————————————————————————
nicknide 2005-05-12
  • 打赏
  • 举报
回复

Q:static 在局部域分配的栈空间(非堆空间)在程序执行完毕是如何回收的
还是系统会帮你把这件事情做好。
A:这个不是在栈中,也不是动态堆中,而是静态堆中,严格说,是程序的只读已初始数据段,因为考虑前向兼容性,所以C++容忍了这种情况,准确而标准的定义应该这样:
static char const* ptr = "hello world";
还有,如果不是字符串常量,则你所有static对象(非局部对象修饰语义),都是编译器有特殊处理的。
全局static是有一个在main函数之前的处理函数来初始化,main函数结束后统一析构。这个过程在代码段。
而函数内部则有一个类似连表的数据结构记录,来确保用到的时候再建立,然后程序结束的时候,按照和构造相反的顺序析构。这个过程也在代码段。

而如果这样:
static char buf[1024];
则是另外一个段:非初始化数据段,在exe中,只是占用一个地址和一个偏移,然后载入的时候,载入器保留这个地址,然后初始化这个内存中的内容为0(其实没有使用写内存操作,大多数系统中,只是内存页上的一个0内存页标记而已,因此不用担心会不会初始化这么大的内存会影响载入速度,当然,虚存管理这里没有考虑开销了)。

static char buf[1024] = "hello";
这个buf则是在可写初始化数据段上

总结:4个地方是static的容身之所: 初始化可写数据段,初始化静态数据段,代码段,非初始化数据段

Q:上边的例子感觉在一个void的函数中出现了return的效果
A:恩,这个例子中差不多是这样,但是这个只是简单例子,真实的情况下,用处大得多。
sharpwind 2005-05-12
  • 打赏
  • 举报
回复
mark
xuelong_zl 2005-05-12
  • 打赏
  • 举报
回复
这个问题,有点意思
monk188 2005-05-12
  • 打赏
  • 举报
回复
回:
—————————————————————————————————————————————
我也说一下自己的观点,以指向指针的指针做为函数的参数在大程序时常用到,这样在使用中,函数的参数可以不需要改变,而只需要改变指向的指针的内容即可,增加了灵活性,比较方便.
—————————————————————————————————————————————
今天早上再次看您的这句话,突然有了体会。可能是昨天各位前辈的意见太多,没法静下来思考,现在觉得前辈说的很有道理。
所以就试着写了个简单的例子来验证前辈的话。
—————————————————————————————————————————————
void print( char **p )
{
cout << *p << endl;
}
void main()
{
char str1[]="I love C++ programing";
char str2[]="This is a test program";
char str3[]="I learned a lot from you reply";
char **pstr=NULL;
*pstr=(char*)str1;
print( pstr );//
*pstr=(char*)str2;
print( pstr );//
*pstr=(char*)str3;
print( pstr );//三个输出函数的调用形式是一样的。。。。
}
前辈是不是这个意思。
————————————————————————————————————————————
但是奇怪的问题是该代码,在运行的时候出错!!!
不知道是怎么回事!
还请前辈指点!!!!
————————————————————————————————————————————
limlzm 2005-05-12
  • 打赏
  • 举报
回复
不错,mark
neushi 2005-05-11
  • 打赏
  • 举报
回复
常总结有利进步
fzu_893 2005-05-11
  • 打赏
  • 举报
回复
UP。学习ING
lingzantia 2005-05-11
  • 打赏
  • 举报
回复
偶也大二,看见楼主的帖子,真是汗颜啊,大学这2年过到狗身上去了.........
lsrj 2005-05-11
  • 打赏
  • 举报
回复
up
oyljerry 2005-05-11
  • 打赏
  • 举报
回复
二维指针来修改一维指针
mostideal 2005-05-11
  • 打赏
  • 举报
回复
ding
nicknide 2005-05-11
  • 打赏
  • 举报
回复
看了搂主标题,寒一个,大师指点...
这年头,怕没人敢自称大师吧?
而且大师级人物,给人感觉都是快化成灰的人了......
jingyueid 2005-05-11
  • 打赏
  • 举报
回复
指针是一个很奇妙的东西,是一块内存区域的标志。
指针的类型用来标志指针所指向的对象的尺寸。

当编译器编译完你的程序后,你的每个类型的长度都是不可变的。


第四点中的用函数的指针做为函数的参数,其实就是多态性的实现。
这一点让C/C++非常的奇妙,你可以把用函数指针,使得你原本的实现变成另一种实现。
例如:

typedef void (*FunPtr)(void)

void func1(void)
{
printf("output :%d\n", 1);
}

void func2(void)
{
printf("output :%d\n", 2);
}

void func(FuncPtr p)
{
p();
}

FunPtr pFunc;

func(func1);
func(func2);

....

上面只是一个简单的用法,你可以把容易变化的模块用函数指针来实现,这样当功能发生变化了,只需要把指向正确功能的函数地址,就能轻松的实现功能修改。

回调函数,HOOK,本质上都是这么做的。

第5点的目的是为了可以在函数内部修改指针所指向的内容,例如:

func1(char **p)
{
*p = new char[1024];
}

func2(char *p)
{
p = new char[1024];
}

func2会产生内存泄露,func1表现正确。


返回值是一个指针并没有什么特别点,这个指针所指的内容是已经在全局或堆上被分配过而已。
nicknide 2005-05-11
  • 打赏
  • 举报
回复
再次更改例子,一直范错误:
void gc(char** par)
{
//char buf[] = "i love C & C++";
static char buf[] = "i love C & C++";//改为静态,否则就引用临时数据,BUG
*par = (char*)buf;
}

int
main()
{
char* p = 0;
gc(&p);
//printf( *p );
printf( p );//手误
return 0;
}
加载更多回复(14)

64,654

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧