6.14C++primeplus(P208-P230)

1.C++内联函数(inline)

C++为了程序的运行速度,添加了“内联函数”。
那么内联函数和常规函数有什么区别呢?

常规调用函数:执行调用函数,操作指针将会跳转到该函数的内存地址,执行函数代码,执行完毕后,操作指针将会跳转回来。如果函数调用次数多,那么指针的来回跳跃会导致浪费时间。

调用内联函数:如果将函数声明为内联函数,那么编译器将使用相应的函数代码替换函数调用,这样操作指针就不需要跳来跳去,直接执行函数代码即可。但是这样会浪费一定的空间。毕竟要将调用函数都替换为函数代码。

什么时候使用内联函数:首先,如果执行函数代码的时间比处理函数调用机制的时间长(处理函数调用机制就是操作指针要找到调用函数的代码位置,毕竟要执行函数,先要找到他在内存的地址),这样的话,节省的时间很少,相对于兑换的空间,不是很划算。但是如果执行函数代码的时间短,而处理函数调用机制时间长,那么这个时候用内联函数就很划算。但是不管内联函数节省多少时间,如果调用函数次数不够多,那么节省的时间也可以忽略不记。那么总结一下:当函数调用次数多,且执行函数代码的时间很短,处理函数调用机制的时间相对于执行函数代码的时间长,那么就可以使用内联函数。
总结一下: 内联函数是一种空间换时间的措施,加快程序运行速度,但是增加了空间的负担。在使用的时候函数类型前面要加上关键字inline. 这样编译器在执行代码的时候会将调用函数替换成该函数具体实现的代码(相当于在当前执行代码下面开了一个函数副本,可以立即执行而不需要跳转地址)。

2.内联与宏

区别
1.内联是在编译过程中具体实现的,而宏是在预处理阶段实现的。
2.宏的形式参数没有类型,可能会导致传入参数的时候发生错误的比较且没有提示,而内联函数是正儿八经的函数,传入参数错误会有提示
3.内联是函数,宏不是函数。

下面是一个示范以区别内联和宏

#define MAX(a,b) ((a)>(b)?(a):(b))   //宏
MAX(a,"Hello")//错误地比较int和字符串,没有参数类型检查,但是仍然可以编译成功
#include <stdio.h>
 
inline int add(int a, int b)   //内联
{
    return (a + b);
}
 
int main(void)
{
    int a;
 
    a = add(1, 2);   //必须严格按照函数的参数类型
    printf("a+b=%d\n", a);
 
    return 0;
}
以上a = add(1, 2);处在编译时将被展开为:  宏在使用的时候也是展开
a = (a + b);

值得注意的一个方面:使用宏的时候要注意加括号,否则会引起二义性。
下面给一个示例来说明:

#define s(x) x*x
int main()
{
	double a = s(5.0);
	double b = s(4.5 + 7.5);
	cout << a << endl << b << endl;
	return 0;
}

输出:输出a=25.000,b=45.75.
这说明,s(4.5+7.5)的展开式=4.5+7.5 * 4.5+7.5 。然而我们想要的实际上是:(4.5+7.5)*(4.5+7.5)。

那么程序要这样修改:

#define s(x) ((x)*(x))  //正确
//#define s(x) x*x //错误
int main()
{
	double a = s(5.0);
	double b = s(4.5 + 7.5);
	cout << a << endl << b << endl;
	return 0;
}
输出a=25
b=144

3.引用变量

C++新增一种复合类型-----引用变量。引用是以定义的变量的别名。
引用的主要作用是作为函数的形参,这样就不需要将函数的实际参数新增一个副本作为形式参数,而是直接用原来的参数数据,即实际参数和形式参数共用一个内存。

1.创建引用变量

int kengdeji;  //一个整型变量
int &kfc=kengdeji;  //kfc是kengdeji的引用,也就是kengdeji的别名,它们是一个东西,改变kfc的值,kengdeji的值也会改变(在同一个内存里面)

这和指针看起来很像,确实。但是还是有一点区别的。
差别之一是:必须在声明引用的时候将其初始化,而指针可以先声明,以后再初始化。

int rats=101;
int &rodent;
rodent=rat;  //错误,不能赋值,上一行的时候就要初始化

**引用更像const指针。**下面式子几乎等价,一旦引用初始化,就不能改变了,和常量指针作用一样

int &rodents=rats;
int *const pr=&rats;

2.作为函数参数
实际上,将引用变量作为函数参数和指针非常相似。用一个例子来体验一下两者的区别

