变量是什么:
所谓的变量,是在程序中代表某些数值的字符串,比如下面几个:
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。
再次总结一下,普通的传值参数,会临时产生一个新的变量。使用指针或引用则不会再重新生成,但是在函数内部修改指针或是引用的值时也会影响外面变量的值。