您的位置 首页 > 数码极客

c语言如何返回3个参数——c语言返回两个参数…

1 函数的封装与代回

为什么需要函数?对于一些重复性的功能实现,为避免复制、粘贴带来的麻烦,而提炼出函数(并通过参数实现一般化),是分治算法的一种实现,代码整体上也更加模块化。所以要理解函数的种种参数传递与返回机制,要能考虑如何提炼出函数和如何将函数打散后再代回原调用处。

2 普通变量、指针变量、引用变量之间的地址语义和值语义

变量的二重属性:变量首先是一个内存单元地址的名称化,这是变量的地址语义,内存单元的比特值根据类型实现其值语义;

2.1 普通变量:显式使用其值语义,隐式使用其地址语义;

2.2 指针变量:显式使用其地址语义,隐式使用其值语义;

2.3 引用变量:声明、定义、初始化时显式使用其地址语义(使用上类似指针变量),此外的其他应用场合显式使用其值语义(使用上类似普通变量);所以说,引用变量其实质是一个有常量属性的实现了自动解引用的特殊指针。所以引用相对于指针,有其使用上的简洁性、安全性,但也有其模糊性。

int i=5; int* p = &i; int& r = i; i=++*p; r++; cout<<i;//7

以上的赋值方式,同样也适用于实参与形参的结合,以及函数返回值的语法机制。

应该使用指针的情况: 可能存在不指向任何对象的可能性,需要在不同的时刻指向不同的对象(此时,你能够改变指针的指向) 。返回函数体中new出的内存空间的地址。多态中使用指针。

应该使用引用的情况: 如果总是指向一个对象并且一旦指向一个对象后就不会改变指向,使用此时应使用引用。

数组用作函数形式参数时会丢失数组元素个数的信息,即退化为指针;

函数返回数组名,实际返回的是指向数组首元素的指针。

3 主调函数caller与被调函数callee之间的关系

主调函数caller与被调函数callee之间的关系主要有两种:

一是两者相互独立;

二是两者通过实参和形参的结合来建立联系,实现内存单元的共享,可读也可写共享的内存单元。

实现的机制是通过副本机制(包括传参和返回)来实现的。

3.1 值传递:副本保存的是值(不是地址和引用),主调函数和被函数之间相互独立;

3.2 指针传递和返回:副本保存的是地址(不是值),主调函数和被函数之间通过传递的参数变量相互影响;

3.3 引用传递和返回:副本保存的是地址(不是值),主调函数和被函数之间通过传递的参数变量相互影响;但引用在初始化时有指针语义,在使用时有普通变量语义(自动解引用);

函数的调用与嵌套调用通过栈来实现回溯,函数内可以用{}来嵌套块作用域。

4 指针和引用做参数及返回时,对应数据处理的方式

当指针做参数时,函数要处理的往往不是指针本身,而是指针所指向的数据,所以通常会以解引用的形式做为左值或右值出现在函数体中,而当指针做为左值出现时,通常是用于指针的修改或移动(更多的情形是将形参指针赋给一个临时变量的指针,以避免形参指针的变更)。

当引用做参数时,函数体中对引用所指向的变量的处理,因为其特殊的语义,使用上就像普通变量。

5 实参与形参结合及函数返回值的理解

5.1 实参与形参结合:局部变量的声明、定义、初始化;

5.2 函数返回值:可以理解为函数名做为一个全局变量,类型就是函数的类型,值就是其返回值;

#include <iostream> using namespace std; int& fr(int b[],int i) { return b[i]; } //int& fr = b[i]; int* fp(int b[],int i) { return &b[i]; } //int* fp = &b[i] int main() { //调用时 int a[] = {1,2,3,4,5}; int n = fr(a,3);// 实参与形参结构相当于:int b[]; b = a,int i = 3; fr(a,3) = 14;//相当于a[3]; cout<<*fp(a,2)<<endl;// 实参与形参结构相当于:int b[]; b = a,int i = 3; system("pause"); return 0; }

5.3 两者都可能会有隐式类型转换,当是类类型和对象时,会自动调用拷贝构造函数。

6 指针做为参数时,注意区分函数体对指针本身或指针所指向的对象的处理

且两种情形的作用域不同,指针参数的作用域是本函数体,指针所指向的对象的作用域在本函数体以外。

