《C++Primer》读书笔记(六)函数

函数基础

  • 我们用调用运算符来执行函数,它是一对圆括号,它作用于一个表达式,该表达式是函数或者指向函数的指针;括号之内是一个用逗号隔开的实参列表

(1)编写函数

int fact(int val){
    int ret=1;
    while (val>1)
        ret*=val--;
    return ret;
}

(2)调用函数

int main(){
    int j=fact(5);
    cout<<"5! is "<<j<<endl;
    return 0;
}

函数的调用完成两项工作:一是实参初始化函数对应的形参,二是将控制权转移给被调用函数

(3)形参和实参

(4)函数的形参列表

(5)函数的返回类型

局部对象

在C++中,名字有作用域,对象有生命周期

  • 名字的作用域是程序文本的一部分,名字在其中可见
  • 对象的生命周期是程序执行中该对象存在的一段时间

(1)自动对象

(2)局部静态对象

// 统计函数它自己被调用了多少次
size_t count_calls(){
    static size_t ctr=0;
    return ++ctr;
}
int main(){
    for(size_t i=0;i!=10;++i)
        cout<<count_calls()<<endl;
    retunr 0;
}
  • 在控制流第一次经过ctr的定义之前,ctr被创建并初始化为0。
  • 每次调用将ctr加1并返回新值
  • 每次执行函数时,变量ctr的值都已经存在并且等于函数上一次退出时ctr的值

函数声明

void print(vector<int>::const_iterator beg,
            vector<int>::const_iterator end);
  • 函数的三要素(返回类型、函数名、形参列表)描述了函数的接口,说明了调用该函数所需的全部信息。函数声明也称作函数原型

(1)在头文件中进行函数声明

分离式编译

(1)编译和链接多个源文件

参数传递

  • 但形参是引用类型时,我们说它对应的实参被引用传递或者函数被传引用调用
  • 当实参被拷贝给形参时,形参和实参是两个独立的对象

传值参数

(1)指针形参
- 当执行指针拷贝操作时,拷贝的是指针的值。拷贝之后,两者为不同的指针
- 因为指针使我们可以间接地访问它所指的对象,所以通过指针可以修改它所指对象的值

int n=0,i=42;
int *p=&n,*q=&i; //p指向n;q指向i
*p=42; //n的值改变;p不变
p=q; //p现在指向了i;但是i和n的值不变
  • 指针形参的行为与之相似
void reset(int *ip){
    *ip=0; // 改变指针ip所指对象的值
    ip=0; // 只改变了ip的局部拷贝,实参并未改变
}
  • 调用该函数需要传递对象的地址
    熟悉C的程序员经常使用指针类型的形参来访问函数外部的对象。在C++语言中,建议使用引用类型的形参替代指针

传引用参数

// 该函数接受一个int对象的引用,然后将对象的值置为0
void reset(int &i){  // i是传给reset函数的对象的另一个名字
    i=0;  // 改变了i所引对象的值
}
  • 调用该函数时,我们直接传入对象,而无须传入对象地址

(1)使用引用避免拷贝

如果函数无须改变引用的形参的值,最好将其声明为常量引用

// 比较两个string对象的长度
bool isShorter(const string &s1,const string &s2){
    return s1.size()<s2.size();
}

(2)使用引用形参返回额外信息
- 但我们需要获得两个或以上的返回数据时,函数只能有一个返回值,有两种办法
- 定义一个新的数据类型,让它包含不同的成员,作为返回值
- 给函数传入一个额外的应用实参,令其保存另外的返回数据

// 返回s中c第一次出现的位置索引
// 引用形参occurs负责统计c出现的次数
string::size_type find_char(const string &s,char c,string::size_type &occurs)
{
    auto ret=s.size();
    occurs=0; //设置表示出现次数的形参的值
    for(decltype(ret) i=0; i!=s.size() ; ++i){
        if(s[i]==c){
            if(ret==s.size()){
                ret=i; //记录第一次出现的位置
            }
            ++occurs;
        }
    }
    return ret;
}

const形参和实参

  • 关于顶层const作用于对象本身:
const int ci=42; //不能改变ci,const时顶层的
int i=ci; //正确:当拷贝ci时,忽略了它的顶层const
int * const p=&i; //const是顶层的,不能给p赋值
*p=0; //正确:通过p改变对象的内容是允许的,现在i变成了0
  • 当用实参初始化形参时会忽略掉顶层const
  • 即当形参有顶层const时,传给它常量对象或者非常量对象都是可以的
void fcn(const int i){
    // fcn能够读取i,但是不能向i写值
}

(1)指针或引用形参与const
- 形参的初始化方式和变量的初始化方式时一样的
- 我们可以使用非常量初始化一个底层const对象,当反过来不行
- 一个普通的引用必须用同类型对象来初始化

int i =42;
const int *cp=&i; //正确:但是cp不能改变i
const int &r=i; //正确:但是r不能改变i

const int &r2=42; //正确

int *p=cp; //错误:p的类型与c的类型不匹配
int &r3=r; //错误:r3的类型和r的类型不匹配
int &r4=r2; //错误:不能用字面值初始化一个非常量引用

