[C++] 函数总结

本文详细介绍了C++中的函数,包括函数的定义、参数传递(按值、按指针、按引用),特别是按引用传递时推荐使用常量引用,内联函数的使用原则,函数默认参数的设定,以及函数模板的概念、作用和重载。通过实例展示了各种函数特性的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.函数的定义

定义函数三要素:函数名、参数列表、返回值;

1.2.函数原型

在函数使用前,必须对它进行声明,这个声明也称为函数原型。如:

#include <iostream>
//add()函数原型
int add(const int& i,const  int& j);
int main()
{
	using namespace std;

	int i = 45;
	int j = 200;
	int sum = add(i,j);
	system("pause");
	return 0;
}

int add(const int& i, const int& j) {
	std::cout << "i:" << i << ",j:" << j << std::endl;
	return i + j;
}

函数原型和函数唯一的区别就是函数原型不提供函数体,它描述了函数到编译器的接口,由于不需要方法体,因此函数声明不要求提供变量名,不过应尽可能写上。

函数原型一般在头文件中声明。

2.函数参数的传递

函数参数的传递有三种方式:按值传递、指针传递、引用传递,下面我们来看他们各自的特点。

2.1.按值传递

c++中通常按值传递(非引用类型),也就是说当实参传递给形参时,将实参的值赋给了形参,实参并没有影响,如:

#include <iostream>

void Swap(int i,int j);
int main()
{
	using namespace std;
	int fir = 100;
	int sec = 300;
	Swap(fir, sec);
	cout << "fir:" << fir << ",sec:" << sec << endl;
	system("pause");
	return 0;
}

void Swap(int i, int j) {
	int temp;
	temp = i;
	i = j;
	j = temp;
}

运行该程序,发现并没有交换fir和sec的值,在函数调用时,函数将创建一个int类型的变量i,然后将fir的值100赋给它,因此实际上函数Swap()中操作的是局部变量i,而非变量fir。

按值传递导致被调用函数使用调用程序传递的值的拷贝。

2.2.按指针传递

当函数传递指针类型参数时,实际上,也是按值传递,但是这个值是一个地址,因此,就可以通过这个指针来修改所指对象的值了。如:

#include <iostream>

void Swap(int* i,int* j);
int main()
{
	using namespace std;
	int fir = 100;
	int sec = 300;
	int* p1 = &fir;
	int* p2 = &sec;
	cout << "fir:" << fir << ",sec:" << sec << endl;
	Swap(p1, p2);
	cout << "fir:" << fir << ",sec:" << sec << endl;
	system("pause");
	return 0;
}

void Swap(int* i, int* j) {
	int temp;
	temp = *i;
	*i = *j;
	*j = temp;
}

在运行Swap()函数时,函数首先创建了两个指向int类型的指针i和j,然后分别将指针p1、p2的值赋给他们,而这个值是一个地址,因此,i和j也就指向p1和p2所指的对象了,从而操作所指的对象的值。

2.2.1.数组形参

数组有两个特殊性质:

  • 1.不允许拷贝;
  • 2.当且仅当数组作为函数参数时,数组名和指针等价。

因此,当数组作为函数参数时,将其当做指针处理。以下三个函数原型声明的函数完全相同:

const double add(const double* );
const double add(const double arr[]);
const double add(const double [10]);

以上每个函数的参数都是const double*

无论函数参数为指针还是数组,传递参数时并不违反按值传递的方法,只不过这个值是一个地址,而不是数组的内容。

2.3.按引用传递

对于基本数据类型而言,按值传递参数可以实现功能,但如果是数据较大的类型,如类、结构等,按值传递则影响不仅消耗内存,还影响时间。因此可以将指针作为参数传递,除了传递指针以外,还可以传递引用。

我们知道,引用是一个变量的别名,在声明引用时就会为它初始化。引用变量的主要用途就是用于形参,如下示例中使用引用来交换两个变量的值:

#include <iostream>

void Swap(int& i, int& j);
int main()
{
	using namespace std;
	int fir = 100;
	int sec = 300;
	cout << "fir:" << fir << ",sec:" << sec << endl;
	Swap(fir, sec);
	cout << "fir:" << fir << ",sec:" << sec << endl;
	system("pause");
	return 0;
}
void Swap(int& i, int& j) {
	int temp = i;
	i = j;
	j = temp;
}
/*
fir:100,sec:300
fir:300,sec:100
*/

在这个示例中,当调用Swap(fir,sec)函数时,将引用类型的形参i和j作为了实参fir和sec的别名,从而完成值的交换。

2.3.1.尽量使用常量引用作为形参

当引用作为形参时,因尽量使用常量引用,其原因有三:

  • 1.const限定符可以保护实参,避免无意识的修改;
  • 2.使用const限定符可以处理const类型实参和非const类型实参,否则只能使用非const类型数据;
  • 3.使用const限定符后,如果函数参数类型不匹配,则将会生成一个临时变量,并让形参指向这个临时变量,否则不会生成临时变量。

如:

double multiply(double& h, double& k);
double add(const double& a, const double& b);
int i = 3,j = 4;
multiply(i,j);//BAD,不能使用int类型的值初始化double类型的引用
add(i,j);//OK,此时a指向一个临时变量,将i值拷贝给了临时变量

