函数
- 函数基础
- 函数表示的是一个命名的代码块,可以通过函数来调用相应的代码,
C++
允许函数重载,也就是一个函数名称可以有多个同名函数; - 函数通常由:返回值,函数名字,多个或者形参组成;
- 函数执行:初始化形参,
return
语句执行完成的功能:返回表达式的值,同时将控制权从被掉函数返回给主调函数; - 函数的实参类型必须和形参类型相匹配,按照形参的定义顺序进行初始化;
- 函数的形参是不允许重名的;
- 在
C++
语言中名字具有作用域,对象具有生命周期,名字的作用域是程序文本的一部分,名字在程序中可见,对象的生命周期是程序执行过程中该对象存在的一段时间; - 函数提本身就是一个语句块,作用的范围,形参和函数体内部定义的变量统称为局部变量,函数内部可见,函数外部隐藏;
- 生命周期之存在与块执行期间的对象称为自动对象.在块域结束的时候,自动对象就会被销毁;
- 局部静态对象:可以使用
static
来定义对象,用于使局部变量在函数执行完之后,仍然可以使用,局部对象在程序执行路径第一次经过对象定义语句初始化,直到程序终止时被销毁; - 函数声明
- 函数值能够定义一次,但是可以声明多次;
- 函数声明是不需要函数体的,只需要使用
;
进行代替就可以; - 函数接口三要素:返回类型,函数名,参数类型;
- 函数的声明应该包含在一个头文件里面;
C++
语言支持函数的分离式编译,分离式编译,就是每个文件独立的进行编译;
- 函数表示的是一个命名的代码块,可以通过函数来调用相应的代码,
- 参数传递
- 再进行参数传递时,如果形参类型是引用类型,那么将绑定对应的实参,如果不是将复制实参给形参,前者称为引用传递,后者称传值调用;
- 指针形参:指针形参的行为和非引用类型的一致,当执行指针拷贝操作时,但是并不会改向指针所指向的值;
- 引用形参:通过引用形参可以更改一个或者多个实参的值;
- 通过使用引用来避免拷贝大的类类型对象或者容器,这种情况下,可以通过引用形参来访问类类型对象;
- 如果函数不需要改变引用形参的值,最好声明为常量引用;
- 引用可以支持返回多个值,可以通过声明需要的返回的引用变量,然后修改值之后,该变量的值就会被改变;
const
和实参与形参:
- 使用实参初始化形参时,可以自动忽略掉顶层
const
,也就是说传递给具有形参变量具有顶层const
,可以是常量对象或者是非常量对象;在C++
里面,void func(const int i)与
void func(int i)`是具有重定义的错误,因为第一个会被自动进行忽略;
- 使用实参初始化形参时,可以自动忽略掉顶层
- 指针或者引用形参与
const
- 可以使用非常量对象初始化一个底层的
const
对象,同样的对于引用的初始化,必须使用同类型的进行引用的初始化; - 在不需要改变形参所传入的值时,尽量使用常量引用,但是不可以使用常量引用来初始化非常量引用;但是反过来是可以的;
- 常量引用允许接受
const
对象,字面值,但是非常量引用是不可以的;
- 可以使用非常量对象初始化一个底层的
- 数组形参
- 数组具有两个特殊的性质:
- 数组不允许直接进行拷贝;
- 数组再进行参数传递时,通常会被转换成指针,这种是隐式转换;
- 数组传递参数时,如果不希望改变就是
void print(const int [])
,通常[]
里面是可以指定维度的,但通常不按照这样进行操作; - 在使用数组进行参数传递时,建议使用
void print(const int *beg,const int *end)
,使用首尾指针的方式进行参数传递;也可以传递数组大小; - 当不需要改变数组里面的值时,就应该声明为常量指针
- 数组引用的声明
int (&arr)[10]
,表示含有10个元素的数组的引用; - 多维数组传递参数
void print(int (*matrix)[10],int RowSize)
,含有10个整数的数组的指针;
- 数组具有两个特殊的性质:
可变形参的参数的传递
- 所有的实参的类型相同,使用
initializer_list
来进行参数传递,是一种模板类型,比如initializer_list<T> list(a,b,c,...)
,在函数形参里面的声明
void error_message<initializer_list<string> il)
,再进行调用时error_message({"message","expected","lixun"})
,来进行参数的传递; list
里面的值是常量值,使不能够进行修改的;
#include<iostream> using namespace std; using std::initializer_list; void init(initializer_list<int> lk){ for(auto h:lk) cout << h << " "; cout << endl; } int main(){ init({10,20,30,4,5,6,7,8,9,0}); }
- 所有的实参的类型相同,使用
返回值
- 不可以返回局部对象的指针或者是引用,因为函数结束时,局部引用或者指针都会被释放;编译器,一般不会提示这个错误,但是这样返回已经不安全;
C++11
标准支持返回{}
包含的列表值,列表中也是需要按照函数返回值类型进行初始化的;return
语句的返回值必须和函数的返回值类型相同,或者能够被隐式的进行转换;- 在含有
return
的循环语句的结尾,应该包含一条return
语句; - 函数返回值的过程:返回值是初始化一个临时变量的过程,然后将这个临时变量进行返回;
- 调用一个返回引用的函数的到的结果是左值,其他返回类型都是右值;
- 左值可以出现在函数的左边或者右边,但是右值只能够出现在函数的右边;
- 右值使用的是这个值的本身而不是这个值代表的含义;
C++11
标准允许使用列表初始化返回值return {"Functionx","expected"};
main
函数是不能够调用自己的;- 数组进行返回
- 如果需要改变数组的值,并且进行返回,那么建议使用类型引用;
- 数组指针类型函数的声明形式:
type *(function_list)[dimension]
,例如int (*func(int i))[10]
,从里面到外面依次进行分析:func(int i)
,表示的含
义是:接受整形参数,*func(int i)
,表示的含义是允许对返回值结果执行解引用操作,(*func(int i)[10])
,表示进行解引用的结果的是一个数组,
int (*func(int i)[10])
,表示进行解引用得到的结果是整形; C++11
支持尾置返回类型来处理,例如auto func(int i)->int(*)[10]
,表示的含义是函数func
返回值是一个指向十个整型的数组指针;
- 函数重载
overload
- 函数重载表示函数名相同,但是参数的类型(类型和数目)不同,这是函数重载的前提;
- 顶层
cosnt
是不能够用于函数重载的判断的,底层cosnt
使可以用于函数重载的判断的;
- 是否拥有顶层
const
不能够作为函数重载时,类型是否相同的区分标准; - 但是如果函数的形参是
某种类型的指针或者是引用
,通过判断所指向的常量对象或者是非常量对象可以用来区分函数重载;
- 是否拥有顶层
- 函数的返回值不同是不能够进行重载的,同样的参数类型前面有
const
并不表示函数的参数类型是不同的; main
函数是不能够进行重载的;const_cast
:
- 函数的形参喊返回值是常量,但是洗碗希望得到的返回值不是常量时,可以使用
const_cast
来进行类型转换;
- 函数的形参喊返回值是常量,但是洗碗希望得到的返回值不是常量时,可以使用
- 函数重载的过程,就是函数匹配的过程,可能出现最佳匹配,无匹配,出现多个函数匹配的错误;
- 在
C++
里面名字查找发生在类型检查之前; - 函数重载和作用域
- 在内层作用域内声明的名字,将隐藏外层作用域声明的同名实体,在不同的作用域内,是不能够进行函数重载的;
- 在同名实体的调用时,优先查找当前作用域,如果当前作用域可用就会忽略外层作用域的同名实体;
特殊的语言特性:
- 默认实参:
- 如果函数的参数在多次调用时,都是同一个值,那么就建议赋值为默认实参,在调用时,可以使用这个实参,也可以重新进行复制;
- 当某个函数有多个形参时,某个形参被赋予了默认值,那么他后面的所有形参都必须有初始值;默认实参负责填充函数调用缺少的尾部实参;
- 在给定的作用域里面,不能够修改已经存在的默认实参的值;
- 函数的实参应该在函数的声明里面进行指定,并且应该放在头文件里面;
- 在函数里面声明的局部变量是不能够作为函数默认实参的;
- 内联函数
- 使用
inline
来使函数变为内联,而不是函数调用,避免函数调用的开销; - 内联一般用于规模较小,调用频繁的函数;
constexpr
函数- 用于常量表达式的函数,需要注意的是函数返回值,形参必须是字面值常量,并且必须有且只有一条
return
语句;constexpr
函数会被生成为内联函数; constexpr
函数不一定非要返回常量表达式;
- 关于程序调试
- 两个预处理选项:
assert(expr)
是一种预处理宏,如果expr
表达式的值为0
,输出错误信息,并且结束程序的执行,如果为真,就什么也不做;
assert
定义在cassert
里面,预处理名字由预处理管理器进行管理,因此不需要提供命名空间的说明;NDEBUG
,assert
依赖于NDEBUG
变量,在进行程序的变异时,使用NDEBUG
选项来告诉编译器进行调试;
- 两个预处理选项:
- 函数匹配的过程
- 函数在重载的过程中,由于类型的转换,会出现函数重载调用的不确定性;
- 候选函数:1,与被调用函数同名,2,声明在可调用点;
- 可行函数:1.形参数量和调用提供的实参数量相等,1,实参的类型和形参的类型相同,或者能够进行转换;
- 如果函数包含默认实参,那么实际传入的参数数量是少于形参数量的;
- 寻找最佳匹配:1.实参和形参越接近,匹配的就越好,精度保留越好匹配越好;
- 多个形参匹配的最佳条件:
- 1.这个函数每个实参的匹配,都不低于其他可行函数需要的匹配,
- 2.至少有一个实参的匹配优于其他可行函数提供的匹配,
- 3.当按照上述规则出线两个以上的最佳匹配时,会因为存在二义性,而出现错误;
- 实现精确匹配的原则:
- 1.实参类型和形参类型相同;
- 2.实参从数组类型或者函数类型转换成对应的指针类型,
- 3.向实参添加顶层
const
,或者从实参中删除顶层const
, - 4.通过算数类型转换或者指针类型实现的匹配;
- 5.通过类类型转换实现的匹配;
- 函数在重载的过程中,由于类型的转换,会出现函数重载调用的不确定性;
- 函数指针
- 函数指针指向的是函数,函数的类型有函数的形参和返回值共同决定;例如:
bool lengthCompare(const string& s1,const string& s2)
,声明为函数指针
bool (*pf)(const string&s1, const string& s2)
,实际上函数名就是函数指针; - 在指向不同类型的指针之间是不存在转换的;
- 重载函数的指针:指针类型必须和重载函数中的某一个精确匹配;
- 函数名在作为另一个函数的实参时,使可以自动被转换为指针类型的;
- 函数指针指向的是函数,函数的类型有函数的形参和返回值共同决定;例如: