欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C++系统学习之六:函数

程序员文章站 2022-04-14 15:14:13
1、函数基础 典型的函数定义包括:返回类型、函数名、由0个或多个形参组成的列表以及函数体。 2、参数传递 形参初始化的机理和变量初始化一样。 有两种方式:引用传递和值传递 2.1 传值参数 当形参是非引用类型时,形参初始化和变量初始化一样,将实参的值拷贝给形参。 指针形参 当执行指针拷贝操作时,拷贝 ......

1、函数基础

  典型的函数定义包括:返回类型、函数名、由0个或多个形参组成的列表以及函数体。

2、参数传递

  形参初始化的机理和变量初始化一样。

  有两种方式:引用传递和值传递

2.1 传值参数

  当形参是非引用类型时,形参初始化和变量初始化一样,将实参的值拷贝给形参。

指针形参

  当执行指针拷贝操作时,拷贝的是指针的值,拷贝之后,两个指针是不同的指针。但通过指针可以修改它所指的对象。

2.2 传引用参数

使用引用避免拷贝

  拷贝大的类类型对象或者容器对象比较低效,甚至有的类类型根本就不支持拷贝操作。当某种类型不支持拷贝操作时,函数只能通过引用形参访问该类型的对象。

使用引用形参返回额外信息

2.3 const形参和实参

  当用实参初始化const形参时会忽略顶层const。因此,当形参有顶层const时,传给它常量对象或者非常量对象都是可以的。

void fun1(const int i){.......}

void fun2(int i){.....}

  上述两个函数不能算是重载,两个函数是一样的,程序会报错,fun2重复定义了fun1.

指针或引用形参与const

  可以使用非常量初始化一个底层const对象,但是反过来不行。同时一个普通的引用必须用同类型的对象初始化。

C++系统学习之六:函数

尽量使用常量引用

2.4 数组形参

数组有两个重要的特性:

  • 不允许拷贝
  • 使用数组时会转换成指针

尽管不能以值传递的方式传递数组,但是可以将形参写成类似数组的形式

void print(const int*);
void print(const int[]);
void print(const int[10]);

以上三种形式的声明等价

NOTE:当函数不需要对数组元素执行写操作的时候,数组形参应该是指向const的指针。只有当函数确实要改变元素值的时候,才把形参定义成指向非常量的指针。

当数组作为函数形参时,因此应该提供一些额外信息来确定数组的确切尺寸,管理数组形参有三种常用的技术:

使用标记指定数组长度

  要求数组本身包含一个结束标记。例如C风格字符串以空字符结尾。

使用标准库规范

  传递指向数组首元素和尾元素的指针。

void print(const int *beg,const int *end)
{
     while(beg!=end)
    {
         cout<<*beg++<<endl;   
    }  
}    
int arr[2]={0,1};
print(begin(arr),end(arr));

显式传递一个表示数组大小的形参

  专门定义一个表示数组大小的形参。

void print(const int ia[], size_t size);

int j[]={0,1};

print(j, end(j)-begin(j));

数组引用形参

  形参可以是数组的引用,此时,引用形参绑定到对应的实参上,也就是绑定到数组上。

void print(int (&arr)[10])
{
    for(auto elem:arr)
    {   
        cout<<elem<<endl;
    }
}
形参是数组的引用,维度是类型的一部分

NOTE:arr两端的括号必不可少

f(int &arr[10]);    //错误,将arr声明成了引用的数组
f(int (&arr)[10]);    //正确,arr是具有10个整数的整型数组的引用

传递多维数组

  数组第二维的大小都是数组类型的一部分,不能省略。传递多维数组传递的是指向数组的指针,实际还是指向首元素的指针。(多维数组就是数组的数组,数组的首元素还是数组,所以是指向数组的指针)。

void print(int (*matrix)[10],int size);  matrix是一个指针,指向有10个整数的数组

也可以用:

void print(int matrix[][10],int size);  matrix和上面一样的意义

2.5 含有可变形参的函数

C++提供两种方法:

实参类型相同,可以传递一个名为initializer_list的标准库类型

initializer_list形参

C++系统学习之六:函数

lnitializer_list和vector一样都是模板类型,不同的是initializer_list对象中的元素永远是常量值,不能改变。

void error_msg(initializer_list<string> ls)
{
	for (auto beg = ls.begin(); beg != ls.end(); ++beg)
	{
		cout << *beg << "   ";
	}
	cout << endl;
}

error_msg({ "hello" });
error_msg({ "hello!", "world!!" });  //注意值的传递要放在花括号里

省略符形参

  省略符形参是为了便于C++程序访问某些特殊的C代码而设置的。通常,省略符形参不应用于其他目的。省略符形参只能出现在形参列表的最后一个位置。

实参类型不同,使用可变参数模板

3、返回类型和return语句

3.1 无返回值函数

  返回类型是void类型的函数

3.2 有返回值函数

值是如何被返回的

  返回一个值的方式和初始化一个变量或形参的方式完全一样:返回的值用于初始化调用点的一个临时量,该临时量就是函数调用的结果。

 不要返回局部对象的引用或指针

返回类类型的函数和调用运算符

auto sz=getstring().size();    //getstring返回的string对象再调用size函数

 引用返回左值

调用一个返回引用的函数得到左值,其他返回类型得到右值。

 列表初始化返回值

函数可以返回花括号包围的值的列表。

vector<string>  process()
{
    return {"ni","hao"};
}

 递归

如果一个函数调用了它自身,不管这种调用是直接还是间接的,都称该函数为递归函数。

int factorial(int val)
{
    if(val>1)
        return factorial(val-1)*val;
    return 1;
}
求1x2x3x4......

 3.3 返回数组指针

因为数组不能被拷贝,所以函数不能返回数组。不过,函数可以返回数组的指针或引用。

