1.函数基础:
调用函数:
函数的调用完成两项工作:一是用实参函数对应的形参,二是将控制权2转移给被调用函数。此时,主调函数(calling function)的执行被暂时中断,被调函数(called function)开始执行。
形参和实参:
尽管实参和形参存在对应的关系,但是并没有规定实参的求值顺序。编译器能以任意可行的顺序对实参求值。要类型匹配,数量一致(不绝对)。
局部对象:
形参和函数体内部定义的变量统称为局部变量。它们对函数而言是“ 局部 ”的,仅在函数的作用域内可见,同时局部变量还会隐藏(hide)在外部作用域中同名的其他所有声明(在此作用域中无法访问到外部的同名变量)。在所有函数体之外定义的对象存在于整执行的过程中。局部变量的生命周期依赖于定义的方式。
自动对象:
我们把只存在于块执行期间的对象称为自动对象(automatic object),当块的执行结束后,其中创建的自动对象的值就变成未定义的了。
局部静态对象:
某些时候,有必要将局部变量的生命周期贯穿函数调用及之后的时间。可以将局部变量定义成 static 类型从而获得这样的对象。局部静态对象(local static object)在程序的执行路径第一次经过对象定义语句是初始化,并且直到程序终止才被销毁,在此期间即使对象所在的函数执行结束也不会对它有影响。重新调用函数,不在对局部静态变量重新初始化,保留之前的值。
函数声明:
定义函数的源文件应该把含有函数声明的头文件包含进来,编译器负责验证函数的定义和声明是否匹配。
2.参数传递:
和其他变量一样,形参的类型决定了形参和实参交互的方式。当形参是引用类型时,我们说它对应的实参被引用传递(passed by reference)或者函数被传引用调用(called by reference)。和其他引用一样,引用形参也是它绑定对象的别名:也就是说,引用形参是它对应的实参的别名。 当实参的值被拷贝给形参时,形参和实参是两个相互独立的对象。我们说这样的实参被值传递(passed by value)或者函数被传值调用(called by value)。
熟悉C语音的程序员经常使用指针类型的形参访问函数外部的对象。在C++中,建议使用引用类型的形参代替指针。
拷贝大的类类型对象或者容器对象比较低效,甚至有的类类型(包括IO类型在内)根本就不支持拷贝操作。当某种类型不支持拷贝操作时,函数只能通过引用形参访问该类型的对象。并且拷贝可能会带来巨大的内存开销。
const形参和实参:
和其他初始化过程一样,当使用实参初始化形参时会忽略顶层const。换句话说,形参的顶层const被忽略掉了。当形参有顶层const时,传给它常量对象或者非常量对象都是可以的,或者说,形参的顶层const属性只是用来限制形参,即不能通过形参来改变传入的拷贝值或者实参本身(当引用或者指针时),对于实参是否具有顶层const属性没要求。
我们可以用非常量初始化 一个底层const对象,但是反过来不行;同时一个普通的引用必须使用同类型的对象初始化,例如:不能用一个具有const属性的对象初始化一个普通引用、不能用字面值初始化一个非常量引用,初始化常量引用可以。
把函数不会改变的形参定义成(普通的)引用是一种常见的错误,这么做会带给函数的调用者一种误导,即函数可以修改它的实参的值。此外,使用引用而非常量引用也会极大的限制函数所能接受的实参类型,例如:我们不能把const对象,字面值或者需要类型转换的对象传递给普通的引用形参。
数组形参:
数组的两个特殊性质:不允许拷贝数组以及使用数组时(通常)会将其转换成指针。这两个特殊性质对于定义和使用作用在数组上的函数有影响。因为不能拷贝数组,所以我们无法以值传递的方式使用数组参数。当我们为函数传递一个数组时,实际上传递的是指向数组首元素的指针。尽管不能以值传递的方式传递数组,但是我们可以把形参写成类似数组的形式。
数组引用参数:
C++语音允许将变量定义成数组的引用,基于同样的道理,形参也可以是数组的引用。此时,引用形参绑定到对应的实参上,也就是绑定到数组上。因为数组的大小是构成数组的一部分,所以只要不超过维度,在函数体内就可以放心的使用,但是者也具有一定的限制作用,我们只能将函数作用于定义大小的数组。
含有可变形参的函数:
为了编写能处理不同数量实参的函数,C++11新标准提供了两种主要的方法:如果所有的实参类型相同,可以传递一个名为initializer_list的标准库类型;如果实参的类型不同,我们可以编写一种特殊的函数,也就是所谓的可变参数模版。
initializer_list对象中的元素永远是常量值,我们无法改变initializer_list对象中的元素。所以拷贝或者赋值一个initializer_list对象不会拷贝列表中的元素(可能是因为没有必要);拷贝后,原始列表和副本共享元素。
省略符形参:
省略符形参是为了便于C++程序访问某些特殊的C代码而设置的,通常省略符形参不应用于其他目的。(不太理解它的产生与作用,说是为了访问某些特殊的C代码而设置的)省略符形参只能出现在形参列表的最后一个位置。
3.返回类型和return语句:
return语句终止当前正在执行的函数并将控制权返回到调用该函数的地方。有两种形式:1.return;2.return expression;
无返回值函数:
没有返回值的return语句只能用在返回类型是void的函数中。返回void语句的函数不要求非得有return语句,因为在这类函数的最后面会隐式地执行return。
一个返回类型是void的函数也能用return语句的第二种形式,不过此时return语句的expression 必须是另一个返回void 的函数。强行令void函数返回其他类型的表达式将产生编译错误。
有返回值的函数:
只要函数的返回类型都不是void ,则该函数内的每条return 语句都必须返回一个值。return语句返回值的类型必须与函数的返回类型相同,或者能隐式地转换成函数的返回类型。
warning:在含有return 语句的循环后面应该也有一条return 语句,如果没有的话该程序就是错误的,很多编译器都无法发现此类错误。
返回一个值的方式和初始化一个变量或形参的方式是完全一样的:返回的值用于初始化调用点的一个临时量,该临时量就是函数调用的结果。同其他引用一样,如果函数返回引用,则该引用仅是它所引对象的一个别名。
不要返回局部对象的引用或指针:
函数完成后,它所占用的存储空间也随之被释放掉。因此,函数终止意味着局部变量的引用将指向不在有效的内存区域。所有返回局部变量的引用是错误的,同样返回局部变量的指针也是错误的。
悬挂引用:
指程序中引用了已经被销毁或者不再有效的对象。
引用返回左值:
如果返回类型是常量引用,我们不能给调用的结果赋值。
列表初始化返回值:
C++11新标准规定,函数可以返回花括号包围起来的值的列表。类似与其他返回结果,此处的列表也用来对表示函数返回的临时量进行初始化。如果列表为空,临时来那个执行值初始化;否则,返回的值由函数的返回类型决定。如果函数返回的是内置类型,则花括号包围的列表最多包含一个值,而且该值所占空间不应该大于目标类型的空间。如果函数返回的是类类型,由类本身定义初始化值如何使用。
主函数main的返回值:
我们允许main函数没有return语句直接结束。如果控制到达了main函数的结尾处而且没有return 语句,编译器将隐式的插入一条返回 0 的return语句。main函数不能调用它自己。
返回数组指针:
因为数组不能被拷贝,所以函数不能返回数组,但是函数可以返回数组的的指针或引用。其中最直接的方法是使用类型别名。(声明一个返回数组指针的函数要注意格式)
使用位置返回类型:
位置返回类型跟在形参列表后面,并以一个" -> "符号开头,为了表示函数真正的返回类型跟在形参列表之后,我们在本应该出现返回类型的地方放置一个auto。
函数重载:
如果同一作用域内的几个函数名字相同但是形参列表不同,我们称之为重载(overloaded)函数。当调用这些函数时,编译器会根据传递的实参类型推断想要的是哪个函数,main函数不能重载。
不允许两个函数除了返回类型外其他所有的要素都相同。假设有两个函数,它们的形参列表是一样的但是返回类型不同,则第二个函数的声明是错误的。
在函数声明中,形参的名字仅仅起到帮助记忆的作用,有没有它并不影响形参列表的内容。
顶层const不影响传入函数的对象。一个拥有顶层const的形参无法和另一个没有顶层const的形参区分开来。当我们传递一个非常量对象或者指向非常量对象的指针时,编译器会优先选用非常量版本的函数。
使用const_cast可以使当它的实参不是常量时,得到的结果是一个普通的引用。
4.函数重载:
重载与作用域:
一般来说,将函数声明置于局部作用域内不是一个明智的选择,它将隐藏外层作用域中声明的同名实体,在不同的作用域中无法重载函数名。
5.特殊用途语言特性:
默认实参:
某些函数有这样一种形参,在函数的很多次调用中它们都被赋予一个相同的值,此时我们把这个反复出现的值称为函数的默认实参。我们可以为一个或多个形参定义默认值,不过,一旦某个形参被赋予了默认值,它后面的所有形参都必须有默认值。默认实参负责填补函数调用缺少的尾部实参。当设计含有默认实参的函数时,其中一样任务是合理设置形参的顺序,尽量让不怎么使用默认值的形参出现在前面。在给定的作用域中一个形参只能被赋予一次默认实参。换句话说,函数的后续声明只能为之前的那些没有默认值的形参添加默认值,而且该形参右侧的所有形参必须都有默认值。局部变量不能作为默认实参。除此之外,只要表达式的类型能转换成形参所需的类型该表达式就能作为默认实参。
内联函数和constexpr函数:
内联函数可避免函数调用时的开销;
constexpr函数是指能用于常量表达式的函数,定义要遵循几项规定:函数的返回类型及所有形参的类型必须都是字面值类型,而且函数体内必须有且仅有一条return 语句。constexpr函数不一定返回常量表达式。
对于某个给定的内联函数或者constexpr函数来说,它的多个定义必须完全一致,因为它们在编译时被处理,如果它们的定义不一致,编译器将无法确定使用哪个定义,从而导致编译错误。
调试帮助:
C++程序员又是会用到一种类似于头文件保护的技术,以便有选择地执行调试代码。基本思想是,程序可以包含一些用于调试的代码,但是这些代码只能在开发程序是使用。当应用程序编写完成准备发布时,要先屏蔽掉调试代码。这种方法用到两种预处理功能:assert 和NDEBUG。
assert预处理宏:
所谓预处理宏其实是一个预处理变量,它的行为有点类似于内联函数,如果表达式的求值结果为假,assert输出信息并终止程序的执行;如果表达式求值结果为真,assert什么也不做。
和预处理变量一样,宏名字在程序内必须唯一。含有cassert头文件的程序不能再定义名为assert的变量、函数或者其他实体。很多头文件都包含的cassert头文件。
NDEBUG预处理变量:
assert的行为依赖于一个名为NDEBUG的预处理变量的状态。如果定义了NDEBUG,则assert什么也不做。默认状态下没有定义NDEBUG,此时assert将执行运行时检查。仅能用于调试阶段。
6.函数匹配:
确定候选函数和可行函数:
函数匹配的第一步是选定本次调用对应的重载函数集,集合中的函数称为候选函数(candidate function)。候选函数有两个特征:一是与被调用的函数同名,二是其声明在调用点可见。
第二步是考察本次调用提供的实参,然后从候选函数中选出能被这组实参调用的函数,这些新选出的函数称为可行函数(viable function)。可行函数也有两个特征:一是其形参数量与本次调用提供的实参数量相等,二是每个实参的类型与对应的形参类型相同,或者能转换成形参的类型。如果没有找到可行函数,编译器将报告无匹配函数的错误。
寻找最佳匹配函数(如果有的话):
它的基本思想是,实参类型与形参类型越接近,它们匹配得越好。
调用重载函数是应该尽量的避免强制类型转换。如果在实际应用中确实需要强制类型转换,则说明我们设计的形参集合不合理。
实参类型转换:
我们应该知道小整型一般会提升到int 类型或者更大的整数类型。假设有两个函数,一个接受int ,一个接受short ,则当只有调用提供的是short 类型的值时才会选择short 版本的函数。有时候即使实参是一个很小的整型值,也会直接将它提升到int 类型;此时使用short版本的反而会导致类型转换。
7.函数指针:
函数指针指向的是函数而非对象。和其他指针一样,函数指针指向某种特定类型。函数的类型由它的返回类型和形参类型共同决定,与函数名无关。要想声明一个可以指向该函数的指针,只需要用指针替换函数名即可。
使用函数指针:
当我们把函数名作为一个值使用时,该函数自动的转化成指针。还可以直接使用指向函数的指针调用该函数,无需提前解引用指针。
在指向不同函数类型的指针间不存在转换规则。但是和往常一样,我们可以为函数指针赋一个nullptr 或者值为0 的整型常量表达式,表示该指针没有指向任何一个函数,但是类型已经确定。
重载函数的指针:
当我们使用重载函数时,上下文必须清晰地界定到底应该选用哪个函数。如果定义的指向重载函数的指针,编译器通过指针类型决定选用哪个函数,指针类型必须与重载函数中的某一个精确匹配。
函数指针形参:
和数组类似,虽然不能定义函数类型的形参,但是形参可以是指向函数的指针。此时,形参看起来是函数类型,实际上却是当成指针使用。需要注意的是,decltype返回函数类型,此时不会将函数类型自动转换成指针类型,因为decltype的结果是函数类型,所以只有在结果前面加上 * 才能得到指针。
返回指向函数的指针:
和数组类似,虽然不能返回一个函数,但是能返回指向函数的指针。然而我们必须将返回类型写成指针形式,编译器不会自动的将函数返回类型当成对应的指针类型处理。往常一样,要想声明一个返回指针类型的函数,最简单的办法是使用类型别名(using)。还可以使用位置返回类型的方式声明一个返回函数指针的函数。
接下来我会重点更新基本算法题目与理解,C++基础内容的更新暂缓。