C++内联函数
参考上图: 常规函数在内存中执行的效果是这样的: 运行程序的时候, 操作系统会将指令载入到计算机内存中, 因此每条指令都有特定的内存地址. 当执行到函数的时候, 程序会从当前位置跳到函数的地址, 并在函数指向结束后, 重新返回到当前位置(就像我们阅读文章时遇到脚注, 会跳到脚注看完后, 返回到脚注继续往后看一样).
C++内联函数提供了另一种选择, 内联函数的编译代码与其他程序代码内联起来, 也就是编译器会将使用相应的函数代码替换函数调用, 也就是说当我们在10个不同的地方调用了同一个内联函数, 那么改程序将包含内联函数的10个副本, 也就是会消耗更多的空间.
要使用内联函数, 必须要有以下步骤:
在函数声明前加上关键字inline;
在函数丁以前加上关键字inline
通常的做法是省略原型, 将整个定义(即函数头和函数体)放在本应该写原型的地方
注意一点, 内联函数不能递归
看一个demo:
// 内联函数
#include <iostream>
using namespace std;
// 内联函数
inline double square(double x)
{
return x * x;
}
int main()
{
double a, b;
double c = 13.0;
a = square(c);
b = square(1.2 + 1.8);
cout << "a = " << a << ", b = " << b << endl;
cout << "c = " << c << endl;
// 先对c进行平方, 在对c+1
cout << "c squared = " << square(c++) << endl;
cout << "Now c = " << c << endl;
return 0;
}
运行结果为:
引用变量
引用变量的主要作用是用作函数的形参, 通过将引用变量作为参数, 函数将使用原始数据, 而不是副本. 这样除了指针之外, 引用也为函数处理大型结构提供了一种非常方便的途径.
创建引用变量
C/C++使用&符号来表示变量的地址, C++给&符号赋予了另一个含义, 将其用来声明引用. 例如:
int rats;
// 将rodents声明成一个rats的引用(别名)
int & rodents = rats;
其中&不是地址运算符, 而是类型标识符的一部分. 就像声明char*指的是指向char的指针一样, int &值得是指向int的引用. 这个引用声明允许将rats和rodents互换--他们指向相同的值和内存单元.
看一个引用变量的简单demo
#include <iostream>
using namespace std;
int main()
{
int rats = 100;
// int和&之间的空格可以去掉
// rodents是rats的一个引用
int& rodents = rats;
cout << "rats = " << rats;
cout << ", rodents = " << rodents << endl;
rodents++;
cout << "rats = " << rats;
cout << ", rodents = " << rodents << endl;
cout << "rats address = " << &rats;
cout << ", rodents address = " << &rodents << endl;
return 0;
}
运行结果:
从运行情况可以开出来,rats和rodents的地址和值都相同。
我们从运行效果来看引用和指针感觉很一样,例如:
int rats = 10;
// 创建一个rats的引用rodents
int & rodents = rats;
// 创建一个指向rats的指针parts
int * parts = &rats;
这样rodents,*parts都能和rats互换也就是一样;而&rodents,parts都能和&rats互换。
从这一点上来看引用看上去很像指针。实际上引用还是不同于指针的:
引用必须在声明的时候就进行初始化,而不能像指针那样先声明再赋值
int rat;
int & rodent;
// 这样是非法的
rodent = rat;
引用更接近于const指针,必须在创建的时候初始化,一旦与某个变量关联起来,就将一直和它绑定:
int & rodents = rats;
实际上是下面代码的伪装表示:
// 是一个常量指针
int * const pr = &rats;
其中rodents扮演的角色和表达式*pr相同。
再来看下面的demo
#include <iostream>
using namespace std;
int main()
{
int rats = 100;
// 定义一个rats的引用
int & rodents = rats;
cout << "rats = " << rats;
cout << ", rodents = " << rodents << endl;
cout << "rats address = " << &rats;
cout << ", rodents address = " << &rodents << endl;
int bunnies = 50;
// 虽然这个赋值语句不报错,但是会有一点问题
// 这个实际是将bunies的值赋值给了rats,因为rodents就是rats
rodents = bunnies;
cout << "bunnies = " << bunnies;
cout << ", rats = " << rats;
cout << ", rodents = " << rodents << endl;
cout << "bunnies address = " << &bunnies;
cout << ", rodents address = " << &rodents << endl;
// 看下面这个代码
// 创建一个指针指向rats
int * pt = &rats;
// 创建一个*pt的引用,也就是int & rodents2 = rats
int & rodents2 = *pt;
int temp = 10;
// 将指针pt指向新的int型变量temp
pt = &temp;
// 输出结果
cout << "*pt = " << *pt << endl;
cout << "rodents2 = " << rodents2 << endl;
cout << "rats = " << rats << endl;
return 0;
}
运行结果为:
最初rodents引用的是rats,随后程序将rodents作为bunnies的引用
rodents = bunnies;
乍一看这个赋值是没问题的,因为rodents的值从100变成了50,但仔细看输出发现rats也变成了50,同时rats和rodents的地址相同,并且和bunnies的地址不同。这是因为rodents是rats的别名,因此实际上是
rats = bunnies;
使用引用变量的时候, 可以通过初始化声明来设置引用, 但是不能通过赋值来设置.
从输出结果看的出来
虽然pt改变了指向,指向了新的变量temp,但是不能改变这样的实事,即rodents2的引用是rats。
将引用用作函数参数
引用经常被用作函数参数, 使得函数中的变量名称为调用程序中的变量的别名, 这种传递参数的方法称为按引用传递.
#include <iostream>
using namespace std;
// 声明一个函数, 参数为引用变量, 也就是能够使用调用函数中的变量
void swap1(int & a, int & b);
// 声明一个函数, 参数为两个int型的指针, 这样也能够访问调用函数中的变量
void swap2(int * p, int * q);
// 声明一个函数, 参数为两个int型变量, 是值传递, 不能真正改变函数中的两个变量
void swap3(int a, int b);
int main()
{
int value1 = 100;
int value2 = 200;
cout << "vlaue1 = " << value1 << endl;
cout << "value2 = " << value2 << endl;
cout << "Using references to swap content: " << endl;
// 使用引用参数进行交换
swap1(value1, value2);
cout << "vlaue1 = " << value1 << endl;
cout << "value2 = " << value2 << endl;
cout << "Using pointers to swap content: " << endl;
// 使用指针进行交换, 需要使用&, 传递变量的地址
swap2(&value1, &value2);
cout << "vlaue1 = " << value1 << endl;
cout << "value2 = " << value2 << endl;
cout << "Using values to swap content: " << endl;
// 使用至传递进行交换
swap3(value1, value2);
cout << "vlaue1 = " << value1 << endl;
cout << "value2 = " << value2 << endl;
return 0;
}
void swap1(int & a, int & b)
{
int temp;
temp = a;
a = b;
b = temp;
}
void swap2(int * a, int * b)
{
int temp;
temp = *a;
*a = *b;
*b = temp;
}
void swap3(int a, int b)
{
int temp;
temp = a;
a = b;
b = temp;
}
程序运行结果为:
可以看出使用引用和指针作为参数的函数成功的把调用函数中的参数进行了交换, 但是使用值传递的函数却没有完成交换,
引用的属性和特别之处
1.使用引用参数时, 要注意引用是会修改调用函数中原数据的, 例如:
#include <iostream>
using namespace std;
double cube(double a);
double recube(double & a);
int main()
{
double x = 3.0;
cout << cube(x);
cout << ", Now x = " << x << endl;
cout << recube(x);
cout << ", Now x = " << x << endl;
return 0;
}
double cube(double a)
{
a *= a * a;
return a;
}
double recube(double & a)
{
a *= a * a;
return a;
}
程序运行结果:
因此如果程序的意图是让函数使用传递给它的信息, 而不对这些信息进行修改的话, 尽量使用值传递, 而如果想使用引用, 则应该使用常量引用例如:
double recube(const double & a);
这时候如果在recube中修改a的数值, 编译器就会报错
按值传递的函数, 如上个例子中的cube(), 可以使用多种类型的实参, 例如:
double z = cube(x + 2.0);
z = cube(8.0);
int k = 10;
z = cube(k);
double arr[3] = {1.1, 1.2, 1.3};
z = cube(arr[1]);
如果将类似上面的参数传递给接收引用参数的函数, 就会发现会出问题, 因为, 如果引用参数是一个变量的别名, 那么实参应该是该变量, 因此下面的代码将会报错:
recube(x + 3.0);
临时变量, 引用参数和const
如果实参与引用参数不匹配, c++将生成临时变量. 当且仅当参数为const引用时, c++才允许这样做, c++在什么时候才会创建临时变量呢?
1.实参的类型正确, 但不是左值
2.实参的类型不正确, 但可以转换为正确的类型
左值:是可被引用的数据对象, 例如: 变量, 数组元素, 结构成员, 引用和解除引用的指针. 非左值包裹字面常量(用引号括起来的字符串除外, 它们是由其地址表示), 和包含多项的表达式
看下面这段代码:
doubel recube(const double & a)
{
return a * a * a;
}
使用上述方法的时候, 如下的调用:
double s = 3.0;
double * pd = &s;
double & rd = s;
long edge = 5L;
double arr[5] = {1.1, 1.2, 1.3, 1.4, 1.5};
// a 是s, 不用创建临时变量
doubel c1 = recube(s);
// a 是arr[2], 不用创建临时变量
doubel c2 = recube(arr[2]);
// a 是s, 不用创建临时变量
doubel c3 = recube(rd);
// a 是s, 不用创建临时变量
doubel c4 = recube(*pd);
// a 是临时变量, 因为edge不是double类型的
doubel c5 = recube(edge);
// a 是临时变量, 因为7.0是字面值
doubel c6 = recube(7.0);
// a 是临时变量, 因为s + 3.0是没有名称的
doubel c6 = recube(s + 3.0);
需要注意几种使用临时变量的情况
为什么对于常量引用, 这种行为是可行的呢, 因为如果不是常量引用, 那么意味着会改变原函数中的变量的值, 那么即使创建临时变量赋值给引用, 引用改变的也是临时变量的值, 临时变量在方法执行完毕后会被销毁, 因此没有效果.
应尽可能使用const
将引用参数声明为常量数据的引用有以下好处:
1.使用const可以避免无意中修改数据的变成错误;
2.使用const使函数能够处理const和非const实参, 否则只能接收非const数据.
3.使用const引用使函数能够正确生成并使用临时变量.
C++11新增了另一种引用--右值引用. 这种给引用可指向右值, 是使用&&声明的:
// 不能使用double &
double && rref = sqrt(36.0);
double j = 15.0;
double && jref = 2.0 * j + 36.0;
// 6.0
cout << rref;