5.函数
5.1函数基本知识
函数由返回值、函数名、形参列表、函数体构成。由于C++的编程风格,将main()函数放在最前面,因此若main()中对某个函数进行调用,需要在main()前面进行函数声明,而函数的定义则放在main()的后面。
int fun_test(int m); //函数声明,又称函数原型
int main(void)
{
...
fun_test(2);
return 0;
}
int fun_test(int m) //函数定义
{ //函数体
return 2*2;
}
1.返回值
对于有返回值的函数,其返回值的数据类型写在函数名的前面,如int fun_test(int m);在函数体中用return语句将数据返回给调用函数(main函数是调用函数,int fun_test(int m)是被调用函数)。注:返回值不能是数组,其他数据类型都可以。
对于没有返回值的函数,其返回值数据类型使用void;函数体中可以不使用return语句,也可以使用return语句。
#include<iostream>
using namespace std;
void max(int &m, int &n);
int main(void)
{
int j = 3, k = 3;
max(j, k);
int a = 11, b = 14;
max(a,b);
cout << a <<endl<<b<< endl;
return 0;
}
void max(int &m,int &n)
{
if (m == n)
{
cout << "相等" << endl;
return; //使用return提前结束函数
}
if(m > n)
n = m;
else
m = n;
}
2.函数名
若函数名相同,则需要进行函数重载,后面再说。
3.形参列表
若没有形参,则采用int fun()或int fun(void)这样形式的函数,形参可以省略或使用void。若形参个数不定,可以用int fun(...)的形式,其中需要用到#include<stdarg.h>文件下的一些函数。
#include<iostream>
#include<stdarg.h>
using namespace std;
int add(int count, ...);
int main(void)
{
cout << add(3, 2, 3, 4) << endl;
return 0;
}
int add(int count,...)
{
int sum = 0;
va_list ap; //定义一个char *指针,typedef char* va_list
va_start(ap, count); //对ap初始化,指向形参列表中的第一个参数m
for (int i = 0; i < count; ++i)
{
sum += va_arg(ap, int); //va_arg(ap, int)返回ap下一个指向int型数据的数值;同时ap地址加一
}
va_end(ap); //释放指针
return sum;
}
(1)形参和数组
int sum_arr(int arr[], int n); //arr实际是数组首元素的指针,n表示数组的长度
int sum_arr(int * arr, int n);
因为数组做形参实际上传递的是数组首元素的指针,因此其操作的是实参,若在被调用函数中不改变数组,则需要加const。
int sum_arr(const int arr[], int n); //函数中不能修改arr数组的数值
int sum_arr(const int * arr, int n);
二维数组:
int sum_arr2(int (*arr2)[4], int n); //表示二维数组arr2,行数为n,列数为4
int sum_arr2(int arr2[][4], int n); //表示二维数组arr2,行数为n,列数为4
(2)形参和C-风格字符串
int par_c(char arr[]); //C-风格字符串是用字符数组来表示的
int par_c(char * arr); //C-风格字符串的本质是char *
由于传递的也是指针,因此可以用const表示字符串只读。
int par_c(const char arr[]);
int par_c(const char * arr);
(3)形参和类/结构体
#include<iostream>
using namespace std;
class Person
{
public:
int age;
const char * name;
void print_info()
{
cout << name << "'s age is " << age << endl;
}
};
void modify_age(Person& per);
int main(void)
{
Person per1;
per1.age = 18;
per1.name = "zhangsan";
modify_age(per1);
per1.print_info(); //打印zhangsan's age is 20
return 0;
}
void modify_age(Person& per)
{
per.age = 20;
}
若modify_age传递的参数不适用引用,则会打印zhangsan's age is 18。因此若要修改类对象中的数据成员,则形参中需要使用引用。否则,不会修改实参中对象的数据成员。
(4)形参和string对象
string的本质也是类,因此若要修改实参中的string对象,则形参需要加引用。
#include<iostream>
#include<string>
using namespace std;
void modify_str(string &str);
int main(void)
{
string str = "this is a test";
cout << str << endl;
modify_str(str);
cout << str << endl;
return 0;
}
void modify_str(string &str)
{
str += "!";
}
运行结果:
this is a test
this is a test!
5.2内联函数
当进行常规的函数调用时,程序将函数调用指令的地址保存在寄存器lr(这里以ARM寄存器为例)中,并将函数的参数复制到栈中;跳到被调用函数的地址开始执行,执行完后将返回值放入寄存器中;最后读取lr中值,并跳到此地址处,开始执行下一条指令。这样来回跳跃给cpu带来了一定的开销。
C++中提供了内联函数,其本质是编译器将相应的函数代码替换了原来的函数调用指令。这样无需跳转,函数执行的速度就会快很多;但是内存是有限的,因此内联函数只能用于那些比较简短的函数中,比如3-4行的函数。
#include<iostream>
#include<string>
using namespace std;
inline void modify_str(string &str);
int main(void)
{
string str = "this is a test";
cout << str << endl;
modify_str(str);
cout << str << endl;
return 0;
}
inline void modify_str(string &str)
{
str += "!";
}
内联函数:在函数声明前加关键字inline;在函数定义前加关键字inline。通常会省略函数声明,而把函数定义放在函数声明的地方。
#include<iostream>
#include<string>
using namespace std;
inline void modify_str(string &str)
{
str += "!";
}
int main(void)
{
string str = "this is a test";
cout << str << endl;
modify_str(str);
cout << str << endl;
return 0;
}
5.3函数重载
函数重载是当函数名相同时,通过判断形参列表的不同来进行选择调用。对于编译器来说,当两个函数名相同时,其形参的数量不同、数据类型不同、不同类型参数的顺序不同都需要进行函数重载。
函数重载的本质是编译器对形参列表进行判断,从而判定这2个函数本质上是不同的。其实编译器会对函数名进行一定规律的改变,这也导致此编译器编译出的代码不能在另一个编译器上运行,因为每个编译器对函数名的处理算法是不同的。
#include<iostream>
using namespace std;
int fun_add(int a, int b);
double fun_add(int a, double b);
double fun_add(double a, int b);
double fun_add(double a, int b, int c);
int main(void)
{
cout<<fun_add(1, 2)<<endl;
cout<<fun_add(1, 2.1)<<endl;
cout<<fun_add(2.1, 1)<<endl;
cout<<fun_add(2.1,1,2)<<endl;
return 0;
}
int fun_add(int a, int b)
{
cout << "int add(int a, int b)" << endl;
return a + b;
}
double fun_add(int a, double b) //参数类型不同
{
cout << "double fun_add(int a, double b)" << endl;
return a + b;
}
double fun_add(double a, int b) //参数顺序不同
{
cout << "double fun_add(double a, int b)" << endl;
return a + b;
}
double fun_add(double a, int b,int c) //参数数量不同
{
cout << "double fun_add(double a, int b,int c)" << endl;
return a + b + c;
}
运行结果:
int add(int a, int b)
3
double fun_add(int a, double b)
3.1
double fun_add(double a, int b)
3.1
double fun_add(double a, int b,int c)
5.1
C++中函数重载中,返回值类型的不同是无法判断函数不同,因为函数都是先执行,再返回。
5.4函数模板
1.函数模板的基本知识
使用泛型编程的思想来定义函数,其实现是函数模板。
#include<iostream>
using namespace std;
template<class T> //可以使用template<typename T>
T fun_add(T a, T b);
int main(void)
{
cout<<fun_add(1, 2)<<endl;
cout<<fun_add(2.1,2.3)<<endl;
return 0;
}
template<class T>
T fun_add(T a, T b)
{
T temp = 10;
cout << "T fun_add(T a, T b)" << endl;
return a + b + temp;
}
函数模板并不是函数,而是当main()执行fun_add(1, 2)时,编译器对函数模板实例化,生成一个int fun_add(int a, int b)函数,此时T = int,即将模板中所有的T替换成int。
函数模板的声明和定义都需要使用template<class T> ,告诉编译器这是一个模板。C++98之后添加了关键字typename,因此也可以使用template<typename T>。其中的T表示通用的数据类型,其可以是基础数据类型、指针、结构体、类,但不能是引用、数组。引用需要template<typename T> T fun_add(T &a, T &b)这样表示;数组需要template<typename T> T fun_add(T arr[], int n)这样表示。
2.函数模板、模板具体化和常规函数的选择
#include<iostream>
using namespace std;
template<typename T>
T fun_add(T a, T b);
double fun_add(double a, double b);
int main(void)
{
cout<<fun_add(1, 2)<<endl;
cout<<fun_add(2.1,2.3)<<endl;
return 0;
}
template<typename T>
T fun_add(T a, T b)
{
cout << "T fun_add(T a, T b)" << endl;
return a + b;
}
double fun_add(double a, double b)
{
cout << "double fun_add(double a, double b)" << endl;
return a + b;
}
运行结果:
T fun_add(T a, T b)
3
double fun_add(double a, double b)
4.4
当函数调用有2种选择时,即常规函数和函数模板,优先调用常规函数。
#include<iostream>
using namespace std;
template<typename T>
T fun_add(T a, T b);
template <> double fun_add<double>(double a, double b);
int main(void)
{
cout<<fun_add(1, 2)<<endl;
cout<<fun_add(2.1,2.3)<<endl;
return 0;
}
template<typename T>
T fun_add(T a, T b)
{
cout << "T fun_add(T a, T b)" << endl;
return a + b;
}
template <>
double fun_add<double>(double a, double b)
{
cout << "template <> double fun_add<double>(double a, double b)" << endl;
return a + b;
}
当函数调用有2种选择时,即函数模板和模板具体化,优先调用模板具体化。
3.关键字decltype
template<typename T1, typename T2>
type? fun_add(T1 a, T2 b)
{
type? = a + b;
}
当使用T1和T2来作为数据类型时,不知道a+b的数据类型,也不知道返回值的数据类型时,该怎么办呢?C++11提供了关键字decltype,用它来声明或初始化一个变量,其数据类型和括号内的数据类型相同。
#include<iostream>
using namespace std;
template<typename T1, typename T2>
auto fun_add(T1 a, T2 b) ->decltype (a + b);
int main(void)
{
cout<<fun_add(1.1, 2)<<endl;
cout<<fun_add(2,2.3)<<endl;
return 0;
}
template<typename T1, typename T2>
auto fun_add(T1 a, T2 b) ->decltype (a + b) //使用auto和decltype来声明返回值的类型和a+b相同
{
cout << "auto fun_add(T1 a, T2 b) ->decltype (a + b)" << endl;
decltype (a + b) temp; //定义一个变量temp,其数据类型和a+b相同
temp = a + b; //或者直接decltype (a + b) temp = a + b;
return temp;
}