chapter7

本文探讨了C++中函数的使用技巧,包括形参与实参的关系、const修饰符的作用、函数重载规则及其实现原理、内联函数与构造函数的特性,以及函数指针的使用方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

作为函数,不能返回内置的数组;
对于返回值的问题,可否建立一个返回值的命名空间域,每次调用命名空间域的内容作为返回,这样也可以避免全局变量;

形参的初始化与变量的初始化一样:如果形参具有非引用类型,则复制实参的值,如果形参为引用类型,则它只是实参的别名。

普通的非引用类型的参数通过复制对应的实参实现初始化。这个约定意味着不管什么函数调用了参数,但是该参数(非引用的类型)在函
数运行结束的时候其值并不会改变。

函数的形参可以是指针(第 4.2
节),此时将复制实参指针。与其他非引用类型的形参一样,该类形参的任何改变也仅作用于局部副本。如果函数将新指针赋给形参,主
调函数使用的实参指针的值没有改变。

如果保护指针指向的值,则形参需定义为指向 const 对象的指针。

在调用函数时,如果该函数使用非引用的非 const 形参,则既可给该函数传递 const 实参也可传递非 const 的实参。
如果在传递参数时,非引用的参数(非指针)只是一个副本,那么有添加const类型的必要么?
如果非引用的参数被申明为const类型,则在函数中,不可以改变实参的局部副本。这样做的意义?

复制实参并不是在所有的情况下都适合,不适宜复制实参的情况包括:

 当需要在函数中修改实参的值时。
 当需要以大型对象作为实参传递时。对实际的应用而言,复制对象所付出的时间和存储空间代价往往过在。
 当没有办法实现对象的复制时。

对于上述几种情况,有效的解决办法是将形参定义为引用或指针类型。

=======从 C 语言背景转到 C++ 的程序员习惯通过传递指针来实现对实参的访问。在 C++ 中,使用引用形参则更安全和更自然。====
如果使用引用形参的唯一目的是避免复制实参,则应将形参定义为 const 引用。

应该将不需要修改的引用形参定义为 const 引用。普通的非 const 引用形参在使用时不太灵活。这样的形参既不能用 const
对象初始化,也不能用字面值或产生右值的表达式实参初始化。

从避免复制 vector 的角度出发,应考虑将形参声明为引用类型。然而,看过第十一章后我们会知道,事实上,C++
程序员倾向于通过传递指向容器中需要处理的元素的迭代器来传递容器

当编译器检查数组形参关联的实参时,它只会检查实参是不是指针、指针的类型和数组元素的类型时是否匹配,而不会检查数组的长度。
如果形参是数组的引用,编译器不会将数组实参转化为指针,而是传递数组的引用本身。在这种情况下,数组大小成为形参和实参类型的
一部分。编译器检查数组的实参的大小与形参的大小是否匹配。int (&arr)[10]
在这个程序中,数组的大小是经过严格检查的,为了实现传递任意大小的数组,可以考虑建立模板函数的方法:
建立一个可以传递任意大小数组的模板函数:
template <class Type, size_t N>
void somefunction(Type(&parm)[N]){/*some things...*/}

一个变量如果位于函数的作用域内,但生命期跨越了这个函数的多次调用,这种变量往往很有用。则应该将这样的对象定义为
static(静态的)。
只有当定义它的函数被调用时才存在的对象称为自动对象。自动对象在每次调用函数时创建和撤销。
设计带有默认实参的函数,其中部分工作就是排列形参,使最少使用默认实参的形参排在最前,最可能使用默认实参的形参排在最后。

内联函数应该在头文件中定义,这一点不同于其他函数。
inline 函数可能要在程序中定义不止一次,只要 inline
函数的定义在某个源文件中只出现一次,而且在所有源文件中,其定义必须是完全相同的。把 inline
函数的定义放在头文件中,可以确保在调用函数时所使用的定义是相同的,并且保证在调用点该函数的定义对编译器可见。
在头文件中加入或修改 inline 函数时,使用了该头文件的所有源文件都必须重新编译。
编译器隐式地将在类内定义的成员函数当作内联函数。


每个成员函数(除了 static 成员函数外)都有一个额外的、隐含的形参 this。在调用成员函数时,形参 this 初始化为调用函数
的对象的地址。

现在,可以理解跟在 Sales_item 成员函数声明的形参表后面的 const 所起的作用了:const 改变了隐含的 this 形参的类型。
double avg_price() const;
Declaring A MEMBER FUNCTION with the const keyword specifies that the function is a "read-only" function that does
not modify the object for which it is called.
用这种方式使用 const 的函数称为常量成员函数。由于 this 是指向 const 对象的指针,const 成员函数不能修改调用该函数的对象。
const 对象、指向 const 对象的指针或引用只能用于调用其 const 成员函数,如果尝试用它们来调用非 const 成员函数,则是错误的。
在成员函数中,不必显式地使用 this 指针来访问被调用函数所属对象的成员。对这个类的成员的任何没有前缀的引用,都被假定为通过
指针 this 实现的引用:

 

构造函数

