函数

函数

函数基础->参数传递->返回类型和return语句->函数重载->默认实参->内联函数和constexpr函数->调试帮助->函数指针

局部对象:会隐藏外层作用域中同名的其他所有声明,包括同名的函数

局部静态对象:局部变量定义成static类型,那么这个变量在程序的执行路径第一次经过对象定义语句时初始化,并且直到程序终止才被销毁。

示例:函数统计自己被调用了多少次

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;
return 0;
}
参数传递

传值和传引用:如果形参是引用类型,那么形参将绑定到对应的实参上。否则,就将实参的值拷贝后赋值给形参。

传引用的好处:有些类型不支持拷贝操作,只能传引用。而且对于大的类类型对象或者容器对象,可以避免拷贝。而且可以用引用形参返回额外信息。

const形参和实参

可以使用非常量初始化一个底层const对象,但是反过来不行。

void reset(int &i);
int i = 0;
const int ci = i;
reset(&ci);    // 错误
void fcn(const int i);
fcn(i);    // 正确
调用 fcn 函数时,用实参初始化形参时会忽略掉顶层 const ,所以定义 fcn 的时候,

void fcn(const int i){} 和 void fcn(int i){}是重复的定义。


数组形参

数组传值:由于不能拷贝数组,所以无法传值方法使用数组参数,可以传递指向数组首元素的指针。

void print(const int*);
void print(const int[]);
void print(const int[10]);

数组传引用

void print(int (&attr)[10]){}

注意&attr两边的括号不能少,否则就变成了引用的数组。

main:处理命令行选项

int main(int argc, cahr *argv[]){...}
int main(int argc, char **argv){...}
prog -d -o ofile data0

argv[0] = “prog”

argv[1] = “-d”

...

argv[4] = “data0”

argv[5] = 0;


含有可变形参的函数

三种方法处理不同数量实参的函数:

(1)如果所有实参类型相同,传入标准库类型initializer_listC++11

