C++Primer学习——函数

时间:2023-03-08 22:32:05
C++Primer学习——函数

编译器能以任意顺序对形参进行求值

函数的返回类型不能是数组类型和函数类型。

函数开始时为形参分配内存,一旦函数结束,形参也就被销毁了。

如果弄成静态局部变量,那么回到程序终止结束时才被销毁。

void fo()
{
static int a ; //只在第一次初始化
a++; //保存了前次被调用后留下的值
return ;
}
//所有的全局变量都是静态变量,而局部变量只有定义时加上类型修饰符static,才为局部静态变量

形参类型决定了形参与实参的交互方式

f(int a,int b) 被调用时可以看成  f(int a = x,int b = y);
f(int &a,int &b) 被调用时可以看成 f(int &a = x,int &b = y);

使用引用避免拷贝:

拷贝大的类类型或者容器的对象比较低效,甚至有的类类型不支持拷贝操作

参数的const:

顶层const会被忽略,所以两个fcn的参数是一样的(顶层const没法区分参数)

void fcn(const int a){}
void fcn(int a){}

尽可能使用常量引用:

void find(string &s){
}
void find_char(const string &s){
}
find("hhh"); //error 普通引用不能引用字面值
find_char("hhh"); //ok

但底层const看成不同的参数(作用于不同的对象 常量or非常量)

编译器可以通过实参是否是常量来判断调用哪一个(而且非常量优先选择非常量版本)

Record look(Account *)
Record look(const Account *)
Record look(Account &)
Record look(const Account &)

含可变参数:

Initializer_list:
initializer_list是C++标准程序库中的一个头文件,定义了C++标准中一个非常轻量级的表示初始化器列表的类模板initializer_list及有关函数。
与vector不同的是,initializer_list对象中的元素永远是常量值,我们无法改变initializer_list对象中元素的值。
initializer_list lis(ls);
initializer_list lis = ls; //原始列表和副本共享元素

C++11允许构造函数和其他函数把初始化列表当做参数

void fcn(initializer_list<int> ls)
{
for(auto i : ls)
{
cout << i <<" ";
}
cout << endl;
}
int main()
{
fcn({1,2,3});
return 0;
}
class node
{
public:
node(int a,int b){}
node(initializer_list<int>) {}
node(){};
};
int main()
{
node a;
node b(1,2);
node c{2,3,4,5,6};
node d = {1,3,4,5,6};
return 0;
}

省略符类型:

只是用与C/C++通用类型,大多数类类型在传递省略符形参时都无法正确拷贝

void foo(int a,...)
void foo(...)

返回值:

返回值的方式和初始化类似,返回值会被拷贝到函数的调用点用于初始化,如果返回引用又是另一回事。

而且别返回局部变量指针或者引用,因为函数结束一会后其所占空间会被释放掉

int& fo(int a)
{
int b = a;
cout << &b <<endl;
return b;
}
int main()
{
int &a = fo(100); //a,b地址相同,说明并没有经过拷贝
cout << &a <<endl;
cout << a <<endl;
}

函数的返回类型决定返回是否为左值:

当函数返回引用的时候所得到的是左值,其它所得到的都是右值。

char &to_Get(string& a,int index)
{
return a[index];
}
int main()
{
string ta("abcd");
cout << ta <<endl;
to_Get(ta,1) = 'B';
cout << ta << endl;
/*
abcd
aBcd
*/
}

返回数组指针:

数组因为不能被拷贝,所以函数不能返回数组。可以返回数组的引用和指针,只是比较麻烦.

①.利用别名简化。

typedef int arr[10];
using arr = int[10];
arr* func(int i);

②.声明一个返回指针的函数(从内往外读)

int (*a)[10];
int (*func(int a,))[10]; //格式相似

③.尾置返回类型

在->符号后面指明函数的真正返回类型

auto func(int a) -> int(*)[10]
{
}

④.使用decltype(但是会返回数组类型,而数组又无法赋值,所以搞成指针)

int odd[] = {1,3,5,7};
decltype(odd)* func()
{
return &odd; //返回数组类型的指
}
int main()
{
auto p = func();
cout << p << " " <<odd <<endl;
}

重载:

函数重载:应该在函数参数个数或者形参类型上面有所不同.

如果在当前作用域中找到了所需的函数名,那么编译器会自动忽略外层作用域的同名实体。(不同作用域不能重载函数名)

void Print(string a);
void Print(double a);
void f(int a)
{
void Print(int);
print("abc"); //error:与int不符
Print(2.5); //输出了int
}
void Print(double a){ cout << "double " << endl;}
void Print(int a){cout << "int" << endl;}
void Print(string a){cout << "string" << endl;}

默认实参:

一旦某个形参被赋予了默认实参,那后面全部都要有默认值。 而且函数调用时,只能省略尾部实参