构造函数是特殊的成员函数,与其他成员函数不同,构造函数和类同名,而且没有返回类型。
构造函数是放在类的 public 部分的。通常构造函数会作为类的接口的一部分。
Sales_item(): units_sold(0), revenue(0.0) { }
在冒号和花括号之间的代码称为构造函数的初始化列表。构造函数的初始化列表为类的一个或多个数据成员指定初值。它跟在构造函数的
形参表之后,以冒号开关。构造函数的初始化式是一系列成员名,每个成员后面是括在圆括号中的初始值。多个成员的初始化用逗号分隔。
合成的默认构造函数一般适用于仅包含类类型成员的类。而对于含有内置类型或复合类型成员的类,则通常应该定义他们自己的默认
构造函数初始化这些成员。

 

函数的重载

如果两个函数声明的返回类型和形参表完全匹配,则将第二个函数声明视为第一个的重复声明。如果两个函数的形参表完全相同,但
返回类型不同,则第二个声明是错误的:
   Record lookup(const Phone&, const Name&);
     // default argument doesn't change the number of parameters
     Record lookup(const Phone&, const Name& = "");
     // const is irrelevent for nonreference parameters
     Record lookup(Phone);
     Record lookup(const Phone); // redeclaration
复制形参时并不考虑形参是否为 const——函数操纵的只是副本。
=======================================IMPORTANT====================================================================
值得注意的是,形参与 const 形参的等价性仅适用于非引用形参。有 const 引用形参的函数与有非 const 引用形参的函数是不同的。
类似地,如果函数带有指向 const 类型的指针形参,则与带有指向相同类型的非 const 对象的指针形参的函数不相同。

虽然,对于通常的操作,重载函数能避免不必要的函数命名(和名字记忆),但很容易就会过分使用重载。在一些情况下,使用不同的
函数名能提供较多的信息,使程序易于理解。
一般的作用域规则同样适用于重载函数名。如果局部地声明一个函数,则该函数将屏蔽而不是重载在外层作用域中声明的同名函数。
由此推论,每一个版本的重载函数都应在================同一个作用域中声明===============。
一般来说,局部地声明函数是一种不明智的选择。函数的声明应放在头文件中。


函数重载确定的第一步是确定该调用所考虑的重载函数集合,该集合中的函数称为候选函数。候选函数是与被调函数同名的函数,
并且在调用点上,它的声明可见。
第二步是从候选函数中选择一个或多个函数,它们能够用该调用中指定的实参来调用。因此,选出来的函数称为可行函数。可行
函数必须满足两个条件:第一,函数的形参个数与该调用的实参个数相同;第二,每一个实参的类型必须与对应形参的类型匹配,
或者可被隐式转换为对应的形参类型(默认参数的情况除外,实际参数的个数可能不同)。

为了确定最佳匹配,编译器将实参类型到相应形参类型转换划分等级。转换等级以降序排列如下:
An exact match. The argument and parameter types are the same.
精确匹配。实参与形参类型相同。

Match through a promotion (Section 5.12.2, p. 180).
通过类型提升实现的匹配(第 5.12.2 节)。

Match through a standard conversion (Section 5.12.3, p. 181).
通过标准转换实现的匹配(第 5.12.3 节)。

Match through a class-type conversion. (Section 14.9 (p. 535) covers these conversions.)
通过类类型转换实现的匹配(第 14.9 节将介绍这类转换)。


定义为char* 的数组没有办法作为输入,必须制定字符数组的大小;

 

关于主函数 main 返回的另一个特别之处在于如何处理它的返回值。在第 1.1 节已知,可将主函数 main 返回的值视为状态指示器。
返回 0 表示程序运行成功,其他大部分返回值则表示失败。非 0 返回值的意义因机器不同而不同,为了使返回值独立于机器,
cstdlib 头文件定义了两个预处理变量(第 2.9.2 节),分别用于表示程序运行成功和失败:

     #include <cstdlib>
     int main()
     {
         if (some_failure)
             return EXIT_FAILURE;
         else
             return EXIT_SUCCESS;
     }

我们的代码不再需要使用那些依赖于机器的精确返回值。相应地,这些值都在 cstdlib 库中定义,我们的代码不需要做任何修改。

 


函数指针

函数指针只能通过同类型的函数或函数指针或 0 值常量表达式进行初始化或赋值。
如果指向函数的指针没有初始化,或者具有 0 值,则该指针不能在函数调用中使用。只有当指针已经初始化,或被赋值为指向某个函数,
方能安全地用来调用函数。

函数的形参可以是指向函数的指针。这种形参可以用以下两种形式编写:
void useBigger(const string &, const string &,
                    bool(const string &, const string &));
     // equivalent declaration: explicitly define the parameter as a pointer to function
     void useBigger(const string &, const string &,
                    bool (*)(const string &, const string &));

函数可以返回指向函数的指针,但是,正确写出这种返回类型相当不容易:

     // ff is a function taking an int and returning a function pointer
     // the function pointed to returns an int and takes an int* and an int
     int (*ff(int))(int*, int);

阅读函数指针声明的最佳方法是从声明的名字开始由里而外理解,将 ff 声明为一个函数,它带有一个 int 型的形参。该函数返回
int (*)(int*, int);
使用typedef可以简化这种表达方式;
typedef int(*pFunc)(int* ,int);
pFunc ff(int);

===================================================important!=======================================================
允许将形参定义为函数类型,但函数的返回类型则必须是指向函数的指针,而不能是函数。
=================================================important!!!=======================================================

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值