C++(10)函数

来源:互联网 发布:药店软件哪个好 编辑:程序博客网 时间:2024/06/11 15:10

函数

--函数定义、参数传递


一、函数定义

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. int gcd(int v1,int v2)  
  2. {  
  3.     while (v2)  
  4.     {  
  5.         int temp = v2;  
  6.         v2 = v1 % v2;  
  7.         v1 = temp;  
  8.     }  
  9.     return v1;  
  10. }  

1、函数调用做了两件事:

    1)用对应的实参初始化函数的形参

    2)将控制权转移给被调用的函数,此时主调函数被挂起,被调函数开始执行。


2、函数不能返回另一个函数或者内置数组类型,但是可以返回指向函数的指针,或者指向数组元素的指针的指针:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. int *foo_bar()  
  2. {  
  3.     //...  
  4. }  

函数返回一个int型指针,该指针可以指向数组中的一个元素。


二、参数传递

每次调用函数时,都会重新创建该函数的所有的形参,此时所传递的实参将会初始化对应的形参。如果形参具有非引用类型,则复制实参的值,如果形参为引用类型,则它只是实参的别名。


1、指针形参

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //比较下面两段程序  
  2. void mySwap1(int *a,int *b)  
  3. {  
  4.     int temp = *a;  
  5.     *a = *b;  
  6.     *b = temp;  
  7. }  
  8. void mySwap2(int *a,int *b)  
  9. {  
  10.     int i = 10;  
  11.     int j = 20;  
  12.     a = &j;  
  13.     b = &i;  
  14. }  

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


2const形参

    尽管函数的形参是const,但是编译器却将fcn的定义视为其形参被声明为普通的int:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. void fcn(const int i)  
  2. {  
  3.     /* fcn can read but not write to i */  
  4. }  
  5. void fcn(int i)  
  6. {  
  7.     /* ... */  
  8. }  
  9. // error: redefines fcn(int)  

3、复制实参的局限性,下列情况不适合复制实参:

    1)需要在函数中修改实参的值时

    2)需要以大型对象作为实参传递时

    3)当没有办法实现对象的复制时,如该对象禁止拷贝...

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //P201 习题7.5  
  2. int get_Bigger(int x,const int *y)  
  3. {  
  4.     return (x > *y ? x : *y);  
  5. }  

4、引用形参

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. vector<int>::const_iterator  
  2. find_val(vector<int>::const_iterator beg,  
  3.          vector<int>::const_iterator end,  
  4.          int val,  
  5.          vector<int>::size_type &occur)  
  6. {  
  7.     vector<int>::const_iterator res = end;  
  8.     occur = 0;  
  9.   
  10.     for (; beg != end; ++beg)  
  11.     {  
  12.         if (*beg == val)  
  13.         {  
  14.             if (res == end)  
  15.             {  
  16.                 res = beg;  
  17.             }  
  18.             ++ occur;  
  19.         }  
  20.     }  
  21.   
  22.     return res;  
  23. }  

5、利用const引用可以避免复制

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


6、非const引用形参的限制:调用这样的函数时,如果传递一个右值,或者具有需要转换的类型的对象同样是不允许的。如:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. int incr(int &val)  
  2. {  
  3.     return ++val;  
  4. }  
  5.   
  6. int main()  
  7. {  
  8.     short val = 0;  
  9.     val = incr(val);    //error  
  10.   
  11.     const int ival = 1;  
  12.     val = incr(ival);   //error  
  13.     val = incr(0);      //error  
  14.   
  15.     int v3 = 1;  
  16.     v3 = incr(v3);      //ok  
  17. }  


应该将不需要修改的引用形参定义为const引用,普通的非const引用形参在是使用时不太灵活:

    1)这样的形参不能用const对象初始化

    2)不能用字面值或产生右值的表达式实参初始化...


7、传递指向指针的引用

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. int *&v;  

从右到左理解:v1是一个引用,与指向int对象的指针相关联。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //理解下面程序  
  2. void ptrSwap(const int *&v1,const int *&v2)  
  3. {  
  4.     const int *temp = v1;  
  5.     v1 = v2;  
  6.     v2 = temp;  
  7. }  
  8.   
  9. int main()  
  10. {  
  11.     int i = 10;  
  12.     const int *pi = &i;  
  13.     int j = 20;  
  14.     const int *pj = &j;  
  15.   
  16.     cout << *pi << " " << *pj << endl;  
  17.     ptrSwap(pi,pj);  
  18.     cout << *pi << " " << *pj << endl;  
  19. }  
  20. //交换的是指针所保存的地址,即指针的值被交换了!  

8vector和其他容器类型的形参

    C++程序员倾向于通过传递指向容器中需要处理的元素的迭代器来传递容器:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. void print(vector<int>::iterator beg,  
  2.            vector<int>::iterator end)  
  3. {  
  4.     while (beg != end)  
  5.     {  
  6.         cout << *beg++;  
  7.         if (beg != end)  
  8.         {  
  9.             cout << ' ';  
  10.         }  
  11.     }  
  12.     cout << endl;  
  13. }  

