第五章 指针与函数

一说到指针和函数的关系,很多人就会想到指针作为函数的参数。但是,可能很少有人会注意指针作为函数的参数时的真正意义。也许我们自己的程序很少用指针作为函数的返回值,但是,它确实存在,而且意义非凡。这部分将介绍指针和函数之间很少被人注意的关系。

5.1 函数的参数

函数的参数的传递方式无外乎两种,一是传值一是传址。一般的教课书上都认为数组和指针都是传址调用,而只有普通的数据类型(如整型、结构体等)才是传值调用。但,我并不这么认为。

我认为只有显示调用传值调用符号&的变量才是传址调用,其他的都是传值调用。对于指针和数组来说传的值是指针的值/数组首元素的地址,此时,再利用指针/数组去访问元素则是相当于操作原先元素的地址了,效果和把原先元素的地址传递过来是一样的,但在形参和实参结合的时候确实进行了一次赋值,而这个值是指针的值,并且指针的值又是原先元素的地址。

变量都有其地址,机器都是通过其地址引用变量的。传值是指将实参的值赋给形参,而形参和实参的地址显然是不同的,他们的作用域也不同,形参只作用于该函数,并存储于堆栈中,在堆栈中占有一个固定的地址。在形参和实参匹配时,会把堆栈中这个地址的内容修改成实参的值,这就是传值。

传址则是指在形参和实参匹配时,将被调用函数中所有形参的代号全部修改成调用函数中实参的地址。这时,被调用函数中访问该形参就是访问该函数空间外的某个地址。

由于在函数的参数中,数组总是被解释为指针,我们只要分析下指针作为函数参数的情形就行了。

int test(int *p){.....};
int main()
{
  int a[] = {1,2,3};
  int *p = a;
  test(p);
  ……
}

此时,在test函数的栈中,为形参p预留了一个4字节的空间,并且符号p就是这个空间的首址。在函数形参和实参匹配时,形参p的值就变成了实参p的值,此时发生了一次赋值,这个赋值的过程就是传值。但碰巧的是,这个值正好是数组a第一个元素的地址,所以后面的*(p+i)之类的操作,就是修改a数组了。某种意义上,这种传值方式对于a来说是传址调用,但此处的形参p和实参p的地址绝对是不一样的,此时修改形参p的值,绝对不会影响实参p的值,只是修改形参p所指向的空间才会影响实参p所指向的空间。

测试用例:

#include <iostream>
using namespace std;
#include <strings.h>
struct Node
{
    int a;
    int b;
};
 
int test(int a,int b[],int *c,int &d,Node ab,Node &abc)
{
    cout<<&a<<"  "<<a<<endl;
    cout<<(unsigned int)&b<<"  "<<(unsigned int)b<<endl;
    cout<<&c<<"  "<<*c<<endl;
    cout<<&d<<"  "<<d<<endl;
    cout<<(unsigned int)&ab<<endl;
    cout<<(unsigned int)&abc<<endl;
    return 1;
}
int main()
{
    int a =1;
    int b[] = {2,3,4};
    int f = 5;
    int *c =&f;
    int d =6;
    Node ab;
    Node abc;
    cout<<&a<<"  "<<a<<endl;
    cout<<(unsigned int)&b<<"  "<<(unsigned int)b<<endl;
    cout<<&c<<"  "<<*c<<endl;
    cout<<&d<<"  "<<d<<endl;
    cout<<(unsigned int)&ab<<endl;
    cout<<(unsigned int)&abc<<endl;
    test(a,b,c,d,ab,abc);
    return 0;
}

结果:

0xbff4a19c  1
3220480372  3220480372
0xbff4a194  5
0xbff4a190  6
3220480392
3220480384
0xbff4a150  1
3220480340  3220480372
0xbff4a158  5
0xbff4a190  6
3220480352
3220480384