(2)尽量使用常量引用

数组形参

  • 数组的两个特殊性质对我们定义和使用作用在数组上的函数又影响,分别是:
    • 不允许拷贝数组
    • 使用数组会将其转换成指针
  • 尽管不能以值传递的方式传递数组,当可以把形参写成类似数组的形式
void print(const int*);
void print(const int[]);
void print(const int[10]);

和其他使用数组的代码一样,以数组作为形参的函数也必须确保使用数组时不会越界

  • 因为数组是以指针的形式传递给函数的,所以一开始函数并不知道数组的尺寸,调用者应该为此提供额外信息。管理指针形参有三种常用技术
    (1)使用标记指定数组长度
void print(const char *cp){
    if(cp) //若cp不是一个空指针
        while(*cp) //只要指针不是空字符
            cout<<*cp++;
}

(2)使用标准库规范
- 传递指向数组首元素和尾后元素的指针

void print(const int *beg,consst int *end){
    //输出beg到end之间全部元素
    while(beg!=end)
        cout<<*beg++<<endl;
}

上述函数的调用可以通过:

int j[2]={0,1};
print(begin(j),end(j));

(3)显式传递一个表示数组大小的形参

void print(cosnt int ia[],size_t size){
    for(size_t i=0 ; i!=size() ; ++i){
        cout<<ia[i]<<endl;
    }
}

(4)数组形参和const

(5)数组引用形参

void print(int (&arr)[10]){
    for(auto elem:arr)
        cout<<elem<<endl;
}

(6)传递多维数组

void print(int (*matrix)[10] ,int rowSize){}

/*等价定义*/
void print(int matrix[][10],int rowSize){}

上述语句将matrix声明成指向含有10个整数的数组的指针

mian:处理命令行选项

含有可变形参的函数

(1)initializer_list形参
- 如果函数的实参数量未知但是全部实参的类型都相同,可以使用initializer_list类型的形参

操作结果
initializer_list lst;默认初始化:T类型元素的空列表
initializer_list lst{a,b,c…>lst的元素数量和初始值一样多,lst是初始值的副本,列表种的元素是const
lst2(lst)拷贝或赋值一个initalizer_list对象不会拷贝列表中的元素;拷贝后两列表共享与元素
lst2=lst同上
lst.size()列表中的元素数量
lst.begin()返回首元素指针
lst.end()返回尾元素下一位置指针
void error_msg(initalizer_list<string> il){
    for(auto beg=il.begin() ; beg!=il.end() ; ++beg)
        cout<<*beg<<"";
    cout<<endl;
}
  • 如果想向initialzer_list形参中传递一个值的序列,必须把序列放进花括号中
error_msg( {"functionX",expected,actual} );

(2)省略符形参

返回类型和return语句

无返回值函数

  • 可以直接执行return语句处推出
void swap(int &v1,int &v2){
    if(v1==v2)
        return;
    int tmp=v2;
    v2=v1;
    v1-tmp;
}

有返回值函数

(1)值是如何被返回的

string make_plural(size_t str,const string &word,const string &ending){
    return (ctr>1)?word+ending:word;
}

(2)不要返回局部对象的引用或指针
- 函数完成后,它说占用的空间也随之被释放。因此,局部变量的引用不再有效

(3)返回类类型的函数和调用运算符

// shorterString函数返回istring,访问其size成员
auto sz=shorterString(s1,s2).size();

(4)引用返回左值

(5)列表初始化返回值
- 如果返回值是引用,它是左值;其它则是右值
- 如果返回值是常量引用,就不能给调用的结果赋值

(5)列表初始化返回值

vectot<sting> process(){
    ...
    ...
    return {"funtionX","OKay"};
}

(6)主函数main的返回值
- return EXIT_FALLURE或者return EXIT_SUCCESS 这两个预处理变量定义再头文件cstdlib头文件中

(7)递归

返回数组指针

  • 最直接的办法就是使用类型别名
typedef int arrT[10]; // arrT是一个类型别名,表示含有10个整数的数组
using arrT=int[10]; // arrT的等价声明

arrT* func(int i); // func返回一个指向含有10个整数的数组的指针

(1)声明一个返回数组指针的函数
- 要想再声明func时不适用类型别名

int arr[10];
int *p1[10];
int (*p2)[10]=&arr; //p2时一个指针,它指向含有10个整数的数组
  • 返回数组指针的形式如下所示
Type (*function(parameter_list))[10];

// 例子
int (*func(int i))[10];

(2)使用尾置返回类型

// func接受一个int类型的实参,返回一个指针,该指针指向含有10个整数的数组
auto func(int i) -> int(*)[10];

(3)使用decltype

int odd[]={1,3,5,7,9};
int even[]={0,2,4,6,8};
// 返回一个指针,该指针指向含有5个整数的数组
decltype(odd) *arrPtr(int i){
    return (i%2)?&odd:&even; //返回一个指向数组的指针
}
  • 其中,decltype表示它的返回类型是一个指针,并且所指对象类型与odd的类型一致
  • decltype并不负责把数组转换成对应指针,所以decltype的结果是个数组,要想表示返回指针,还必须再函数声明时加一个符号

函数重载

(1)定义重载函数和

Record lookup(const Account&);
Record lookup(const Phone&);

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

(3)重载和const形参
- 一个拥有顶层const的形参无法和另一个没有顶层const的形参区别开来
- 如果形参时某种类型的指针或引用,则区分其指向是否常量可以实现函数重载

(4)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)
}