9、数组形参的长度可能会引起误解,因为编译器忽略为任何数组形参指定的长度

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. void printValues(const int ia[10])  
  2. {  
  3.     for (size_t i = 0; i != 10; ++i)  
  4.     {  
  5.         cout << ia[i] << endl;  
  6.     }  
  7. }  

而下面的调用都是合法的【但是,是错误的!】:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. int main()  
  2. {  
  3.     int i = 0,j[2] = {0,1};  
  4.     printValues(&i);  
  5.     printValues(j);  
  6.   
  7.    return 0;  
  8. }  

因此会造成数组内存的越界访问。程序的执行可能产生错误的输出,也可能崩溃!


10、通过引用传递数组

如果形参是数组的引用,则编译器不会将数组实参转化为指针,而是传递数组的引用的本身,在这种情况下,数组大小会成为形参和实参类型的一部分!

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. void printValues(const int (&arr)[10])  
  2. {  
  3.     //...  
  4. }  
  5. int main()  
  6. {  
  7.     int i = 0,j[2] = {0,1};  
  8.     int arr[10] = {0};  
  9.     printValues(&i);    //error  
  10.     printValues(j); //error  
  11.     printValues(arr);   //Ok  
  12.       
  13.     return 0;  
  14. }  

11、多维数组的传递

多维数组的元素的本身就是数组,除了第一维以外所有数组的长度都是元素类型的一部分,必须明确指定:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. void printValues(int (*matrix)[10],int rowSize);  

【注意:以下两个声明的不同】

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. int *matrix[10];        //指针数组,包粗了10个指针的数组  
  2. int (*matrix)[10];      //数组指针,一个指向了“含有10个元素的数组”的指针  

12、三种常见的编程技巧保证不会越界访问数组

    1)在数组本身放置一个标记来检测数组的结束,如C风格字符串的'\0'

    2)使用标准库规范:传递数组第一个和最后一个元素的下一个位置的指针

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. void printValues(const int *beg,const int *end)  
  2. {  
  3.     while (beg != end)  
  4.     {  
  5.         cout << *beg++ << endl;  
  6.     }  
  7. }  
  8. int main()  
  9. {  
  10.     int j[2] = {0,1};  
  11.     printValues(j,j+2);  
  12. }  

    3)显式传递数组大小的形参

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. void printValues(const int *arr,size_t size)  
  2. {  
  3.     for (size_t i = 0; i != size; ++i)  
  4.     {  
  5.         cout << arr[i] << endl;  
  6.     }  
  7. }  
  8. int main()  
  9. {  
  10.     int j[2] = {0,1};  
  11.     printValues(j,sizeof(j)/sizeof(*j));  
  12. }  

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //P210 习题7.13  
  2. int sumArr1(const int *arr)  
  3. {  
  4.     int sum = 0;  
  5.     for (int i = 0; arr[i] != 2147483647; ++i)  
  6.     {  
  7.         sum += arr[i];  
  8.     }  
  9.     return sum;  
  10. }  
  11.   
  12. int sumArr2(const int *beg,const int *end)  
  13. {  
  14.     int sum = 0;  
  15.     while (beg != end)  
  16.     {  
  17.         sum += *beg++;  
  18.     }  
  19.     return sum;  
  20. }  
  21.   
  22. int sumArr3(const int *arr,size_t size)  
  23. {  
  24.     int sum = 0;  
  25.     for (size_t i = 0; i != size; ++i)  
  26.     {  
  27.         sum += arr[i];  
  28.     }  
  29.     return sum;  
  30. }  
  31.   
  32. int main()  
  33. {  
  34.     int arr1[11] = {0,1,2,3,4,5,6,7,8,9,2147483647};  
  35.     int arr2[10] = {0,1,2,3,4,5,6,7,8,9};  
  36.   
  37.     cout << sumArr1(arr1) << endl;  
  38.     cout << sumArr2(arr2,arr2+10) << endl;  
  39.     cout << sumArr3(arr2,sizeof(arr2)/sizeof(*arr2)) << endl;  
  40. }  

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //习题7.14  
  2. double sumVector(vector<double>::const_iterator beg,vector<double>::const_iterator end)  
  3. {  
  4.     double res = 0;  
  5.     while (beg != end)  
  6.     {  
  7.         res += *beg ++;  
  8.     }  
  9.     return res;  
  10. }  
  11.   
  12. int main()  
  13. {  
  14.     vector<double> dvec;  
  15.     for (int i = 0; i != 10 ;++i)  
  16.     {  
  17.         dvec.push_back(static_cast<double>(i));  
  18.     }  
  19.   
  20.     cout << sumVector(dvec.begin(),dvec.end()) << endl;  
  21. }  

