参数
什么是参数?我们买手机,电脑最喜欢看它们的参数,比如CPU是几核心,频率多少,内存多大等。广义 讲,参数可以理解成一个东西的属性的值。在程序中,参数可以看作是一个变量,变量是计算机的专有名词,变量来源于数学,是计算机语言中能储存计算结果 或 能表示值 抽象概念。
在程序中,参数有一些分类和特性:形式参数,实参,可变参数,默认参数等。
形参(形式参数)
在函数定义中出现的参数可以看做是一个占位符,它没有数据,只能等到函数被调用时接收传递进来的数据,所以称为形式参数,简称形参。
实参(实际参数)
函数被调用时给出的参数包含了实实在在的数据,会被函数内部的代码使用,所以称为实际参数,简称实参。
形参和实参的功能是传递数据,发生函数调用时,实参的值会传递给形参。形参,只有在函数被调用时才会被分配内存,函数执行完成后立即被释放内存,所以一般来说形参变量只在函数内部有效。实参,可以是常量、变量、表达式、函数等,无论实参是何种类型的数据,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参,所以应该提前用赋值、输入等办法使实参获得确定值。一般来说,实参和形参在数量上、类型上、顺序上必须严格一致,否则会发生“类型不匹配”的错误。当然,如果能够进行自动类型转换,或者进行了强制类型转换,那么实参类型也可以不同于形参类型。函数调用中发生的数据传递是单向的,只能把实参的值传递给形参,而不能把形参的值反向地传递给实参;换句话说,一旦完成数据的传递,实参和形参就再也没有瓜葛了,所以,在函数调用过程中,一般,针对普通类型形参的值发生改变并不会影响实参。
默认参数
通常情况下,函数调用时,形参从实参那里取得值。C++给出了可以不用从实参取值的方法,给形参设置默认值。
//一个简单的例子
#include <iostream>
#include <string>
using namespace std;
void weatherForcast(string w = "sunny"){
cout<<"today weather: "<<w<<endl;
}
int main()
{
weatherForcast();
weatherForcast("rainny");
return 0;
}
//执行结果
//today weather: sunny
//today weather: rainny
默认参数的声明顺序
默认参数的个数小于等于形参的个数,且默认的顺序只能从右到左默认,不能跳跃!
默认参数只能在函数声明处,默认值可以 常量,全局变量,或是一个 函数。
可变参数
有时我们需要编写一些在源代码编写阶段无法确定参数个数,有时甚至无法确定参数类型的函数。比如有时候我们写多个函数要对同一类型不同函数个数的参数进行计算。如果有一个东西,可以自动帮我们计算出参数的个数,那么我们就不用重载这么多相似的函数了。它们可以在运行时取任意的实参个数并根据实参的个数自动处理不同实参的情形,或者至少可以在运行时指定任意的实参个数。的确存在这么一个东西,具体实现细节请看后续的函数里面的内容
数组
是内存中连续存储的 一种 同种数据类型的元素。
//一个最简单的数组初始化例子
int n[10] = {}; //这种声明会隐式的将元素初始化为0,这种方式只能用于数组的声明。
//数组的几种堆内存申请操作的写法
//一维数组
int *ar = new int[100]{0};
int **ap = new int*[5]{NULL};
//二维数组
int (*arr)[6] = new int[5][6];
//三维数组
int (*arrr)[5][6] = new int[3][5][6];
//释放数组
delete [] ar;
delete [] ap;
delete [] arr;
delete [] arrr;
//申请指针数组
char **rpp = new char*[5];
rpp[0] = "china";
rpp[1] = "sichuan";
rpp[2] = "shenzhen";
rpp[3] = "google";
for(int i = 0;i < 5;i++){
cout<<rpp[i]<<endl;
}
int a,b,c;
int *pArr[] = {&a, &b, &c};
数组名存在的意义?
数组名是数组中首元素的地址,本质是一个常量指针。
C++中数组没有边界检查,边界检查可防止计算机引用不存在的元素
将数组传递给函数时,应该同时传递数组的大小,而不是让函数确定数组的大小,这可使函数更具有通用性
按引用传递数组能提高效率
函数
函数是一组一起执行一个任务的语句。每个 C++ 程序都至少有一个函数,即主函数 main() ,所有简单的程序都可以定义其他额外的函数。C++中关于函数,可以知道有内置函数(标准库提供),自定义函数,内联函数,匿名函数(Lambda 函数),仿函数等。调用函数的方式一般有:传值,指针,引用三种调用方式。这里单独说一点,个人认为可以参考其它高级语言的一个叫法上的区分:如果一个函数位于一个类中,则应该把这个函数叫做是这个类的方法。
有两个用法在我们实际工作中经常用到:
函数指针:
包含函数在内存中的地址。函数名实际上是执行这个函数的任务的代码在内存中的起始地址。函数指针可以传给函数,从函数返回,保存在数组中,赋予另一个函数指针或调用底层函数。
//用法
int (*f) (int x); /* 声明一个函数指针 */
f=func; /* 将func函数的首地址赋给指针f */
函数指针数组:
一个用法是出现在菜单驱动系统中。例如:程序可以提示用户通过输入一个整数值来选择菜单中的一个选项。用户的选择可以用作函数指针数组的下标,而数组中的指针可以用来调用函数
实现可变参数的函数:
请看作者:https://blog.csdn.net/qq_35280514/article/details/51637920,已经写的很详细了,所以不再过多说明。
在C++中实现一个变参函数的方法有三种:第一种方法,将函数形参声明为C++11新标准中的initializer_list标准库类型;第二种方法继承自C语言,形参声明为省略符,函数实现时用参数列表宏访问参数;最后一种方法利用C++泛型特性,声明一个可变参数模板来实现。把上述链接的作者的例子代码贴出来:
方式2:(不安全,不推荐)
#include <iostream>
#include <stdarg.h>
using namespace std;
int sum(int count, ...); //count 表示可变参数的个数
int sum(int count, ...){
va_list ap;
va_start(ap, count);
int sum = 0;
for(int i = 0;i < count;i++)
sum += va_arg(ap, int);
va_end(ap);
return sum;
}
int main()
{
int ret = 0;
ret = sum(10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
cout<<ret<<endl; //55
return 0;
}
方式1:
#include <iostream>
#include <initializer_list>
using namespace std;
int sum(initializer_list<int> il);
int sum(initializer_list<int> il){
int sum = 0;
for(auto p = il.begin(); p != il.end(); p++)
sum += *p;
return sum;
}
int main()
{
int ret = 0;
initializer_list<int> ts = {1, 2, 3, 4, 5, 6};
ret = sum(ts);
cout<<ret<<endl; //21
return 0;
}
方式3:
#include <iostream>
#include <initializer_list>
using namespace std;
template <typename T>
std::ostream &print(std::ostream &os, const T &t){
return os << t;
}
template <typename T, typename... Args>
std::ostream &print(std::ostream &os, const T &t, const Args &... rest){
os << t<< ",";
return print(os, rest...);
}
int main()
{
return 0;
}
内联函数:
C 语言中有宏函数的概念。宏函数的特点是内嵌到调用代码中去,避免了函数调用的开销(压栈出栈)。但是由于宏函数的处理发生在预处理阶段,缺失了语法检测和有可能带来的语意差错。如何让函数,即有宏函数的快速也有普通函数的优点呢,那就是内联函数,内联函数 的生成,只需要在普通函数的前面加 inline关键字即可。
优点: | 避免调用时的额外开销(入栈出栈) |
代价: | 由于内联函数的函数体在代码段中会出现多个“副本”,因此会增加代码段的空间 |
本质: | 以牺牲代码段空间为代价,提高程序的运行时效率 |
适用场景: | 函数体很 小,且被 频繁 调用 |
inline int sqr(int x) { return x*x; }
匿名函数:
学过python的同学应该知道匿名函数,看上去没有函数名,只有一个函数体。简短的函数,就地书写。lambda是匿名函数的英文翻译,也是一个表达式。先看看lambda表达式的结构:[capture](paras)mutable->returntype{statement}。
[capture]:捕获列表。总是出现在 lambda 函数的开始处。事实上 [] 是 lambda 的引用符。换句话说,编译器根据引出符判断接下来的代码是否是lamba函数。
(paramers):参数列表。与普能函数的参数列表一致。如果不需要传递参数,可以 连同()一起省略。
mutable:默认情况下,lambda函数总是一个 const 函数,mutable 可以取消其常 量性。在使用该修饰符时,参数列表不可以省略(即使参数为空)。
->return-type:返回类型。用于追踪返回类型形式声明函数的返回类型。出于方便, 不需要返回值的时候可以连同->一起省略。此外返回类型明确的情况下,也可以省略该部分。
{statement}:函数体。内容与普通函数一样,不过除了可以使用参数之外,还可以使用所有捕获的变量。
//lambda
//lambda函数总是一个const函数
//mutable: 可以取消其常量性
#include <iostream>
using namespace std;
int main()
{
/*[]{} 闭包+函数体 */
auto foo = []{ return 1 +2;};
cout<<foo()<<endl; //3
cout<<[]{ return 1+2;}()<<endl; //3
/* [](){} 闭包+参数+函数体 */
auto foo1 = [](int x, int y){ return x+y;};
cout<<foo1(1,2)<<endl; //3
cout<<[](int x, int y){ return x+y;}(1, 3)<<endl; //4
/* []()->{}闭包+参数+返回值+函数体 */
auto foo2 = [](int x, int y)->int{ return x+y ;};
cout<<foo2(1, 2)<<endl; //3
cout<<[](int x, int y)->int{ return x+y;}(2, 4)<<endl; //6
int x = 10; int y = 100;
cout<<"main:"<<x<<y<<endl; //10100
/* []()mutable->{}闭包+参数+可修改+返回值+函数体 */
auto foo3 = [=]()mutable{
x = 20;
y = 200;
cout<<"lambda:"<<x<<y<<endl; //20200
};
foo3();
cout<<"main:"<<x<<y<<endl; //10100
return 0;
}
多嘴一句 []闭包:
lambda函数能够捕获 lambda函数外的具有自动存储时期的变量。函数体与这些变 量的集合合起来叫闭包。闭包的概念在lambda中通过[]来体现出来。
- [] 不截取任何变量。
- [bar] 仅对外部变量 bar值传递在函数体中使用。
- [&bar] 仅对外部变量 bar引用传递在函数体中使用。
- [x,&y] 仅x按值传递,y按引用传递在函数体中使用。。
- [&} 截取外部作用域中所有变量,并作为引用传递在函数体中使用。
- [=] 截取外部作用域中所有变量,并按值传递在函数体中使用。
- [=,&foo] 截取外部作用域中所有变量,并值传递在函数体中使用,但是对 foo 变量使用引用传递。
- [&,=foo] 截取外部作用域中所有变量,在函数体中作引用传递使用,但是对 foo变量作值传递。
上述中涉及到 值传递 要发生 拷贝 行为,而引用传递则不会发生拷贝行为。捕获列表中不允许重复。比如:[=,a] [&,&this]。 闭包的本质,初始化lamda表达式。
仿函数
cpp官方及其它作者已有详细解释请跳转阅读:
https://blog.csdn.net/K346K346/article/details/82818801
定义:仿函数(Functor)又称为函数对象(Function Object)是一个能行使函数功能的类。仿函数的语法几乎和我们普通的函数调用一样,不过作为仿函数的类,都必须重载operator()运算符。因为调用仿函数,实际上就是通过类对象调用重载后的operator()运算符。
Demo1:
//仿函数例子1
#include <iostream>
using namespace std;
class Add{
public:
int operator()(int x, int y){
return x + y;
}
};
int main()
{
int a = 1, b = 2;
Add add;
cout<<add(a, b)<<endl; //3
return 0;
}
Demo2:
//带状态的 functor
//相对于函数,仿函数可以拥有初始状态,一般通过class定义私有成员,并在声明对象的时候,
//进行初始化。私有成员的状态,就成了仿函数的初始状态。而由于声明一个仿函数对象可以拥有多个不同初始状态的实例
#include <iostream>
using namespace std;
class Tax{
public:
Tax(float r, float b):_rate(r), _base(b){}
float operator()(float money){
return (money - _base)*_rate;
}
private:
float _rate;
float _base;
};
int main()
{
Tax high(0.40, 30000);
Tax mid(0.25, 20000);
Tax low(0.12, 10000);
cout<<"大于3W的税:"<<high(37500)<<endl;
cout<<"大于2W的税:"<<mid(27500)<<endl;
return 0;
}
仿函数还和lambda合作使用:
#include <iostream>
using namespace std;
class Tax{
public:
Tax(float r, float b):_rate(r), _base(b){}
float operator()(float money){
return (money - _base)*_rate;
}
private:
float _rate;
float _base;
};
int main()
{
float rate = 0.40;
float base = 30000;
auto high = [&](float money){
return (money - base)*rate;
};
//Tax high(0.40, 30000);
//Tax mid(0.25, 20000);
//Tax low(0.12, 10000);
cout<<"大于3W的税:"<<high(37500)<<endl;
//cout<<"大于2W的税:"<<mid(27500)<<endl;
return 0;
}