#include <iostream> using namespace std; void f(char* p) { *p = 'A'; //对指针指向的内存单元的操作(解引用) p++; //对指针本身的操作,p=p+1,指针值发生了变化,相当于指针移到了下一个位置, //这里是演示用,通常是用一个临时指针来做指针移动 *p = 'B'; cout<<p<<endl;//Bc } int main() { char arr[] = "abc"; f(arr); system("pause"); return 0; }

7 可以返回指针或引用的数据结构

因为函数调用是通过栈机制来实现的,函数实现了一个独立的作用域机制,所以函数不能返回函数体内定义的非静态局部变量的指针或引用。

可以返回指针或引用的数据结构包括:

7.1 引用或指针参数指向内容(包括数组);

#include <; #include <; #include <; int *func(int *p) { (*p)++; printf("%p\n",p); return p; //p本身虽是局部变量,但其值却是从主调函数传过来的地址值 } /* int& func(int& p) { p++; return p; } */ int main() { int i=10; int *p=&i; printf("%p\n",p); printf("%d\n", *(func(p))); system("pause"); return 0; } /* 0012FF44 0012FF44 11 */

数组也是如此:

int* f2(int a[],int i) { return &a[i]; int& f2(int a[],int i) { return a[i]; }

7.2 静态局部变量;

int& func() { static int i=10; //因为是全局的静态区 int& p=i; p++; return p; //OK:可以做为返回值 }

7.3 全局变量;

7.4 堆上数据;

避免返回函数内部new分配的内存的引用,但可以返回new的指针(也违背“谁申请,谁释放”的原则,存在内存泄漏的安全隐患)。

7.5 成员函数对私有数据成员的引用

#include<iostream> using namespace std; class C { public: //返回一个引用,也就是n的引用 int& getReFN() { return n; } int getN() { return n; } private: int n; }c; int main() { //将返回的引用赋值给k,k和n是一样的 //尽管n声明为私有,但是执行下条语句时候,就可以在外界通过k随意访问该变量 int& k = c.getReFN(); k = 7; cout<<c.getN()<<endl;//7 c.getReFN() = 9; cout<<c.getN()<<endl;//9 system("pause"); return 0; }

8 二级指针或指针引用做函数参数

8.1 二级指针或指针引用做函数参数:

#include <iostream> using namespace std; void GetMemory(char* *pp,int n) { *pp = new char[n];//pp的解引用是*pp } int main() { char* p = NULL; GetMemory(&p,100);//注意二级指针的赋值char**p = &p; strcpy(p,"Hello!"); cout<<p; delete[]p; p=NULL; system("pause"); return 0; }

以上为什么不能用一级指针作为参数?还是要回到前面讨论的在函数体内当用指针做形参时,在函数体操作指针本身与指针指向的对象的区别。也就是指针也其解引用的区别。

8.2 指针引用做函数参数

相对于二级指针更简洁:

#include <iostream> using namespace std; void GetMemory(char* &pp,int n) { pp = new char[n]; } int main() { char* p = NULL; GetMemory(p,100);//注意指针引用的赋值char*&p = p; strcpy(p,"Hello!"); cout<<p; delete[]p; p=NULL; system("pause"); return 0; }

8.3 当然也可以用指针函数

#include <iostream> using namespace std; char* GetMemory(int n) { char* pp = new char[n]; return pp; } int main() { char* p = NULL; p=GetMemory(100);//注意指针引用的赋值char*&p = p; strcpy(p,"Hello!"); cout<<p; delete[]p; p=NULL; system("pause"); return 0; }

以上美中不足的是,违背了“谁创建、谁释放”的原则,容易造成内存泄漏。

但有时却不得已为之:

const int MAX = 55; typedef struct Heap { int sizeHeap; int* heapData; }HEAP,*LPHEAP; LPHEAP createHeap() { LPHEAP heap=(LPHEAP)malloc(sizeof(HEAP)); heap->sizeHeap=0; heap->heapData=(int*)malloc(sizeof(int)*MAX); return heap; }

8.4 最常见的是链表操作:

struct node { char data; node * next; } void Insert(node * &head,char keyWord,char newdata) //keyWord是查找关键字符 { node *newnode=new node; //新建结点 newnode->data=newdata; //newdata是新结点的数据 node *pGuard=Search(head,keyWord); //pGuard是插入位置前的结点指针 if (head==NULL || pGuard==NULL) //如果链表没有结点或找不到关键字结点 { //则插入表头位置 newnode->next=head; //先连 head=newnode; //后断 } else //否则 { //插入在pGuard之后 newnode->next=pGuard->next; //先连 pGuard->next=newnode; //后断 } }

9 三种传递方式的效率比较

如果是单个的基本数据类型,值传递、指针传递、引用传递三者的区别不是很大,但当传递的是复杂的数组、结构体、类对象时,区别就很大了。

#include <; #include <string> #include <iostream> #include <sstream> #include <; using namespace std; string test = "origion" ; string changed = "changed" ; string getTime() { time_t tt =time(NULL); tm* t =localtime(&tt); string mon; stringstream ss; ss<<t->tm_mon; ss>>mon; (); string day; ss<<(t->tm_mday); ss>>day; string hour; (); ss<<(t->tm_hour); ss>>hour; string min; (); ss<<(t->tm_min); ss>>min; string sec; (); ss<<(t->tm_sec); ss>>sec; string times = mon+"-"+day+":"+hour+":"+min+":"+sec ; return times ; } void func1(string s)//值传递 { s = changed ; } void func2(string& s)//引用传递 { s = changed ; } void func3(string* s)//指针传递 { *s = changed; } int main() { cout<<getTime()<<endl; for (int i = 0; i < 3000000; i++) { func1(test); } cout<<getTime()<<endl; for (i = 0; i < 3000000; i++) { func2(test); } cout<<getTime()<<endl; for (i = 0; i < 3000000; i++) { func3(&test); } cout<<getTime()<<endl; //func3(&test); system("pause"); return 0 ; } /* 9-26:18:29:50 9-26:18:29:54 9-26:18:29:56 9-26:18:29:58 */

如果参数是一个较复杂类对象,较效果更明显。

10 赋值运算符重载与引用返回

赋值运算符重载一般使用引用作为返回,简洁,高效,还可以实现链式表达式操作:

String 的赋值函数 operator = 的实现如下: String & String::operator=(const String &other) { if (this == &other) //自己赋值给自己会形成循环调用构造函数的错误 return *this; delete m_data; //因为赋值相当于更新,原来的空间释放,使用新的堆空间 m_data = new char[strlen)+1]; strcpy(m_data, o); return *this; // 返回的是 *this 的引用,无需拷贝过程 }

this是隐含的指向函数调用对象的指针,一般是隐式使用,在特殊场合下可以显式使用。

为什么是return *this而不是return this?要注意引用在地址语义和值语义上的的语法特性,引用是用一个变量名(或对象名)而不是指针来初始化的。

看下面实例:

int& at() { return m_data_; //副本机制是变量地址 } int at() { return m_data_; //副本机制是变量值 }

11 其它

11.1 复制构造函数的参数为什么一定要用引用传递,而不能用值传递?

值传递的参数在参数传递时有一个构造过程,即用实际参数的值构造形式参数,这个构造过程是由复制构造函数完成的。如果将复制构造函数的参数设计成值传递,会引起复制构造函数的递归调用。

11.2 下标运算符重载函数为什么要用引用返回?

下标运算符的第一个运算数是数组名,即当前类的对象。将下标运算符重载成成员函数时,编译器会将程序中诸如a[i]的下标变量的引用改为a.operator[](i)。如果a不是当前类的对象,编译器就会报错。下标变量是左值,所以必须用引用返回。

11.3 链式表达式的实现要用可以做为左值的引用或指针做为函数返回值,使用引用更为简洁。 重载操作符>>或<<时,返回的类型应该是一个流类型,而且返回的类型必须是引用,也是为了链式表达式实现的需要。

11.4 一般用指针参数做为输出参数,放在参数列表的右边(输入参数放在左边),而函数的参数有时用于输出逻辑值。

11.5 当为了避免副本机制而使用址传递或返回而又不需修改共享内存时,可以附加const修饰,以增强安全性。

-End-

责任编辑: 鲁达

1.内容基于多重复合算法人工智能语言模型创作,旨在以深度学习研究为目的传播信息知识,内容观点与本网站无关,反馈举报请
2.仅供读者参考,本网站未对该内容进行证实,对其原创性、真实性、完整性、及时性不作任何保证;
3.本站属于非营利性站点无毒无广告,请读者放心使用!

“c语言如何返回3个参数,c语言返回两个参数,c语言如何返回多个值,c语言返回多个参数”边界阅读