windows = screen(,,'s');      //error
windows = screen('s');

函数可以被声明多次,但是一个形参只能被赋予一个默认值。

string screen(sz,sz,char = ' ');
string screen(sz,sz,char = '?'); //error,重复声明
string screen(sz=24,sz=80,char); //correct,添加默认实参

(局部变量不能作为默认实参)

int pa = 100;
void fo(int a = pa); //默然实参为pa
void f()
{
int pa = 80; //隐藏了外部的pa
fo(); //没改变默认值
}
int main()
{
f();
}
void fo(int a)
{
cout << a <<endl;
}

内联函数:

调用函数比一般的求表达式的值要慢一点,大多数机器上,一次函数调用包含着很多工作:调用前先保存寄存器,并在返回时恢复;

可能需要拷贝实参;程序转下一个新的位置继续执行。

内联函数(优化规模小,流程直接,调用频繁的函数; 有的编译器不支持):

cout<<shorterString(s1,s2)<<endl;
//在编译器看来会转换成
cout << (s1.size() <= s2.size() ? s1:s2) <<endl;

constexpr函数:

编译器把对constexpr函数的调用替换成它的结果了,被隐式地指定为内联函数。

允许返回值并非一个常量?

A (non-template) constexpr function must have at least one execution path that returns a constant expression

//必需要能返回至少一个常量表达式

constexpr int f(bool b) { return b ? throw 0 : 0; }     // OK
constexpr int f() { return f(true); } // ill-formed, no diagnostic required
template<bool B> constexpr int g() { return f(B); } // OK
constexpr int h() { return g<true>(); } // ill-formed, no diagnostic required
http://*.com/questions/31206937/a-constexpr-function-is-not-required-to-return-a-constant-expression\

但如果把函数用在需要常量表达式的上下问时,则编译器会检测函数结果是否符合要求

内联函数和constexpr函数可以在程序中被定义多少,通常被定义在头文件

assert:

一种预处理宏,行为类似内联函数。 assert(expr)//cassert头文件,由预处理器管理,不需要命名空间

assert(word.size() > threshold);

如果定了NDEBUG预处理变量,那么assert什么也不做

参数匹配:

1.与被调用函数同名 2.其声明在调用点可见

3.形参数量相同 4.实参和形参的类型相同,或者能够转换

然后从可行的当中找出最佳匹配

匹配成功:①每个实参的匹配都不劣于其它可行函数的匹配

②至少有一个实参匹配优于其它

否则会产生二义性

void f(int a,int b)
void f(double a,double b)
int main()
{
f(3,2.15);
}

上面这个例子,两个函数中找不到一个脱颖而出的。 所以会产生二义性。

//要避免强制类型转化,出现只能说明我们的形参集合设置的不合理

匹配等级:

①精确匹配:

1.实参和形参的类型相同

2.从数组类型或者函数类型转换它们的对应的指针类型

3.想实参中添加顶层const或者删除顶层const

//2016年11月20日 22:00:24

②通过const转换 (添加底层const:const&什么的)

③通过类型提升 (小类型提升为大类型)

④通过算术类型转换或者指针转换 (运算符的运算对象转换成最宽;0或NULL转换成任意指针,任意非常量对象指针->void,任意对象->const void

⑤类类型转换

void f(float x);
void f(int x);
f(3.45); //double型,而所有算术转换等级相同,产生二义性
void f(int a);
void f(short b);
f('a'); //类型提升,但是会提升为较大范围的int。只有类型是short时才会调用short版本

函数指针:

函数的类型是由它的返回类型和行参类型决定的。 函数可以自动地转换成指针,指针可以调用该函数

bool lengthCompare(const string&,const string &);
bool (*p)(const string&,const string&);
p = lengthCompare;
p = &lengthCompare; //等效,函数名会转换成指针
bool t = p("hello","bye");
bool t = (*p)("hello","bye"); //等效

对于重载函数,编译器会通过指针类型选哪个函数,指针类型必需与重载函数的某一个精确匹配.

函数指针可以作为一个函数的形参。

简化函数指针代码 decltype and typedef:

typedef bool func(const string&,const string&);         //函数
typedef bool (*func)(const string&,const string&); //函数指针
typedef decltype(lengthCompare) func2;
typedef decltype(lengthCompare) *func2;
decltype()会返回一个函数类型而且不会自动转换成指针

返回函数的指针:

using p = int(int*,int );
using tp = int(*)(int*,int ); tp f1(int); //correct tp是指向函数的指针
p f1(int); //error p是函数类型,不能返回一个函数
p* f1(int); //correct
int (*f1(int*,int))(int*,int ); //直接声明一个返回函数指针的函数

首先f1前面*,所以f1返回一个指针;指针类型包含形参列表,所以指针指向一个函数

尾置型:

auto f1(int) -> int (*)(int*,int);