6.多种变量及函数的参数

变量是什么:
所谓的变量,是在程序中代表某些数值的字符串,比如下面几个:

int a = 1;
int b = 2;
float fa = 3.5f;
float fb = 4.78;

在这几行代码中,a, b, fa, fb都是变量,可以看到, 每个变量代表了一个数值,而且都有唯一的一个名字,以后再使用时,可以直接使用这些名字。变量的出现,可以把具体的值用字符串表示,这样在写程序或是在阅读代码时可读性更强,代码的概括性和灵活性也更好。
变量的命名规则
在C++中命名有几个要注意的地方,

  • 不能以数字开始: 变量的
  • 不能以关键字为名字
  • 不能包含特殊字符

在写程序时,肯定会用到数值做一些计算,比如下面代码:

	int a = 10 + 12;

这是把10+12的值放到了a中。而10和12是参与计算的数值。而程序在运行过程中,肯定是无法预先知道要处理的数据 ,在第一节也说过,程序一定是有输入和输出的。那么在这里,简单的加法计算,里面说到的输入,就是10和12,输出就是a,怎么让这段程序更灵活呢,一个改进方法是把这个计算封装成函数,然后用函数的参数做为输入,改进后如下:

int sum ( int p1, int p2 )
{
	return p1 + p2;
}
int main( int argc, char *argv[] )
{
	int a = sum( 10, 12 );
	int b = sum( 15, 23 );
}

这里原来的代码就包含了一个有简单计算功能的函数,然后这个函数还能有可变化的输入,根据变化给出不同的结果,到这里,这个代码算是有一定的灵活性了,而且它还能计算所有输入为整数的两个数的和。

做为另一个改进,还可以从文件读取输入,然后计算文件内所有整数的和,暂时先不展示这段代码。

在这里,主要还是观察函数sum,它包括两个输入这p1和p2, 这两个值都叫做sum的参数。在函数定义处,这叫做形参,这时形参并没有实际的值,只是起到标识的做用,也就是在函数内部可以用这两个名字的值做计算。
在调用函数时,给出的两个值,叫做实参,在这里就已经确定了参数的值。

函数参数的个数一般来说没有限制,但是参数不宜过多,如果有过多的参数,建议把这些参数定义一个结构体,以指针或引用的方式传到函数内部。

这时,又有两个新的概念:指针,引用。
所谓指针,是指用内存地址表示实际的数值。指针又叫做地址,和计算机的结构相关,可以参考指针这篇来简单理解。
而引用,相当于某个变量的别称,比如某个人叫张三,又会被叫做老张或是小张,对于张三来说,别人这么叫他也没错,也可以呼唤到他,而在张三这个朋友圈里,只要张三在,那么老张这个称呼就一定是指他,而不能再指向别人。而老张这个称呼,一定是先有张三个这个以后才会出现,也就是说引用的存在,一定是晚于某个变量的。
虽然指针和引用有自己的专有名字,但是说到底,指针和引用也是一种变量,只是保存的东西有点不一样。

而指针相当于一个信封,里面有某个人的地址,可以通过信封里面的内容找到这个人,也可以把信封里的内容装进另一个人的地址,这时就找到了另一个人。
而这个信封,最开始一定是空的,只有把某个人的地址放进去后才会有具体的值,而且这个地址还可以换来换去。所以在代码中定义时,可以先让某个指针为空,在需要的时候才给它值。

对于简单变量,可以在变量前面加上&这个符号获取变量的指针。而引用变量则是在定义某个变量时在变量前面加上&符号,如下代码

int *p = NULL;   // 定义一个空指针p
int b = 0;       // 定义一个变量b
int &r = b;      // 定义一个引用,并且让它指向了变量b
p = &b;          // 把b的地址给指针p

这是指针和引用的一般定义方法。

这段代码也说明指针和引用都有类型,即在定义指针和引用时,一定要说明是指向什么类型变量的指针和引用,而且也不能乱放值,如下代码:

int *p = NULL;
float f = 12.5;
p = &f;
int &r = f;

这段代码就无法编译通过, 显示错误如下:

param.cpp:46:13: error: cannot convert ‘float*’ to ‘int*’ in assignment
   46 |         p = &f;
      |             ^~
      |             |
      |             float*
param.cpp:47:18: error: cannot bind non-const lvalue reference of type ‘int&’ to a value of type ‘float’
   47 |         int &r = f;
      |                  ^

在使用时,一定要注意类型。

下面代码展示了指针和引用如何在程序中使用和各自产生的影响:

#include <stdlib.h>
#include <stdio.h>
#include <iostream>

