热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

C++中函数的用法小结

这篇文章主要为大家分享下本人在阅读《C++Primer》函数一章时的读书总结,需要的朋友可以参考下

函数在C++中的使用,无非2种地方,一处是函数的定义,一处是函数的调用。而函数的定义则非常简单,由三个部分组成:函数的返回类型、函数名和函数的形参表。当然,这里不同的函数定义可以还会稍有不同,比如类的成员函数、内联函数等。这里我们主要讨论函数的调用时需要注意的一些问题。

一、参数传递

我们将函数定义或声明里的参数叫形参,而在调用函数时传入的参数叫实参。那么根据形参类型的不同,有几下形式的参数传递。

1,非引用形参

1)普通的内置类型

普通非引用类型的参数通过复制对应的实参实现形参的初始化。当用实参的副本初始化形参时,函数并没有访问调用所传递的实参的本身,因此函数不可能改实参的值。比如下面的交换两个数的程序:

代码如下:

void swap(int v1, int v2)
{
    int temp = v1;
    v2 = v1;
    v1 = temp;
}

swap(a, b);// 调用swap

上面程序中,实参为a与b,但是在调用时,v1与v2接受的是a与b的副本,所以实际上a与b的值没有变化。

2)指针形参

函数的形参可以是指针,此时将复制实参指针,其实这类跟1)原理类似,函数内并无法改变实参的指针值。只是函数可以通过复制到的地址改变实参指针所指向的值。

代码如下:

void swap(int* v1, int* v2)
{
    int temp = *v2;
    *v2 = *v1;
    *v1 = temp;
}
int main()
{
    int a = 10,b = 20;
    int *p1 = &a,*p2 = &b;
    swap(p1,p2);
    return 0;
}

上面程序中定义的swap的形参为指针类型,main中调用swap,实际上swap并不能改变p1与p2的值,只是改变了它们所指向的值。

3)const 形参

对于普通的非引用类型用const修饰实际上是没有意义的,因为本来函数就不会改变实参的值。像下面的定义,实际中编译器会忽略const的定义,而将其视为int型。

代码如下:

void fcn(const int i);

2,引用形参

1)在上面的程序中我们看到,如果想交换两个变量的值,通过调用普通的非引用类型形参的函数,并不能实现。用它们的指针可以,同时我们也可以用引用。

代码如下:

void swap(int& v1, int& v2)
{
    int temp = v2;
    v2 = v1;
    v1 = temp;
}
int main()
{
    int a = 10,b = 20;
    swap(a,b);
    return 0;
}

在实际调用swap时,v1与v2实际相当于a与b的另一个名字。

2)在有的时候我们需要向函数传递大型对象,需要使用引用形参,如果直接使用复制实参的形式可以,但是它的效率太低了,甚至有些对象是无法复制的。但是使用引用形参时,我们不希望函数改变了实参传入的值,我们就可以使用const来限定形参。下面程序用来判断哪个字符串更长,明显我们不希望函数会改变字符串的内容,我们就可以用const引用型的形参。

代码如下:

bool isLonger(const string &s1, const string &s2)
{
    return s1.size() > s2.size();
}

所以,如果使用引用形参的惟一的目的是避免复制实参时,则应将形参定义为const引用。

3)在使用引用形参函数时,有两点值得注意:

不要用const限定的实参或字面值来调用非const引用形参函数。因为这样函数内,可以改变实参的值,这不合法。

非const引用形参只能与完全同类型的非const对象关联。

4)传递指向指针的引用

如下有下面的程序:

代码如下:

void swap(int* &v1, int* &v2)
{
    int* temp = v2;
    v2 = v1;
    v1 = temp;
}
int main()
{
    int a = 10,b = 20;
    int* p1 = &a, *p2 = &b;
    swap(p1,p2);
    return 0;
}

上面的程序依然不能改变a与b的值,但是它改变了p1与p2的值,现在p1指向了b,而p2指向了a。

3,其他类型的形参

1)vector和其他类型的形参:一般在这种类型作为形参时,为了避免复制应该考虑形参声明为引用类型。C++程序员倾向于传递容器中需要处理的元素的迭代器来传递容器。

2)数组形参:由于数组不能复制,所以不能直接编写数组类型的形参函数,一般通过传递指向数组的元素的指针来处理数组。值得注意的是在通过引用传递数组时,在调用函数时形参与实参的类型要匹配。

代码如下:

void printValues(int (&ar)[10]);
int main()
{
    int i = 0, j[2] = { 0, 1 };
    int k[10] = {0,1,2,3,4,5,6,7,8,9};
    printValues(i);  //error int不能初始化 int(&)[10]
    printValues(j);  //error int[2] 不能初始化 int(&)[10]
    printValues(k);   // ok
    return 0;
}

二、函数的返回值

1)没有返回值