13main:处理命令行选项

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //P211 习题7.15  
  2. int main(int argc,char **argv)  
  3. {  
  4.     if (argc != 3)  
  5.     {  
  6.         cout << "Please use a.out i j" << endl;  
  7.         return 0;  
  8.     }  
  9.   
  10.     cout << atoi(argv[1]) + atoi(argv[2]) << endl;  
  11. }  

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //习题7.16  
  2. int main(int argc,char **argv)  
  3. {  
  4.     for (int i = 1; i!= argc; ++i)  
  5.     {  
  6.         cout << *(argv + i) << endl;  
  7.     }  
  8. }  

14、当无法列举出传递给函数的所有实参的类型和数目时,则可以使用省略符形参

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. void printArgv(char **argv,int count,...)  
  2. {  
  3.     for (int i = 0; i != count; ++i)  
  4.     {  
  5.         cout << *(argv + i) << endl;  
  6.     }  
  7. }  
  8.   
  9. int main(int argc,char **argv)  
  10. {  
  11.     printArgv(argv,argc);  
  12. }  

C++程序中,只能将简单数组结构类型传递给含有省略符形参的函数,实际上,当需要传递给省略符形参时,大多数类类型对象都不能正确的赋值!


函数

--return语句、递归调用、函数声明



三、return语句

1、没有返回值的函数

    在返回值类型为void的函数中,return返回语句不是必需的,隐式的return发生在函数的最后一个语句完成时。

    一般情况下,返回类型是void的函数使用return语句是为了引起函数的强制结束,这种return的用法类似于循环结构中的break语句的作用。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. void do_swap(int &v1,int &v2)  
  2. {  
  3.     int tmp = v1;  
  4.     v1 = v2;  
  5.     v2 = tmp;  
  6. }  
  7.   
  8. void swap(int &v1,int &v2)  
  9. {  
  10.     if (v1 == v2)  
  11.         return;  
  12.     return do_swap(v1,v2);  
  13. }  

2、在含有return语句的循环后没有提供return语句是很危险的,因为大部分的编译器不能检测出这个漏洞,运行时会出现什么问题是不确定的

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. bool str_subrange(const string &str1,const string &str2)  
  2. {  
  3.     if (str1.size() == str2.size())  
  4.         return str1 == str2;  
  5.   
  6.     string::size_type size = (str1.size() > str2.size() ? str1.size() : str2.size());  
  7.     string::size_type i = 0;  
  8.     while (i != size)  
  9.     {  
  10.         if (str1[i] != str2[i])  
  11.             return//Error  
  12.         ++i;  
  13.     }  
  14.     //return true;  //Warning  
  15. }  

3、主函数main的返回值:为了使返回值独立于机器,cstdlib头文件定义了两个预处理变量,分别用来表示程序运行成功和失败:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. int main()  
  2. {  
  3.     if (some_failure)  
  4.         return EXIT_FAILURE;  
  5.     else  
  6.         return EXIT_SUCCESS;  
  7. }  

4、返回非引用类型:如果函数返回非引用类型,则其返回值可以是局部对象,也可以是求解表达式的结果:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. string make_plural(size_t ctr,const string &word,const string &ending)  
  2. {  
  3.     return (ctr == 1 ? word : word + ending);  
  4. }  

    这个函数要么返回其形参word的副本,要么返回一个未命名的临时string对象,这个临时对象是由字符串word和 ending的相加而产生的。这两种情况下,return都在调用该函数的地方复制了返回的string对象。


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

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. const string &shorterString(const string &str1,const string &str2)  
  2. {  
  3.     return (str1.size() < str2.size() ? str1 : str2);  
  4. }  

形参和返回类型都是指向conststring 对象的引用,调用函数和返回结果时,都没有复制这些string对象


6、在函数执行完毕时,将释放分配给局部对象的存储空间,此时,对局部对象的引用就会指向不确定的内存!

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. const string &manip(const string &str)  
  2. {  
  3.     string s = str;  
  4.     return s;   //Warning  
  5. }   

因此,千万不要返回:

    1)局部对象的引用

    2)指向局部对象的指针(悬垂指针!)


7、返回引用左值

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. char &get_val(string &str,string::size_type index)  
  2. {  
  3.     return str[index];  
  4. }  
  5.   
  6. int main()  
  7. {  
  8.     string s("a value");  
  9.     cout << s << endl;  
  10.     for (string::size_type index = 0; index != s.size(); ++index)  
  11.     {  
  12.         s[index] = toupper(get_val(s,index));  
  13.     }  
  14.     cout << s << endl;  
  15. }  

