C++Primer笔记-函数

前言

该系列是《C++Primer第五版》的笔记,包含本人认为值得记录和整理的主要的知识点,并不是全部内容,也不是具体的内容。
该系列文章的作用应该是作为复习或预习的参考,有哪些知识点忘记或想学,可以大致浏览下该文章,然后再去书中寻找详细解答。(本系列文章基本是按书本顺序罗列的知识点,便于大家去书中寻找)
所以看该文章前,需要有一定的C++基础,否则阅读起来可能有困难。

本文大致整理了第六章的知识点,涉及到C++函数相关的知识,内容比较多,有几个难点有一定难度。

链接目录

函数基础

函数调用方式:

  • 通过括号调用
  • 通过指向函数的指针调用

函数参数:

  • 形参:形式参数,用来接收传递进来的数据
  • 实参:用来初始化形参,将数据传给形参

局部对象:形参和函数体内部定义的变量
局部静态对象:将局部变量定义成static,第一次调用时初始化,到程序终止才会被销毁

函数可以多次声明,但只能定义一次,对函数声明时,可以不要形参的名字
例如:void function(int, double);

函数应该在头文件中声明,在源文件中定义。

参数传递

参数传递类型:

  • 引用传递:形参是引用类型,形参是实参的别名
  • 值传递:形参不是引用类型,形参是实参的一份拷贝

初始化一个非引用类型的形参,不会影响实参。但是可以通过指针形参修改指针指向对象的值,但无法修改实参指针本身的指向。即指针作为参数,实际上会将指针复制一份再传入。

void fun(int* p2){
	*p2 = 2;//可以通过指针修改对象
	p2 = nullptr;//不影响函数外的指针p
}
int main(){
	int i = 1;
	int *p = &i;
	fun(p);//传入p的一个副本
	cout << *p << endl;//函数内的操作不影响p
	return 0;
}

传引用参数:
引用形参可以修改实参的值,形参和实参进行绑定

  • 使用引用可以避免拷贝,对于大的类型对象可以减少开销
  • 使用引用形参返回额外信息,通过引用传递某些额外信息

把形参定义成普通引用会限制函数所能接受的实参类型(不能把const对象、字面值或者需要类型转换的对象传递给普通引用)

void find_char(string &s, char c);

find_char("hello", 'l');//错误,常量不能传递给普通引用

数组形参:
传递数组时,实际上传递的是指向数组首元素的指针。另外函数并不知道数组的大小,可以通过显式传递大小参数来进行相关操作。
void print(const int*)等价于void print(const int[])

如果传递多维数组,数组第二维及后面的维度大小不能省略。

含有可变形参数量的函数

传递参数数量不确定,该方法通常只用于与C函数交互的程序接口。
initializer_list:标准库类型;实参数量未知,但类型全部相同。

void printList(initializer_list<string> il){
	for(auto beg = il.begin(); beg != il.end(); ++beg)
		cout << *beg << " ";
	cout << endl;
}
int main(){
	printList({"hello", "world"});
	return 0;
}

返回类型和return语句

无返回值函数:void函数,可以不要return语句,会隐式地执行return

有返回值函数:
值返回过程:在调用时会初始化一个临时量,在函数结束时将结果拷贝至调用点,所以返回的是一个副本或未命名的临时对象。如果返回的是引用类型,则不需要进行拷贝的操作。

不要返回局部对象的引用或指针!函数完成后,局部对象的内存会被释放,则该引用或指针不再有效,是一个未定义的值。

const string &fun(){
	string ret;
	return ret;//错误,返回局部对象的引用
	return "Empty";//错误,返回局部临时量
}
//如果函数返回类型是string则没有问题,会对结果进行拷贝
//但这里返回的是string的引用,所以错误

当返回类型是一个左值时,可以将函数放在表达式左边


char &get_val(char &c){
	return c;
}
int main(){
	char c = 'a';
	get_val(c) = 'A';//函数返回结果作为左值,此时c的值为'A'
	return 0;
}

主函数main的返回值:main中返回0代表成功,其他值的含义取决于机器。为了使返回值与机器无关,cstdlib头文件定义了两个预处理变量,不能加上std::,也不能在using声明中出现。

int main(){
	if(success)
		return EXIT_SUCCESS;
	else
		return EXIT_FAILURE;
}

返回数组指针

因为数组不能被拷贝,所以函数不能返回数组,只能返回数组的指针或引用。

声明一个返回数组指针的函数:

//形如
Type(*function(parameter_list))[dimension];//最外层括号不可省略
//例如
int(*func(int i))[10];

//使用尾置返回类型(C++11特性)
auto func(int i) -> int(*)[10];

函数重载

函数名字相同,但形参列表不同,称为函数的重载,但不允许只有返回类型不同的重载。

重载和const:

//一个拥有顶层const的形参无法和另一个没有顶层const的形参区分开
Record lookup(Phone);
Record lookup(const Phone);//重复声明

//对于引用和指针则有区别
Record lookup(Phone&);
Record lookup(const Phone&);

Record lookup(Phone*);
Record lookup(const Phone*);

重载确定:

  • 编译器找到一个与实参最佳匹配的函数
  • 找不到任何一个函数与实参匹配,发出无匹配错误
  • 多于一个函数可以匹配,但每个都不是明显的最佳选择,发生二义性调用

重载与作用域:

string read();
void print(int i);
void print(string s);
void fooBar(int ival){
	bool read = false;//新作用域,隐藏了外层的read
	string s = read();//错误
	void print(double);//新作用域,隐藏了外层的print
	print("Value");//错误,外层的print被隐藏了
	print(3.14);//正确,满足内层的print函数
}

特殊用途语言特性

  • 默认实参
  • 内联函数
  • constexpr函数

