C++ Primer笔记(六)函数

1、函数调用的结果类型就是函数返回值的类型,该运算的结果本身就是函数的返回值。

函数调用做了两件事:

1)用对应的实参初始化函数的形参,并将控制权转移给被调用函数。

2)主调函数的执行被挂起,被调函数开始执行。函数的运行以形参的(隐式)定义和初始化开始。

2、局部变量:

在函数体内定义的变量只在该函数中才可以访问。其只在函数运行时存在。

实参必须具有与形参类型相同、或者能隐式转换为形参类型的数据类型。

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

int *foo_bar( )   { ....  }  //函数返回一个int型指针,该指针可以指向数组中的一个元素。

在定义或声明函数时,没有显示指定返回类型是不合法的。


3、参数名是可选的,但在函数定义中,通常所有参数都要命名,参数必须在命名后才能使用。C++是一种静态强类型语言,对于每一次的函数调用,编译时都会检查其实参。

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

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


4、返回值为void类型的函数不能放在赋值表达式的右边。

形参表中的每一个形参都需要一个显式的类型。

如果函数确定需要返回一个结果,则语句return 表达式会计算表达式的值并把结果返回给调用者。


5、函数签名:函数原型的函数名和实参类型部分。并不指定函数的返回类型。

在同一作用域内的函数必须有不同的签名。

实参类型强制转换:把实参转换成函数原型中声明的形参类型。

枚举类型:是一组有标识符表示的整数常量,默认起始值是0且顺序增1,标识符必须是唯一的。

关键字auto和register可以用来声明自动存储类别的变量。只有函数中的局部变量可以是自动存储类别的。

6、声明于任何函数或者类之外的标识符具有全局名字空间作用域。对于从其声明处开始直到文件结束处为止出现的所有函数都是可访问的,如位于函数之外的全局变量、函数定义、函数原型等。

局部作用域:始于标识符的声明处,止于标识符声明所在语句块的结束右花括号处。

声明为static的局部变量具有局部作用域。


7、函数调用堆栈:用于支持函数调用/返回机制。堆栈也支持每个被调用函数的自动变量的创建、维护和销毁。

每个函数最终都必须把控制权返回给调用它的函数。大多数函数都有自动变量,自动变量在函数执行时应当存在。如果函数调用其他函数,它们需要处于活动状态。但是当被调用函数返回到调用者时,被调用函数的自动变量需要销毁。被调用函数的堆栈结构是保留被调用函数自动变量所用内存的理想地方。


8、inline限定符(在函数返回值之前)只适用于小的、经常使用的函数。 内联函数应该在头文件中定义。编译器隐式的将在类内定义的成员函数当做内联函数。

按值传递:传递实参的副本,对副本的修改不影响调用者中原始变量的值。

按引用传递:实际上引用的是调用函数中的原始变量,被调用函数可以直接修改原始变量。


9、在函数内引用作为别名。

int count = 1;    //引用变量必须在它们的声明中完成初始化,并且不能再指定为其他变量的别名。

int &cRef = count;

cRef++;   //除非是对常量的引用,否则引用必须是左值(如变量名),而不是常量或者返回右值的表达式(如计算结果)


10、引用被初始化后更改它的引用对象编译器不报错,因此是一个逻辑错误。

常量引用既避免了较大数值的赋值,又保证传递的数据不会被修改。

如:convert ( const  int &a);

从函数返回引用:当返回一个在被调用函数中声明的变量的引用时,这个变量应该在函数中声明为static。否则该引用指的是该引用在函数执行结束时被销毁的自动变量。造成虚悬引用。


11、默认实参:必须是形参列表中最靠右边的形参。不能同时在函数声明和函数头部指定默认实参,会报错。 默认实参应该在函数名第一次出现时指定,通常在原型中。

当全局变量和局部变量重名时,全局变量被屏蔽,::运算符可以访问被屏蔽的全局变量。


12、函数重载:定义多个具有相同名字的函数,只是形参不同。

