S06函数

S06函数


一、函数基础

二、参数传递

1、引用传递(by reference)和值(by value)传递,使用引用传递来替代指针形参

2、当某种类型不支持拷贝操作或是较大时,通过引用传递来访问该类型的对象

3、C++中允许定义名字相同的函数,但是不同函数的形参列表应不同(部分情况下也视为相同,例如会忽略顶层const故const int和int视为相同的形参)

注意:和其他初始化一样,实参初始化形参时会忽略形参顶层const

4、使用常量引用可以接受更多实参的类型且不会改变实参的值,因此尽可能使用常量引用
(1)普通引用可以修改实参内容
(2)普通引用不能接受const对象、字面值常量、需要类型转换的对象等,限制了参数类型

5、数组形参
(1)使用标记指定数组长度:如字符数组末尾的’\0’
(2)使用标准库规范:如传入const int *beg, const int *end来指定数组
(3)显式传递一个表示数组大小的形参:如传入const int ia[], size_t size
(4)数组引用形参:如传入int (&arr)[10],这里指定大小的10不可缺少且传入数组的维度必是10,否则会报错,形参是数组的引用,维度是类型的一部分,维度不同则类型不同
(5)传递多维数组:如传入int (*matrix)[10]或int matrix[][10],指定大小的10不可缺少,同时这里传入的实际上是指向含有10个整数的数组的指针

6、命令行运行程序的参数

>prog -d -o ofile data0
main(int argc, char *argv[])
argc = 5
argv[0] = "prog"       //也可以是空串,空串时使用参数从argv[1]开始
argv[1] = "-d"
argv[2] = "-o"
argv[3] = "ofile"
argv[4] = "data0"
argv[5] = 0

7、可变形参
(1)如果所有形参类型相同,传递initializer_list的标准库类型
(2)如果所有形参类型不完全相同,使用可变参数模板,参考S16模板与泛型编程

vector<int> iveca{ 1,2,3 };  
initializer_list<int> x{ 1,2,3 };
vector<int> ivecb(x);          //正确,此时ivecb中有1、2、3
list<int> lsta(x);             //正确,此时lsta中有1、2、3
list<int> lstb(iveca);         //错误,不同容器不能相互初始化

8、initializer_list与vector不同,前者的元素永远是常量值,向类型为initializer_list的形参传递值时需要把序列放置在花括号中,可以用initializer_list来初始化其他容器

initializer_list lst;                 //默认初始化,T类型元素的空列表
initializer_list lst{a, b, c....};    //lst的元素是对应初始值的副本,列表中的元素是const
lst2(lst)/lst2 = lst                     //拷贝或赋值一个对象不会拷贝实际元素,lst和lst2共享元素
lst.size()                               //列表中的元素数量
lst.begin()                              //返回指向列表首元素的指针
lst.end()                                //返回指向尾后元素的指针

三、返回类型和return语句

1、 返回非空值的函数务必要确保有合法的返回值,很多编译器无法检查出是否有合法的返回值

int fun(vector<int> x)
{
    if(x.size() == 0)            //显然条件不满足,然而由于编译器无法判断故不会报错
        return 1;
}
int main()
{
    vector<int> x = { 1,2 };
    fun(x);                      //实际程序能继续执行,并且fun会返回一个无意义的任意值
    return 0;
}

2、不要返回局部对象的引用或指针,由于函数结束局部对象被释放,返回局部对象的引用或指针是错误的

3、引用返回左值:调用一个返回引用的函数得到左值,其他返回类型得到右值

char &get_val(string &str, string::size_type ix)
{
    return str[ix]
}
get_val(s, 0) = 'A'    //此时返回的是s[0]的非常量引用,故可以在等号左侧,若是常量引用则不能赋值

4、列表初始化返回值:函数可以返回花括号包围的值的列表,如果返回的是内置类型,则花括号内只能包围一个值,如果返回的是类类型,则由类本身定义初始值如何使用

注意:main函数不能递归调用自己

5、返回数组指针
(1)声明一个返回数组指针的函数

Type (*function(parameter_list))[dimension]
int (*func(int i))[10];

