C++ Primer Plus学习笔记:第八章:函数探幽

1.C++的内联函数

内联函数和常规函数的不同在于他如何和程序在合在一起,对于一般函数,我们是在一个函数里面调用一个函数,那么编译器的操作是先把数据存起来,把参数列表参数复制一份,转到被调用函数的地址进行操作,内联函数不同的是他会将被调用的函数的内容直接替换掉调用函数,这意味着节省了一些时间,但是由于替换操作,他浪费了空间,不仅仅是一倍的空间,而是你有几个地方调用它,他就复制了几个副本(所以内联函数不能递归)
要使用内联函数只需要在函数声明和定义的时候前面加入inline即可
用宏实现:
#define也提供了替换操作,如下

#define g(X) X*X
using namespace std;
int main() {
     
	cout << g(50);
}

1.注意先后顺序
2.必须有括号,否则不识别(总不能但凡是一个就给你做替换吧)
3.这里的X是参数标记,不要替换,至于怎么实现的,C++的宏我们学会照猫画虎就可以了
4.宏不能按值传递(书这么写的,但是我可以,网上搜也可以)

#define g(X) X*X

using namespace std;

int work(int t) {
     
	return g(t);
}

int main() {
     
	cout<<work(g(5));
}

2.引用变量

引用相当于取别名,他们的地址是相同的

int t
int& p=t

这里的& 是表示的一部分,当然&还可以被重载为取地址
他和指针长得很像,但是有不同
1.引用不可以为空,但指针可以为空。前面也说过了引用是对象的别名,引用为空——对象都不存在,怎么可能有别名!故定义一个引用的时候,必须初始化。因此如果你有一个变量是用于指向另一个对象,但是它可能为空,这时你应该使用指针;如果变量总是指向一个对象,你的设计不允许变量为空,这时你应该使用引用。
2.而声明指针是可以不指向任何对象,也正是因为这个原因,使用指针之前必须做判空操作,而引用就不必。
3. 其次,引用不可以改变指向,对一个对象"至死不渝";但是指针可以改变指向,而指向其它对象。说明:虽然引用不可以改变指向,但是可以改变初始化对象的内容。例如就++操作而言,对引用的操作直接反应到所指向的对象,而不是改变指向;而对指针的操作,会使指针指向下一个对象,而不是改变所指对象的内容。见下面的代码:
4. 就是数组等的指针是一个起始变量,但是引用是针对整个数组
5. 最后,引用比指针更安全。由于不存在空引用,并且引用一旦被初始化为指向一个对象,它就不能被改变为另一个对象的引用,因此引用很安全。对于指针来说,它可以随时指向别的对象,并且可以不被初始化,或为NULL,所以不安全。const 指针虽然不能改变指向,但仍然存在空指针,并且有可能产生野指针(即多个指针指向一块内存,free掉一个指针之后,别的指针就成了野指针)。

所以尤其注意不在另一个函数中引用了这个数据,然后随意改动

如果出现了引用和传入不匹配的情况呢这种情况

#include 
#include 

using namespace std;
void work(int t,const double& m) {
     
	cout << m+t;
}

int main() {
     
	
	work(1, 'H');
}

正常运行
下面去掉const,不行了
去掉&,可以的
试试在去掉&的情况下加上const,还可以
关于下面两种情况实际上是发生了类型转换,但是为什么引用不可以呢?你要是还相对这个m执行其他操作,编译器没办法处理被引用变量,但是如果写成const,编译器就放心了,就是这样这个就扯出了左右值的问题
变量分为左值和右值,左值指的是可以取地址的变量,右值指的是非左值。二者的根本区别在于能否获取内存地址。可以将左值看作是一个关联了名称的内存位置,允许程序的其他部分来访问它。在这里,我们将 “名称” 解释为任何可用于访问内存位置的表达式。所以,如果 arr 是一个数组,那么 arr[1] 和 *(arr+1) 都将被视为相同内存位置的“名称”。相对而言,右值则是一个临时值,它不能被程序的其他部分访问。

现在又有了一种引用叫做右值引用,
让引用发挥更大优势的应该是对于struct的引用,这样节省了大量的时间和空间,同样也适用const

  • 返回一个引用
    Q:为什么要返回一个引用
    A:为了可以返回的大变量可以快速返回避免复制(错!)
    函数体自动存储里的变量是在栈里的,先进后出,一旦程序结束存储空间释放,怎么可能存的下数据,引用也随之消失,如果你非想这么做,唯一的办法是new一个结构体,那还不如直接返回指针,牢记new出来的是放在堆里要手动释放而自动存储是放在栈里面{}结束自动消失,new和delete一定是一对对出现返回局部变量不仅导致数据错乱,而且也是一个安全漏洞
    A:如果数据是引用进来的,那我可以把它引用出去,这样我就不用new了( 错!)可以这么写,但是你这个引用就是从调用函数哪里来的,你直接用调用函数访问不就完事了吗?
    Q:那到底为什么呢?
    书上说是快速返回避免复制

