目录
6.1 调用函数
函数调用主要完成两项工作:用实参初始化函数对应的形参,将控制权转移给被调用函数。
return语句完成的工作:返回return语句中的值,将控制权从被调函数转移回主调函数。
6.1.1 局部静态对象
某些时候需要令局部变量的生命周期贯穿函数调用及之后的时间,可以将局部变量定义成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;
}
程序将输出1到10的数字,若去掉static,则每次输出都是1。
6.2 参数传递
6.2.1 传值参数
(1) 当初始化一个非引用类型的变量时,初始值被拷贝给变量,此时,对变量的改动不会影响初始值。
(2)指针形参
当执行指针拷贝操作时,拷贝的是指针的值,拷贝之后,两个指针是不同的指针。因为指针可以使我们间接地访问所指对象,所以通过指针可以修改他所指的值。
void reset(int *ip)
{
*ip=0;
ip=0;//改变了ip所指对象,但实参并未改变
}
int i=42;
reset(&i);
cout<<i<<endl;/*/结果输出0
6.2.2 传引用参数
void reset(int &i)
{
i=0;
}
int j=42;
reset(j);
cout<<j<<endl;//输出结果为0
(3)使用引用避免拷贝:
拷贝大的类类型对象或者容器对象比较低效,甚至有的类类型根本不支持拷贝操作。当某种类型不支持拷贝操作时,函数只能通过引用形参访问该类型的对象。
(4)使用引用形参返回额外信息
一个函数只能返回一个值,然而有时候需要函数同时返回多个值,引用形参为我们一次返回多个结果提供了有效的途径。
6.2.3 数组形参
数组的两个性质:不允许拷贝数组以及使用数组时通常会将其转换成指针,所以无法以值传递的方式使用数组参数。因为数组会被转换成指针,所以当我们为函数传递一个数时,实际上传递的是指向数组首元素的指针。
void p(const int *);
void p(const int[]);
void p(const int[10]);
上述三个函数是等价的。
因为数组是以指针形式传递给函数的,所以无法知道数组的大小,必须通过一些别的方法让函数知道数组大小。常用方法:
- 使用标记指定数组长度
要求数组本身包含一个结束标记,这种方法常用于c语言的字符串
- 使用标准库规范
传递数组首元素和尾后元素指针
void print(const int *beg,const int *end)
{
while(beg!=end)
cout<<*beg++<<endl;
}
调用:
int j[2]={1,2};
p(begin(j),end(j));
- 显示传递一个表示数组大小的形参
专门定义一个表示数组大小的形参。
传递多维数组:
c++没有真正的多维数组,将多维数组传递给函数时,真正传递的是指向数组首元素的指针,因为我们处理的是数组的数组,所以首元素本身就是一个数组,指针就是一个指向数组的指针。
void p(int (*matrix) [10],int rowsize)
上述语句将matrix声明成指向含有10个整数的数组的指针。
或
void p(int matrix[][10],int rowsize)
matrix声明看起来是一个二维数组,实际上形参是指向含有10个整数的数组的指针。
6.2.4 含有可变形参的函数
如果函数的实参数量未知,但全部实参的类型都相同,可以使用initializer_list类型的形参。
定义initializer_list对象时,必须说明列表所含元素的类型
initializer_list<string> ls;
initializer_list<int> li;
initializer_list对象中的元素永远是常量值,我们无法改变initializer_list对象中元素的值
6.3 返回类型和return语句
6.3.1 有返回值函数
(1)不要返回局部对象的引用或指针
函数完成后,它所占用的存储空间也随之被释放掉,因此,函数终止意味着局部变量的引用将指向不再有效的内存区域。
const string &manip()
{
string ret;
if(!ret.empty())
return ret;//返回的是局部对象的引用
else
return "EMPTY";//字符串字面值转换成一个局部临时对象,返回失败
}
(2)列表初始化返回值
函数可以返回花括号包围起来的值的列表。
vector<string> process()
{
if(expected.empty())
return {};
else
return {"functionX",expected};
}
6.3.2 返回数组指针
因为数组不能被拷贝,所以函数无法返回数组,但是可以返回数组的指针或引用。可使用类型别名进行定义
using arrT=int[10];
arrT* func(int i);//返回一个指向含有10个整数的数组的指针
若不想使用别名:
int arr[10];
int *p1[10];
int (*p2)[10]=&arr;//p2是一个指针,指向含有10个整数的数组
返回数组指针的函数定义:数组的维度必须跟在函数的名字后面。形式:
Type (*function(parameter_list)) [dimension]
例: int (*func(int i))[10];
逐层解析:
func(int i)表示调用func函数时候需要一个int类型的实参
(*func(int i))表示对函数调用的结果执行解引用操作
(*func(int i))[10]表示解引用func的调用将得到一个大小是10的数组
int (*func(int i))[10] 【10】表示数组中的元素是int类型
6.4 函数重载
定义:同一个作用域内的几个函数名字相同但形参列表不同,称之为函数的重载。
不允许两个函数除了返回类型外其他所有的要素都相同。假设有两个函数,他们的形参列表一样但是返回类型不同,则第二个函数的声明是错误的。
Record lookup(const Account&);
bool lookup(const Account&);//错误
(1)重载和const形参
前面介绍,顶层const不影响传入函数的对象。一个拥有顶层const的形参无法和另一个没有顶层const的形参区分开来。
Record lookup(phone);
Record lookup(const phone);//两个函数实际上是一个函数,重复声明。
Record lookup(phone*);
Record lookup(phone* const);//重复声明
如果形参是某种类型的指针或引用,则通过区分其指向的是常量对象还是非常量对象可以实现函数的重载。此时const是底层的。
Record lookup(Account &);//函数作用于Account的引用
Record lookup(const Account&);//函数作用于常量引用
6.4.1 重载与作用域
如果在内层作用域中声明名字,将隐藏外层作用域中声明的同名实体
string read();
void print(const string &);
void print(double);//重载print函数
void foobar(int ival)
{
bool read=false;
string s=read();//错误,read是一个布尔值而非函数
void print(int);//外部声明的print函数都被隐藏掉了
print("value ");//错误
print(ival);//正确
print(3.14);//正确,但调用的是print(int)
}
6.5 特殊用途语言特性
6.5.1 内联函数和constexpr函数
(1)内联函数
前面编写过一个函数,比较两个string形参长度并返回长度较小的string的引用。
const string &shortString(const string &s1,const string &s2)
{
return s1.size()<=s2.size()?s1:s2;
}
调用该函数时存在一些缺点:调用函数比求等价表达式的值要慢一些。
c++中内联函数可以避免函数调用时的开销
将函数定义为内联函数就是将他在每个调用点上“内联地”展开。假设将shortString定义成内联函数,则调用时候
cout<<shortString(s1,s2)<<endl;
将在编译时展开成类似下面形式:
cout<<(s1.size()<=s2.size()?s1:s2)<<endl;
在shortString函数前加上inline关键字就可以将其声明为内联函数了。
inline const string &shortString(const string &s1,const string &s2)
{
return s1.size()<=s2.size()?s1:s2;
}
内联机制用于规模较小,流程直接,频繁调用的函数。
(2)constexpr函数
定义:值能用于常量表达式的函数,,且调用时候,constexpr函数会被隐式地指定为内联函数
定义constexpr函数时需要遵循几条约定:函数的返回类型及所有形参的类型都得是字面值类型,而且函数体中有且只有一条return语句
constexpr int new_sz(){ return 42;}
constexpr int foo=new_sz();
6.5.2 调试帮助
cassert头文件中存在一个assert的预处理的宏,用来调试程序:
assert(expr);
首先对expr求值,若表达式为假(即0),assert输出错误信息并终止程序,若表达式为真(即非0),assert什么也不做
例
//检查输入的单词是否大于两个字符
string word;
cin >> word;
assert(word.size() > 2);