c语言和c++语言中函数参数的传递

1 函数参数传递的本质

在调用一个函数时进行参数传递(不只是讲参数列表里的参数,包括函数返回值的参数传递),其本质上进行的工作都是一样的,即使用实参初始化形参

实参与形参本质上是两个完全不同的变量,它们之间并没有更深入的联系,仅仅只是变量与初始值的关系而已。

1.1 传值参数(包括传指针)

很普通的那种,大家都了解的差不多。

在此处需要强调一下,所谓传值,其实是指在使用实参初始化形参时,将实参的值拷贝一份到形参。此处我将传指针也归纳到了传值这边,因为都有拷贝操作。但是此处需要稍微提一下,有几种类型(也许还有其他?以后遇到会补充)是不能通过这种形式进行拷贝的(也就是不能进行真正意义上的传值操作),那就是数组函数(还有IO对象如cin、cout等)。所以当参数列表或者返回值类型中如果出现数组名函数名(只要参数类型不是引用),编译器会自动将其转换成常量指针类型,然后再使用这个常量指针进行传值操作。

例子(函数指针):

#include<iostream>
#include<string>
using std::string;
bool useBigger(const string &s1, const string &s2, bool (*pf)(const string &, const string &)){
    return pf(s1,s2);
}
//细节:函数的类型只与函数的参数还有返回类型有关,与函数名无关
bool lengthCompare(const string &s1, const string &s2){
    if(s1.size()>s2.size())	return true;
    else	return false;
}
int main(){
    //此处自动将函数lengthCompare转换成指向该函数的指针
    useBigger("1234","123456",lengthCompare);
}

1.2 传引用参数

传引用的方式也是使用实参初始化形参,但是它与传值完全不同,它并没有拷贝操作,而是利用引用的特点,将引用类型的形参绑定到实参上。从而达到可以直接操作实参的效果。c语言中不存在引用,所以需要使用指针来完成类似的操作。(多嘴一句,引用在编译器底层其实是通过常量指针实现的)

一个经典的例子如下:

#include<iostream>
using namespace std;
void reset(int &i){
    i=0;
}
int main(){
    int j=42;
    reset(j);
    cout<<"j="<<j<<endl;
}

如上例,在调用reset函数时,使用实参初始化形参实际相当于int j=42; int &i=j;因为形参为一个引用并且被绑定到变量j上,因此可以通过i对变量j的值进行修改。这样就可以替代指针的部分作用了,而且更简单。 

同时,使用传引用调用还有其他一些好处:

  1. 使用引用避免拷贝,提高效率(进行大的类类型对象的拷贝很低效);因为引用类型不是一个对象,而仅仅是一种绑定关系,为已存对象另取了一个名字而已。

  2. 可以传递额外信息,因为引用参数可以改变原变量值,所以并不是只有返回值可以传递信息了,参数也可以(这点指针形参也可以做到)。

注意的问题:

  1. 如上int &i类型的形参在传入实参时只能是变量,不能是字面值常量,因为其不能用字面值常量初始化;

  2. 如果想要使得函数实参可以传入字面值常量,形参需要改成const int &i形式,底层const的引用类型可以使用字面值常量初始化,一般只要不会对参数进行修改,就将其设置成底层const的引用;

科普一下,const可以分为顶层const底层const两种。一般对象只会有顶层const,表示对象本身是常量不能修改;而对于指针与引用变量除了顶层const外(表示自身是常量,一般只对指针而言,引用一般只关心底层const),还有底层const,表示自身指向或者引用的对象是常量

例子:

int i = 0;
int *const p1 = &i;	//不能改变p1的值,顶层const
const int ci = 42;	//顶层const
const int *p2 = &ci;	//底层const
const int &r1 = i;	//底层const,不能通过r1改变i的值

1.3 main函数参数:处理命令行选项

#include<iostream>
using namespace std;
int main(int argc, char *argv[]){
    cout<<"argc = "<< argc << endl; 
    for(int i=0;i<argc;i++) 
        cout<<"argv["<< i << "] = "<< argv[i] <<endl; 
    return 0; 

如上,是主函数的带参数形式,此时在将源文件编译生成可执行文件后,运行时可以带参数。举个例子,比如编译生成的可执行文件叫做main_arg,则可以输入如下命令执行:

./main_arg -o -d data0 

输出如下结果

xhy@ubuntu:~/cpp_learn/6/ch06$ ./main_arg -o -d data0 
argc = 4
argv[0] = ./main_arg
argv[1] = -o
argv[2] = -d
argv[3] = data0
xhy@ubuntu:~/cpp_learn/6/ch06$ 
 

如上可以知道,其中第一个参数int argc为命令行中字符串的数量,后面char *argv[]为一个数组,数组元素为一个指向char *类型的指针,指向一个c风格的字符串。最后一个指针之后的元素值保证为0(因此不需要argc其实也能确定是否读完了参数)。

在这里了要科普一下(引用也一样):

int *matrix[10];	//10个指针组成的数组
int (*matrix)[10]	//一个指向含有十个整数的数组的指针

这两种书写形式含义是不一样的。其中*优先级小于[],对于int (*matrix)[10]可以按如下顺序来理解该声明的含义:

  1. *matrix表示对变量matrix进行解引用操作;

  2. (*matrix)[10]表示解引用后将得到一个大小为 10 的数组

  3. int (*matrix)[10]表示数组中的元素是int类型。

同理,对于int *matrix[10]可以按如下顺序来理解该声明的含义:

  1. matrix[10]表示matrix是一个大小为10的数组;

  2. *matrix[10]表示数组元素是指针类型;

  3. int *matrix[10]表示数组元素时候int的指针类型。

其实这么写可能比较易读,但是不方便,上述main函数其实还有一种写法是:

int main(int argc, char **argv){}

之所以有这第二种写法,是因为前文中提到过,数组是不能使用传值操作的,所以传递数组其实是将数组名转换成了指针,所以一个指针的数组其实在传值操作时被转换成了一个指针的指针。并且一般情况下,第二种形式比较习惯一点,指针的指针。

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值