由于函数返回的是一个引用,因此这是正确的,该引用是被返回元素的同义词。如果不希望引用返回值被修改,返回值应该声明为const:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. const char &get_val(...   

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //P215 习题7.19  
  2. int &get(int *arr,int index)  
  3. {  
  4.     return arr[index];  
  5. }  
  6.   
  7. int main()  
  8. {  
  9.     int ia[10];  
  10.     for (size_t i = 0; i != 10; ++i)  
  11.     {  
  12.         get(ia,i) = i;  //OK  
  13.     }  
  14.   
  15.     for (size_t i = 0; i != 10; ++i)  
  16.     {  
  17.         cout << ia[i] << endl;  
  18.     }  
  19. }  

四、递归调用

    直接或间接调用自己的函数称为递归函数。

1、两个简单例子

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //1  
  2. int factorial(int val)  
  3. {  
  4.     if (val > 1)  
  5.         return val * factorial(val - 1);  
  6.     return 1;  
  7. }  

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //2  
  2. int rgcd(int v1,int v2)  
  3. {  
  4.     if (v2 != 0)  
  5.         return rgcd(v2,v1 % v2);  
  6.     return v1;  
  7. }  

递归函数必须定义一个终止条件:否则,函数就会“永远”递归下去,直到程序栈耗尽!

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //P217 习题7.20  
  2. int factorial(int val)  
  3. {  
  4.     int res = 1;  
  5.     for (int i = 2; i <= val; ++i)  
  6.     {  
  7.         res *= i;  
  8.     }  
  9.     return res;  
  10. }  

五、函数声明

    在函数声明中的形参名会被忽略,如果在声明中给出了形参的名字,它应该只用作辅助说明文档:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. void print(int *arr,size_t size);  

1、应该在头文件中提供函数声明:

    在源文件中声明函数的方法比较呆板而且容易出错。解决的方法是把函数声明放在头文件中,这样可以确保对于指定函数其所有声明保持一致。如果函数接口发生变化,只要修改其唯一的声明即可

    定义函数的源文件应该包含声明该函数的头文件。将提供函数声明头文件包含在定义该函数的源文件中,可使编译器能检查该函数的定义和声明时是否一致。


2、默认实参

    如果有一个形参具有默认实参,那么,后面所有的形参都必须有默认实参。因此,设计带有默认实参的函数,其中部分工作就是排列形参,应该使最少使用默认实参的形参排在最前面,最可能使用默认实参的形参排在最后。


3、指定默认实参的约束

    既可以在函数声明也可以在函数定义中指定默认实参。但是,在一个文件中,只能为一个实参指定默认实参一次:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //ff.h  
  2. int ff(int = 0);  
  3.   
  4. //ff.cc  
  5. #include "ff.h"  
  6. int ff(int a = 0)   //Error  
  7. {  
  8.     //...  
  9. }  

通常,应该在函数声明中指定默认实参,并将该声明放在合适的头文件中

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //P219 习题7.26  
  2. string make_plural(int cnt,const string &word,const string &ending = "s")  
  3. {  
  4.     return (cnt == 1 ? word : word + ending);  
  5. }  
  6.   
  7. int main()  
  8. {  
  9.     string word = "Book";  
  10.     string ending = "s";  
  11.     cout << make_plural(1,word,ending) << endl;  
  12.     cout << make_plural(2,word) << endl;  
  13.     cout << make_plural(2,word,ending) << endl;  
  14. }   

函数

--局部对象、内联函数、类的成员函数



一、局部对象

    在C++语言中,每个名字都有作用域,每个对象都有生命期;名字的作用域指的是该名字的程序文本区,对象的生命期是在程序的执行过程中对象的存在时间。



自动对象

1、只有当定义它的函数被调用时才存在的对象称为自动对象。自动对象在每次调用函数时创建和撤销

2、对于未初始化的内置类型的局部变量,其初值不确定。

3、形参也是自动对象,形参所占用的存储空间在调用函数时创建,而在函数结束时撤销。



静态局部对象

    一个变量如果位于函数的作用域内,但生命期跨越了这个函数的多次调用,这种变量往往很有用。则应该将这样的对象定义为static(静态的)

    static局部对象确保不迟于在程序执行流程第一次经过该对象的定义语句时进行初始化(比较难于理解o(∩∩)o...)。这种对象一旦被创建,程序结束前都不会撤销。当定义静态局部对象的函数结束时,静态局部对象不会撤销在该函数被多次调用的过程中,静态局部对象会持续存在并保持它的值

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /* 
  2. *在第一次调用函数count_calls之前,cnt就已经创建并赋初值为0 
  3. *在执行函数 count_calls 时, 变量 ctr 就已经存在并且保留上次调用该函数时的值。  
  4. */  
  5. size_t count_calls()  
  6. {  
  7.       
  8.     static size_t cnt = 0;  
  9.     return ++cnt;  
  10. }  
  11. int main()  
  12. {  
  13.     for (int i = 0; i != 10; ++i)  
  14.     {  
  15.         cout << count_calls() << endl;  
  16.     }  
  17.     return 0;  
  18. }   

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //P221 习题7.27 用static实现阶乘  
  2. int factorial()  
  3. {  
  4.     static int j = 0;  
  5.     static int res = 1;  
  6.     ++j;  
  7.   
  8.     return (res *= j);  
  9. }  
  10.   
  11. int main()  
  12. {  
  13.     for (int i = 0; i != 5; ++i)  
  14.     {  
  15.         cout << factorial() << endl;  
  16.     }  
  17. }  

二、内联函数

使用“小操作”函数的好处:

    1)阅读和理解函数的调用,要比读一条用等价的表达式要容易得多。

    2)如果需要做任何修改,修改函数要比找出并修改每一处等价表达式容易得多。

    3)使用函数可以确保统一的行为,每个测试都保证以相同的方式实现。

    4)函数可以重用,不必为其他应用重写代码。