#include<iostream>
using namespace std;
void swapr(int& a, int& b)  //形参是引用
{
	int temp;
	temp = a;
	a = b;
	b = temp;
}

void swapp(int* const p, int* const q) // 形参是指针
{
	int temp;
	temp = *p;
	*p = *q;
	*q = temp;
}

void swapv(int a, int b)  //形参是值,也是实参的副本
{
	int temp;
	temp = a;
	a = b;
	b = temp;
}

int main()
{
	int wallet1 = 300;
	int wallet2 = 350;
	cout << "wallet =" << wallet1;
	cout << "wallet =" << wallet2 << endl;
	cout << "using reference to swap contents:\n";
	swapr(wallet1, wallet2);
	cout << "wallet1=" << wallet1;
	cout << "wallet2=" << wallet2 << endl;
	cout << "using pointers to swap contents:\n";
	swapp(&wallet1, &wallet2);
	cout << "wallet =" << wallet1;
	cout << "wallet =" << wallet2 << endl;
	cout << "tring to use passing by value:\n";
	swapv(wallet1, wallet2);
	cout << "wallet =" << wallet1;
	cout << "wallet =" << wallet2 << endl;
	return 0;
}

3.引用的属性和特别之处
这里我觉得比较深入,鉴于学习的完整体系,还是记录一下;
首先,将引用变量作为函数的实参没有问题,比如下面

int max(int &a,int &b)
{
	if(a>b)
		return a;
	return b;
}
int main()
{
	int a,b;
	a=3,b=4;
	int c=max(a,b);   //正确
	cout<<c;
	return 0;
}

但是如果通过引用传递值,那么下面这种情况会产生一个二义性

int c=max(a+1,b);  //错误,产生2义,编译报错

这是为什么?首先,如果可以这样做,那么:

(a+1)做为一个整体,传递给形参a,但是a是一个存在内存的变量,(a+1)只是一个表达式,不是一个变量,那么它如果要传递给形参a(引用是传递地址),则说明(a+1)需要临时生成一个变量,然后将该变量的地址值传递给形参,使形参a指向临时变量(a+1),那么在执行调用函数期间,形参a的改变不会影响实参a的改变,毕竟实参a和实参(a+1)是两个不同的变量,分配在不同的内存区域。当函数结束,临时变量(a+1)将会释放。

但是,这样会产生2义,产生2义的关键就在于,我们使用引用的目的就是不在调用函数传递参数的时候生成临时变量,以节省空间时间,但是使用引用参数时却仍然建立了临时变量,这样在调用函数的期间,改变引用变量的值,会产生疑惑:比如改变max函数里a的值,那么main函数里面的a会改变吗?按常规使用来说,传递引用类型,其功能和指针引一样。然而实际上像上面的程序int c=max(a+1,b)这样传递参数,在max函数里面修改a的值,实参a的值不会随之改变。这样岂不是不知道哪个才正确吗?(尽管前面说出了正确的行为)

所以为了避免这种错误,C++规定,如果要使用表达式代替引用变量,则需要在函数参数前加(const)。下面给一个程序:

#include<iostream>
using namespace std;

double max1(int a, int b)
{
	if (a > b)
		return a;
	return b;
}

double min1(int &a, int &b)  //当形参是常量引用的时候,说明,a的值是不能变的,在此期间,const &a等价于const *a。
{
	if (a > b)
	{
		a = a + b; 
		//a = a + b; //可以
		return b;
	}
	return a;
}

double min2(int const& a, int const& b)
{
	if (a > b)
	{
		//a = a + b; //这一句会报错(表达式必须为可修改的左值)
		return b;
	}
	return a;
}
int main()
{
	double ans = max1(1, 2 + 3);
	cout << "max=" << ans << endl;
	int x = 3;
	int y = 2;
	ans = min1(x,y);
	cout << "min1=" << ans << endl;
	//ans = min1(x+2, y);  //报错;非常量引用的初始值必须为左值
	ans = min2(x + 2, y);  //虽然形参是引用,但是不是将(x+2)直接传递,而是将(x+2)的副本作为形参给函数,这是为什么呢?下面会说
	cout << "min2=" << ans << endl;
	ans = min2(1, 2);  //都是表达式,也可以。
	cout << "min2=" << ans << endl;
	return 0;
}

简而言之:如果接受引用参数的函数的意图是修改作为参数传递的变量,则创建临时变量将阻止这种意图的实现。解决方法是:禁止创建临时变量。

