第六章 函数

C++11

  • initialize_list -- 6.2.6
  • 列表初始化返回值 -- 6.3.2
  • 尾置返回类型 -- 6.3.3

6.1 函数基础

  • 函数调用的两项工作
    • 控制权:主调函数中断,被调函数执行
    • 隐式定义并初始化形参
  • return的两项工作
    • 返回值
    • 控制权: 转移到主调函数
  • 实参的数目和类型与形参对应,如果不对应需要能够隐式转化
  • 形参列表不能忽略,如果没有形参可以为空或者void
  • 每个形参必须带有自己的类型
  • 形参不能重名,也不能与函数最外层的局部变量重名
  • 返回类型不能是数组或者函数,可以是其指针

6.1.1 局部对象

名字有作用域,对象有生命周期

  • 自动对象:控制路径经过变量定义语句时创建对象,到达所在块的末尾时销毁
  • 局部静态对象:控制路径经过变量定义语句时创建对象,程序结束时销毁

6.1.2 函数声明

  • 函数声明/函数原型:不需要写函数体,使用分号代替
  • 应该在头文件中声明,源文件中定义,然后原文件包含头文件,其他使用函数的原文件或头文件也只包含头文件,这样修改函数接口时只需要修改一处声明。

6.1.3 分离式编译

  • 对于将函数定义在其他源文件中的情况,分离式编译使得可以只重新编译修改过的源文件,生成对象代码.obj或.o,然后链接在一起
CC -c factmain.cc # factmain.o
CC -c fact.cc # fact.o
CC factmain.o fact.o #factmain.exe
CC factmain.o fact.o -o main #main.exe

6.2 参数传递

  • 引用传递:形参是实参的别名
  • 值传递:形参与实参是两个对象

6.2.1 传值参数

  • 改动不影响实参
  • 指针形参:指针形参不影响实参,但是可以用于修改所指对象;建议用引用传递,而非指针

6.2.2 传引用参数

  • 可以避免使用拷贝,对于大的类对象或者容器对象比较合适;此外有些类类型不能拷贝,如IO
  • 可以使用引用传递返回值

6.2.3 const形参和实参

  • 如果const是顶层const,实参是常量或者非常量都可以,因此此时不能用const形参与非const形参做区分,实际是相同的参数
  • 指针或者引用的形参与const:
    • 对于底层const的指针或者引用,可以使用常量或者非常量做实参,引用也可以使用字面值做实参
    • 对于非底层const的指针或者引用,只能使用非常量,也不能使用字面值
  • 如果不修改参数值尽量使用常量引用,因为一般引用的实参选择范围更小,会在传递参数时出现不匹配,此外在不同函数之间重复调用时会出现参数不匹配

6.2.4 数组形参

  • 数组不允许拷贝,所以不能进行对数组的值传递,需要通过指针或者引用;
  • 指针的类型是数组的元素类型,而引用的类型是数组本身的类型

(1) 指针

  • 形参有三种等效的书写方式,其实质上都会被编译器视作const int*,此外,对数组大小的说明只有一个提醒作用,不具备实际效果;同样的,尽量使用const。
void print(const int*)
void print(const int[])
void print(const int[10])

(2) 数组大小

  • 通过指针传递不能获得数组大小的信息,有三种方法
  • 方法一:标记
    该方法适合于数组末尾有特殊标记的数组,比如(const) char*的末尾是空字符。
void print(const char* p)
{  
  if(p)//判单空指针
    while(*p)//判断空字符
      std::cout<<*p++;
}
  • 方法二:迭代器
    该方法通过传入begin以及end指针来实现
void print(const int* begin, const int* end)
{
  while(begin!=end)
    cout<<*begin++;
}
  • 方法三:尺寸形参
    该方法直接传入数组大小

(3) 应用传递

  • 引用传递的数组大小要求只能使用对应大小的数组作为实参,因此限制了自由。
void print(int(&arr)[10])
 {
  for(auto item:arr)
    cout<<item;
}

(4)传递多维数组

void print(int (*arr)[10], int rowsize)
void print(int arr[][10], int rowsize)

6.2.5 main:处理命令行选项

  • 第一个参数是字符串数量,第二个参数是字符串数组
  • 字符串数组的第一个值是程序名或者是空字符串,最后一个是0,其他是输入参数