(5)调用重载的函数

重载与作用域

特殊用途语言特性

默认实参

typedef string::size_type sz;
string screen(sz ht=24,sz wid=80,char backgrnd='');
  • 其中,为每个形参都提供了默认实参
  • 注意:只能给全部形参同时提供默认值

(1)使用默认实参调用函数
- 直接再调用时省略该实参即可
- 注意:只能省略尾部的实参,或者省略全部实参

(2)默认实参声明
- 通常在头文件中,进行函数实参的声明

通常,应该在函数声明中指定默认实参,并将该声明放在合适的头文件中

(3)默认实参初始值
- 除局部变量之外,表达式能实现对应转化则可以作为默认实参

内联函数和constexpr函数

(1)内联函数可避免函数调用的开销
- 在函数的返回类型前面加上关键字inline,就声明成为内联函数了

很多编译器都不支持递归内联函数

(2)constexpr函数
- constexpr函数的全部形参都必须时字面值类型,而且函数体中必须有且只有一条return

调试帮助

(1)assert预处理宏
- assert是一种预处理变量,它的行为类似于内联函数

assert(expr);
  • 首先,对expr求值,如果为假(0),assert输出信息并终止程序的执行;如果为真,assert声明也不做
  • 常常用来检查“不能发生的事”

(2)NDEBUG预处理变量
- assert的行为依赖于一个名为NDEBUG的预处理变量状态。如果定义了NDEBUG,则assert什么也不做。默认状态下没有定义NDEBUG,此时assert将执行检查
- 定义NDEBUG能避免检查各种条件所需的运行时开销

  • 小例子:
void print(const int ia[],size_t size){
    #ifndef NDEBUG
        cerr<<__func__<<":array size is "<<size<<endl;
    #endif
}
  • 如果NDEBUG未定义,将执行 #ifndef 和 #endif之间代码,如果定义了NDEBUG,将被忽略

  • 编译器定义的用于调试的名字

代码结果
_ _ func _ _存放当前调试的函数的名字的const char静态数组
_ _ FILE _ _存放文件名的字符串字面值
_ _ LINE _ _存放当前行号的整型字面值
_ _ TIME _ _存放文件编译时间的字符串字面值
_ _ DATE _ _存放文件编译日期的字符串字面值
if(word.size() < threshold)
    cerr<<"Error:"<<_ _FILE_ _
        <<":in function"<<_ _func_ _
        <<"at line"<<_ _LINE_ _<<endl
        <<"     Compiled on"<<_ _DATE_ _
        <<"at"<<_ _TIME_ _<<endl
        <<"     Word read was \""<<word
        <<"\":Length too short"<<endl;

函数匹配

(1)确定候选函数

(2)寻找最佳匹配(如果有的话)

(3)含有多个形参的函数匹配

实参类型转换

(1)需要类型提升和算术类型转换的匹配

(2)函数匹配和const实参

函数指针

bool (*pf)(const string &,const string &);

(1)使用函数指针

pf=lengthCompare; //pf指向名为lengthCompare的函数
pf=&lengthCompare; //等价的赋值语句

另外,还可以直接使用指向函数的指针条用函数

bool b1=pf("hello","goodbye");
bool b2=(*pf)("hello","goodbye"); //一个等价的调用

(2)重载函数指针

void ff(int*);
void ff(unsigned int);

void (*pf1)(unsigned int)=ff; //pf1指向ff(unsigned)

编译器通过指针类型决定选用那个函数,指针类型必须与重载函数中的某一个精确匹配

(3)函数指针参数
- 函数虽然不能定义数组、函数类型的形参,但是形参也可以是指向

void useBigger(const string &s1,const string &s2,bool pf(const string &,const &));

// 等价的声明
void useBigger(const string &s1,const string &s2,bool (*pf)(const string &,const string &));

(4)返回指向函数的指针
- 最简单的办法是声明一个类型别名作为返回类型

using F=int(int*,int); //F是函数类型,不是指针
using PF=int(*)(int*,int); //PF是指针类型
  • 将F定义成函数类型,将PF定义成指向函数类型的指针
PF f1(int); //正确:PF是指向函数的指针,f1返回指向函数的指针
F=f1(int); //错误:F是函数类型,f1不能返回一个函数
F *f1(int); //正确:显式地指定返回类型是指向函数的指针
  • 处于完整性考虑,可以使用尾置返回类型的方式
auto f1(int)->int(*)(int*,int);

(5)将auto和decltype用于函数指针类型

string::size_type sumLength(const string&,const string&);
string::size_type larger(const string&,const string&);
// 根据其形参的取值,getFcn函数返回指向sumLength或者largerLength的指针
decltype(sumLength) *getFcn(const string &);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值