函数就是封装某个功能块的代码以便复用。
1、函数定义
函数的定义的一般形式如下
返回值类型 函数名(参数列表){
函数体
return 返回值;
}
举个例子:
如果一个函数没有返回值,那么它的返回值类型可以用void来代替,return语句则不需要再添加了。例如:
void showName(string name) {
cout << "my name is " << name << endl;
}
注意点:
1、参数列表可以为空,这样就定义了一个无参数的函数。
2、函数后面的大括号表示函数体,在函数体内可以进行变量的声明以及语句的添加等。
2、函数的声明
调用一个函数前,可以先声明函数,再定义函数具体内容。例如:
void showName(string name);
可以看到该函数声明的特点是只有函数名及参数,并没有函数体,也就是没有函数的具体实现内容。类似于Java中抽象类或接口里面的方法定义。函数声明被称为函数原型,函数声明时可以省略变量名,保留参数类型。例如:
void showName(string) ;
当然,多个参数声明中间用“,”隔开就行了,例如:
void showName(string,int);
函数声明完了,就需要去实现函数的具体内容了。例如实现上面定义的函数声明:
#include <iostream>
using namespace std;
void showName(string, int); //声明函数语句
void showName(string name, int age) { //函数语句的具体实现
cout << "my name is " << name << ",and age is " << age << endl;
}
int main() {
showName("树哥", 45); //函数语句的调用
return 0;
}
//输出:my name is 树哥,and age is 45
3、函数的参数及返回值
3.1 空函数
没有参数和返回值,函数的作用域也为空的函数就是空函数。例如:
void showName(){}
空函数存在的意义:在程序设计中往往需要根据确定若干模块功能,分别由一些函数来实现。而最初只是设计,并未实际添加逻辑代码操作。总而言之,占坑!
3.2 形参与实参
带参数函数中的参数在函数声明和定义时被称为形式参数,简称形参。
在函数被调用时被赋予具体值,具体值被称为实际参数,简称实参。
就是一句话,形参就是函数声明时里的参数,实参就是你传递进去的参数。
实参的类型要和形参的类型一一对应。另外再未引入指针的前提下,形参的改变不会影响实参的数据。
3.3 默认参数
在调用有参函数时,如果经常需要传递同一个值到这个调用函数中,在定义函数时,可以设置参数有一个默认值,这样在调用函数时可以省略一些参数,此时程序将采用默认值作为函数的实际参数。例如:
#include <iostream>
using namespace std;
void showName(string name, int age = 45) {
cout << "my name is " << name << ",and age is " << age << endl;
}
int main() {
string name = "树哥";
showName(name); //函数语句的调用,并没有传入age参数
showName("马蓉",38);//函数语句的调用,传入age参数
return 0;
}
//输出打印:my name is 树哥,and age is 45
my name is 马蓉,and age is 38
以上程序中,age设置一个默认参数值为45。那么函数调用的时候,如果未传入age的值,那么就会那45这个值,如果重新传入一个新的age的值,则会覆盖默认参数设置的值。
注意:在定义函数默认值参数时,如果函数具有多个参数,应该保证默认值参数出现在参数列表的右方,没有默认值得参数出现在参数列表的左方,即默认值参数不能出现在非默认值参数的左方。例如下面的函数定义就是非法的。
void showName( int age = 45,string name) {//非法的函数定义,默认参数age在参数name的左方
cout << "my name is " << name << ",and age is " << age << endl;
}
3.4 返回值
函数的返回值是指函数被调用之后,执行函数体的程序所取得的并返回给调用者的值,函数的返回值通过return语句返回。
return语句的一般形式如下:
return (表达式);
没啥好研究的。注意以下几点就行了。
1、函数返回值的类型和函数定义中函数的类型标识符应保持一致。
2、在函数中运行有多个return语句,但每次只能有一个return语句被执行。
3、不返回函数值得函数,可以明确定义为void类型。
4、变量的作用域
根据变量声明的位置可以将变量分为局部变量和全局变量。在函数体内定义的变量称为局部变量,在函数体外声明的变量称为全局变量。例如:
#include <iostream>
using namespace std;
string name = "树哥"; //定义全局变量
void showName() {
int age = 45; //定义局部变量
cout << "my name is " << name << ",and age is " << age << endl;
}
int main() {
showName();//调用函数
return 0;
}
变量都有它的生命周期,全局变量在程序喀什的时候 创建并分配空间,在程序结束的时候释放内存并销毁;局部变量是在函数调用的时候创建,并在栈中分配内存,在函数调用结束后销毁并释放。
5、函数常见的样式
5.1 无参数无返回值
void showName() {}
5.2 无参数有返回值
string showName(){
return "树哥";
}
5.3 有参数无返回值
void showName(string name){
}
5.4 有参数有返回值
string showName(string name){
return name;
}
6、函数重载
定义同名的变量,程序会编译出错,定义同名的函数也带来冲突的问题,但c++中使用了名字重组的技术,通过函数的参数类型来识别函数,所谓重载函数就是指多个函数具有相同的函数名,但函数参数类型、参数的顺序或者参数个数不同的函数,注意返回值类型的不作为判定函数重载的条件。函数调用时,编译器以参数的类型和个数来区分调用哪个函数。例如:
void showName(){}
void showName(string name){}
void showName(int age){}
void showName(string name,int age){}
以上四个函数,无论拿出任意两个函数对比都满足函数参数类型或参数个数以及顺序不一样的重载条件。但是如果添加如下代码:
string showName(){
return "树哥";
}
编译器机会报错,因为它区分不了与void showName(){}函数的区别。所以还是再次强调下,在定义重载函数时,应该注意函数的返回值类型不作为区分重载函数的条件。
如果函数重载遇到指针参数呢?
#include <iostream>
using namespace std;
#include <string>
void showName(int age) { cout << "int age" << endl; }
void showName(int* age) { cout << "int* age" << endl; }
int main() {
int a = 10;
int* p = &a;
showName(a);
showName(p);
return 0;
}
其实函数重载的条件之一就是参数类型不一样,可以设想一下 上面的例子,一个参数是int类型的,另外一个函数的参数类型是 int*指针类型的。那么运行看下结果:
开始运行...
int age
int* age
运行结束。
可以看到分别调用了对应的函数。说明指针参数也可以算函数重载里面的参数类型不一作为条件进行判断。
7、内联函数
内联函数是指用inline关键字修饰的函数,编译器会在每个调用该函数的地方展开一个函数的副本。使用内联函数可以减少函数调用带来的开销(在程序所在文件内移动指针寻找调用函数地址带来的开销)。
应用场景:注意在函数实现代码简短或者调用该函数次数相对较少的情况下应该将函数定义为内联函数,一个递归函数不能在调用点完全展开一个一千行代码的函数也不大可能在调用点展开,内联函数只能在优化程序时使用。例如:
#include <iostream>
using namespace std;
inline int numAdd(int a, int b) { return a + b; }
int main() {
int c = numAdd(2, 4);
cout << "C=" << c << endl;
return 0;
}
以上代码定义了一个numAdd()的函数并用inline修饰,然后在主函数中调用,并打印相加后的结果。然后在实际代码执行中,会将numAdd()函数展开进行编译运行,如下:
#include <iostream>
using namespace std;
int main() {
int c = 2 + 4;
cout << "C=" << c << endl;
return 0;
}
注意点:如果某个内联函数要作为外部全局函数,即它将被多个源代码文件使用,那么就把它定义在头文件里,在每个调用该inline()函数的源文件中包含该头文件,这种方法保证对每个inline()函数只有一个定义,防止在程序的生命周期中引起无意义的匹配。
8、函数的分文件编写
作用:让代码结构看起来更清晰些。
分2个步骤:
一、创建头文件(.h后缀结尾)并在头文件中写函数的声明
二、创建源文件(.cpp后缀结尾)并在源文件中引入头文件并写函数的定义
例如:
第一步:创建名为name.h的头文件,声明showName()函数。
#include <string>
using namespace std;
void showName(string name, int age);
第二步:创建源文件名为name.cpp并在源文件中引入头文件并写函数的定义
#include "name.h"
#include <iostream>
using namespace std;
void showName(string name, int age) {
cout << "my name is " << name << ", age is " << age << endl;
}
这里注意引入自定义的头文件需要用双引号引入,例如#include “name.h”;
这样如果其他源文件要用到这个showName函数,只要引入name.h这个头文件就可以了,例如在main.cpp 源文件下调用并打印输出:
#include "name.h"
#include <iostream>
using namespace std;
int main() {
showName("树哥", 45);
return 0;
}
//my name is 树哥, age is 45
9、void函数
看下以下三个函数的区别
1. void test(){}
2. void test(void){}
3. void test(void *){}
1和2是相同的,表示test函数不接受任何参数,无论在c还是c++中如果函数不接受参数用2的方式书写是一种良好的习惯,3表示test函数接受一个指针类型的参数,无论是什么指针,只要是指针就可以传入。