很多函数并没有返回值,尤其是现在C++风格,习惯于把需要的结果作为引用形参。这类型函数一般没有return语句,有时候有return是使函数中途中断执行。

2)返回非引用类型

这种情况在函数调用处,程序会用一个临时变量复制函数的返回值。

3)返回引用

当函数返回引用类型时,并没有复制返回值。相反,返回的是对象本身。

在返回引用这种情况下,注意不要返回局部变量的引用,因为局部变量在函数体内定义,当函数执行完后就销毁了,所谓的引用也就没有意义了。同理,不要返回指向局部变量的指针。

三、重载函数

出现在相同作用域中的两个函数,如果具有相同的名字而形参不同,则称为重载函数。

1)注意区分函数重载与重复声明

有些看起来不同的形参,本质是相同的。下面代码中的都是重复声明的例子

代码如下:

typedef double newDouble;
int func(double i);
int func(newDouble i);  // 没有新类型

int func1(int, int = 1); //只是提供默认参数
int func1(int ,int);

int func2(int);
int func2(const int);  //对于普通非引用形参用cosnt修饰是没有意义的

2)重载与作用域

局部声明的函数,将屏蔽所有全局作用的同名函数。下面例子显示,即使全局作用的函数更加匹配调用的实参类型,但是仍然调用的是局部的函数。

代码如下:

void print(int);
int main()
{
    void print(double);
    print(42);
    return 0;
}

上面程序中,将调用void print(double)函数,虽然42是int型。

3)重载确定的三个步骤

如果定义了众多的函数重载,将存在函数调用到底与哪个重载函数相匹配的问题。我们通过下面的示例代码来说明问题:

代码如下:

void f();  // 1
void f(int);// 2
void f(double);// 3
void f(int, int);// 4
void f(double, double);// 5

第一步:确定候选函数

假如我们调用f(4.2),那么先找到同名函数,并且在作用域内可见,上面例子中5个函数都满足。

第二步:选择可行的函数

必须满足2个条件:一是函数形参与该调用实参个数相同;第二,每个实参的类型必须与对应的类型匹配,或者可以被隐式转换为对应的形参类型。这里我们再调用f(4.2)时,排除了1、4、5号函数,只剩下2与3。其中2号函数可以通过类型转换来满足。

第三步:寻找最佳匹配

在经过第二步确定后,剩下2与3函数,那么2需要进行类型转换,显然3是最佳匹配了。

但是如果这样调用f(42,4.2)。这时候就会出现二义性,编译器将提示。

还有一种要注意的就是有默认参数的函数,比如我们定义6号函数为void f(double,int =1);那么在调用f(4.2)时就会有二义性。

可基于函数的引用形参是指向const对象还是指向非const对象实现函数重载。


推荐阅读
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • 本文讨论了使用差分约束系统求解House Man跳跃问题的思路与方法。给定一组不同高度,要求从最低点跳跃到最高点,每次跳跃的距离不超过D,并且不能改变给定的顺序。通过建立差分约束系统,将问题转化为图的建立和查询距离的问题。文章详细介绍了建立约束条件的方法,并使用SPFA算法判环并输出结果。同时还讨论了建边方向和跳跃顺序的关系。 ... [详细]
  • C语言注释工具及快捷键,删除C语言注释工具的实现思路
    本文介绍了C语言中注释的两种方式以及注释的作用,提供了删除C语言注释的工具实现思路,并分享了C语言中注释的快捷键操作方法。 ... [详细]
  • 本文介绍了如何在给定的有序字符序列中插入新字符,并保持序列的有序性。通过示例代码演示了插入过程,以及插入后的字符序列。 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 本文介绍了P1651题目的描述和要求,以及计算能搭建的塔的最大高度的方法。通过动态规划和状压技术,将问题转化为求解差值的问题,并定义了相应的状态。最终得出了计算最大高度的解法。 ... [详细]
  • 本文介绍了UVALive6575题目Odd and Even Zeroes的解法,使用了数位dp和找规律的方法。阶乘的定义和性质被介绍,并给出了一些例子。其中,部分阶乘的尾零个数为奇数,部分为偶数。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • 从零学Java(10)之方法详解,喷打野你真的没我6!
    本文介绍了从零学Java系列中的第10篇文章,详解了Java中的方法。同时讨论了打野过程中喷打野的影响,以及金色打野刀对经济的增加和线上队友经济的影响。指出喷打野会导致线上经济的消减和影响队伍的团结。 ... [详细]
  • 猜字母游戏
    猜字母游戏猜字母游戏——设计数据结构猜字母游戏——设计程序结构猜字母游戏——实现字母生成方法猜字母游戏——实现字母检测方法猜字母游戏——实现主方法1猜字母游戏——设计数据结构1.1 ... [详细]
author-avatar
牛仔曼宁
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有