int main(int argc, char *argv[])
int main(int argc, char **argv)

6.2.6 含有可变形参的函数

  • 有时函数不确定传入的实参个数
  • 如果实参个数不确定但是类型相同,可以使用initialize_list,如果类型不相同可以使用可变参数模板
  • 此外,c有省略符

(1) initialize_list

  • 标准库类型,定义在initialize_list.h中
initialize_list<T> li;
initialize_list<T> li{a,b,c,...};
l2(l1);
l2=l1;
l.size();
l.begin();//指针
l.end();//指针
  • initialize_list与vector类似,也是容器模板,但是其内容不可变
  • 对应的实参采用花括号传递
string b="bbb",c="ccc";
errmog({"aaa",b,c});

(2)省略符

  • 只能够使用c与c++的通用类型,大多数类类型的对象不可用
  • 省略符的类型不做检查
  • 逗号可选
void foo(...);
void foo(list,...);//逗号可以省略

6.3 返回类型和return

6.3.2 有返回值

  • 返回值类型相同或者可以隐式转化
  • 将return放在循环中可能造成最终没有返回,编译器检查不出错误
  • 返回值用于初始化一个临时变量
  • 不能返回局部变量的引用或者指针,因为空间会释放
  • 函数调用运算符的优先级与点运算符或箭头运算符相同,且符合左结合律,因此可以对函数返回的临时变量做点运算符或箭头运算符来访问成员
  • 如果返回值是引用,则是左值;否则是右值。当然,常量引用不能赋值。
  • 可以使用花括号列表作为返回值,如果列表为空做默认初始化,否则按照列表初始化;如果返回值是内置类型,则列表内只有一个元素。
  • main函数可以不包含返回语句,默认return 0;返回非零值表示执行失败,具体意义与机器相关,不通用,为此cstdlib定义了预处理变量EXIT_FAILUREEXIT_SUCCESS

6.3.3返回数组指针

(1) 类型别名

typedef int arrT[10];
using arrT = int[10];
arrT* func();

(2) 一般方法

int (*func())[10];

(3) 尾置类型

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

(4) decltype

  • decltype对数组的类型提取是数组,而非数组指针,因此需要额外加上*。
int a[2]={1,2};
int b[2]={3,4};
decltype(a) *func(int i){
  return (i%2)?a:b;
}

6.4 函数重载

  • 一个作用域内,函数名相同但是形参列表不同
  • main不能重载
  • 不能除了返回值类型不同之外,其他都相同,即必须存在形参列表不同

(1) 判断两个形参的类型是否相同

  • 声明中忽略形参名字,不是新函数
  • 类型别名不是新函数
  • 顶层const不能用于区分形参
  • 指针或者引用的底层const可以用于区分形参,指向常量的指针或者引用常量的引用只能用于底层const,指向非常量的指针或者引用非常量的引用可以用于底层const,但是优先用于非常量底层。

(2) const_cast和重载

const string &comp(const string& s1, const string& s2)
{
  return s1.size()>s2.size()?s1:s2;
}

string &comp(string& s1, string& s2)
{
  auto &r = comp(const_cast<const string&>(s1), const_cast<const string&>(s2));
  return const_cast<string&>(r);
}

(3) 调用重载的函数

  • 函数匹配/重载确定:编译器比较实参与形参列表
    • 最佳匹配:找到一致的重载
    • 无匹配:找不到重载
    • 二义性匹配:存在多个匹配,但是都不是最优匹配

(4) 重载与作用域

  • 在调用函数时,编译器会查找函数名,优先在当前作用域查找,如果在当前作用域找到函数名,就会隐藏其他作用于的同名函数
  • 因此,在局部作用域声明重载函数不是好的选择

6.5 特殊用途语言特性

6.5.1 默认实参

  • 默认实参的右侧必须是默认实参
  • 调用默认实参的函数,可以包含默认实参,也可以忽略
  • 函数调用时参数列表的实参会按照从左到右的顺序修改默认实参,其他的保持默认实参
  • 一个作用域中一个形参只能给定一次默认实参,不能修改,但是可以多次声明该函数并从右向左给予默认实参
  • 可以使用表达式作为默认实参,但是该表达式必须不能是局部变量;相应的,表达式默认实参不会选取相同名字的局部变量,仍然采取非局部变量,即不存在隐藏;此外,如果在局部作用域中修改了表达式,那么默认实参也会被修改。