但是这样的函数也存在者一些缺陷,调用函数比求解等价表达式要慢得多。在大多数的机器上,调用函数都要做很多工作:

    1)调用前先保存寄存器,并且要在返回时恢复

    2)复制实参

    3)程序必须转向一个新的位置执行


1、使用内联函数避免函数调用的开销

   将函数指定为inline函数,就是将它在程序中每个调用点上“内联地”展开。从而消除了把表达式写成函数的额外执行开销。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. inline const string &shorterString(const string &s1,const string &s2)  
  2. {  
  3.     return (s1.size() < s2.size() ? s1 : s2);  
  4. }  

   内联说明(inline)对于编译器来说只是一个建议,编译器可以选择忽略这个建议;一般来说,内联机制使用与优化小的,只有几行而且经常被调用的函数。


2、把内联函数写入头文件

   内联函数要在头文件中定义,这一点不同于其他函数!

   在头文件中加入或修改内联函数时,使用了该头文件的所有源文件都必须重新编译!因为修改此处的头文件相当于修改了各个源文件!

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //P222 习题7.39  
  2. inline bool isShorter(const string &str1,const string &str2)  
  3. {  
  4.     return str1.size() < str2.size();  
  5. }  

三、类的成员函数

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. class Sales_item  
  2. {  
  3. public:  
  4.     double avg_price() const;  
  5.     bool same_isbn(const Sales_item &rhs) const  
  6.     {  
  7.         return isbn == rhs.isbn;  
  8.     }  
  9.   
  10. private:  
  11.     std::string isbn;  
  12.     unsigned int units_sold;  
  13.     double revenue;  
  14. };  

    函数原型必须在类中定义,而函数体既可以在类中定义,也可以在类外定义,一般比较短小的函数定义在类的内部。

    编译器隐式的将在类内定义的成员函数当作内联函数。

    类的成员函数可以访问该类的private成员。


1、成员函数含有额外的、隐含的形参

    每个成员函数都有一个额外的、隐含的形参将该成员函数与调用该函数的类对象绑定在一起。而这个形参就是this指针!

    因此,语句

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. total.sime_isbn(trans);  

就如同编译器这样重写了这个函数调用:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. Sales_item::same_isbn(&total,trans);  

2const成员函数的引入

    const改变了隐含的this形参的类型。在调用total.same_isbn(trans),隐含的this形参将是一个指向total对象的constSales_Item*类型的指针:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. bool Sales_item::same_isbn(const Sales_item *const this,  
  2.                            const Sales_item &rhs) const  
  3. {  
  4.     return (this -> isbn == rhs.isbn);  
  5. }  

使用这种方式的const函数称为常量成员函数。由于this是指向const对象的指针,const成员函数不能修改调用该函数的对象,因此,函数avg_price和函数same_isbn只能读取而不能修改调用他们的对象的数据成员。


3this指针的使用

由于this指针是隐式定义的,因此不需要在函数的形参表中包含this指针,实际上,这样做也是非法的。但是,在函数体中可以显式地使用this指针。如:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. bool Sales_item::same_isbn(const Sales_item &rhs) const  
  2. {  
  3.     return (this -> isbn == rhs.isbn);  
  4. }  
  5. double Sales_item::avg_price() const  
  6. {  
  7.     if (units_sold)  
  8.         return (this -> revenue) / (this -> units_sold);  
  9.     else  
  10.         return 0;  
  11. }  

在类外定义成员函数时,返回类型和参数表必须和函数声明一致,如果函数被声明为const成员函数,那么函数定义时形参表后面也必须有const


4、定义构造函数

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. Sales_item::Sales_item():units_sold(0),revenue(0){};  

    1)通常构造函数会作为类的接口的一部分,因此必须将构造函数定义为public的。

    2)在冒号和花括号之间的代码称为构造函数的初始化列表。构造函数的初始化列表为类的一个或多个数据成员指定初值。它跟在构造函数的形参表之后,以冒号开头。构造函数的初始化式是一系列成员名,每个成员后面是括在圆括号中的初始值。多个成员的初始化用逗号分隔。

    3)如果没有为一个类显式定义任何构造函数,编译器将自动为这个类生成默认构造函数。由编译器创建的默认构造函数通常称为合成的默认构造函数。