(2)如果实参类型不同,编写可变参数模板(C++11

(3)省略符

示例:定义不确定数量参数的函数

void error_msg(initializer_list<string> il)
{
for(auto beg = il.begin(); beg != il.end(); ++beg)
    cout << *beg << “ “;
cout << endl;
}
//或者void error_msg(ErrCode e, initializer_list<string> il);参数类型不同

//或者void foo(parm_list, ...);  void foo(...);

使用:

error_msg({“functionX”, expected, actual}); // expected和actual是string对象

返回类型和return语句

不要返回局部对象的引用或指针

const string &manip()
{
string ret;
if(!ret.empty())    
    return ret;    //错误
else    
    return “Empty”;    //错误
}

C++11中,函数可以返回花括号包围的值的列表。

vector<string> process()
{
return {};
//return {“functionX”, “okay”};
}
主函数 main 可以没有 returny 语句直接结束,编译器将隐式地插入一条返回 0 return 语句。

返回数组指针

示例:声明func函数

(1)直接写

int (*func(int i))[10];

这样写比较麻烦,可以使用类型别名使语句写起来简单:

示例:

typedef int arrT[10];    //arrT是一个类型别名,表示含有10个整数的数组
using arrT = int[10];    //与上一句等价
arrT* func(int i);    //声明函数func,返回指向包含10个整数的数组的指针
(2)使用尾置返回类型

C++11中还有更简单的声明方法,就是用尾置返回类型,也就是把返回类型放在最后面,原先应该放返回类型的地方写auto

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

(3)使用decltype

如果已经明确知道返回值是指向哪个数组的,就可以用decltype关键字声明返回类型。

int odd[] = {1, 3, 5, 7, 9};
decltype(odd) *arrPtr(int i)
{
    return (i % 2) ? &odd : &even;
}
注意, decltype 返回的类型是数组,如果要函数返回指向数组的指针,那么需要在函数名 arrPtr 前面加上 *

函数重载

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

这两条语句是一样的,不能区分。但是如果形参是引用和指针,那const是可以区分的:

Record lookup(const Account&);
Record lookup(Account&);
这两条语句是重载。

const_cast和重载

示例:

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

由于这个函数在传实参的时候,可以传给他非常量string。但是这时候返回的还是常量string。我们希望传入非常量string的时候返回的也是非常量string。这时候我们可以再写一个重载的函数

string &shorterString(string &s1, string &s2)
{
auto &r = shorterString(const_cast<const string&>(s1), const_cast<const string&>(s2));
return const_cast<string&>(r);
}
如果传入非常量 string ,那么先强制转换成常量,调用刚刚那个函数,然后把结果去掉 const

调用重载的函数

调用重载函数时有三种可能发生的情况:

(1)找到实参的最佳匹配

(2)找不到任何一个可匹配,编译器产生错误信息

(3)多于一个函数可以匹配,但是每一个都不是最佳匹配,编译器产生错误,这是二义性调用。

默认实参

一旦某个形参被赋予了默认值,那么他后面的所有形参都必须有默认值。

声明的时候,给定的作用域中一个形参只能被赋予一次默认实参,函数声明可以有多条,那么这一条后续的声明只能为之前那些没有默认值的形参添加默认实参。

内联函数和constexpr函数

内联函数

为什么要有内联函数?因为有一些简单的要频繁调用的函数,函数调用的开销大。一次函数调用包括:调用前保存寄存器;返回时恢复;拷贝实参;程序转向新的位置继续执行。内联函数会在调用点上展开,不需要函数调用。

inline const string &shorterString(const string &s1, const string &s2)
{
return s1.size() <= s2.size() ? s1 : s2;
}
constexpr函数

constexpr函数是能用于常量表达式的函数。返回类型和所有形参都是字面值类型,函数体中有且只有一条return语句。constexpr函数是内联函数,编译器在该函数的调用处,把他替换成其结果值。

constexpr函数不一定返回常量表达式,如果传入的实参不是常量表达式,那么返回值也不是常量表达式。

示例:

constexpr int new_sz() {return 42;}
constexpr size_t scale(size_t cnt){return new_sz() * cnt;}
int arr[scale(2)];    //正确
int i = 2;
int a2[scale(i)];//错误,这里需要的是常量表达式,但是传入int型,返回的不是constexpr
调试帮助

assert预处理宏

assert(expr);

如果expr为假(0),assert输出信息并终止程序的执行。如果expr为真(非0),assert什么也不做。

NDEBUG预处理变量

如果定义了NDEBUG,那么assert什么也不做,默认状态下没有NDEBUGassert将运行。

NDEBUG也可以自己调试

如果未定义NDEBUG,执行#ifndef#endif之间的代码;如果定义了,忽略这些代码。

示例:

void print(const int ia[], size_t size)
{
#ifndef NDEBUG
    cerr << “Error:” << __FILE__ << “ : in function “ << __func__ << “ at line “ << __LINE__ << endl;
#endif
}
NDEBUG 可以用 #define 定义,也可以 $ CC -D NDEBUG main.c

函数指针

示例:

函数bool lengthCompare(const string &, const string &);

声明一个指向这个函数的指针:

bool (*pf)(const string &, const string &); //注意*pf两端括号不能少

使用函数指针:

pf = lengthCompare;
pf = &lengthCompare;    //两句都可
bool b1 = pf(“hello”, “goodbye”);
bool b2 = (*pf)(“hello”, “goodbye”);
bool b3 = lengthCompare(“hello”, “goodbye”); // 这三句等价
重载函数的指针

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

函数指针形参

和数组类似,有两种定义函数形参的方法,第一种写成函数的样子,但是实际上却是当成指针来用。另一种就是直接写成函数指针。

void useBigger(const string &s1, const string &s2, bool pf(const string &, const string &));
void useBigger(const string &s1, const string &s2, bool (*pf)(const string &, const string &));
使用:

useBigger(s1, s2, lengthCompare);

返回指向函数的指针

1)直接写

int (*f1(int))(int*, int);

2)尾置

auto f1(int)->int (*)(int*, int);

3)使用类型别名

using F = int(int*, int);
using PF = int(*)(int*, int);
PF f1(int);    //正确
F f1(int);    //错误,F是函数不是函数指针
F *f1(int);    //正确
(4)使用 decltype
size_type sumLength(const string&, const string&);
decltype(sumLength) *getFcn(const string &);

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值