从上述结果,我们可以看到,只有在形参前面加了传址符号&后,形参和实参的地址才是一样的,此时只是将被调用函数中形参符号全部改成调用函数中的地址。对于数组和指针的方式只是指针的传值调用罢了,指针的地址是不一致的。

注:当结构体作为函数的参数,并且结构体中有指针成员时要特别注意。在结构体作为函数的参数时,形参—实参匹配的过程虽然是个传值的过程,即把实参结构体中的所有成员的值赋给形参结构体中相应的成员。按理说形参和实参中所有的成员都有自己的地址是没有联系的,但是不要忘了对于指针成员来说虽然它们都有自己的存储地址,但是他们所指向的空间是一致的。这点,同样适用于结构体赋值。即=默认重载赋值方式。

5.2 指针作为函数的返回值

函数的返回值不能是数组也不能是函数,但函数的返回值可以是指针,而这个指针可以是指向数组的指针,也可以是指向函数的指针。由于指针可以指向任何由地址表示的东西,所以,某种意义上函数的返回值可以任何东西,我们需要做的仅仅是用个指针指向它而已。只是当函数的返回值是指针的情况下要注意指针指向的空间在函数返回时不能释放。为此,我们不能用局部变量的地址作为指针的值,再将该指针返回。我们可以用以下的方法代替:

方法1:用常量区作为返回值。(只适用于字符串常量)

char* test(){return "const data";}
由于字符串常量存储于常量区,而常量区里的内容不会随着该函数的返回而失效。

方法2:用全局变量。

全局变量不利于数据的封装,而且全局变量过多将会使程序更加混乱。但对于小程序,全局变量不失为一种简单有效的方法。

方法3:用静态变量。

静态变量虽然在函数返回时并不释放,但是它同全局变量一样,再次调用此函数会影响前面的结果,使这类函数成为线程不安全型。

方法4:动态开辟空间。

这种方法是相对比较常用的,但是需要调用者在使用后主动释放内存,同时,并不是所有申请内存都能成功,并且多次申请和释放内存后会引起大量的内存碎片降低系统性能。

方法5:放弃用返回值传出大量信息,改用函数的参数。

利用这种方法,我们也可以让函数返回指向什么东西的指针,但此时这个返回值就是指向该函数的某个实参(不是形参)的指针,那么对于调用函数的参数来说,这个返回值就是可有可无的了。

函数的返回值的情况多种多样,本文着重讨论比较不常见的情况,但为了便于理解,对于常见的情况也简要涉及。现简要说明下函数返回值的各种情况。

情况1:一般数据类型作为函数返回值。
  例子:int test();
情况2:结构体作为函数的返回值。
  例子:Node test();
情况3:无返回值
  例子:void test();
情况4:一般数据类型指针作为函数返回值。
  例子:int* test();
情况5:结构体指针作为函数返回值。
  例子:Node * test();
情况6:万能指针作为函数返回值。
  例子:void* test();
情况7:指向函数的指针作为函数的返回值。
  例子:void (*test())(); 这个函数的返回值是个指向无返回值的无参函数的指针。
情况8:用指向实参的指针作为返回值。
  例子:Node* test(Node &Var){return &Var;}
       Node* test(Node* pVar){return pVar;}

备注:

1.由于函数名即为函数的地址,而且函数属于代码段在整个程序的运行中一直有效,所以对于返回值是指向函数的指针,只要返回该函数的名字就行了。
2.int test(); int (*ptest)(); ptest = test; ptest = &test;则,有以下几种方法访问test函数。
    test();  ptest();  (*ptest)();
3.函数名在使用时总是会被编译器转换为函数的指针,所以在赋值时我们可以把函数名直接赋值给函数指针即用ptest = &test;代替ptest = test。同理,我们也可以用ptest();代替(*ptest)();
4.用形参数指针作为其返回值是不正确的,因为形参存储于该函数的堆栈中,函数返回后就销毁了。
  例如Node* test(Node pVar){return &pVar;}是不正确的。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容