6.5.2 内联函数与constexpr函数

  • 内联函数inline:只是请求,编译器可以不理会
  • constexpt函数:被期待用于常量表达式的函数
    • 要求:返回值与形参都是字面值,并且有且只有一个return。
    • 编译时,编译器将函数替换成返回值字面值,因此默认是内联函数
    • 允许返回值不是直接给出的常量,相应的形参也不要求是字面值,此时返回值是否是常量表达式取决于传入的实参是否是字面值,如果是则可以用于常量表达式,否则会报错

6.5.3 调试帮助

(1) assert预处理宏
  • 首先计算表达式,如果为假或者说0,即不能发生的情况发生了,则输出信息并停止程序;否则,没有反应。
assert(expr);
  • 定义在cassert.h中,为了避免重名,不要在程序中定义名为assert的其他实体
  • 预处理宏由预处理器而非编译器管理,所以不必处理名字空间。
(2) NDEBUG预处理变量
  • 如果定义了NDEBUG,则assert无效
#define NDEBUG
  • 可以使用命令行等效的在cpp文件的开始位置定义NDEBUG
CC -D NDEBUG main.C
  • 除了assert,可以自行实现调试代码,并利用NDEBUG实现条件调试
void print(size_t size)
{
#ifndef NDEBUG
  cerr<<__func__<<":size is "<<size<<endl; 
#endif
}

此外,还有

__func__//当先函数名的字符串字面值
__FILE__//当前文件名
__LINE__//当前行号
__TIME__//当前时间
__DATE__//当前日期

6.6 函数匹配

(1) 候选函数:

  • 同名且声明在调用点可见

(2) 可行函数:

  • 实参与形参的数量相同(或者在默认实参的情况下满足条件),且类型相同(或者可以转化)
  • 否则,无匹配函数

(3) 最佳匹配:

  • 有且只有一个函数满足:
    • 每个实参的匹配都不劣于其他匹配
    • 至少有一个实参的匹配优于其他匹配
  • 否则,二义性错误
  • 在设计重载时应当尽量避免类型转换

6.7 函数指针

(1) 函数指针

  • 函数指针的函数类型由她的返回类型和形参类型决定
bool (*p) (int, const int*);
  • 函数名是函数类型,但可以自动转化为函数指针,也可以通过取值符号
p = fun;
p = &fun;
  • 对函数指针的使用,可以解引用,也可以不解引用
bool x=p(1, &y);
bool x=(*p)(1, &y);
  • 不同类型的函数指针之间不存在类型转换
  • 可以使用nullptr或者0
  • 对于重载函数,函数指针必须能够跟其中一个重载精确匹配

(2) 函数指针形参

  • 不能使用函数作为形参,但是可以使用函数指针
  • 使用函数类型或者函数指针类型都可以作为形参,但是前者实则是表示函数指针类型
void m(bool (*p) (int, const int*));
void m(bool p (int, const int*));
  • 可以使用函数或者函数指针做实参,但是前者会被转化成函数指针
  • 可以使用类型别名来简化函数类型或者函数指针类型的表示,其中decltype返回的是函数类型,需要额外的*
typedef bool p (int, const int*);//函数类型
typedef decltype(fun) p;//函数类型
typedef bool (*p) (int, const int*);//函数指针类型
typedef decltype(fun) *p;//函数指针类型
using f=bool (int, const int*);//函数类型
using p=bool (*) (int, const int*);//函数指针类型

(3) 函数指针返回值

  • 不能返回函数类型,但是可以返回函数指针
  • 函数类型不能自行转化为函数指针类型返回值,因此不能使用函数类型作为函数的返回值声明
f f1();//错
p f1();
f *f1();
  • 直接声明
int (*f1()) (int,int*);
  • 尾置类型
auto f1() -> int(*)(int,int*);
posted @ 2023-04-30 23:57  ETHERovo  阅读(13)  评论(0编辑  收藏  举报