int main( int argc, char *argv[] )
{
    int a = 10;
    int b = 20;
    int c = a;
    //先输出三个变量的值
    printf("a = %d, b = %d, c = %d\n", a, b, c);
    // 输出三个变量的指针
    printf("a = %p, b = %p, c = %p\n", &a, &b, &c);
    // 定义一个指针,指向a的地址
    int *pa = &a;
    a = 12; // 改变a的值
    // 输出三个变更的值
    printf("pa = %d, a = %d, c = %d\n", *pa, a, c );
    // 定义b的引用
    int &rb = b;
    // 输出rb和b的值
    printf("rb = %d, a = %d\n", rb, b );

	  // 改变b的值
    b = 30;
    printf("rb = %d, a = %d\n", rb, b );
    // 改变rb的值
    rb = 15;
    printf("rb = %d, a = %d\n", rb, b );
    // 输出pa和a的指针,会发现两个值相同
    printf("pa : %p, a : %p\n", pa, &a );
    // 输出rb和b的指针,发现两个值也相同
    printf("rb : %p, b : %p\n", &rb, &b  );

    // 重新把b的指针给pa,然后通过指针修改值
    pa = &b;
    *pa = 16;
    // 发现三个变量的地址相同
    printf("rb : %p, b : %p, pa : %p\n", &rb, &b, pa  );
    // 三个变量的值也相同
    printf("rb =  %d, b = %d, pa = %d\n", rb, b, *pa  );

    return 0;
}

编译后输出:

fosky@fosky-CW65S:~/workspace/cppbook$ ./param
a = 10, b = 20, c = 10
a = 0x7ffdb04b925c, b = 0x7ffdb04b9260, c = 0x7ffdb04b9264
pa = 12, a = 12, c = 10
rb = 20, a = 20
rb = 30, a = 30
rb = 15, a = 15
pa : 0x7ffdb04b925c, a : 0x7ffdb04b925c
rb : 0x7ffdb04b9260, b : 0x7ffdb04b9260
rb : 0x7ffdb04b9260, b : 0x7ffdb04b9260, pa : 0x7ffdb04b9260
rb =  16, b = 16, pa = 16

回到上面的地方,当函数参数过多时可以使用结构体打包变量一起传送,同理,当函数有多个返回值时也可以使用结构体对这些返回值进行封装。
如下代码:

#include <stdlib.h>
#include <stdio.h>
#include <iostream>

struct ParamIn
{
    int _a;
    int _b;
};

struct ResultOut
{
    int _a;
    int _b;
    int _c;
};

ResultOut compute( ParamIn pi )
{
    ResultOut ro;
    ro._a = pi._a + pi._b;
    ro._b = pi._a - pi._b;
    ro._c = ( pi._a + pi._b ) / 2;

    return ro;
}

int main( int argc, char *argv[] )
{
    ParamIn pi;
    pi._a = 10;
    pi._b = 4;

    ResultOut ro = compute( pi );
    printf("ro._a = %d, ro._b = %d, ro._c = %d\n", ro._a, ro._b, ro._c );

    return 0;
}

编译后输出:

fosky@fosky-CW65S:~/workspace/cppbook$ ./paraminout 
ro._a = 14, ro._b = 6, ro._c = 7

可以看到ro中正确的保存了计算结果。

修改代码,在compute函数return前增加下面两行,然后在main函数,compute调用后输出pi的值,会发现pi的值没有变化。

ResultOut compute( ParamIn pi )
{
    ResultOut ro;
    ro._a = pi._a + pi._b;
    ro._b = pi._a - pi._b;
    ro._c = ( pi._a + pi._b ) / 2;

    pi._a = 365;
    pi._b = 150;


    return ro;
}


int main( int argc, char *argv[] )
{
    ParamIn pi;
    pi._a = 10;
    pi._b = 4;

    ResultOut ro = compute( pi );
    printf("ro._a = %d, ro._b = %d, ro._c = %d\n", ro._a, ro._b, ro._c );
    printf("%d,%d\n", pi._a, pi._b);

    return 0;
}

fosky@fosky-CW65S:~/workspace/cppbook$ ./paraminout 
ro._a = 14, ro._b = 6, ro._c = 7
10,4

这说明形参和实参在函数调用前后没有关系了。传到compute函数里面的参数只在这个函数内部有效,是个临时变量,出了函数就没了。
按第4节的说法,struct也可以看做是C++的类,那也可以添加构造函数和析构函数,这就给我们一个观察变量创建和销毁的机会。再次修改以上代码,增加结构体的构造函数和析构函数:

#include <stdlib.h>
#include <stdio.h>
#include <iostream>