(2)尾置返回类型:形参列表后以->开头声明返回类型,原返回类型位置改为auto

auto func(int i) -> int (*)[10];

(3)使用decltype:若已知返回的指针将指向哪种数组,但decltype并不负责把数组名转化为指针,故还需*

int odd[] = {
    1, 3, 5, 7, 9};
int even[] = {
    0, 2, 4, 6, 8};
decltype(odd) *arrptr(int i);  //decltype(odd)返回和odd一样有5个元素的数组,后加上*表示是指向这种数组的指针

四、函数重载

1、函数重载:在同一作用域内的几个函数名字相同但形参列表不同,称之为重载函数;编译器会根据实际传递的参数类型来推断调用的函数

注意:main函数不能重载
注意:不允许两个函数除了返回类型以外其他要素全部相同,否则第二个函数报错,二义性错误

2、重载和const形参
(1)顶层const不影响传入函数的对象,因此无法区分出是否有const,故函数不能实现重载

Record lookup(Phone);
Record lookup(const Phone);   //重复
Record lookup(Phone*);
Record lookup(Phone* const);  //重复

(2)底层const可以通过传入函数的对象区分出来,故函数可以实现重载

Record lookup(Account&);              
Record lookup(const Account&);    //传入的是常量引用
Record lookup(Account*);
Record lookup(const Account*);    //传入指向常量的指针

注意:由于non-const可以转换成const,故(2)的四个函数都能用于non-const对象或指向non-const的指针,但是编译器会优先选用对应的,其次再考虑将非常量转换成const,最佳匹配原则

3、const_cast和重载

const string &shorterString(const string &s1, const string &s2)
{
    return s1.size() <= s2.size() ? s1 : s2;
}
string &shorterString(string &s1, string &s2)
{
    auto &r = shorterString(const_cast<const string&>(s1), const_cast<const string&>(s2));
    return const_cast<string &>(r);
}

注意:若复杂的工作在const和non-const函数中重复,则可以考虑用non-const函数调用const函数,避免代码重复,参考《Effective C++》 item 03

4、函数匹配是指在一个过程中把函数调用与一组重载函数中的某一个关联起来,也叫重载确定

5、调用重载函数时
(1)最佳匹配:生成调用该函数的代码
(2)无匹配:生成错误信息
(3)二义性调用:多个函数可以匹配,且无最佳匹配,生成错误信息

注意:在不同的作用域无法重载函数名,会发生隐藏外层的同名实体,因此函数声明不应该放在局部作用域中
注意:C++中名字查找发生在类型检查前

五、特殊用途语言特性

1、默认实参在调用时按位置解析,默认实参负责填补函数调用时缺少的尾部实参(默认实参必须靠右),且在给定的作用域中一个形参只能被赋予一次默认实参,并且有默认实参的形参右侧的所有形参都必须有默认实参

string screen(sz ht, sz wid, char backgrnd);
string screen(sz, sz, char = ' ');
string screen(sz, sz, char = '*');      //错误,不能赋予形参两次默认实参
string screen(sz = 24, sz = 80, char);  //正确,此时三个形参都有了默认实参

2、用作默认实参的名字在函数声明所在的作用域内解析,而这些名字的求值过程发生在函数调用时;且局部变量不能作为默认实参

//声明必须在函数外,局部变量不能作为默认实参
sz wd = 80;
char def = ' ';
sz ht();
string screen(sz = ht(), sz = wd, char = def);

void f2()
{
    def = '*';             //改变了默认实参的值
    sz wd = 100;           //隐藏了外界的wd,但实际上默认实参是外界的wd而不是这个新wd
    window = screen();     //实际调用了screen(ht(), 80, '*')
}

注意:默认实参只能在函数原型中声明一次(往往在.h文件中)而不能在.cpp中到处声明,本质原因在于默认实参是在编译期处理的,此时编译器只能处理一个文件

注意:尽可能少用默认参数

3、内联函数:在每个调用点内联展开,可以避免函数调用导致的开销,内联只需在返回类型前加上inline

注意:inline只是向编译器提出内联请求,编译器可以选择忽视这个请求,一般只有规模小流程直接调用频繁的函数才会被内联展开

