举个例子:在中学时期,我们会给关系好的朋友起一个外号,比如”黑狗“,“阿白”比较有特点的外号以证明关系很铁。那么在日常生活中,我们叫他的真名和他的外号是不是本意都是指向同一个人。对滴!!!这个外号跟别名有着异曲同工之妙!!!
引用的定义:
类型& 引⽤别名 = 引⽤对象;
我们举例说明
int a = 0;
// 引⽤:b和c是a的别名
int& b = a;
int& c = b;
这里b是a的别名,c是b的别名,所以a,b,c都是指向的同一块空间。
我们用结果看一下:
cout << a << endl;
cout << b << endl;
cout << d << endl;
cout << &a << endl;
cout << &b << endl;
cout << &d << endl;
三者的结果是一样的,对abc分别取地址,发现他们的地址都是相同的。 都是00000009805AFC54。
我们都知道空间可以被赋值,那么别名的赋值是怎么样的哪?下列代码中的e的输出结果是什么?
int a = 0;
// 引⽤:b和c是a的别名
int& b = a;
int& c = b;
//
int e=20;
d=e
cout << a << endl;
cout << b << endl;
cout << d << endl;
cout << e << endl;
输出结果显示
对滴!!!别名指向的空间被赋值,空间变成了e的值,有点牵一发而动全身的感觉拉哈!!!
首先时必须进行初始化,不然编译器会对其进行报错。
int a = 10;
// 编译报错:“ra”: 必须初始化引⽤
//int& ra;
int& b = a;
int c = 20;
未初始化编译器提示(vs2022)
讲到这里了都
我们写一个交换函数试试水
void swap(int& rx, int& ry)
{
int tmp = rx;
rx = ry;
ry = tmp;
}
int main()
{
int a=10;
int b=20;
swap(a,b)
}
这里我们详细解释一下,为什么a和b可以直接传实参,按惯性思维来说传的时形参吗?
这是因为a和b作为参数进入函数时,编译器将rx看作变量a的别名,ry看作变量b的别名,所以在函数实现功能时,又因为rx和ty是别名,还是分别指向a和b的空间。所以还是实质还是a和b的交换。
我们接下来用几个例子详细说明一下引用的特性
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;
void STInit(ST& rs, int n = 4)
{
rs.a = (STDataType*)malloc(n * sizeof(STDataType));
rs.top = 0;
rs.capacity = n;
}
int main()
{
ST st1;
STInit(st1);
}
这种跟swap函数的方式是大致一样的,都是相当于传实参。
我们对比下列的两个函数
//1.
int* STTop(ST& rs)
{
assert(rs.top > 0);
//return &(rs.a[rs.top - 1]);
return rs.a + (rs.top - 1);
}
//2.
int &STTop(ST& rs)
{
assert(rs.top > 0);
return rs.a[rs.top - 1];
}
(STTop(st1))+= 1;
第一个函数是返回的是指针类型,返回的是一个地址。但是在C++中return返回的并不是顺序中相关参数的地址,而是C++会创建一个常性临时创建对象,我们叫做临时对象,返回的是临时对象的空间。所以并不会返回顺序表中相关参数的地址。
第二个函数我们仔细观察会发现STTop前面有一个&的运算符,这样我们返回的就是顺序表中的参数别名,可以直接用参数进行运算。
所以结论很明显:第一个函数是不会进行运算的,第二个可以进行运算。
对的,学到这里你可能会发现,引用和指针有点像呀!!!但打一个预防针,他俩虽然本质差不多,底层都是用指针实现,但是表层的用法是不一样的哦。所以既然差不多,所以小心野引用
举个例子
int &func()
{
int ret = 10;
ret++;
//...
return ret;
}
这就是野引用,ret在函数内被定义,但是ret在出函数的时候就被销毁了。所以返回的并不是ret。
越界读不报错
越界写不一定报错
报错机制是抽查:
来来来,让我们剖析一下先数据结构教材中让人迷惑的点!!!
这里实际是对STL起了个别名,让下面的函数内书写比较方便,并不是取地址
int main()
{
//权限不能放大
const int a = 10;
const int* p1 = &a;
int* p2 = p1;
//权限可以缩小
int b = 20;
int* p3 = &b;
const int* p4 = p3;
int* const p5 = &b;
int* p6 = p5;
//不存在权限放大,因为const修饰的是p5本身不是指向的内特容
}
这里需要注意得是
类似 int& rb = a*3; double d = 12.34; int& rd = d; 这样⼀些场 景下a*3的和结果保存在⼀个临时对象中
int main()
{
int a = 10;
const int& ra = 30;
// 编译报错: “初始化”: ⽆法从“int”转换为“int &”
// int& rb = a * 3;
const int& rb = a*3;
double d = 12.34;
// 编译报错:“初始化”: ⽆法从“double”转换为“int &”
// int& rd = d;
const int& rd = d;
return 0;
}
调试情况下解决默认inline不展开的方法:
还有一个问题
// 实现⼀个ADD宏函数的常⻅问题 //#define ADD(int a, int b) return a + b; //#define ADD(a, b) a + b; //#define ADD(a, b) (a + b) // 正确的宏实现 #define ADD(a, b) ((a) + (b) ) // 为什么不能加分号? // 为什么要加外⾯的括号? // 为什么要加⾥⾯的括号?
NULL实际是⼀个宏,在传统的C头⽂件(stddef.h)中,可以看到如下代码:
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
• C++中NULL可能被定义为字⾯常量0,或者C中被定义为⽆类型指针(void*)的常量。不论采取何种 定义,在使⽤空值的指针时,都不可避免的会遇到⼀些⿇烦,本想通过f(NULL)调⽤指针版本的 f(int*)函数,但是由于NULL被定义成0,调⽤了f(int x),因此与程序的初衷相悖。f((void*)NULL); 调⽤会报错。 • C++11中引⼊nullptr,nullptr是⼀个特殊的关键字,nullptr是⼀种特殊类型的字⾯量,它可以转换 成任意其他类型的指针类型。使⽤nullptr定义空指针可以避免类型转换的问题,因为nullptr只能被 隐式地转换为指针类型,⽽不能被转换为整数类型。
#include<iostream>
using namespace std;
void f(int x)
{
cout << "f(int x)" << endl;
}
void f(int* ptr)
{
cout << "f(int* ptr)" << endl;
}
int main()
{
f(0);
// 本想通过f(NULL)调⽤指针版本的f(int*)函数,但是由于NULL被定义成0,调⽤了f(int
x),因此与程序的初衷相悖。
f(NULL);
f((int*)NULL);
// 编译报错:error C2665: “f”: 2 个重载中没有⼀个可以转换所有参数类型
// f((void*)NULL);
f(nullptr);
return 0;
}