这篇笔记的内容是函数。
引入
一、函数基础
1.基本知识
1)编写与调用
2)形参与实参
关于形参实参可参考:形参与实参的区别_百度知道
关于形参实参最精髓的一条就是:形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。因此,形参只在函数内部有效。函数调用结束返回主调用函数后则不能再使用该形参变量。
函数的形参怎么写
这一点是容易疏忽的。
2.局部对象
关键词:作用域、生命周期、局部变量
1)自动对象
2)局部静态对象
实测,静态局部static变量确实是不会被销毁的。测试代码如下:
size_t cout_calls()
{
static size_t ctr = 0;
return ++ctr;
}
int main()
{
for(int i=0;i<4;i++)
cout<<cout_calls()<<endl;//依次输出1、2、3、4
}
3.函数声明
简单理解就是头文件里为声明,源文件里为定义。关于声明与定义可以看看学习笔记1:C++Primer学习笔记(1)_qq_42987967的博客-CSDN博客
4.分离式编译
这一块的内容可太深了,涉及到了编译原理,再往深里想还能想到设计模式中的高内聚低耦合:怎么样能以最小的工作量进行修改。
二、参数传递
C++一般分为引用传递、指针传递、值传递 ,其他语言也类似,如C#包含了值传递以及ref和out关键字,可参考:C# - 函数参数的传递 - DonLiang - 博客园
1.指针形参
2.引用参数
小结一下,使用引用参数即会真正地将传入的实参进行运算的,并且最后的实参值是会变的。
下面讲讲使用引用参数的好处。
1)可避免拷贝
使用引用可以减少拷贝消耗,如果不使用引用的话,对比较大的变量需要整块都拷贝,非常浪费。
2)可以返回额外信息
3.const形参与实参
1)指针或引用形参与const
大致解释一下,就是形参不为const的话,那么无法传入const类型的实参的;但是形参为const的话,是可以传入不为const的实参的。例子如下:
int i = 0;
const int ci = i;
void reset(int *p);
void reset(int& r);
void reset1(const int& r);
void main()
{
reset(ci);//报错
reset(&ci);//报错
reset(i);//不报错
reset(&i);//不报错
reset1(ci);//不报错
reset1(i);//不报错
}
2)尽量使用常量引用
4.数组形参
1)使用标记指定数组长度
这个主要是适用于字符数组。
2)使用标准库规范
3)显式传递一个表示数组大小的形参
4)数组形参与const
5)数组引用形参
注意区分有括号
6)传递多维数组
同样,注意区分括号
5.main函数中的参数(处理命令行选项)
这个估计得用Linux进行C++编程会更清楚些,main函数是有输入参数的,如果是在Windows中可以通过命令行执行exe文件输入命令运行程序,输入的命令即为argv字符数组参数,而argc会根据输入argv个数自动生成。
关于这个,可参考:cmd中运行exe的简单命令_clp786080772的博客-CSDN博客_cmd运行exe文件命令
关于在cmd命令行中输入参数运行VS2017 - 程序员大本营
6.含可变形参的函数
这部分感觉就比较深了,在这里估计不会介绍的太详细,细节还是得看后面的16.4节课。
1)initializer_list形参
2)省略符形参
进一步可参考:省略符形参的使用_outsiderJT的博客-CSDN博客_省略符形参
三、返回类型和return语句
1.无返回值函数
关于void函数返回一个void函数举个例子:
void test() { cout << "test"<<endl; };
void test1()
{
return test();//其实还不如不写return
}
int main()
{
test1();//将打印一个test
return 0;
}
2.有返回值的函数
1)值如何返回
比较有趣的是返回引用的函数,之前也算是举过例子了,不过今天再回顾一下吧。
const int& Learn5Function::testconst(const int& r)
{
return r;
}
void main()
{
int i = 0;
const int& a = testconst(i);
i=2;
cout << a << endl;//引用a被绑到了i上了
}
2)不返回局部对象的引用或指针
3)返回类类型的函数和调用运算符
4)引用返回左值
把函数当成左值来用,这个看起来确实是有点奇怪,感觉了解一下就行,不太有用。
5)列表初始化返回值
6)主函数main的返回值
7)递归
有用的就最后一句,main函数不能调用它自己。
3.返回数组指针
这种是取别名的方法。
1)声明一个返回数组指针的函数
2)使用尾置返回类型
比较花里胡哨的写法
3)使用decltype
这也是一种比较复杂的写法了。
四、函数重载
主要记住两个点,一个是函数名字相同但形参列表不同,一个是main函数不能重载。
1.重载的相关概念
1)定义重载函数
2)判断两个形参类型是否相异
3)重载和const形参
这一块可以结合之前的笔记1:C++Primer学习笔记(1)_qq_42987967的博客-CSDN博客
比较有意思的是上面的最后一点,编译器会优先选用非常量版本的函数。
4)建议:何时不应该重载函数
函数的重载要结合实际情况来,看看到底是重新命名更好理解还是重载更好。
5)const_cast和重载
这一块没看太懂,有点被绕晕了。
6)调用重载的函数
大致是在告诉我们编译器是怎么处理重载函数的。
2.重载与作用域
五、特殊用途语言特性
1.默认实参
注意最后一点,是后面的形参必须有默认值,不是全部。
1)使用默认实参调用函数
值得注意的有两点,一个是只能省略尾部实参、一个是让不常用的形参放在前面,常用的尽量放在后面。
2)默认实参声明
3)默认实参初始值
2.内联函数和constexpr函数
1)内联函数可避免函数调用时的开销
内联函数一般都比较短。
2)constexpr函数
需要注意的是不能在头文件中定义一个constexpr变量,但是可以定义一个constexpr函数;在头文件中定义的const变量或constexpr函数仍然无法在源文件中去定义一个数组,会报this不能在常量表达式中使用,但是当这些变量或函数只定义在源文件当中则不会出问题。这个可能涉及到了编译原理了,当前能力有限还不能解释为什么
3)把内联函数和constexpr函数放在头文件内
3.调试帮助
1)assert预处理宏
2)NDEBUG预处理变量
关于这两个调试的方法因为不是很熟,平时用的也少,可参考:特殊用途语言特性(默认实参/内联函数/constexpr函数/assert预处理宏/NDEBUG预处理变量) - geloutingyu - 博客园
六、函数匹配
关键词:形参数量相等以及某些形参的类型可由其他类型转换得来。
1.候选函数、可行函数、函数匹配
1)确定候选函数和可行函数
关键是要理解什么是候选函数,什么是可行函数。
2)寻找最佳匹配(如果有的话)
在可行函数的基础上找到最佳匹配,其满足数据类型能不转换就不转换。
3)含多个形参的函数匹配
这个讲的就是没有最佳匹配的例子了,因为好几种可行函数都旗鼓相当,这时候就会报二义性的错。
2.实参类型转换
告诉我们几种转换的优先级。并且注意区分类型提升和算数类型转换。
1)需要类型提升和算数类型转换的匹配
2)函数匹配和const实参
七、函数指针
1.函数指针的声明
注意加上括号。
2.使用函数指针
上面最重要的一句话是:当把一个函数当值使用时,函数会自动转换为指针。
3.重载函数的指针
4.函数指针形参
把函数指针当成形参,关于函数指针形参怎么用,可参考:函数指针变量作为函数形参_LC的专栏-CSDN博客_函数指针作为形参
举例说明:(值得注意的是非函数指针形式也可做为形参的)
void a(int i=1){
cout<<i<<endl;
}
void b(int i=2){
cout<<i<<endl;
}
void d1(void fun(int i)){//非函数指针形式也可传递
fun(3);
}
void d2(void (*fun)(int i)){
fun(4);
}
int main() {
d1(a);
d2(b);
return 0;
}
再整整其他花活,考虑试试弄个函数指针数组(经过个人测试,需要声明成指针形式才可返回)。
void a(int i=1){
cout<<i<<endl;
}
void b(int i=2){
cout<<i<<endl;
}
using f=void(int);//一定要用别名,否则也报错
using F = void(*)(int);
int main() {
f* farry[2];//等价F farry[2],直接f farry[2]则报错
farry[0]=a;
farry[1]=b;
farry[0](1);
farry[1](2);
return 0;
}
5.返回指向函数的形参
即告诉我们咋样让一个函数返回一个函数指针,目前很少遇到这种问题。
建议要真遇到了返回函数指针的函数,尽量采用别名,要不然花里胡哨的把自己给整晕壳了都。
void a(int i=1){
cout<<i<<endl;
}
void b(int i=2){
cout<<i<<endl;
}
using f=void(int);//f=void(*)(int)
using F = void(*)(int);
f* c(void fun(int i)){//void (*fun)(int i)
return fun;
}
int main() {
auto function1=c(a);
function1(7);
F function2=c(b);
function2(9);
return 0;
}
p.s: 这一块好难,好容易绕晕,记得又多,这或许正是C++内容又臭有多的典例吧。不过大概得记得哪种表达式是什么意思,刚开始不要求能会写,但得该知道查哪一块的内容,做到看别人的代码能看懂。
6.将auto和decltype用于函数指针类型
7.课后题代码
这一节的几道课后题不错,代码如下:
## Exercise 6.54
```cpp
int func(int a, int b);
using pFunc1 = decltype(func) *;
typedef decltype(func) *pFunc2;
using pFunc3 = int (*)(int a, int b);
using pFunc4 = int(int a, int b);
typedef int(*pFunc5)(int a, int b);
using pFunc6 = decltype(func);
std::vector<pFunc1> vec1;
std::vector<pFunc2> vec2;
std::vector<pFunc3> vec3;
std::vector<pFunc4*> vec4;
std::vector<pFunc5> vec5;
std::vector<pFunc6*> vec6;
```
## Exercise 6.55
```cpp
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
int multiply(int a, int b) { return a * b; }
int divide(int a, int b) { return b != 0 ? a / b : 0; }
```
## Exercise 6.56
```cpp
std::vector<decltype(func) *> vec{ add, subtract, multiply, divide };
for (auto f : vec)
std::cout << f(2, 2) << std::endl;
```
----
see @Mooophy 's [complete codes](ex6_54_55_56.cpp).
有好多种方法,这个我也是抄的,以后看到了可以学习。
最后总结:这一章的内容都比较难,涉及到了大量的指针问题、引用问题、常量问题,这些知识点都是内容又臭有多的,特别是返回数组和返回函数,都必须得用指针,看来以后需要对这一章多看看多复习才是。