C++ Primer 07 函数

来源:互联网 发布:振动数据采集器 编辑:程序博客网 时间:2024/06/05 09:30
1. C++ 是一种静态强类型语言,对于每一次的函数调用,编译时都会检查其实参。

2. 求两个int型数的最大公约数:
int gcd(int v1, int v2){    while(v2)    {        int temp = v2;        v2 = v1 % v2;        v1 = temp;    }    return v1;}
函数调用时,主调函数被挂起,被调函数执行。
函数不能返回数组类型,但可以返回指向数组第一个元素的指针类型。

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

4. 可以指向const对象的指针初始化为指向非const对象,但不可以让指向非const对象的指针指向const对象..
int i = 0;const int* p = &i; //ok, 指向const对象的指针初始化为指向非const对象const int &q = 3;int* p = q;    //error:指向非const对象的指针指向const对象

5. 在C语言中,具有const形参或非const形参的函数并无区别。
void fcn(const int i);void fcn(int i); //error,fcn重定义

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

7. 实现对实参的访问:在C语言中,习惯用指针;在C++中,使用引用形参则更安全和自然。

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

9. 将数组形参直接定义为指针要比使用数组语法定义更好。这样就明确的表示,函数操纵的是指向数组元素的指针,而不是数组本身。由于忽略了数组的长度,形参定义中如果包含了数组长度则特别容易引起误解。
void printValues(int* );void printValues(int[]);void printValues(int[10]);//这三个函数是等价的。
//编译器检查数组形参关联的实参时,不会检查数组的长度。
//不需要修改数组形参的元素时,函数应该将形参定义为指向const对象的指针。

10. 数组以普通的非引用类型传递,此时数组会悄悄的转换为指针。
       如果形参是数组的引用,编译器不会将数组实参化为指针,而是传递数组引用的本身,数组大小成为形参和实参类型的一部分。
        
f(int &arr[10]);f(int (&arr)[10]);//&arr两边的圆括号是必需的,因为下标操作符具有更高的优先级


11. 多维数组的传递:除了第一维以外的所有维的长度都是元素类型的一部分。
//*matrix两边的括号是必须的void printValues(int (*matrix)[10], int rowSize);void printValues(int matrix[][10], int rowSize);//编译器忽略第一维的长度

12. 确保函数的操作不超出数组实参的边界:
  • 在数组本身放置一个标记来检测数组的结束。
  • 传递指向数组第一个和最后一个元素的下一个位置的指针。
  • 将第二个形参定义为表示数组的大小。

13. int main(int argc, char *argv[]);
       int main(int argc, char** argv);

14. 含有可变形参的函数 (省略符  ... )

省略符暂停了类型检查机制。对于显示声明的形参对应的实参进行类型检查,而对于省略符对应的实参则暂停类型检查。

只能将简单的类型传递给含有省略符形参的函数。实际上,当需要传递给省略符形参时,大多数类类型对象都不能正确的复制。

void foo(parm_list, ...);void foo(...);一般采用第一种形式,函数会在前面的参数中获取后面省略参数的信息。

15. 返回值不是void的类型必须返回一个值,特例:运行main函数没有返回值就可以结束。
为使返回值独立于机器,cstdlib头文件定了两个预处理变量:
EXIT_FAILURE;   EXIT_SUCCESS;