对于具有类类型的成员,如isbn,则会调用该成员所属类自身的默认构造函数实现初始化。而内置类型成员的初值却要依赖于对象如何定义,如果对象定义为全局对象,或定义为静态局部对象,则将这些成员初始化为0,不然,则不提供这些成员的初始化工作,这些成员没有初始化!


【建议:】

    合成的默认构造函数一般适用于仅包含类类型成员的类。而对于含有内置类型或复合类型成员的类,则通常应该定义他们自己的默认构造函数初始化这些成员。


5、类代码文件的组织【摘抄】

    通常情况下,将类的声明放置在头文件中,在类外定义的成员函数放置在源文件中,C++程序员习惯使用一些简单的规则给头文件及其关联的类定义代码命名。类定义应置于名为type.htype.H的文件 中,type指在该文件中定义的类的名字成员函数的定义则一般存储在与类同名的源文件中。依照这些规则,我们将类Sales_item放在名为Sales_item.h的文件中定义。任何需使用这个类的程序,都必须包含这个头文件。而Sales_item的成员函数的定义则应该放在名为Sales_item.cc的文件中。这个文件同样也必须包含Sales_item.h头文件

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //P227 习题7.31  
  2. class Sales_item  
  3. {  
  4. public:  
  5.     Sales_item():units_sold(0),revenue(0) {};  
  6.     double avg_price() const;  
  7.     bool same_isbn(const Sales_item &rhs) const;  
  8.   
  9.     void scan();  
  10.     void print() const;  
  11.   
  12. private:  
  13.     std::string isbn;  
  14.     unsigned int units_sold;  
  15.     double revenue;  
  16. };  
  17.   
  18. bool Sales_item::same_isbn(const Sales_item &rhs) const  
  19. {  
  20.     return (this -> isbn == rhs.isbn);  
  21. }  
  22. double Sales_item::avg_price() const  
  23. {  
  24.     if (units_sold)  
  25.         return (this -> revenue) / (this -> units_sold);  
  26.     else  
  27.         return 0;  
  28. }  
  29.   
  30. void Sales_item::scan()  
  31. {  
  32.     cout << "Please input the isbn、units_sold & price" << endl;  
  33.     double price;  
  34.     cin >> isbn >> units_sold >> price;  
  35.     revenue += units_sold * price;  
  36. }  
  37.   
  38. void Sales_item::print() const  
  39. {  
  40.     cout << "ISBN: " << isbn << endl;  
  41.     cout << "Units_sold: " << units_sold << endl;  
  42.     cout << "Revenue: " << revenue << endl;  
  43.     cout << "Avg_price: " << avg_price() << endl;  
  44. }  
  45.   
  46. int main()  
  47. {  
  48. //  freopen("input.txt","r",stdin);  
  49.     Sales_item item;  
  50.     item.scan();  
  51.     item.print();  
  52. }  

函数

--重载函数、指向函数的指针



一、重载函数

1、函数不能仅仅基于不同的返回值类型而实现重载。

2、如果局部的声明一个函数,则该函数将屏蔽而不是重载在外层作用域中声明的同名函数。由此推论:每一个版本的重载函数都应在同一个作用域中声明。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. <span style="background-color: rgb(255, 255, 255);">string init();  
  2. int main()  
  3. {  
  4.     int init = 0;  
  5.     string s = init();  //Error  
  6. }  
  7. //在C++中,名字查找要发生在类型检查之前。</span>  

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. <span style="background-color: rgb(255, 255, 255);">void print(const string &);  
  2. void print(double);  
  3. void print(int);  
  4. void fooBar(int ival)  
  5. {  
  6.     print("Value: ");   //OK  
  7.     print(ival);        //OK  
  8.     print(3.14);        //OK  
  9. }  
  10. </span>  

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. <span style="background-color: rgb(255, 255, 255);">//P231 习题7.34  
  2. void error(const string &,int,int);  
  3. void error(const string &);  
  4. void error(const string &,char);  
  5. int main()  
  6. {  
  7.     int index,upperBound;  
  8.     char selectVal;  
  9.     //...  
  10.     error("Subscript out of bounds: ",index,upperBound);  
  11.     error("Division by zero");  
  12.     error("Invalid selection",selectVal);  
  13. }</span><span style="color:#ff0000;">  
  14. </span>  

3、重载确定的三个步骤

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. void f();  
  2. void f(int);  
  3. void f(int,int);  
  4. void f(double,double = 3.14);  
  5.   
  6. f(5.6);  

     1)候选函数

     第一步是确定该调用所考虑的重载函数集合,该集合中的函数称为候选函数,候选函数是与被调用函数同名的函数。

    2)选择可行函数

    选择可行函数必须满足两个条件:函数的形参个数与该调用的实参个数相同;每一个实参的类型必须与对引形参的类型匹配,或者可以被隐式转换成为对应的形参类型。

    如果没有找到可行函数,则该调用错误。

   3)寻找最佳匹配

    实参类型与形参类型越接近则匹配越佳,因此,实参类型与形参类型之间的精确匹配比需要转换的匹配要好。