当函数返回类型为引用时,有一点非常重要需要注意,要避免返回函数结束时不再存在的内存单元的引用,即不能返回自动变量(局部变量)。

2.4.总结

  • 1.如果数据对象很小,如基本类数据或小型结构,则按值传递,但若需要修改数据对象,则使用指针;
  • 2.如果数据对象是数组,则使用指针。若不需要修改数据,则声明为指向const的指针(如const int *);
  • 3.如果数据对象为结构或类,则使用引用,若不需要修改数据,则声明为const引用。

3.内联函数

在函数原型或函数定义时,加上关键字inline就可以普通函数变为内联函数。
一般的做法是省略函数原型,将整个函数的定义放在函数原型处。

inline void show(const int& i);

内联函数和普通函数相比,区别不在于他们的编写方式,而是C++编译器如何将他们进行组合。

C++ 编译器在调用普通函数时,是让程序跳转到独立的代码段,执行完毕后再跳转回来。

而对于内联函数,C++编译器将使用函数代码替换函数的调用,不会进行代码段的跳转。

因此,内联函数的运行速度比普通函数快,但却需要占用更多的内存。

使用内联函数时需要注意:

  • 1.只有对于较短的函数,才可以使用内联函数;
  • 2.内联函数不能递归。

4.函数默认参数

可以在声明函数原型时,指定一个默认参数,如:

double play(std::string name, int times=1);

使用默认参数时需要注意两点:

  • 1.默认参数只在函数原型中指定,函数定义中和普通参数完全相同;
  • 2.必须从右向左添加默认值。

通过定义默认参数,可以减少方法的重载数量。

函数重载

函数重载是指两个或多个函数名相同,但参数列表不同的函数。

特点

  • 1.其特征标是函数参数列表,对返回值无要求。
  • 2.引用和类型本身视为同一个特征标;
int show(const int & i,double d);
//void show(int & i1,double d);//bad

重载和const形参

重载函数中const形参比较有趣。在一组重载函数中,如果形参为顶层const,则和非const形参不会进行区分,比如:

void show(const std::string s);
void show(std::string s);//BAD,重复声明

但如果如果形参是底层const,则会进行区分,如:

void show(const std::string& s);
void show(std::string& s);//OK,函数重载

顶层const:表示任何的对象是常量。
底层const:表示指针所指的对象是一个常量,以及用于声明引用的const。

重载函数的调用

定义了一组重载函数后,需要以合理的实参去调用他们,对于参数个数不同或类型不同的重载函数,编译器可以很容器确定要调用的函数,但是在有些情况下,就比较困难了,比如一组重载函数的参数数量相同且参数类型可以互相转换。

当调用重载函数时,有三种可能的结果:

  • 1.编译器找到一个和实参最佳匹配的函数并调用;
  • 2.编译器找不到任何一个可匹配的函数,此时编译器将报出无匹配错误信息;
  • 3.编译器找到多于一个的函数匹配,但都不是最佳匹配,此时编译器将报出二义性错误信息。

如下面示例中定义了一组重载函数:

#include <iostream>
#include <string>

void print(std::string& s );
void print(double);
void print(int);
void print(const char *);
void print(const std::string &);

int main()
{
	using namespace std;
	string s1 = "welcom c++ world";
	const string s2 = "hello world";
	const char * ch = "let's do it";
	int i = 30;
	double d = 12.3;
	print(s1);
	print(d);
	print(i);
	print(ch);
	print(s2);
	
	const int& i2 = 23;
	print(i2);
	system("pause");
	return 0;
}

void print(std::string&  s) {
	std::cout << "print(std::string&  s):" << s << std::endl;
}
void print(double d) {
	std::cout << "print(double d):" << d << std::endl;
}
void print(int i) {
	std::cout << "print(int i):" << i << std::endl;
}
void print(const char * pc) {
	std::cout << "print(const char * pc):" << pc << std::endl;
}
void print(const std::string & s) {
	std::cout << "print(const std::string & s):" << s << std::endl;
}
/*
print(std::string&  s):welcom c++ world
print(double d):12.3
print(int i):30
print(const char * pc):let's do it
print(const std::string & s):hello world
print(int i):23
*/

5.函数模板

函数模板属于C++的泛型编程,可以通过泛型来定义函数,在定义函数时,将类型作为参数传递给模板,从而使得编译器生成该类型的定义。

5.1.为何需要函数模板?

在程序中,可能会存在多个函数功能相同,但参数不同的情况,比如在交换值时分别定义了交换int类型和double类型的函数Swap()

void Swap(int * i, int * j );
void Swap(double * d, double * p);

如果还有其他类型,则还需要重载Swap()函数多次。

因此,C++编译器提供了函数模板特性,当需要对不同类型使用同一种算法时,就可以使用汗俗话模板了。

5.2.格式

定义函数模板时,格式如下:

template <typename T>
void Swap(T t1, T t2);

关键字typename可以使用class代替。

在函数原型和函数定义处,都需要有关键字template 修饰。

5.3.工作原理

函数模板不会创建任何函数,函数模板实际上经过了二次编译,分别在编译cpp文件时和函数调用时,在函数调用时,将会根据传入的参数生成对应的函数。

5.4.模板的重载

和常规函数一样,函数模板也可以进行重载,如下面示例中:

template <typename T,typename N>
void Swap(T & t1, N & n1);
template <typename T>
void Swap(T & t1, T & t2);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值