16. 确保返回引用安全的一个好方法是,确保指向在调用之前就存在的对象。
返回的引用函数返回一个左值,可用在任何返回左值的地方。
char& get(string &str, sring::size_type ix);int main(){    .....     get(s, 0) = 'A';  //正确,get返回一个左值。}

    如果不希望返回值被修改,返回值应该声明为const。

17. 递归
//阶乘的计算// 1*2*3*...*valint facctorial(int val){    if(val > 1)        return factorial(val-1) * val;    return 1;}

//求最大公约数int rgcd(int v1, int v2){    if(v2 != 0)    {        return rgcd(v2, v1%v2);    }    return v1;}

18. 一个函数只能定义一次,但是可以声明多次。
函数原型: 函数的返回类型,函数名 和 形参列表组成。

19. 默认实参:
如果有一个形参具有默认实参,那么,它后面的所有的形参都必须有默认实参。
默认实参只能用来替换函数调用缺少的尾部实参。
string screenInit(string::size_type height = 24, string::size_type width = 80, char background = ' ');string screen;screen = screenInit();screen = screenInit(66);screen = screenInit(66, 256);screen = screenInit(66, 256, '#');screen = screenInit(, , '?'); //error 
设计带有默认实参的函数,其中部分工作就是排列形参,使最少使用默认实参的形参排在最前,最可能使用默认实参的形参排在最后。
在一个文件中,只能为一个形参指定默认实参一次。通常,应在函数声明中指定默认实参,并将该声明放在合适的头文件中。

20. 自动对象: 在函数调用时创建和撤销。
    局部对象:对于未初始化的内置类型的局部变量,其初值不确定。
    静态局部对象:位于函数的作用域,但生命周期却跨越了这个函数的多次调用。第一次调用时初始化,在程序结束前都不会撤销。

21. 内联函数 inline ,内联函数避免函数调用的开销。
//调用函数需要做的工作//调用前需要先保存寄存器,并在返回时恢复;//复制实参//程序还需转向一个新位置执行。
内联函数适用与优化小的、只有几行的而且经常被调用的函数。

内联函数应该在头文件定义,因为内联函数的定义对编译器而言必须是可见的,以便编译器能够在调用点内联展开该函数的代码。

22. 类的成员函数:函数原型必须在类中定义,但是函数体则既可以在类中,也可以在类外定义。
    编译器隐式地将在类内定义的成员函数当作内联函数。
    每个成员函数(除了static成员函数外)都有一个额外的、隐含的形参this。形参this初始化为调用函数的对象的地址。
bool Sales_item::same_isbn(const Sales_item &rhs) const     //const表示函数为常量成员函数,//不能修改isbn的值,因为const成员函数不能修改调用该函数的对象 {return isbn == rhs.isbn;}
等价于:return this->isbn == rhs.isbn;

如果函数被声明为const成员函数,那么函数定义时形参表后面也必须有const。

23. 构造函数:构造函数和类同名,而且没有返回值。
    构造函数通常应确保其每个数据成员都完成了初始化。
构造函数的初始化列表,以冒号开头,多个成员的初始化以逗号分隔Public:Sales_item()::units_sold(0), revenue(0.0) {}//构造函数是放在Public部分的,如果放置Private,则不能定义Sales_item对象,这样的话,这个类就没什么用了。

24. 合成默认构造函数一般适用于仅包含类类型成员的类。而对于含有内置类型或复合类型成员的类,则通常定义自己的默认构造函数来初始化这些成员。
变量初始化:1. 类类型:调用默认构造函数初始化;2. 内置类型成员:依赖于对象如何定义。如果对象是全局对象或静态对象,则这些成员将全部初始化为0;若对象在局部作用域定义,则这些成员没有初始化。

25. 函数重载

出现在相同作用域里面,若有相同的名字,而形参表不同,则称为重载函数。

任何程序都有一个main函数的实例。main函数不能重载。

函数重载:函数名相同,参数不同。(返回值是否相同没关系)函数重复声明:若两函数形参表和返回类型完全一致,则将第二个函数声明视为第一个的重复声明;              若两函数形参表相同,但返回类型不相同,则第二个声明是错误的。//此也无法成为函数重载
Record lookup(Phone);Record lookup(const Phone); //此为函数重新声明,不是函数重载。
Record lookup(Phone&);Record lookup(const Phone&); //此为函数重载,两个函数不一样。//如上,两个改为指针类型也是不同的两个函数。原因:如果是非引用或非指针类型,const和非const类型的形参,函数操纵的至少副本,复制形参时并不考虑形参是否为const,既可将const形参复制为非const形参,也可以复制为const形参。

26. 重载与作用域

如果局部的声明一个函数,则函数将屏蔽而不是重载在外层作用域中声明的同名函数。

所以,每一个版本的重载函数都应在同一个作用域中声明。

void print(const string&);void print(double);void fooBar(int ival){    void print(int);    print("Value:");  // error    print(ival);    //ok    print(3.14);  //ok,调用的是内部的print(int)}内部的print屏蔽了外面的print函数。若print(int)定义在外面,与其他的在同一作用域,则三个调用分别调用三个函数。
调用print时,编译器首先检索这个名字的声明,一旦找到名字,就不会再继续检查这个名字是否在外层作用域中存在。

C++中,名字查找发生在类型检查之前。


27. 重载确定的3个步骤:
    (1). 候选函数:调用处可见且函数名相同的函数集合。
    (2). 选择可用函数:参数个数一样,类型符合或者能转换。
    (3). 寻找最佳匹配。

28. 二义性:存在多个与实参匹配的函数,但是没有一个是明显最佳选择,此情况是错误的。
虽然可以使用强制转换来强制实现函数匹配,但是实际上遇到这种情况,意味着函数的声明设计的形参不合理。
f(static_cast<double>(42), 2.56);  //强制匹配f(double, double);f(42, static_cast<int>(2.56));  // 强制匹配f(int ,int);

29. 实参类型转换
//对任意整型的实参值,int型版本都是优于short型版本的较佳匹配void ff(int);void ff(short);ff('a');//a提升为int型,调用ff(int)函数。//对于char型实参来说,有int型形参的函数是由于有double型形参的函数的较佳匹配//从char型到unsigned char型的转换的优先级并不比从char型到double型的转换高。

extern void manip(long);extern void manip(float);manip(3.14); //error, 有二义性//3.14既可以转换为long型也可以转为float型

30. 参数匹配和枚举类型
enum Tokens {INLINE = 128, VIRTUAL = 129};void ff(Tokens);void ff(int);int main(){    Tokens curTok = INLINE;    //无法将整型值传递给枚举类型的形参    ff(128); //ff(int)    ff(INLINE);   //ff(Tokens)    ff(curTok);    //ff(Tokens)    //枚举类型,只能用同一枚举类型的另一个对象或一个枚举成员进行初始化    return 0;}

void newf(unsigned char);void newf(int);unsigned char uc = 129;newf(VIRTUAL); //call ff(int)newf(uc);//call ff(unsigned char)//可以将枚举类型传递给整型形参。虽然129可以用unsigned char表示,但是此处还是会提升为int类型。//枚举类型具体会提升为什么类型,要根据枚举值和机器来决定。

31. 重载和const形参
仅当形参是引用或指针时,形参是否为const才会有影响。
//不能基于指针本身是否为const来实现函数的重载:f(int *);f(int *const); //这两个函数不是重载。const是用于修饰指针本身,而不是修饰指针所指向的类型。

32. 函数类型: 由返回类型 以及 形参表确定,与函数名无关。
bool (*pf)(const string &, const string &);//pf两侧的圆括号是必需的//用typedef简化函数指针的定义typedef bool (*cmpFcn)(const string &, const string &);

33. 在引用函数名但又没有调用该函数时,函数名将被自动解释为指向函数的指针。
bool lengthCompare(const string &, const string &);cmpFcn pf1 = 0;  //0表示该指针不指向任何函数cmpFcn pf2 = lengthCompare;pf1 = lengthCompare;pf2 = pf1;//直接引用函数名等效于在函数名上应用取地址操作符cmpFcn pf1 = lenthCompare;cmpFcn pf2 = &lengthCompare;//函数指针只能通过同类型的函数或函数指针或0值常量表达式进行初始化或赋值。

34. 指向不同函数类型的指针之间不存在转换
string::size_type sumLength(const string &, const string &);bool cstringCompare(char *, char *);cmpFun pf;pf = sumLength; //error,返回类型不一样pf = cstringCompare; //error, 形参类型不一样pf = lengthCompare; //OK

35. 通过指针调用函数
cmpFun pf = lengthCompare;lengthCompare("hi", "bye");pf("hi", "bye");(*pf)("hi", "bye");//上面3个调用都是同等的。//只有当指针初始化了,才能安全的调用函数。未初始化或者0指针不能调用

36. 函数指针形参
void useBigger(const string&, const string&, bool(const string&, const string&)); //第3个参数void useBigger(const string&, const string &, bool (*)(const string&, const string&));

37. 返回指向函数的指针
//如下两个是等效的int (*ff(int))  (int*, int);   //他的返回类型为int (*)(int*, int)等效于:typedef int (*PF) (int*, int);PF ff(int);

38.具有函数类型的形参所对应的实参将被自动转换为指向相应函数类型的指针。而返回的是函数时,同样的转换操作则无法实现。
所以,允许形参定义为函数类型,但是返回类型必须是指向函数的指针,而不能是函数。
typedef int func(int*, int);void f1(func);  //okfunc f2(int);  //errorfunc *f3(int);  //ok

39. 指向重载函数的指针: 指针的类型必须与重载函数的一个版本精确匹配
extern void ff(vector<double>);extern void ff(unsigned int);void (*pf1)(unsigned int) = &ff; //ok,ff(unsigned)void (*pf2)(int) == &ff; //error,没有int的精确匹配double (*pf3)(vector<double>);pf3 = &ff; //error,返回类型不一样

0 0
原创粉丝点击