默认实参:为某个形参提供默认值,它后面所有的形参都必须有默认值,有默认值的形参必须在参数表的末尾。

默认实参的例子:

int func(int i, int j = 0, int k = 1);
//调用时如果想使用默认实参,省略该参数即可
func(4);
func(4, 5);
func(4,,5);//无法使用该种方法只调用中间的默认实参

默认实参声明:一个函数可以多次声明,但一个函数的后续声明只能为之前没有默认值的形参添加默认实参,而该形参右侧的所有形参都必须有默认值。

string screen(int, int, char = ' ');
string screen(int, int, char = '*');//错误
string screen(int i = 24, int j = 80, char);//正确

默认实参初始值:局部变量不能作为默认实参,其他类型只要能转换成形参所需的类型,都可以作为默认实参。例如可能出现这样的实参初始值:int func(int i = getN(), int j = HEIGHT);

内联函数:内联函数实际上会在所有调用到的地方进行展开操作。内联函数相比于外部的函数,可以避免函数调用的开销,不需要保存寄存器、恢复现场、拷贝实参等操作。通常内联机制用于优化规模小、流程直接、频繁调用的函数。

内联函数的例子:

inline int fun(int i, int j){
	i += j;
	return i;
}

constexpr函数:能用于常量表达式的函数

  • 函数的返回类型及所有形参必须都是字面值类型
  • 函数体内有且只有一条return语句
//当scale的实参是常量表达式时,它的返回值也是常量表达式,反之则不然
constexpr int scale(int cnt){
	return 5 * cnt;
}
int arr[scale(2)];//正确
int i = 2;
int arr2[scale(i)];//错误,i不是常量表达式
//但是此处发生的错误不是因为constexpr函数不能使用非常量表达式,而是数组维度的声明参数必须是常量表达式
int j = scale(i);//正确,此时scale函数返回的不是常量表达式

可以把内联函数和constexpr函数放在头文件内,内联函数和constexpr函数可以在程序中多次定义,但是多个定义必须完全一致,所以内联函数和constexpr函数通常定义在头文件中。

调试帮助

assert预处理宏:定义在cassert头文件中。

//表达式为假,输出信息并终止运行,表达式为真不做任何操作
assert(expr);

assert(word.size() > threshold);

NDEBUG预处理变量:在assert头文件前加入#define NDEBUG可关闭调试状态,使assert函数失效,大量的assert函数会造成一定的性能损失,应该在不调试时终止。

C++内置预定义宏:

  • __func__:当前函数的名字
  • __FILE__:文件名的字符串字面值
  • __LINE__:当前行号的整型字面值
  • __TIME__:文件编译事件的字符串字面值
  • __DATE__:文件编译日期的字符串字面值
cout << "Error:" << __FILE__
	<< ": in function:" << __func__
	<< "at line:" << __LINE__
	<< ...

函数匹配

某些重载函数形参数量相等且形参类可以由同一类型转换时。

确定候选函数和可行函数:

void f(int);
void f(int, int);
void f(double, double = 3.14);
f(5.6);//f(int)和f(double, double)都是满足的

寻找最佳匹配:上述代码最终匹配的是f(double, double)实参类型与形参最接近

含有多个形参的函数匹配:如果只有一个函数满足条件,则成功,否则产生二义性。例如调用f(14, 2.5)将产生二义性,可以通过类型转换指定实参类型。

实参类型转换

匹配等级:

  1. 精确匹配:
    • 实参类型和形参类型相同
    • 实参从数组类型或函数类型转换成对应的指针类型
    • 向实参添加顶层const或从实参中删除顶层const
  2. 通过const转换实现的匹配
  3. 通过类型提升实现的匹配
  4. 通过算术类型转换或指针转换实现的匹配
  5. 通过类类型转换实现的匹配

类型提升:

void f(int);
void f(short);
f('a');//char提升成int
f(1);//默认整型字面值为int

函数指针

使用函数指针,不同函数类型的指针不存在转换

auto pf = lengthCompare;//pf指向名为lengthCompare的函数
auto pf = &lengthCompare;//等价
bool b1 = pf("hello", "world");//函数指针可以不解引用
bool b2 = (*pf)("hello", "world");//等价
bool b3 = lengthCompare("hello", "world");//等价

重载函数的指针:必须精确匹配

void f(int *);
void f(unsigned int);

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

函数指针形参:

void fun(int i, bool pf(int j, int k));//自动转换成指向函数的指针
//等价,第二个参数为指向函数的指针
void fun(int i, bool (*pf)(int j, int k));
//fun2为形参列表匹配的函数的函数名,自动转换成指向该函数的指针
fun(1, fun2);

返回指向函数的指针,我们不能返回一个函数,但是可以返回指向函数类型的指针。可以使用类型别名简化。

using F = int(int*, int);//F是函数类型,不是指针
using PF = int(*)(int*, int);//PF是指针类型

//返回类型不能自动转换成指针
PF f1(int);//正确,f1返回指向函数的指针
F f1(int);//错误,f1不能返回一个函数
F *f1(int);//正确,显式地指定返回类型是函数指针

//等价于直接声明
int (*f1(int))(int*, int);//返回一个指向返回类型是int的函数的指针

//也可以使用尾置返回类型
auto f1(int) -> int(*)(int*, int);

如果返回类型过于复杂,建议使用decltype

将auto和decltype用于函数指针类型:

int sumLength(const string&, const string&);
//getFcn返回一个指向sumLength函数的指针
decltype(sumLength) *getFcn(const string&);
//另外需要注意,使用decltype时,它返回的是函数类型而非指针
//所以需要显式的加上解引用符来表明返回指针
上一篇 下一篇

评论 | 0条评论