以我个人体会,最早推出引用,很大程度上是作为指针一部分功能的语法糖(少量只是同作用域下纯粹的别名可被优化掉,但那不是引用最想干的事情):引用初始化时不用取地址,后期使用时不用解引用。还有就是数组类型很多情况下会退化成指针,即int a[3][5];后,大多数情况下再次使用a都是int (*)[5]类型,而非int[3][5]类型,除了定义时之外,也仅有取地址,sizeof等少数操作不会让数组退化成指针,因此引用之前语法没法让函数返回不退化成指针类型的数组。通过指明返回类型为数组的引用,如:int (&f)(int,int)[3][4];

除了某些特殊情况(例如 std::forward
等类型转换工具函数)一般的函数(自由函数)确实没有必要返回引用。但有些时候我们希望一些类的成员函数返回引用,以便于修改类内部的数据。例如
“std::vector、std::map 等 STL 容器的
operator[]”返回容器内数据的引用以方便外部修改。

返回成员变量或者堆变量的引用,方便外部修改。
指针也可以做到,但如果全部返回指针,那还不如直接用C语言。
返回局部变量避免来复制在C++ 11之前是做不到的,不能返回局部变量引用。
C++ 11添加右值引用&&,可以用来避免无意义的内存复制。

这里不能引用的意思是怕会引用出奇奇怪怪的东西不是说编译器报错不让你引用

当然如何避免函数体内部被自动存储创建的数据被删除呢?(下面是书上写的,我不同意,我也懒得敲上去)
1.用new(用指针不就好了)
2.C++ Primer Plus学习笔记:第八章:函数探幽_第1张图片
你相当于隐式调用了new…有什么好说的
这里我提供一个比较好的解决方法:rvo
当然不是没有用,比如要大改程序,这个时候懒得改就直接用引用了

#include 
using namespace std;
class Myclass {
     
	int a;
public:
	Myclass(int m = 0) :a(m) {
     }
	void operator=(const Myclass& oth)
	{
     
		this->a=oth.a;
		
	}
	void set(int dd)
	{
     
		a = dd;
	}

};
int main()
{
     
	Myclass a, b,c;
	int h;
	cin >> h;  
	a.set(h);
	c=b=a;
	return 0;
}

要不要引用改成这个效果

Myclass& operator=(const Myclass& oth)
	{
     
		this->a=oth.a;
                return *this;
		
	}

改成指针

#include 
using namespace std;
class Myclass {
     
	int a;
public:
	
	void set(int dd)
	{
     
		a = dd;
	}

	Myclass* operator=(const Myclass& oth)
	{
     
		this->a = oth.a;
		return this;

	}

};
int main()
{
     
	Myclass a, b;
	int h;
	cin >> h;
	a.set(h);
	b = a;
	Myclass* c = &b;
	return 0;
}


但是无法实现连等,除非所有参数全部指针话要么直接返回消耗内存
懒得改指针,直接复制太消耗内存

再比如有的时候调用函数有需要

struct ss{
     

	friend ostream& operator<<(ostream& os, llint& r) {
     	
		if (r.is_f)os << "-";	
		for (int i = INF - r.lenth; i < INF; i++) {
     		
			os << r.ls[i];
		}
		return os;
	}	
}

int main(){
     
	ss t,p;
	...
	cout<<t<<p
}

这里要返回一个ostream类否则无法连续输出了
发现了吗这里的I/O得连续使用重载和前面的连等重载一样的问题导致不能使用指针,这样可以避免大量复制
 
 

这也是一个引用的大实例,下面细说


3.输入输出部分函数和部分头文件的关系

第六版原书P734
C++ Primer Plus学习笔记:第八章:函数探幽_第2张图片
头文件之间的包含关系如上
因为fstream继承了iostream类所以他可以使用基类iostream类的文件,所以他可以使用基类的方法(但是大概率使用了虚函数)关于继承后面再说,
算了,这些留在17张说吧,懒得写了


4.默认参数

有的时候调用函数的时候参数写不全也可以调用,写法

int work(int a,int b,int c=1){
     
...
}

work(1,2)

这样a,b必须写,c选写
可以在这里写默认参数,也可以在原型那里写默认参数,但是只能在原型或者定义挑一个写,不能两个都写上,原型那里写可以混乱写变量名,比如

