目录
前言:
C++语法基础部分,主要包括变量与常量,基本数据类型,表达式,语句,函数,类等,而函数部分作为前面几个章节的综合,掌握难度较大,本文旨在通过对该部分的有关概念,用法进行较为完整的梳理,以便巩固所学。
1.函数基础
函数定义的组成:返回类型,函数名,形参列表,函数体
调用运算符:类似和f(),f是函数或者函数指针,f是一个表达式,它的类型就是函数的返回类型
函数调用进行的工作:首先会向被调函数传入实参。其次,程序执行转向被调返回
return语句执行的工作: 返回return语句中的值(如果有)。其次,将程序执行权返回调用该函数的语句所在的函数(主调函数)
函数调用时传递实参的原则:实参数目跟形参相同,两者做到类型匹配(或实参可以转化为形参类型
函数形参列表:可为空,多个形参类型必须分别声明
void fun1() {/.../}
void fun2(voide) {/.../}
void fun3(int a,b) {/.../} //错误
void fun4(int a,int b) {/.../} //正确
函数的返回类型:除了函数跟数组(可以通过对应的函数指针、数组指针间接返回)以外的大部分类型
名字的作用域与生命周期:函数体是一个语句块,块内未用static声明的变量叫作局部变量或自动对象,局部变量在定义是创建,程序执行离开块后销毁。而定义在任何函数之外的变量则在程序结束后才会销毁,其生命周期更长。若要延长局部变量的生命周期,可以通过static进行声明为局部静态对象
函数的声明:类似于变量名,函数也要先定义后使用,且函数定义于头文件中,声明于源文件处。函数声明的格式为:
函数返回类型 函数名(形参列表);//形参列表中可以只指明形参类型
分离式编译:随着程序变得越来越复杂,可以将程序分成几个部分,放在不同文件中分别编译,最后再通过链接形成完整的可执行文件。
2.参数传递
在参数传递的部分主要涉及这几个问题:
区分传值调用跟传引用调用;
在形参列表中引入const后的变化;
const对引用形参类型的影响;
指针形参类型;
数组作为形参;
如何向函数传递多个实参;
2.1 传值调用跟传引用调用
传值调用时,是将实参的值拷贝一份,传给形参对应的临时变量中(该变量在函数调用结束后销毁),其作用原理类似于:
int ini_n=0;
int i=n;
i++;
cout<<ini_n<<endl;//输出还是0,ini_n 变量的值不会因为i的改变而受到影响
传引用调用跟传值调用不同,调用函数时传入的引用实参并不会进行内容拷贝,而是以别名的形式在函数内部使用,若在函数内改变了跟该实参绑定的形参的值,则相当于修该了实参的值。
那么什么时候可以使用传引用调用呢?
首先,但你希望改变实参的内容时;
其次,效率角度考虑,拷贝类类型或容器类型时效率较低,可以通过引用调用
再次,一些类型根本就不支持拷贝,例如I/O类型,只能通过传引用形式调用
最后,由于一个函数虽然可以由多条return语句,但只能执行其中一条,即返回一条信息,如果希望函数返回更多信息,可以通过传入引用实参。
2.2 const对形参的影响
一般变量的初始化规则,可以通过常量或者非常量给声明为顶层const的变量赋予初始值;
const int i=1;
int a=i;
i=2;//错误
顶层const:本身具备常量属性
用实参初始化形参时更上述类似,即使形参被声明为const的,也可传入常量或者非常量对象
2.3 const跟引用形参
当函数形参为如下形式时,形参被声明为const的,并且是底层const(不会修改所引对象的值)
void fun(const int & b)
{
/.../
}
因此,无论传入常量还是变量对象,只要基本数据类型匹配,则都是可以的;
建议在定义函数时尽量使用常量引用,这样普通实参也能调用该函数,反之,若形参是一般的引用,则const版本的实参不能调用该函数。
2.4 指针形参类型
函数指针形参的定义如下所示:
void fun_ptr(int *a){/.../}
void fun_const_ptr(const int *b){/.../}//形参是const的
const对指针形参的影响,跟引用形参类似,这里补充一点,即形参中的const对函数匹配的影响。
函数匹配:编译器根据函数调用传入的实参,从一批同名函数中查找最佳匹配的那个的过程
如下,当函数仅仅是const跟非const时,函数被多次定义,编译器将会报错
int funt(int i){/.../}
int funt(const int i){/.../}
2.5 数组形参
由于数组被允许拷贝,切使用时会被转换为指针,因此将数组作为形参时,本质上是将指向数组首元素的指针作为形参。如下:
int f_arr(const int*);//三种方式均是等价的
int f_arr(const int [ ]);
int f_arr(const int [100]);
把代表数组首地址的指针声明为const说明不能在函数中通过该指针改变数组元素。同理,如果数组形参被定义为引用类型,则可以改变数组元素。
在使用数组的过程中保证范围不越界是一个重要的考虑,一般有三种管理指针形参的方式,以防止指针越界访问:
首先,可以通过标记指针遍历的序列元素,例如C风格字符串,便用‘\0'作为结束标志。
其次,首标准库迭代器的启发,可以通过指向数组首元素,尾后元素的两个指针来访问数组。
最后,可以通过将数组大小作为一个参数传入函数。
当传入的数组是多维数组是,较为重要的一点是,此时指向数组首地址的指针,对应地址里面的元素变成了数组,以二维数组为例,定义如下:
void fun_mul_arr(int (*matrix)[10] ){/.../}//一个指向数组的指针
void fun_mul_arr(int matrix[ ][10] ){/.../}//等价定义
2.6 如何传递多个实参
主要有两个方法:使用initializer_list标准库类型(实参数量未知但类型相同),可变参数模板;
在C与C++交互的接口程序中还可使用省略符形参来传递可变数量的实参;
initializer_list类型类似于vector<T>类型,或者说是一个常量数组(元素不可改变),定义在同名头文件中。
initializer_list<string> ls;//元素类型是string
void print_msg(itializer_list<string> ls)//initializer_list类型对象在函数种的使用
{
for(auto beg=ls.begin(); beg!=ls.end(); beg++ )
{
cout<<*beg<<endl;
}
cout<<endl;
}
3.返回类型和return语句
函数可分为有返回值跟无返回值两类
3.1 没有返回值的函数
但函数返回类型为void时,表示函数没有返回值,此时可以在函数体中省略return语句
return 语句有两种形式:
return ;
return expression;
void函数使用第二种形式的返回语句时,表达式必须是另一个返回void的函数。
3.2 有返回值的函数
对于此类函数首先必须知道这几点:
return 返回的类型必须跟函数定义的返回值类型匹配,或者类型不同但存在类型转化机制;
当函数体有选择控制逻辑时,必须保证任何情况下都能执行到一条return语句
如下所示:
int fun_re(bool a)
{
if(bool)
return 0;//错误,当a==false, return 0会被跳过,即导致函数体未执行return语句;
}
3.2.1 值被返回的过程
返回一个值类似于初始化一个变量,函数返回时,返回值会放到一个临时变量中,并将该临时变量作为函数调用的结果
此外,根据前文提到的块内定义的变量是局部变量,若函数返回了一个局部变量的引用或者指针,也将引发错误,因为该引用或指针绑定的对象在函数调用后就被销毁了,引用跟指针将指向无效内存,不可访问!
当函数返回值是类类型时,无论函数调用结果是临时量还是通过引用返回的类对象本身,都可以对调用结果直接使用调用运算符。
auto sz = shorter_string(s1,s2).size();
同理,当返回类型是可修改的左值(返回一个非常量引用)时,可以通过赋值运算符直接对它赋值。
3.3 返回数组指针
返回数组指针时,有三种方式:类型别名,直接声明,尾置返回
//方式1
using arr_t = int[10];
arr_t * func(int i);
//方式2
int (*func(int i)) [10];//从里层向外层解读,对一个参数类型是int 的方式解引用,得到一个元 //素是int类型的数组
//方式3
auto func(int i) ->int (*)[10]
4.函数指针
函数指针指向的是函数类型,而函数类型由返回类型和形参类型共同决定,声明一个函数指针,只用把返回名替换 为(*ptr)形式,ptr是函数指针的名字。
4.1函数指针的声明
可以通过函数指针进行函数调用,如下所示:
bool len_compare(const string &,cosnt string &){/.../} ;//声明函数类型,定义函数
bool (*pf)(const string &,cosnt string &);//声明函数指针
pf=&len_compare; //给函数指针赋值,pf指向了len_compare函数
bool b1=len_compare("hello","hhhhh");//函数的直接调用
bool b2=*pf("hello","hhhhh");//使用函数指针间接调用len_compare函数
bool b3=pf("hello","hhhhh");//等价调用方式
4.2 函数指针作为形参
4.2.1定义带函数指针参数的函数
void use_bigger(const string s1,const string s2, bool (*pf)(const string &,cosnt string &) );
void use_bigger(const string s1,const string s2, bool pf(const string &,cosnt string &) );
//两条等价声明
4.2.2使用类型别名简化定义
首先,对函数类型定义别名,如下:
typedef bool Func(const string &,cosnt string &) ;
typedef decltype(len_compare) Func2; //等价的别名定义方式
其次,再对函数指针定义别名,如下:
typedef bool (*Func_ptr) (const string &,cosnt string &) ;
typedef decltype(len_compare) *Func2_ptr; //等价定义方式
简化后的函数声明:
void use_bigger(const string s1,const string s2, Func);
void use_bigger(const string s1,const string s2, Func2_ptr);// 等价声明
4.3 函数指针作为返回类型
最简单的方式是使用类型别名,直接对函数指针类型进行别名,或者对函数类型进行别名通过加上*,再定义函数指针;
using F =int (int*,int); //对函数类型进行别名
usint P_F=int(*)(int*,int); //对函数指针类型进行别名
//声明返回函数指针类型的函数
P_F f1(int);
F * f1(int); //等价声明
F f1(int);//错误,返回了一个函数
此外,还可以直接声明返回函数指针的函数,如下所示:
int (*f1(int)) (int *,int);//左右两侧说明了返回类型是函数指针指向的函数类型
auto f1(int)->int(*)(int*,int); //尾置返回的等价声明
总结,本文首先总结了函数的基础知识,随后从形参列表,形参与实参,返回类型等方面对函数部分内容进行了梳理,同时对函数指针的声明,使用进行了说明。关于函数重载跟默认实参等特殊函数性质本文并未涉及,该部分内容将在后续文章中陈述。