const的好处

  • 避免修改不需要修改的数据
  • const变量可以接受const和非const数据,而非const变量只能接受非const数据
  • 使用const引用使函数能够正确生成并使用临时变量

C++11新增了一种引用—右值引用。以后再说;

4. 将引用用于类对象

先看程序:


#include<iostream>
#include<string>
using namespace std;

string version1(const string& s1, const string& s2)  //函数返回值使string类型,说明返回的时候会创建一个临时变量
{
	string temp;  //临时变量,函数结束将销毁
	temp = s1 + s2;
	return temp;
}

const string& version2(string& s1, const string& s2)  //返回值使引用,且不能被改变。这样避免version2(a,b)=c这种情况
{
	s1 = s2 + s1 + s2;
	return s1;
}

const string& version3(string& s1, const string& s2)
{
	string temp;
	temp = s2 + s1 + s2;
	return temp;
}

int main()
{
	string input;
	string copy;
	string result;

	cout << "Enter a string:";
	getline(cin, input);
	copy = input;
	cout << "Your string as entered:" << input << endl;
	result = version1(input, "***");
	cout << "Your string enhanced:" << result << endl;
	cout << "Your original string:" << input << endl;

	result = version2(input, "###");
	cout << "Your string enhanced:" << result << endl;
	cout << "Your original string:" << input << endl;

	cout << "Resetting original string.\n";
	input = copy;
	result = version3(input, "@@@");
	cout << "Your string enhanced:" << result << endl;
	cout << "Your original string:" << input << endl;

	return 0;
}

几个问题讨论:
1.函数version1()的两个形参都是const string &,但是实参的类型是string和const char *。

原因有2;
1.string类定义了一种char *到string的转换功能,这使得C风格字符串来初始化string对象。
2.当函数形参为const的引用,那么,如果实参类型与引用参数类型不匹配,但可以被转换为引用类型,程序将创建一个正确类型的临时变量,使用转换后的实参值来初始化它,然后传递一个指向该临时变量的引用。

2.version3中的临时变量temp

程序试图引用已经释放的内存,这导致程序崩溃。实际上,刚刚释放temp内存的时候,栈空间里面还保持着temp的信息,只是系统将其“释放“,实际上释放空间就是将其标记为无效,尽管里面的内容还存在。当后面有新的语句的时候,将覆盖该内存。既然已经丢失了temp的信息,那么就无法引用。

5.默认参数

C++新增一项内容-----默认参数。
默认参数指的是当函数调用中省略了实参时自动使用的一个值,这个值就是默认参数。

#include<iostream>
using namespace std;

int max(int a = 1, int b=0)  //这里a=1,b=0就是默认变量
{
	if (a > b)
		return a;
	return b;
}

int main()
{
	cout<<max()<<endl;
	cout<<max(2,3)<<emdl;  //2将会覆盖1,3覆盖0
	cout<<max(2)<<endl;  //2覆盖1
	return 0;
}

6.函数重载

函数重载也叫函数多态.是C++新增功能。
函数重载:可以使用一个函数名定义多个不同的函数,只要满足这些同名函数的参数类型或者格个数各不相同即可。
for example:

int max(int a,int b);
int max(int a,int b,int c);
int max(double a,double b);

但是这样有会有一个问题:比如:

#include<iostream>
using namespace std;

int max1(int a, int b)
{
	if (a > b)
		return a;
	return b;
}

int max1(double a, int b)
{
	if (a > b)
		return b;
	return a;
}
int main()
{
	long int a = 1;
	double b = 2.0;
	int c = max1(a, b);   //错误,显示”对重载函数的调用不准确“
	cout << c;
	return 0;
}

也就是编译器产生混乱,到底是调用哪个函数呢?当实参不匹配形参的时候,当可以进行类型转换的情况,编译器将会进行强制类型转换。但是很不巧,这里两个max1都可以进行强制类型转换,那么到底使用哪个?函数表示很迷茫。所以编译报错。所以要严格按照形参来写实参。

再一个值得注意的,函数重载的特征标是参数列表的不同,而不是函数类型的不同。比如下面两个函数是互斥的,不允许这样重载,想要重载,必须使参数列表不一样

int max(int a,int b);
double max(int a,int b);

7.名称修饰

C++使如何判别不同的重载函数的呢?C++编译器在编译程序的时候,会执行一种操作----名称修饰。它根据函数原型中指定的形参类型对每个函数名进行加密以此来描述该函数接口。这样虽然函数名在我们看来是一样的,但是编译器却已经给它们”重命名了“。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值