4、含有多个形参的函数确定

编译器会通过依次检查每一个实参来决定哪个或哪些函数匹配最佳:

   1)其每个实参的匹配都不劣于其他可行函数需要的匹配。

   2)至少有一个实参的匹配优于其他可行函数提供的匹配

示例:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. f(42,3.56);  

    42匹配int最佳,而3.56匹配double= 3.14更佳,因此,该调用错误,编译器提示“二义性”或“有歧义”,可以通过显示的强制类型转换强制匹配:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. f(static_cast<double>(42),3.56);  
  2. f(42,static_cast<int>(3.56));  

    但是在实际应用中,调用重载函数要尽量避免对实参进行强制类型转换,需要使用强制类型转换意味着所设计的形参不合理。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //P234 习题7.37  
  2.     f(2.56,42);     //Error  
  3.     f(42);          //f(int);  
  4.     f(42,0);            //f(int,int);  
  5.     f(2.56,3.14);   //f(double,double = 3.14);  

5、为了确定最佳匹配,编译器将实参类型到相应形参类型转换划分等级。转换等级以降序排列如下:

    1)精确匹配。实参与形参类型相同。

    2)通过类型提升实现的匹配

    3)通过标准转换实现的匹配

    4)通过类类型转换实现的匹配


6、需要类型提升或转换的匹配

    1较小的整型提升为int:对于任意整型的实参值,int型版本都是由于short版本的较佳匹配,即使从形式看short版本匹配较佳:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. void ff(int);  
  2. void ff(short);  
  3. ff('a');            //匹配ff(int);  

    2通过提升实现的转换要优于其他标准转换。如:对于char型实参来说,有int型形参的函数是优于有double型形参的函数较佳匹配。


7、参数匹配和枚举类型

   1)整数对象即使具有与枚举元素相同的值也不能用于调用期望获得枚举类型实参的函数。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. enum Tokens {INLINE = 128,VIRTUAL = 129};  
  2. void ff(Tokens);  
  3. void ff(int);  
  4. int main()  
  5. {  
  6.     Tokens curTok = INLINE;  
  7.     ff(128);  
  8.     ff(INLINE);  
  9.     ff(curTok);  
  10.     return 0;  
  11. }  

    虽然无法将整型值传递给枚举类型的形参,但是可以将枚举值传递给整型实参。此时,枚举类型被提升为int型或更大的整型。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. enum Tokens {INLINE = 128,VIRTUAL = 129};  
  2. void newf(unsigned char);  
  3. void newf(int);  
  4. unsigned char uc = 128;  
  5. newf(INLINE);   //calls newf(int)  
  6. newf(uc);       //calls newf(unsigned char)  

    由于不同枚举类型的枚举常量值不相同,在函数重载确定过程中,不同的枚举类型会具有完全不同的行为。其枚举成员决定了它们提升的类型,而所提升的类型依赖于机器


8、重载和const形参

    仅当形参是引用或指针时,形参是否为const才有影响。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. Record lookup(Account &);  
  2. Record lookup(const Account &);  
  3. const Account a(0);  
  4. Account b;  
  5.   
  6. lookup(a);  //calls lookup(const Account &)  
  7. lookup(b);  //calls lookup(Account &)  

    如果形参是普通的引用,则不能将const对象传递给这个形参。如果传递了const对象,则只有带const引用形参的版本才是该调用的可行函数。

    对指针形参的相关处理如同引用。可将const对象的地址值只传递给带有指向const对象的指针形参的函数。也可将指向非const对象的指针传递给函数的const或非const类型的指针形参。如果两个函数的形参只是在”指针形参是否指向const对象“这一点上有所不同,指向非const对象的指针形参对于指向const对象的指针来说是更佳的匹配

    重复强调,编译器可以判断:如果实参是const对象,则调用带有const*类型形参的函数;否则,如果实参不是const对象,将调用带有普通指针形参的函数。

    注意不能基于指针本身是否为const来实现函数的重载:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. void f(int *);  
  2. void f(int *const); //重复定义  

  在上述两种情况中,都复制了指针,指针本身是否为const并没有带来区别。


二、指向函数的指针

    函数指针指的是指向函数而非指向对象的指针。像其他指针一样,函数指针也指向某一特定的类型。函数类型由其返回类型以及形参表确定,而与函数名无关

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. bool (*pf)(const string &,const string &);  //*pf两侧的圆括号是必需的  

   这个语句将pf声明为指向函数的指针,它所指向的函数带有两个conststring & 类型的形参和bool类型的返回值。

1、用typedef简化函数指针的定义

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //表示cmpFcn是一种指向函数的指针类型的名字  
  2. typedef bool (*cmpFcn)(const string &,const string &);  