struct ParamIn
{
    ParamIn()
    {
        printf("create paramin\n");
    }
    // 这里又出现一个新东西:拷贝构造函数,即从另一个ParamIn变量中
    // 创建新的变量
    ParamIn(ParamIn const& pi)
    {
        this->_a = pi._a;
        this->_b = pi._b;
        printf("create paramin from other value\n");
    }
    ~ParamIn()
    {
        printf("destory paramin\n");
    }
    int _a;
    int _b;
};

struct ResultOut
{
    ResultOut()
    {
        printf("create resultout\n");
    }
    ResultOut(ResultOut const& ro)
    {
        this->_a = ro._a;
        this->_b = ro._b;
        this->_c = ro._c
        printf("create resultout from other\n");
    }
    ~ResultOut()
    {
        printf("destory resultout\n");
    }
    int _a;
    int _b;
    int _c;
};

ResultOut compute( ParamIn pi )
{
    ResultOut ro;
    ro._a = pi._a + pi._b;
    ro._b = pi._a - pi._b;
    ro._c = ( pi._a + pi._b ) / 2;

    pi._a = 365;
    pi._b = 150;

    return ro;
}

int main( int argc, char *argv[] )
{
    ParamIn pi;
    pi._a = 10;
    pi._b = 4;

    ResultOut ro;
    ro = compute( pi );
    printf("ro._a = %d, ro._b = %d, ro._c = %d\n", ro._a, ro._b, ro._c );
    printf("%d,%d\n", pi._a, pi._b);

    return 0;
}

编译运行,得到如下输出:

fosky@fosky-CW65S:~/workspace/cppbook$ ./paraminout 
create paramin                   # 这是main函数中定义ParamIn时的输出
create resultout                 # 这是main函数中定义ResultOut时的输出
create paramin from other value  # 这是调用compute函数时临时变量的输出
create resultout                 # 这是在compute中定义ResultOut时的输出
destory resultout                # 这是compute返回计算结果时的输出
destory paramin                  # 这是compute返回计算结果时的输出
ro._a = 14, ro._b = 6, ro._c = 7
10,4
destory resultout                # 这是main函数结束时的输出
destory paramin                  # 这是main函数结束时的输出

可以看到传值的参数其实是在调用时临时创建了一个新的变量,函数外面和内部的变量这时成了两个,只有值相同,这也就解释了为什么在函数内部改变参数的值不影响外面值的问题。

为了能让函数在内部改变外部参数的值,上面提到的指针和引用就有用了,把compute的定义改成如下形式:

ResultOut compute( ParamIn &pi )
{
    ResultOut ro;
    ro._a = pi._a + pi._b;
    ro._b = pi._a - pi._b;
    ro._c = ( pi._a + pi._b ) / 2;

    pi._a = 365;
    pi._b = 150;

    return ro;
}
int main( int argc, char *argv[] )
{
    ParamIn pi;
    pi._a = 10;
    pi._b = 4;

    ResultOut ro;
    ro = compute( &pi );   // 函数的调用也做修改
    printf("ro._a = %d, ro._b = %d, ro._c = %d\n", ro._a, ro._b, ro._c );
    printf("%d,%d\n", pi._a, pi._b);

    return 0;
}

编译后运行,得到如下输出:

fosky@fosky-CW65S:~/workspace/cppbook$ ./paraminout 
create paramin
create resultout
create resultout
destory resultout
ro._a = 14, ro._b = 6, ro._c = 7
365,150
destory resultout
destory paramin

函数的参数用引用的方式传进去,就相当于变量本身进到了函数内部,这时没有构建临时变量。

再次修改compute函数的定义:

ResultOut compute( ParamIn *pi )
{
    ResultOut ro;
    ro._a = pi->_a + pi->_b;
    ro._b = pi->_a - pi->_b;
    ro._c = ( pi->_a + pi->_b ) / 2;

    pi->_a = 365;
    pi->_b = 150;


    return ro;
}


int main( int argc, char *argv[] )
{
    ParamIn pi;
    pi._a = 10;
    pi._b = 4;

    ResultOut ro;
    ro = compute( &pi );    // 调用处也做相应修改,得到pi的指针后传到compute中
    printf("ro._a = %d, ro._b = %d, ro._c = %d\n", ro._a, ro._b, ro._c );
    printf("%d,%d\n", pi._a, pi._b);

    return 0;
}

编译后运行:

fosky@fosky-CW65S:~/workspace/cppbook$ ./paraminout 
create paramin
create resultout
create resultout
destory resultout
ro._a = 14, ro._b = 6, ro._c = 7
365,150
destory resultout
destory paramin

这时,paramin变量修改后的值也被保留了下来。

引用和指针的传参方法可用于任何类型的参数,比如int, float。

再次总结一下,普通的传值参数,会临时产生一个新的变量。使用指针或引用则不会再重新生成,但是在函数内部修改指针或是引用的值时也会影响外面变量的值。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值