常用于创建执行相似任务、但是作用于不同的数据类型的具有相同名字的函数。重载的函数可以有不同的返回类型,如果返回类型相同,那么它们必须具有不同的形参列表。创建具有相同形参列表和不同返回类型的重载函数会产生编译错误。


13、签名:由函数的名字和它的形参类型(按顺序)组成。

函数模版:形参列表中每个形参由typename或class开头。template<class T>;

类型形参的名字在模版形参列表中必须是唯一的。

static局部变量在函数返回到其调用者之后仍旧保留它的值。


14、调用函数时如果该函数使用非引用的、非const形参,则既可以给函数传递const实参也可以传递非const实参。

虽然函数的形参是const int类型的,但是编译器却将该形参定义为普通的int类型,在内部处理中const与非const是没有区别的。

编译器在编译时进行检查确保我们不会修改const类型的变量。


15、当传递的实参为大型对象时为了降低传值赋值实参的开销,应该将参数定义为传引用或传指针。

应该将不修改相应实参的形参定义为const引用,这不仅是为了防止在函数中不小心修改了实参,还有更深层的含义。

string::size_type find_char(string &s,char c)

{
string::size_type i=0;
while(i!=s.size()&&s[i]!=c)
{
i++;
}
return i;
}

如果将s定义为非const引用将会导致find_char("Hello World!!",'0');无法被调用。

虽然字符串可以经过调用string的单参构造函数执行隐式转换,但由于能不能将const引用类型的实参传递给非const引用类型的形参,上述调用将会失败。

bool is_sentence(const string &s)
{
return (find_char(s,'.')==s.size()-1)
}

该函数的调用仍然会发生错误!

综上:

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

注意:以上都是对const和非const引用,对于非引用类型处理方式是不同的。


16、通常函数中不应该有vector或是其他标准库容器类型的形参,调用普通的非引用vector形参的函数将会复制vector的每一个元素。应该将形参声明为引用类型。C++程序员更倾向于通过传递指向容器中需要处理的元素的迭代器来传递容器。


17、将数组传递给函数时数组名会被转换为指针。使用sizeof(array)/sizeof(array[0])不再能计算出数组的元素个数了。要避免这种转换可以通过引用传递数组。

int printArray(int (&array)[10]);

此时数组大小成为形参和实参类型的一部分,编译器会检查实参的大小是否与形参规定的数组元素个数相等。(&array)使用括号是必须的,下标操作符具有更高的优先级。不加时编译器会报错。


二维数组作为函数参数时形参的声明方式为:

1:int (matrix*)[10];  2:int matrix[ ][20];

无论哪种方式第二维的元素个数不能少。


二维数组也可以通过引用传递给函数。有两种方式可以确保函数对数组的操作不超出数组的边界。

1:将数组元素个数作为参数传递。

2:向函数传递数组的第一个和最后一个元素下一个位置的指针。



18、返回值为void类型的函数可以使用return;提前强制结束函数执行。

对于有返回值的函数函数内有if用来判断当条件满足时返回一个值,函数末尾如果没有返回值,大部分编译器无法检测这个错误。如
bool foo()
{
int i=0;
while(1)
{
cout<<"!!!!!!! "<<endl;
if(i==20)break;
if(i==300)return false;
i++;
}
//函数最终会执行到此,但是由于i==300不会被满足,此函数没有返回值。
}
运行时出现什么错误都是不确定的。

注意:不要返回局部对象的引用!


19、默认实参仅能用来替代函数调用缺少的尾部实参。如果一个形参具有默认实参那么它后面所有的形参都必须有默认实参。既可以在函数声明中也可以在函数定义中指定默认实参。但是一个文件中只能为一个形参指定默认实参一次。如
//hh.h
int ff(int =0)
//hh.cpp
#include"hh.h"
int ff(int i=0)
{
}
此句是错误的。在h文件中已经指定默认实参,函数定义中就不可以再指定。通常应在函数声明中指定默认实参,并将声明放在合适的头文件中。如果仅仅在函数定义的形参列表提供默认实参,那么只有包含该函数定义的源文件中调用该函数默认实参才是有效地。