int work(int,int,int w=1);
int work(int a,int b,int c){
     
...
}

work(1,2)

这是可以的
最后注意的是默认参数对应的变量只能写在最后面,否则编译器也不知道哪个是跳过了的,下面这个例子就不可以

int work(int a=2,int b,int c=1,int d){
     
...
}

work(1,2,3)

你说这个是那个对应哪个,如果像下面这样写就规避了

int work(int a,int b,int c=2,int d=1){
     
...
}

work(1,2,3)
a=1
b=2
c=3
d=1(默认)

5.函数重载

和多态的区别:
多态是建立在重写的基础之上的,是类与类之间的关系,是发生在不同的类之间的,子类重写父类的方法。实现不同的子类,不同的实现形态。
函数多态(函数重载)是可以使用多个同名函数,存在于同一个程序,针对不同的数据类型实现同一个目的(当然,目的不同也可以,谁也管不着你写什么)
函数重载的关键是函数的参数列表,参数列表相同或者相似无法重载
正确的例子

#include 
using namespace std;

int work(string a) {
     
	cout << "1";
	return 0;
}

bool work(int b) {
     
	cout << "w";
	return false;
}

int main()
{
     
	work(1.123);
	return 0;
}

错误的例子(相似,相同的不必写出来了吧)

#include 
using namespace std;

int work(int a,double b) {
     
	cout << "1";
	return 0;
}

bool work(double a,int b) {
     
	cout << "w";
	return false;
}

int main()
{
     
	int w = 1;
	bool i = work(1,w);
	return 0;
}

过不去,两个函数都需要进行一次强制类型转化,所以不知道选哪个,你要是

	double w = 1;
	bool i = work(1,w);

没问题
多亏C++是编译型语言,要是换成Python就完了

这里留个坑

这个在C++的库中有广泛应用,比如说我可以cout一个int,也可以cout一个double这就是cout重载导致的,

注意不能通过函数的返回值来区分函数,包括const

#include 
using namespace std;

int work(int a) {
     
	cout << "1";
	return 0;
}

bool work(int b) {
     
	cout << "w";
	return false;
}

int main()
{
     
	bool i = work(1);
	return 0;
}

报错

C4305	“初始化”: 从“int (__cdecl *)(int)”到“bool”截断
E0311	无法重载仅按返回类型区分的函数	
E0311	无法重载仅按返回类型区分的函数	
C2556	“bool work(int): 重载函数与“int work(int)”只是在返回类型上不同
参见“work”的声明
C2371	“work”: 重定义;不同的基类型
参见“work”的声明	
C2065	“work”: 未声明的标识符

话说C++编译器是如何处理重载函数的呢?
就像我们可以通过给双胞胎穿不同颜色的衣服区分两个人,我们也可以给同名函数加上不同的ID确定是那个人,C++中称这个为名称修饰或者名称矫正,通过参数列表指定的参数对函数名进行加密

默认参数和函数重载一起使用会导致冲突,看下面的例子:

// C++Test.cpp : 定义控制台应用程序的入口点。
//
 
#include "stdafx.h"
#include 
using namespace std;
 
//函数声明
void DefaultArguTest(int arg1 = 1, int arg2 = 2, int arg3 = 3);
//重载
void DefaultArguTest();
 
int _tmain(int argc, _TCHAR* argv[])
{
     
	//不给参数
	DefaultArguTest();
 
	system("pause");
	return 0;
}
 
//函数定义
void DefaultArguTest(int arg1, int arg2, int arg3)
{
     
	cout<<arg1<<" "<<arg2<<" "<<arg3<<endl;
}

error C2668: “DefaultArguTest”: 对重载函数的调用不明确
1> 可能是“void DefaultArguTest(void)”
1> 或 “void DefaultArguTest(int,int,int)”
对于当我们不给参数的时候,默认的DefaultArguTest和无参数的DefaultArguTest都可能被调用,所以就造成了调用不明确的错误。
仔细想一下,为什么C++的默认构造函数在我们自己定义了构造函数就自动不生成了呢?
个人感觉,有可能是害怕我们自己定义构造函数时,如果加上默认参数,那么就和编译器为我们提供的默认构造函数冲突了,为了防止这种隐患,索性如果自己写了构造函数,那就不生成默认构造函数了。

int fun(int a,int b,int c=0)
fun(5,8)是可以调用上面那个函数的,但如果又存在一个函数int fun(int a,int b)
则,fun(5,8)因不知道调用哪个会出错。


6.函数模板

你可能感兴趣的:(#,C++)