2、指向函数的指针的初始化和赋值

    在引用函数名但又没有调用该函数时,函数名将被自动解释为指向函数的指针

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. bool lengthCompare(const string &,const string &);  

    除了用作函数调用的左操作数以外,lengthCompare的任何使用都被解释为如下类型的指针

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. bool (*)(const string &, const string &);   

    可以使用函数名对函数指针做初始化或赋值

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. cmpFcn pf1 = 0;  
  2. cmpFcn pf2 = lengthCompare;  
  3. pf1 = lengthCompare;        //在有些编译器中会报错  
  4. pf2 = pf1;                  //在有些编译器中会报错  

    此时,直接引用函数名等效于在函数名上应用取地址符

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. cmpFcn pf1 = lengthCompare;  
  2. cmpFcn pf2 = &lengthCompare;  

    函数指针只能通过同类型的函数或函数指针或0值常量表达式进行初始化或赋值。而指向不同函数类型的指针之间不存在转换。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. typedef bool (*cmpFcn)(const string &,const string &);  
  2.   
  3. string::size_type subLength(const string &,const string &);  
  4. bool cstringCompare(const char *,const char *);  
  5.   
  6. cmpFcn pf1 = subLength;         //Error  
  7. cmpFcn pf2 = cstringCompare;    //Error  

3、通过指针调用函数

    指向函数的指针可用于调用它指向的函数,可以不使用解引用操作符,直接通过指针调用函数

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. cmpFcn = lengthCompare;  
  2. pf("hi","bye");  
  3. (*pf)("hi","bye");  

【注意】

    如果指向函数的指针没有初始化,或者具有0值,则该指针不能在函数调用中使用。

4、函数指针形参

    函数的形参可以是指向函数的指针。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. bool lengthCompare(const string &,const string &);  
  2.   
  3. typedef bool (*cmpFcn)(const string &,const string &);  
  4.   
  5. //下面三个声明作用相同  
  6. void useBigger(const string &,const string &,bool(const string &,const string &));  
  7. void useBigger(const string &,const string &,bool (*)(const string &,const string &));  
  8. void useBigger(const string &,const string &,cmpFcn);  

5、返回函数的指针

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

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. int (*ff(int))(int *,int);  

    将ff声明为一个函数,它带有一个int型参数。该函数返回int(*)(int *,int);

    可以使用typedef使该定义更简明易懂:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. typedef int (*PF)(int *,int);  
  2. PF ff(int);  

    允许将形参定义为函数类型或函数指针类型,因为具有函数类型的形参所对应的实参将被自动转换为指向相应类型的指针,但是函数的返回类型必须是指向函数的指针,而不能是函数,因为当返回时,同样的转换操作无法实现。


6、指向重载函数的指针

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. void ff(vector<double>);  
  2. void ff(unsigned int);  
  3. void (*pf)(unsigned int) = &ff; //OK  
  4. void (*pf2)(int) = &ff;         //Error  

    指针的类型必须与重载函数的一个版本精确匹配。如果没有精确匹配的函数,则对该指针的初始化或赋值将导致编译错误!



本文借鉴:http://blog.csdn.net/column/details/zjf666.html?&page=5


0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 被海鲜划伤出血怎么办 海域使用证缴纳金没交怎么办 海峡中线 金门海域怎么办 对工作失去热情怎么办 取款机多出钱怎么办 风扇声音很响怎么办 稳压器输出没电怎么办 稳压器不稳10压怎么办 dnf凯蒂不见了怎么办 马桶里掉进塑料瓶盖怎么办 塑料瓶子盖子打不开怎么办 按压瓶盖坏了怎么办 瓶盖拧错位了怎么办 红酒盖子开不了怎么办 胶盖罐头打不开怎么办 玻璃瓶的塑料盖打不开怎么办 香水按压不出来怎么办 电高压锅盖子打不开怎么办 杯子螺口错位怎么办 散粉盖子扭不开怎么办 玻璃瓶饮料盖子打不开怎么办 玻璃瓶玻璃盖子打不开怎么办 美甲没有胶水怎么办 按压式瓶盖打不开怎么办 睫毛胶水瓶盖打不开怎么办 玻璃杯盖子滑丝怎么办 瓶盖滑扣了怎么办 胶水瓶口被塞住怎么办 美林盖子打不开怎么办 美林瓶盖打不开怎么办 泰诺瓶盖打不开怎么办 玻璃罐头瓶盖打不开怎么办 塑料罐头瓶盖打不开怎么办 喷笔壶盖打不开怎么办 陶瓷壶盖卡住了怎么办 贝德玛瓶盖摔坏怎么办 塑料盖子错位拧不开怎么办 安全瓶盖坏了怎么办 刚买面霜打不开怎么办 可乐瓶盖鼓起来怎么办 暖壶塞子吸住了怎么办