20、const成员函数中形参表后的const改变了隐含的this指针的类型。
隐含的this指针将是一个指向const对象的指针。

用这种方式使用const的函数被称为const成员函数。此类成员函数不能修改调用该函数的对象的成员变量。

注意:const对象,指向const对象的指针或引用只能用于调用const成员函数,之所以这样规定是因为它们调用一般成员函数时可能会修改对象的内容。



21、对于没有显式为类提供任何构造函数的类,编译器自动为这个类生成默认构造函数。它被称为合成构造函数。

对于类类型的成员它会调用该成员所属类的默认构造函数实现初始化。对于内置类型的成员变量将不会被初始化。

如果函数形参完全相同仅仅返回值不同,则第二个声明是错误的。不能仅仅基于不同的返回类型实现重载。
bool lookup(Phone p);
bool lookup(const Phone p);

注意以上函数完全等价,因为函数为传值,由于操作的只是实参的副本,函数无法修改实参,因此无论形参是const和非const效果是一样的。前面介绍过const和非const的差异是建立在传引用的基础之上的。


22、在函数中局部生命的名字会屏蔽在全局作用域内声明的同名名字。这样的规则对于函数名也同样适用。因为函数名也是标识符的一种。如果局部的声明一个函数,那么该函数将屏蔽而不是重载在外层作用域声明的同名函数。如
void print(const string&);
void print(double);
void foo(int ival)
{
void print(int);//函数声明
print("Hello world!!");//错误
print(39);//正确。
print(3.33);//正确,存在double向int的隐式转换。
}
函数内声明的void print(int);将屏蔽foo函数外的其他print声明。因此在foo函数内仅仅void print(int)是可见的。在调用print时编译器首先检索这个名字的声明,找到一个只有int类型形参的print函数的局部声明,一旦找到,编译器不再继续检查这个名字是否在外层作用域中存在。然后再检查这个名字是否有效。屏蔽发生在不同作用域的声明之间,而重载仅仅发生在相同作用域的声明之间,这一点要弄清楚。如:此时便不存在屏蔽的问题。
void print(const string&);
void print(double);
void print(int);//函数声明
void foo(int ival)
{
print("Hello world!!");//正确
print(39);//正确。
print(3.33);//正确,
}


23、函数指针与其他指针一样也指向某个特定的类型。其类型是由返回类型以及形参表确定,而与函数名无关。
如:bool(*pf)(const string&,const string &);

此语句将pf声明为指向函数的指针。(*pf)括号是必须的。

使用typedef可以简化函数指针的定义。如
typedef bool (*cmpFcn)(const string&,const string&);

该定义表示cmpFuc是一种指向函数的函数指针。该类型为指向返回值bool类型,带有两个const string 引用形参的函数指针。要使用时只需 cmpFunc pfFunc即可。不必每次都把整个类型声明全部写出来。可以使用函数名或NULL对函数指针进行初始化。直接引用函数名与在函数名取地址符等价,如;
cmpFunc pf1=lengthCompare;
cmpFunc pf2=&lengthCompare;

它们是等价的。


24、函数指针只能通过同类型的函数指针或是NULL来进行初始化。不同类型的函数指针可以通过强制类型转换,转换为同一类型,然后再赋值。

函数指针可以作为函数的形参。它可以有两种形式:
1)void fun(const string &,const string&,
                                      bool (const string&,int ));

2)void funI(const string &,const string &,
                                      bool(*)(const string &,int));

可以返回指向函数的指针。如:

int ( *ff ( int ) (int *,int);

它等价于

typedef int (*func) (int *,int);

func ff(int);

ff(int)声明一个函数,它带有一个int类型的形参,该函数返回值类型为:int(*)(int*,int);


25、具有函数类型的形参所对应的实参将被自动转换为指向相应函数类型的指针,但是当返回的是函数时,同样的转换操作则无法实现。如:

typedef int func(int *,int);

void fff(func);//正确,函数类型被转换为函数类型的指针。

func fff1(int);//错误,返回的是函数类型,而不是指针类型。

unc *fff2(int );//正确,返回的是函数指针。






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值