C++函数知识概要总结(二)

关于返回类型和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函数不一定返回常量表达式

发布了75 篇原创文章 · 获赞 26 · 访问量 7654

猜你喜欢

转载自blog.csdn.net/qq_40962234/article/details/104703213