最直接的方法是使用类型别名

typedef int arrT[10];
using arrT=int[10];

 声明一个返回数组指针的函数

int arr[10];    //arr是一个含有10个整数的数组
int *p1[10];    //p1是一个含有10个整型指针的数组
int (*p2)[10]=&arr;    //p2是一个指针,其指向一个有10个整数的数组

 如果要定义一个返回数组指针的函数,则数组的维度必须跟在函数名字之后,并且函数的形参列表应该先于数组的维度。

int (*func(int a,int b))[10];

 此函数返回的是一个指向有10个整数数组的指针。

 使用尾置返回类型

任何函数的定义都能使用尾置返回,但是这种形式对于返回类型比较复杂的函数最有效,比如返回类型是数组的指针或引用。

尾置返回类型跟在形参列表后面并以一个->符号开头。为了表示函数真正的返回类型跟在形参列表之后,我们在本应该出现返回类型的地方放置一个auto。

auto func(int i)->int (*)[10];

 使用decltype

4、函数重载

如果同一作用域内的几个函数名字相同但形参列表不同,称为函数重载。注意必须是形参列表不同,仅仅只是返回类型不同不可以称为重载。

重载和const形参

顶层const不影响传入函数的对象。一个拥有顶层const的形参无法和另一个没有顶层const的形参区分开来。

int f1(int i);
int f1(const int i);    //不构成重载,重复声明了f1

int f2(int *i);
int f2(int *const i);    //不构成重载,重复声明了f2

但底层const不同,可以构成重载

int f1(int &i);
int f1(const int &i);    //重载,新函数

int f2(int *i);
int f2(const int *i);    //重载,新函数

NOTE:最好只重载那些确实非常相似的操作。

const_cast和重载

const_cast在重载函数的情景中最有用。

const string &shorterString(const string &s1, const string &s2)
{
	return s1.size() <= s2.size() ? s1 : s2;
}

string &shorterString(string &s1, string &s2)
{
	auto &r = shorterString(const_cast<const string&>(s1), const_cast<const string&>(s2));

	return const_cast<string&>(r);
}

4.1 重载与作用域

如果在内层作用域中声明名字,它将隐藏外层作用域中声明的同名实体。

void func()
{    
}

int main()
{    
    int func=0;
    func();    //错误,此时func是int类型的变量,不是函数,隐藏了外层的函数定义
    return 0;
}

5、特殊用途语言特性

  默认实参、内联函数和constexpr函数。

5.1 默认实参

一旦某个形参被赋予了默认值,它后面的所有形参都必须有默认值。

string  screen(int i=10, int a=1, stirng s=" ");

使用默认实参调用函数

在调用函数的时候省略该实参就可以。

默认实参声明

在给定的作用域中一个形参只能被赋予一次默认实参。

默认实参初始值

局部变量不能作为默认实参。除此之外,只要表达式的类型能转换成形参所需的类型,该表达式就能作为默认实参。

5.2 内联函数和constexpr函数

调用函数一般比求等价表达式的值要慢一些。

内联函数可避免函数调用的开销

在函数的返回类型前面加上关键字inline。

一般来说,内联机制用于优化规模较小、流程直接、频繁调用的函数。

constexpr函数

constexpr函数是指能用于常量表达式的函数。

定义constexpr函数要遵循:函数的返回类型及所有形参的类型都得是字面值类型,而且函数体中必须有且只有一条return语句。

constexpr int new_sz()
{    
    return 42;
}

constexpr int foo=new_sz();    //foo是一个常量表达式

为了能在编译过程中随时展开,constexpr函数被隐式地指定为内联函数。

NOTE:constexpr函数不一定返回常量表达式。

把内联函数和constexpr函数放在头文件内

和其他函数不一样,内联函数和constexpr函数可以在程序中多次定义。不过,对于某个给定的内联函数或者constexpr函数来说,它的多个定义必须完全一致。因此,内联函数和constexpr函数通常定义在头文件中。

5.3 调试帮助

两项预处理功能:assert和NDEBUG

assert预处理宏

assert宏常用于检查“不能发生”的条件。

assert(expr);

 如果expr为假,assert输出信息并终止程序执行,如果为真,assert什么也不做。

 NDEBUG预处理变量

assert的行为依赖于一个名为NDEBUG的预处理变量的状态。如果定义了NDEBUG,则assert什么也不做,默认情况下没有定义NDEBUG。

可以使用#define语句定义NDEBUG,从而关闭调试状态。

 6、函数指针

函数指针指向的是函数而非对象。和其他指针一样,函数指针指向某种特定类型。函数的类型由它的返回类型和形参类型共同决定,与函数名无关。

int func(int a, string s);

该函数的类型是int(int , string).要想声明一个可以指向该函数的指针,只需要用指针替换函数名即可。

int (*p)(int ,string )  //未初始化

 NOTE:*p的括号必须加上

使用函数指针

当把函数名作为一个值使用时,该函数自动地转换成指针。

int (*p)(int ,string )=func;

 可以使用函数指针直接调用该函数,而不需要解引用该指针。

指向不同函数类型的指针之间不存在相互转换,可以给函数指针赋值nullptr和0,表示指针没指向任何一个函数。

 重载函数的指针

如果定义了指向重载函数的指针,编译器通过指针类型决定选用哪个函数,指针类型必须与重载函数中的某一个精确匹配。

函数指针形参

和数组类似,虽然不能定义函数类型的形参,但是形参可以是指向函数的指针。可以直接把函数作为实参使用,此时它会自动转换成指针。

C++系统学习之六:函数

返回指向函数的指针

将auto和decltype用于函数指针类型

注意将decltype用于函数名时,返回的是函数类型,而非指针类型,如果要表示函数指针,需要自己加上*。