前言
- 参考前章内容【我的C++入门之旅】(上)
1.引用
取别名,一块空间有多个名字或者说是一个变量有多个名字
比如:李逵,在家称为"铁牛",江湖上人称"黑旋风"。
我们可以看到下面a的地址和b的地址是一样的
也就是说a就是b,b就是a
#include <iostream>
using namespace std;
int main()
{
int a = 1;
int& b = a;//&跟类型在一起的适合叫引用符
cout << &a << endl << &b << endl;
return 0;
}
结果:
引用在定义时必须初始化
引用一旦引用实体,再不能引用其他实体
int main()
{
int a = 1;
int& b = a;
int c = 10;
b = c;//这行代码是给c取了个名字叫b吗?还是把c的值赋给b
cout << a << endl << b << endl;
return 0;
}
显而易见b = c的意思是把c的值赋给b
引用做参数(输出型参数)
引用做参数(提高效率)(大对象/深拷贝类对象)
引用作为返回值
但如果返回的那个别名的值空间归还给操作系统了,就会越界访问,相当于野指针。
平常写的交换两个数,使用指针,还要解引用
void Swap(int* left, int *right)
{
int tmp = *left;
*left = *right;
*right = tmp;
}
用引用作为参数
void Swap(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
引用作为返回值:
没有被static修饰的n:
我们知道每个函数都有自己的函数栈帧,出了栈帧,就会把使用权还给操作系统,我们返回的是n的别名,那我们再去访问的时候,n的那块空间的使用权以及不归我们使用了,可能结果是对的。
被static修饰的n:
出了函数栈帧也不会销毁,因为n是在静态区创建的。
看看下面两段代码有什么区别
被static修饰的n
int& Count()
{
static int n = 0;
n++;
return n;
}
没被static修饰的
int& Count()
{
int n = 0;
n++;
return n;
}
总结:
基本任何场景都可以用引用传参
谨慎使用引用做返回值,除了函数作用域,对象不在了,就不能用引用返回,对象还在,就可以用引用返回
常引用
不同类型会产生临时变量(临时变量具有常性)
传值、传引用效率比较
以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低
返回值类型的性能比较
通过上述代码的比较,发现传值和指针在作为传参以及返回值类型上效率相差很大
引用和指针的区别
- 引用概念上定义一个变量的别名,指针存储一个变量地址
- 引用在定义时必须初始化,指针没有要求
- 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型
实体- 没有NULL引用,但有NULL指针
- 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
- 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
- 有多级指针,但是没有多级引用
- 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
- 引用比指针使用起来相对更安全
2.auto关键字
auto自动推导类型
autu不能在同一行定义多个不同类型变量
auto不能作为函数参数
auto不能直接用来声明数组
使用场景
- 类型难于拼写
2.含义不明确导致容易出错
当类型很长的时候,可以使用auto
auto与指针和引用结合起来使用
用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&
int main()
{
int x = 10;
auto a = &x;//把x的地址交给指针
auto* b = &x;//
auto& c = x;
cout << typeid(a).name() << endl;
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
*a = 20;
*b = 30;
c = 40;
return 0;
}
在同一行不能定义多个不同的变量
void TestAuto()
{
auto a = 1, b = 2;
auto c = 3, d = 4.0; // 该行代码会编译失败,因为c和d的初始化表达式类型不同
}
3.范围for 语法糖
以前我们C语言要遍历数组是这样的:
对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号 ,“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围
- 依次取arr数组中值赋给e
- 自动迭代,自动判断结束
- 适用于数组
void TestFor()
{
int array[] = { 1, 2, 3, 4, 5 };
for (auto& e : array)//如果要改变数组中的值,auto后面加&
e *= 2;//每次对数组的值*=2
for (auto e : array)
cout << e << " ";//打印
}
4.inline函数
C语法:宏函数 Add(x, y) ((x)+(y))
优点:不需要建立栈帧,提高效率
缺点:复杂,容易出错,代码可读性变差
以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率
在函数前面加个inline就是内联函数
inline适用于短小的且频繁调用的函数,否则会造成代码膨胀,代码膨胀的后果就是可执行程序变大
inline对于编译器仅仅只是一个建议,最终是否成为inline,取决于编译器
类似下面的函数加了inline,也会被编译器否决掉
1.比较长的函数不行
2.递归函数也不能搞inline
默认debug模式下,inline不会起作用,否则就无法调试
5.指针空值nullptr
NULL实际是一个宏,在传统的C头文件(stddef.h)中,可以看到如下代码
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void *)0
正常情况下,打印的应该是f(int)和f(int*),但是情况并不是这样:
- 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。
- 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。
所以在C++中,推荐使用nullptr