注意:inline函数一般在.h文件中声明并定义而不需要在.cpp文件中再次声明,这是由于内联是编译期展开的,编译器一次只能处理一个文件,因此若一个内联函数在.cpp中声明并定义,另一个.cpp文件即使声明了内联函数也无法展开,会变成函数调用(inline只是内联请求,做不到就变成函数调用)

注意:有关inline的更多特点参考《Effective C++》item 30&31

4、constexpr函数

  • 能用于常量表达式的函数
  • 返回类型及所有形参的类型都是字面值类型
  • 函数体中必须有且只有一条return语句
  • 不一定返回常量表达式
  • 为了能随时展开被隐式指定为内联函数

注意:为了所有使用到的地方都能展开,inline函数和constexpr函数通常定义在头文件中

5、assert(expr):预处理宏,定义在cassert中,对expr求值,若为假则输出信息并中止程序执行,当程序中有#define NDEBUG时则关闭assert的功能,默认情况下没有这句宏时assert会执行运行中检查

__FILE__    //存放文件名的字符串字面值
__LINE__    //存放当前行号的整型字面值
__TIME__    //存放文件编译时间的字符串字面值
__DATE__    //存放文件编译日期的字符串字面值
__func__    //存放当前调试的函数的名字

注意:多使用assert使得debug更易于进行

六、函数匹配

1、函数匹配过程
(1)选定本次调用对应的重载函数集合,候选函数:与被调用函数同名、在被调用点可见
(2)选定能被这组实参调用的函数集合,可行函数:形参数量与实参数量相等、类型相同或能转换,如果此时没有找到可行函数,编译器报告无匹配函数错误
(3)寻找最佳匹配函数:实参类型与形参类型越接近越匹配,最佳函数每个实参的匹配都不劣于其他可行函数的匹配、至少有一个实参的匹配优于其他可行函数,如果此时没有找到最佳匹配函数,编译器报告二义性调用错误
(4)调用最佳匹配函数:若有必要,对部分参数进行强制类型转换,调用重载函数时需尽量避免强制类型转换

void f();
void f(int);
void f(int, int);
void f(double, double = 3.14);
f(5.6);     //最佳匹配void f(double, double = 3.14)
f(4, 5.6);  //错误,第一个参数精确匹配f(int,int),第二个参数精确匹配f(double,double),发生二义性错误

2、实参类型到形参类型的转换
(1)精确匹配:实参形参类型相同、实参从数组或函数类型转换成相应的指针、向实参添加顶层const、从实参删除顶层const
(2)通过const转换实现的匹配
(3)通过类型提升实现的匹配
(4)通过算术类型转换实现的匹配:所有算术类型转换级别都一样

void manip(long);
void manip(float);
manip(3.14);   //二义性错误,字面值3.14是double,向long和float转换级别一样,故无最佳匹配

(5)通过类类型转换实现的匹配

七、函数指针

1、函数名作为一个值使用时,函数自动转换成指针

bool lengthCompare(const string &, const string &);
bool (*pf)(const string &, const string &);    //声明函数指针pf
pf = lengthCompare;                            //pf指向lengthCompare函数
pf = &lengthCompare;                           //等价,&符可加可省略
b1 = pf("hello", "abc");                       //可以直接用pf调用lengthCompare函数
b2 = (*pf)("hello", "abc");                    //等价,*符可加可省略

注意:在指向不同函数类型的指针间没有转换规则,只能把函数指针赋值nullptr或0使其不指向任何函数或是让它指向声明时确定的类型的函数,若使其指向返回类型、形参类型不匹配的函数会报错

2、函数指针作为形参,调用时可以直接把函数名作为形参,自动转换为函数指针

3、返回指向函数的指针的函数,一个函数不能直接返回另一个函数,但是可以返回指向一个函数的指针

注意:函数作为形参时,会自动转换为指针,但是返回函数时不会自动转换,因此要显式指明返回的是函数指针

注意:由于复杂类型的声明不易处理,可以充分考虑使用using、typedef、decltype、auto、->等方式来简化

你可能感兴趣的:(C++primer学习笔记,c++)