关于返回类型和return语句
两种形式:
- return;
- return expression;
关于无返回值函数
返回void的函数不要求非得有return语句,因为在这类函数最后都会隐式的执行return;所以并不是它真的不需要,而是他本来就有。
注意其实返回void的函数也可以是上面的第二种返回方式,即return expression的形式,只要保证expression是一个返回void的函数即可。不然会报错。
关于有返回值的函数
在含有return语句的循环后面应该也有一条return语句,如果没有的话改程序就是错误的,但是很多编译器不会报错。
关于值是如何被返回的
返回一个值的方式和初始化一个变量或形参的方式完全一样:返回的值用于初始化调用点的一个临时量,该临时量就是函数调用的结果。
如果是用的是引用,那不管是调用函数还是返回结果都不会真正拷贝对象。
函数完成后,它所占用的存储空间也随之被释放掉,因此,函数终止意味着局部变量的引用将指向不再有效的内存区域。
所以不能返回局部对象的引用或指针。
int *zz(){
int a=6677;
int *p = &a;
return p;
}
int main()
{
int *a = zz();
cout << *a;
}
并不会报错,据书上说会引发未定义的行为。但是我尝试了并没有发生奇怪行为,应该是数据不够大。
总之,这样非常之不安全。
引用函数返回左值
char &get_val(string &str, string::size_type ix){
return str[ix];
}
int main()
{
string s("string");
get_val(s, 0) = 'A';
cout << s;
}
就这个引用函数会比较陌生,因为从来都没有使用过。会返回左值,但返回的是常量引用的时候,也不能这样写。(废话,返回的常量,人家怎么再赋值)
列表初始化返回值
这个也很陌生,完全就没用过
写一个例子
vector<string> p(){
return {"I have", "apple", "pen"};
}
int main()
{
vector <string> s = p();
for(auto c : s)
cout << c;
}
主函数main的返回值
如若函数的返回值不是void, 那么它必须返回一个值,这是没有问题的,但是我们发现我们的主函数的最后有时并不加return也没有问题,那是因为编译器发现没有的时候会隐式的插入一条返回为0的return 语句。
主函数main可以看作是状态指示器,返回0表示成功,返回其他值表示执行失败,其中非0值的具体含义依机器而定。
为了使返回值与机器无关
在cstdlib头文件中定义了两个预处理变量。
int main(){
if(some_fail) return EIXT_FAILURE;
else return EXIT_SUCCESS;
}
关于递归
递归就是自己调用自己
这里需要注意main函数不能调用自己。
来个小练习递归输出vector
void print_v(vector<int>v,int len, int i){
if(i==len) return ;
cout << v[i]<<endl;
return print_v(v, len, i+1);
}
int main()
{
vector <int> s{1, 2, 3, 4, 5};
print_v(s, s.size(), 0);
}
关于返回数组指针
因为数组不能背拷贝,所以函数不能返回数组,但是却可以返回数组的指针或是引用。
typedef int arrT[10];//
using arrT = int[10];//跟上个句子一毛一样,个人更喜欢用using
arrT* func(int); //表示func返回一个指向10个整数的数组的指针。
使用类型别名简化了定义数组指针的过程????并不觉得,反而我觉得这样更容易混淆,个人更倾向于下面的最原始的方式。
基本方式
int (*func(int i))[10];
书上没例子,只好自己想,这个例子有点难想的。
int a[10];
int (*func(int l))[10]{
for(int i=0; i<10; i++)
a[i] = l;
int (*b)[10];
b = &a;
return b;
}
int main()
{
int (*x)[10];
x = func(88);
for(auto c : *x)
cout << c <<endl;
}
尾置返回类型
c++11新标准添加了尾置返回类型,形如
auto func(int i) -> int(*)[10];
稍微改动了点
int a[10];
auto func(int l)->int(*)[10]{//注意这里只能用auto
for(int i=0; i<10; i++)
a[i] = l;
int (*b)[10];
b = &a;
return b;
}
使用decltype
直接例子就可
int a[10];
//int (*p)[10];
decltype(a) *func(int l){//上一行注释去掉的话这一行变成decltype(p) func(int l);
for(int i=0; i<10; i++)
a[i] = l;
int (*b)[10];
b = &a;
return b;
}
再回顾一下,decltype(a)返回的不是指针,而是数组,所以这里在其后还需要加上*。
来个小练习,编写一个函数的声明,使其返回数组的引用并且该数组包含10 个string对象。不要使用尾置返回类型、decltype 或者类型别名。
string v[10];
string (&func(const string &s))[10]{
string (&p)[10] = v;
for(auto &c : p) c = s;
return p;
}
int main()
{
string (&o)[10] = func("hahahah");
for(auto &c : o) cout << c<<endl;
}
//关于函数定义的改写
//auto func(const string &s)->string(&)[10] {
//decltype(v) &func(const string &s){
关于函数重载
回顾一下函数重载会忽略顶层const。
main函数不能重载
注意
int lookup(const account &acct)
int lookuo(const account)
第二个省略了形参的名字,其实这俩是一样的
当调用重载函数时有这么三种可能的结果
找到最佳匹配,并生成调用该函数的代码
找不到任何匹配,发出错误。
有多余一个函数可以调用,也将发生错误,称为二义性调用
关于重载与作用域
在c++语言中,名字查找发生在类型检查之前。
void read(){cout << "read";}
void func(int iv){
read(); // 这一行不会报错
bool read = false;
read(); //这一行会报错
}
通过上述例子我举得很容易理解作用域。
关于特殊用途语言特征
介绍三种:
- 默认实参
- 内联函数
- constexpr函数
关于默认实参
string screen(int ht = 24, int wid = 80, char backgrnd=' ');
这就是默认实参。
注意注意注意:一旦某个形参被赋予了默认值,它后面所有的形参都必须有默认值。
函数调用时实参按其位置解析,默认实参负责填补函数调用缺少的尾部实参(靠右侧位置)
举个例子
string window;
window = screen(); // 等于screen(24,80,' ' );
window = screen(66);//等于screen(66,80,' ' );
window = screen(66, 256);// 等于screen(66,256,' ' );
window = screen(, , '?'); //错误,只能省略尾部实参
因此当设计含有默认参数的函数时,需要合理的设置形参的顺序,尽量让不怎么使用默认值的形参出现在前面,让那些经常使用默认值的形参出现在后面。
关于默认实参声明
void fun(int a,string str="apple",double b=3.14);
void fun(int a=1,string str,double b);//补充声明只需指明补充部分即可,已有部分不能覆盖
void fun(int a=1,string str,double b=3.14);//错误,不能在对b进行覆盖。
可以补充声明,但不能覆盖,就这么简单的定义,书上写的那叫一个及其拗口,读了半天,不如自己试验样例来的实在。
局部变量不能作为默认实参。只要表达式的类型能转换成形参所需的类型,该表达式就能作为默认实参。
用作默认实参的名字在函数声明的作用域内解析,而求值过程发生在函数调用时。
内联函数和constexpr函数
函数当然有各种好处,什么方便啦已读啦安全啦啥的
但是有一个潜在的缺点,调用函数一般比求等价的表达式的值要慢一些。因为一次函数调用包含着一系列的工作
调用前要先保存寄存器,并在返回时恢复;可能还需要拷贝实参等等
就比较慢
这个时候 内联函数登场了。
内联函数,在编译过程会被内敛展开。例子
cout << shorterString(s1, s2)<<endl;
编译时会变成
cout << (s.size()<s2.size() ? s1: s2) << endl;
inline cosnt string &shortString(const string &s1, const string &s2);
内联说明只是向编译器发出的一个请求,编译器可以选择忽略这个请求。
一般用于优化规模较小,流程直接并且频繁使用的函数。据说很多编译器不支持内敛递归函数。
关于constexpr函数
用这个函数有这么几点要求:
- 函数的返回类型只能是字面值类型
- 所有形参类型也只能是字面值类型
- 有且只能有一条return语句。
编译器会把constexpr函数的调用替换成其结果值,为了能在编译过程中随时展开,constexpr函数会被隐式的指定为内联函数。
constexpr函数内也可以包含其他语句,只要这些语句在运行时不执行任何操作就行,比如空语句,类型别名,using声明。
注意:constexpr函数不一定返回常量表达式