核心内容摘要
“草草浮力发地布地址50826”:解锁生活新维度,探寻无限可能
默认参数下面介绍C的另一项新内容—默认参数。
默认参数指的是当函数调用中省略了实参时自动使用的一个值。
例如如果将void wowint n设置成n 有默认值为1则函数调用wow( )相当于wow1。
这极大地提高了使用函数的灵活性。
假设有一个名为left( )的函数它将字符串和n 作为参数并返回该字符串的前n 个字符。
更准确地说该函数返回一个指针该指针指向由原始字符串中被选中的部分组成的字符串。
例如函数调用left(“theory”,
将创建新字符串“the”并返回一个指向该字符串的指针。
现在假设第二个参数的默认值被设置为1则函数调用left(“theory”,
仍像前面讲述的那样工作3 将覆盖默认值。
但函数调用left(“theory”)不会出错它认为第二个参数的值为1并返回指向字符串“t”的指针。
如果程序经常需要抽取一个字符组成的字符串而偶尔需要抽取较长的字符串则这种默认值将很有帮助。
如何设置默认值呢必须通过函数原型。
由于编译器通过查看原型来了解函数所使用的参数数目因此函数原型也必须将可能的默认参数告知程序。
方法是将值赋给原型中的参数。
例如left( )的原型如下char * left(const char *str,int n
;您希望该函数返回一个新的字符串因此将其类型设置为char *指向char 的指针您希望原始字符串保持不变因此对第一个参数使用了const 限定符您希望n 的默认值为1因此将这个值赋给n。
默认参数值是初始化值因此上面的原型将n 初始化为1。
如果省略参数n则它的值将为1否则传递的值将覆盖1。
对于带参数列表的函数必须从右向左添加默认值。
也就是说要为某个参数设置默认值则必须为它右边的所有参数提供默认值int harpo(int n,int m4,int j
; int chico(int n,int m6,int j); int groucho(int k1,int m2,int n
;例如harpo( )原型允许调用该函数时提供1 个、2 个或3 个参数beepsharpo(
; beepsharpo(1,
; beepsharpo(8,7,
;实参按从左到右的顺序依次被赋给相应的形参而不能跳过任何参数。
因此下面的调用是不允许的beepsharpo(3, ,
默认参数并非编程方面的重大突破而只是提供了一种便捷的方式。
在设计类时您将发现通过使用默认参数可以减少要定义的析构函数、方法以及方法重载的数量。
#include iostream const int ArSize 80; char* left(const char* str, int n
; int main() { using namespace std; char sample[ArSize]; cout Enter a string:\n; cin.get(sample, ArSize); char* ps left(sample,
; delete[] ps; ps left(sample); cout ps endl; delete[] ps; return 0; } char* left(const char* str, int n) { if (n
n 0; char* p new char[n 1]; int i; for (i 0; i n str[i]; i) p[i] str[i]; while (i n) p[i] \0; return p; }函数重载函数多态是C在C 语言的基础上新增的功能。
默认参数让您能够使用不同数目的参数调用同一个函数而函数多态函数重载让您能够使用多个同名的函数。
术语“多态”指的是有多种形式因此函数多态允许函数可以有多种形式。
类似地术语“函数重载”指的是可以有多个同名的函数因此对名称进行了重载。
这两个术语指的是同一回事但我们通常使用函数重载。
可以通过函数重载来设计一系列函数—它们完成相同的工作但使用不同的参数列表。
重载函数就像是有多种含义的动词。
例如Piggy 小姐可以在棒球场为家乡球队助威root也可以在地里种植root菌类作物。
根据上下文可以知道在每一种情况下root 的含义是什么。
同样C使用上下文来确定要使用的重载函数版本。
函数重载的关键是函数的参数列表—也称为函数特征标function signature。
如果两个函数的参数数目和类型相同同时参数的排列顺序也相同则它们的特征标相同而变量名是无关紧要的。
C允许定义名称相同的函数条件是它们的特征标不同。
如果参数数目和/或参数类型不同则特征标也不同。
例如可以定义一组原型如下的print( )函数void print(const char *str,int width); void print(double d,int width); void print(long l,int width); void print(int i,int width); void print(const char *str);使用print( )函数时编译器将根据所采取的用法使用有相应特征标的原型print(Pancakes,
; print(Syrup); print(
1
0,
; print(1999,
; print(1999L,
;例如print(“Pancakes”,
使用一个字符串和一个整数作为参数这与#1 原型匹配。
使用被重载的函数时需要在函数调用中使用正确的参数类型。
例如对于下面的语句print( )调用与哪个原型匹配呢它不与任何原型匹配没有匹配的原型并不会自动停止使用其中的某个函数因为C将尝试使用标准类型转换强制进行匹配。
如果#2 原型是print( )唯一的原型则函数调用print(year,
将把year 转换为double 类型。
但在上面的代码中有3 个将数字作为第一个参数的原型因此有3 种转换year 的方式。
在这种情况下C将拒绝这种函数调用并将其视为错误。
一些看起来彼此不同的特征标是不能共存的。
例如请看下面的两个原型double cube(double x); double cube(double x);您可能认为可以在此处使用函数重载因为它们的特征标看起来不同。
然而请从编译器的角度来考虑这个问题。
假设有下面这样的代码coutcube(x);参数x 与double x 原型和double x 原型都匹配因此编译器无法确定究竟应使用哪个原型。
为避免这种混乱编译器在检查函数特征标时将把类型引用和类型本身视为同一个特征标。
void dribble(char * bits); void dribble(const char *cbits); void dabble(char * bits); void drivel(const char *bits);下面列出了各种函数调用对应的原型const char p1[20]Hows the weather?; char p2[20]Hows business?; dribble(p
; dribble(p
; dabble(p
; dabble(p
; drivel(p
; drivel(p
;dribble( )函数有两个原型一个用于const 指针另一个用于常规指针编译器将根据实参是否为const来决定使用哪个原型。
dribble( )函数只与带非const 参数的调用匹配而drivel( )函数可以与带const 或非const 参数的调用匹配。
drivel( )和dabble( )之所以在行为上有这种差别主要是由于将非const 值赋给const变量是合法的但反之则是非法的。
请记住是特征标而不是函数类型使得可以对函数进行重载。
例如下面的两个声明是互斥的long gronk(int n,float m); double gronk(int n,float m);因此C不允许以这种方式重载gronk( )。
返回类型可以不同但特征标也必须不同重载引用参数类设计和STL 经常使用引用参数因此知道不同引用类型的重载很有用。
请看下面三个原型void sink(double r
; void sank(const double r
; void sunk(double r
;左值引用参数r1 与可修改的左值参数如double 变量匹配const 左值引用参数r2 与可修改的左值参数、const 左值参数和右值参数如两个double 值的和匹配最后左值引用参数r3 与左值匹配。
注意到与r1 或r3 匹配的参数都与r2 匹配。
这就带来了一个问题如果重载使用这三种参数的函数结果将如何答案是将调用最匹配的版本void staff(double rs); void staff(const double rcs); void stove(double r
; void stove(const double r
; void stove(double r
;这让您能够根据参数是左值、const 还是右值来定制函数的行为double x
5
5; const double y
3
0; stove(x); stove(y); stove(xy);如果没有定义函数stove(double )stove(xy)将调用函数stove(const double )。
重载示例本章前面创建了一个left( )函数它返回一个指针指向字符串的前n 个字符。
下面添加另一个left( )函数它返回整数的前n 位。
例如可以使用该函数来查看被存储为整数的、美国邮政编码的前3 位—如果要根据城区分拣邮件则这种操作很有用。
该函数的整数版本编写起来比字符串版本更困难些因为并不是整数的每一位被存储在相应的数组元素中。
一种方法是先计算数字包含多少位。
将数字除以10 便可以去掉一位因此可以使用除法来计算数位。
更准确地说可以用下面的循环完成这种工作unsigned digits1; while(nameof/
digits;上述循环计算每次删除n 中的一位时需要多少次才能删除所有的位。
前面讲过n / 10 是n n / 10的缩写。
例如如果n 为8则该测试条件将8/10 的值0由于这是整数除法赋给n。
这将结束循环digits 的值仍然为1。
但如果n 为238第一轮循环测试将n 设置为238/10即23。
这个值不为零因此循环将digits 增加到2。
下一轮循环将n 设置为23/10即2。
这个值还是不为零因此digits 将增加到3。
下一轮循环将n 设置为2/10即0从而结束循环而digits 被设置为正确的值—3。
现在假设知道数字共有5 位并要返回前3 位则将这个数除以10 后再除以10便可以得到所需的值。
每除以10 次就删除数字的最后一位。
要知道需要删除多少位只需将总位数减去要获得的位数即可。
例如要获得9 位数的前4 位需要删除后面的5 位。
可以这样编写代码ctdigits-ct; while(ct--) num/10; return num;程序清单
10 将上述代码放到了一个新的left( )函数中。
该函数还包含一些用于处理特殊情况的代码如用户要求显示0 位或要求显示的位数多于总位数。
由于新left( )的特征标不同于旧的left( )因此可以在同一个程序中使用这两个函数。
#include iostream unsigned long left(unsigned long num, unsigned ct); char* left(const char* str, int n
; int main() { using namespace std; const char* trip Hawaii!!; unsigned long n 12345678; int i; char* temp; for (i 1; i 10; i) { cout left(n, i) endl; temp left(trip, i); cout temp endl; delete[] temp; } return 0; } unsigned long left(unsigned long num, unsigned ct) { unsigned digits 1; unsigned long n num; if (ct 0 || num
return 0; while(n/
digits; if (digits ct) { ct digits - ct; while (ct--) num / 10; return num; } else return num; } char* left(const char* str, int n) { if (n
n 0; char* p new char[n 1]; int i; for (i 0; i n str[i]; i) p[i] str[i]; while (i n) p[i] \0; return p; }何时使用函数重载虽然函数重载很吸引人但也不要滥用。
仅当函数基本上执行相同的任务但使用不同形式的数据时才应采用函数重载。
另外您可能还想知道是否可以通过使用默认参数来实现同样的目的。
例如可以用两个重载函数来代替面向字符串的left( )函数char * left(const char *str,unsigned n); char *left(const char *str);使用一个带默认参数的函数要简单些。
只需编写一个函数而不是两个函数程序也只需为一个函数而不是两个请求内存需要修改函数时只需修改一个。
然而如果需要使用不同类型的参数则默认参数便不管用了在这种情况下应该使用函数重载。