函数是一个命名了的代码块,通过调用函数执行相应的代码,函数可重载。
一、函数基础
实参初始化形参。return有两项工作:1、返回值(如果有的话);2、将控制权从被调函数转移回主调函数。
int fact(int val)
{
int ret = 1;
return ret > val ? ret : val;
}
int main()
{
int j = fact(5);
return 0;
}
实参的类型必须与对应的形参类型匹配(或者可以实参可以隐式转换为形参),数量也必须匹配(除非提供了默认实参);
【Note】:
1)C++中的函数必须有返回类型,不需要返回值时可以指定其类型为void,只能有一个返回值。
2)形参列表可以为空(显式(void)或者隐式()),但是不能省略,任意两个形参都不能同名。
3)函数返回类型不能是数组和函数,但是可以返回指向数组或者函数的指针。
1、局部对象
名字有作用域,对象有生命周期。形参和函数体内定义的变量统称为局部变量。仅在函数的作用域内可见,存储在栈上,函数结束时被销毁。因此不要返回局部对象的引用或者指针。局部变量如果没有初始化则会产生未定义的值。
自动对象:只存在于代码块({...})执行期间的对象,执行时才分配内存。形参是一种自动对象。
令局部变量的声明周期贯穿函数调用之后的时间,可以将局部变量定义为static类型。static对象一直都在内存中,每次都被初始化为上次调用的值。内置类型的局部静态变量初始化为0,只能使用常量表达式初始化局部静态变量。
int global = 100;//局部静态变量,外部链接性(可以在其他文件中使用)。
static int a = 50;//局部静态变量,内部链接性(只能在本文件中使用)。
void fun()
{
//局部静态变量,没有链接性(在其他地方被使用),一直在内存中。
static int cnt = 0;//每次都被初始化为上次调用的值。
//局部自动变量,没有链接性,运行时分配内存。
int b = 1;
return ++cnt;
}
int main()
{
for (size_t i =0 ; i < 10 ; ++i)
cout << fun() << endl;//计算函数被调用了多少次。
return 0;
}
2、函数声明
含有函数声明的头文件(.h)应该被包含到定义函数的源文件(.cpp)中。
二、参数传递
形参初始化机理与变量初始化一样。
1、传值参数
初始值被拷贝给变量。当指针作为参数时,拷贝的是指针的值。
void reset(*p)
{
*ip = 0;//改变ip所指的值。
ip = 0;//ip的局部拷贝被改变了,不会影响实参。
}
int i = 42;
reset(&i);//改变i的值。
cout << i << endl;//输出0。
2、传引用参数(建议使用此方式)
拷贝大的类型对象或者容器对象比较低效,甚至有的类型不支持拷贝,此时只能通过引用传递参数。
void reset(int &i)
{
i = 0;//改变i的值。
}
int i = 42;
reset(i);
cout << i << endl;//输出0。
【Note】:
最好将其声明为常量引用。
返回“多个值”(例如返回字符出现的总次数及其位置),此时可以在函数形参列表中添加一个引用参数。
string::size_type find_char(const string &s,char c,string::size_type &occurs)
{
/***/
}
3、const形参和实参
因此const普通形参不可以重载:
void fun(const string s,char c) { /***/ }
void fun(string s,char c) { /***/ }
因此const引用或者指针形参可以重载:
void fun(const string &s,char c) { /***/ }
void fun(string &s,char c) { /***/ }
【Note】:
1)尽量使用常量引用,可以提高程序的效率。
4、数组形参
不允许拷贝数组;使用数组名字时会将其转换为指针。使用数值时注意不要越界。
void fun(const int*);
void fun(const int[]);//传递数组时,实际上是传递指向首元素的指针。
void fun(const int[10]);//数组的大小对函数调用没有影响。
int i = 0,j[2] = {0,1};
fun(&i);
fun(j);
管理数组的方式:标准库(begin,end)、显示传递一个表示数组大小的形参(size_t等)。
传递多维数组,实际上传递的是指向首元素的指针:
void fun(int (*matrix)[10]);
5、main:处理命令行选项
int main(int argc, char *argv[]) { /***/ }
int main(int argc, char **argv) { /***/ }
argv是一个指针数组(每个元素都是指针),它的第一个元素指向程序的名字或者一个空字符串,接下来的元素依次传递命令行提供的实参。第一个参数argc表示数组中字符串的数量。例如:
prog -d -o ofile data0
argv[0]保存程序的名字,可选参数从argv[1]开始。
6、含有可变参数的函数
为了能处理不同数量实参的函数,C++11提供了两种方法:
实参数量未知,但是类型相同,可以传递一个名为initializer_list【C++11】的标准库类型(类似于容器)。
可变参数模板。
void error_msg(ErrCode e,initializer_list<string> il)//可以拥有其他形参。
{
cout << e.msg() << ": ";
for (const auto &elem : il)
cour << elem << " ";
cout << endl;
}
if(expected != actual)//expected和actual都是string对象。
{
error_msg(ErrCode(42),{"FunctionX",expected,actual});//必须使用列表初始化。
}
else
{
error_msg(ErrCode(0),{"FunctionX","ok"});
}
三、返回类型和return语句
1、无返回值函数
返回值为void的函数,可以没有return语句(被隐式执行)。
2、有返回值函数
return语句返回值的类型必须与函数的返回类型相同,或者能隐式地转换为函数的返回类型。只能通过一条有效的return语句退出。
引用返回的是左值。
char &fun(string &s,string::size_type ix)
{
return s[ix];
}
string s("abc");
fun(s,0) = 'A';//如果是常量引用,就不能赋值。
cout << s ;//输出Abc。
【Note】:
不要返回局部对象的引用或者指针。函数结束时,局部对象所占用的空间也随之消失,所以局部对象的引用和指针都指向了不存在的空间,因此会发生内存错误。
如果函数的返回类型不是void,则必须有返回值(main函数除外)。
main函数不能递归调用。
3、返回数组指针
因为数组不能被拷贝,所以函数不能返回数组。但是可以返回数组的指针或者引用。
#include <bits/stdc++.h>
using namespace std;
int odd[] = {1,3,5,7,9};//使用全局数组,存储在全局/静态存储区。
int even[] = {0,2,4,6,8};
auto func(int i) -> int(*)[5]//使用尾置类型返回
{
return (i % 2) ? &odd : &even;
}
int main(int argc, char const *argv[])
{
int (*arr)[5];//arr是一个指针,指向含有5个元素的数组。
arr = func(3);//其实arr也是一个**类型。
for (int i=0 ; i<5 ; ++i)
cout << (*arr)[i] << " ";
system("pause");
return 0;
}
任何函数的定义都能使用尾置类型返回【C++11】,这种形式对于返回类型比较复杂的函数最有效。比如返回数组的指针或者引用。
四、函数重载
同一个作用域内的几个函数名字相同,但是形参列表不同。
【Note】:
1)main函数不能重载。
2)最好只重载那些确实非常相似的操作。
3)在C++的编译器中,如果出现了函数重载和函数带有默认参数(可以省略,所以会和函数重载冲突)时,编译是会出错的。
1、重载的几种方式
正确的重载:
1)形参列表的类型或者数量不同:
int lookup(int a);
int lookup(double b);
int lookup(int a,double b);
2)如果形参类型是指针或者引用,那么通过区分是常量对象或者非常量对象可以实现重载:
int lookup(const int &a);
int lookup(int &a);
int lookup(const int *);
int lookup(int *);
错误的重载:
1)不允许除返回类型外其他的要素都相同,即返回类型不能作为判断依据(C++有时候会忽略返回类型):
bool lookup(int a);
int lookup(int a);
2)形参的名字不能作为判断依据:
int lookup(const int &a);
int lookup(const int &);
3)顶层const不能作为判断依据:
int lookup(int a);
int lookup(const int a);
使用函数匹配来调用重载的函数,可能有三种结果:最佳匹配、无匹配、二义性调用。
2、重载与作用域
#include <bits/stdc++.h>
using namespace std;
string read();
void print(const string &);
void print(double);
void foobar(int val)
{
bool read = false;//隐藏了外层的read。
string s = read();//错误:read是bool是类型。
void print(int);//在局部作用域内声明函数不是一个明智的选择。
print("value");//错误:print(const string &)被隐藏掉了。
print(val);//正确:当前print(int)可见。
print(3.14);//正确:调用print(int),print(double)被隐藏掉了。
}
【Note】:
1)在C++语言中,名字查找发生在类型检查之前。
如果我们在内层作用域中声明名字,将隐藏外层作用域中声明的同名实体。
3)在局部作用域内声明函数不是一个明智的选择。
函数可以嵌套声明,可以嵌套调用,但是不能嵌套定义。
五、特殊用途语言特性
1、默认参数
反复出现的值称为默认实参。
using string::size_type sz;
string screen(int x, int y, sz ht = 24, char bg = ' ');
screen(1,2);//等价于screen(1,2,24,' ');
【Note】:
所以尽量把默认实参放在后面。
2)只能省略尾部的实参。
局部变量不能作为默认实参。
4)通常,应该在函数声明中指定默认参数,并将该声明放在合适的头文件中。
2、内联函数
将函数指定为内联函数,通常就是将它在每个调用点上“内联地”展开(非拷贝)。
inline const string &ShortString(const string &s1,const string &s2)
{
return s1.size() <= s2.size() ? s1 : s2;
}
内联机制用于优化规模较小、流程直接、频繁调用的函数。
3、constexpr函数
用于常量表达式的函数,函数的返回类型及所有形参的类型都必须是字面值类型。
constexpr函数在编译期就能获得结果,编译器把对constexpr函数的调用替换成其结果值。constexpr不一定返回常量表达式。尽量把一些简单、容易得到结果的函数作为constexpr函数。
constexpr int new_sz() { return 42; }
constexpr int foo = new_sz();
把内联函数和constexpr函数放在头文件中。
4、assert预处理宏
assert是一种预处理宏,assert使用一个表达式作为其条件。
assert(expr);
这个宏通常原来判断程序中是否出现了明显非法的数据,如果出现了终止程序以免导致严重后果,同时也便于查找错误。
以下是使用断言的几个原则:
(1)使用断言捕捉不应该发生的非法情况。不要混淆非法情况与错误情况之间的区别,后者是必然存在的并且是一定要作出处理的。
(2)使用断言对函数的参数进行确认,每个assert只检验一个条件。
(3)在编写函数时,要进行反复的考查,并且自问:“我打算做哪些假定?”一旦确定了的假定,就要使用断言对假定进行检查。
(4)一般教科书都鼓励程序员们进行防错性的程序设计,但要记住这种编程风格会隐瞒错误。当进行防错性编程时,如果“不可能发生”的事情的确发生了,则要使用断言进行报警。
六、函数匹配
void print();
void print(double);
void print(int val);
void print(const string &);
函数匹配的步骤:
1)选择候选函数:其与被调函数同名,而且其名在调用点可见。
2)选择可行函数:其形参数量和类型与本次调用提供的实参相对应。
3)寻找最佳匹配:如果没有的话,报告错误。
【Note】:
调用重载函数时要尽量避免强制类型转换。
七、函数指针
函数指针指向的是函数而非对象。虽然不能返回函数,但是可以返回指向函数的指针。
#include <bits/stdc++.h>
using namespace std;
int add(int a,int b){return a+b;}
int multiply(int a,int b){return a*b;}
int subtract(int a,int b){return a-b;}
int divide(int a,int b){return b!=0 ? a/b : 0;}
bool L_M(const string &s1,const string &s2)
{
return s1.size() > s2.size() ? true : false;
}
int main()
{
auto pf = &L_M;//pf是指向函数的指针。
cout << pf("ab","abc") << endl;
typedef int(*p)(int a,int b);//使用typedef定义指向函数的指针。
vector<p> vec{add,multiply,subtract,divide};
for (auto f : vec)
{
cout<<f(2,3)<<endl;
}
return 0;
}
标签:const,函数,形参,int,void,C++,Primer,string From: https://blog.51cto.com/u_6526235/7788141