目录
引用的概念
给已经存在的变量取一个别名,编译器不会为引用的变量开辟新的空间,引用的变量共同指向同一块内存空间
void Text()
{
int a = 1;
int b = a;
int& c = a;
b--;
c++;
cout << "a: " << a << endl;
cout << "c: " << c << endl;
cout << "b: " << b << endl;
}
- 引用的变量改变,也就是原来的变量发生改变
引用的特点
引用定义时必须初始化
void Text()
{
int& b
}
引用的类型必须与被引用对象的类型相同
int a = 1;
const double& b = a;
- 注意:如果使用常量限定"const"关键字,则可以改变类型
int a = 1;
const double& b = a;
一个变量可以设置多个引用
int a = 1;
int& b = a;
int& c = a;
引用只能对应一个实体
int a = 1;
int c = 2;
int& b = a;
int& b = c;
引用有传递性
void Text()
{
int a = 1;
int& b = a;
int& c = b;
cout<< "a: " << a << endl;
cout<< "b: " << b << endl;
cout<< "c: " << c << endl;
}
赋值与引用的区别
void Text()
{
int a = 1;
int b = a;//把a的值赋值给b
int& c = a;//c对a进行引用
cout << "a的地址: "<< &a << endl;
cout << "c的地址: "<< &c << endl;
cout << "b的地址: "<< &b << endl;
}
int main()
{
Text();
return 0;
}
由此可证明:引用的变量共同指向同一块内存空间,赋值的新变量会开辟出新的空间
引用作参数
引用作参数-利用引用的变量共同指向同一块内存空间
交换两个变量
- C语言交换两个变量
void Swap (int* left , int* right)
{
int temp = *left;
*left = *right;
*right = temp;
}
int main()
{
Swap(&a , &b)//传地址
}
- C++利用引用的变量共同指向同一块内存空间,交换变量
void Swap (int &left , int &right)
{
int temp = left;
left = right;
right = temp;
}
int main()
{
Swap(c , d)//"left"是"c"的别名,"right"是"d"的别名
}
- Left & right 和 c&d指向同一块 ,left 和 right 的改变就是 c 和d的改变
传递单链表节点指针
//单链表——定义结构体节点
typedef struct SListNode
{
struct SListNode* next;
int val;
}SLTNode ,*PSListNode;
void SListPushBack (PSLTNode& phead , int x)
{
//......
}
PSLNode plist = NULL;
SListPushBackk(plist , 1);
SListPushBackk(plist , 2);
*PSListNode:每一个单链表节点指针,指向节点自身地址
PSListNode plist:创建结构体指针变量 plist
PSListNode& phead phead是一个别名,别名的类型是PSListNode(结构体指针),相当于是plist的别名
所以,改变phead相当于直接改变plist
引用传参的优势
struct A
{
int a[10000];
};
void TestFunc1(struct A a) {}//通过值传递,每次调用TestFunc1时,都会复制一个A类型的对象
void TestFunc2(struct A& a){}//接受一个struct A类型的引用参数a。通过引用传递可以避免复制整个结构体
//每个结构体算一个域,不同域中是可以有同名变量的
void TestReturnByRefOrValue()
{
//在当前作用域内声明一个A类型的变量a
A test;//C++中结构体声明变量可以不使用struct,只要构体的定义在当前作用域内是可见
/
size_t begin1 = clock();// 记录当前时间(以时钟周期为单位)
for (size_t i = 0; i < 100000; ++i)
{
TestFunc1(test);//for循环调用TestFunc1函数100000次,每次都会通过值传递test
}
size_t end1 = clock();//记录调用结束后的时间
size_t begin2 = clock();// 记录当前时间
for (size_t i = 0; i < 100000; ++i)
{
TestFunc2(test);//for循环调用TestFunc2函数100000次,每次都会通过值传递test
}
size_t end2 = clock();// 记录调用结束后的时间
///
// 计算两个函数运算完成之后的时间
cout << "TestFunc1 time:" << end1 - begin1 << endl;
cout << "TestFunc2 time:" << end2 - begin2 << endl;
}
int main()
{
// 调用测试函数
TestReturnByRefOrValue();
// 返回0表示程序成功执行
return 0;
}
很明显,通过引用传递可以避免复制整个结构体,从而显著提高性能。
引用作返回值
传值返回
思考:int Count函数的返回值是否是n
//传值返回
int Count()
{
int n = 0;
n++;
return n;
}
int main()
{
int ret = Count();
return 0;
}
①ret存储Count()的返回值
②Count 函数返回时,是 n 是一个局部变量,它的生命周期仅限于Count 函数的作用域内,n的生命周期结束,其内存会被释放
③所以实际传递的n的拷贝,存储在寄存器中
④ret获取的是Count()的返回值的拷贝
传引用返回错误案例
当接收返回值的变量不是别名类型
错误案例一
//传引用返回
int& Count()
{
int n = 10;
n++;
return n;
}
int main()
{
int ret = Count();
return 0;
cout << "ret:" << ret << endl;
}
调试后结果
①ret存储Count()的返回值
②Count 函数返回时,是 n 是一个局部变量,它的生命周期仅限于Count 函数的作用域内,n 的生命周期结束,其内存会被释放
③返回值类型为int&,所以返回n的引用(由编译器自动生成),相当于n自身, n 的引用会导致悬空引用,即引用指向一个已经不再有效的内存地址。
④ret得到的是已经销毁的n,致未定义行为系统会自动返回随机值(在VS编译器会返回1)
当接收返回值的变量也是别名类型
错误案例一:
int& Count()
{
int n = 10;
n++;
return n;//返回n的引用
}
int main()
{
int& ret = Count();//注意,由 int ret -> int& ret ret是函数返回值的引用
cout << "ret: " << ret << endl;
return 0;
}
由 int ret -> int& ret,此时ret变成n的引用的引用,得到ret是随机值
错误案例二:
int& Add(int a, int b)
{
int c = a + b;
return c; //返回c的引用
}
int main()
{
int& ret = Add(1, 2);//注意,相当于ret就是函数的返回值的引用的引用
//(和返回值C一样直接指向同一块内存空间)
Add(3, 4);
cout << "Add(1, 2) is :"<< ret << endl;
}
1.Add()没有销毁时,Add()返回c的引用
2.函数调用结束,Add()内存空间销毁,返回值C内存空间随着函数销毁,返回值C的引用也随着C销毁而销毁,
3.再一次调用Add(),没有设置变量接收返回值,但是可以看到ret依旧发生了变化
思考:为什么没有修改ret,ret依旧改变
①虽然没有设置变量接收返回值 ->ret是返回值C引用的引用(和返回值C一样直接指向同一块内存空间)->ret依旧指向返回值C第一次销毁前的内存空间
②打开反汇编,发现Add(1, 2)与Add(3, 4)两次调用Add函数,两次调用Add函数存储在同一块空间,关于函数栈帧的销毁是另一个知识点,这里仅简单带过
总结:所以ret指向的第一次销毁后的返回值C的空间,在第二次调用Add函数时又重新使用,并且填入新的返回值C的数据,ret指向的内存空间发生变化,ret得到了新的返回值数据,所以,以上对引用返回的使用均是不正确
4.函数调用结束,Add()内存空间销毁,返回值C内存空间随着函数销毁,返回值C的引用也随着C销毁而销毁,
修改方式:
方法一
int& Add(int a, int b)
{
static int c = a + b;//使用静态变量,或者malloc动态内存分配
return c;
}
- 局部静态变量,只在第一次进入函数时初始化,所以static int C只初始化了Add(1,2)
- 改变c存储的位置
方法二
int& Add(int a, int b)
{
static int c; //每次进入函数都会初始化一次
c = a + b;
return c;
}
传引用返回正确使用
- 传值返回:顺序表每个位置的值++
typedef struct SeqList
{
int a[100];
int size;
}SL;
void SLInit(SL* ps, int* arr, int len)
{
assert(ps); // 确保传入的指针不为空
ps->size = len; // 设置顺序表的大小
for (int i = 0; i < len; ++i)
{
ps->a[i] = arr[i]; // 将数组arr中的值复制到顺序表中
}
}
void SLModify(SL* ps, int pos,int x)
{
assert(ps);
assert(pos < ps->size);
ps->a[pos] = x;
}
int main()
{
SL s;
int arr[] = { 1, 2, 3, 4, 5 }; // 创建一个整数数组
int len = sizeof(arr) / sizeof(arr[0]); // 计算数组的长度
SLInit(&s, arr, len);
//顺序表每个位置的值++
for(size_t i = 0;i<s.size; i++)
{
s.a[i]++; // 将顺序表中每个位置的值加1
}
cout << "传值返回++:";
for (size_t i = 0; i < s.size; ++i)
{
cout << s.a[i] << " "; // 输出顺序表中每个元素的值
}
cout << endl;//换行
}
- 传引用返回:顺序表每个位置的值++
typedef struct SeqList
{
int a[100];
int size;
}SL;
void SLInit(SL* ps, int* arr, int len)
{
assert(ps); // 确保传入的指针不为空
ps->size = len; // 设置顺序表的大小
for (int i = 0; i < len; ++i)
{
ps->a[i] = arr[i]; // 将数组arr中的值复制到顺序表中
}
}
void SLModify(SL* ps, int pos,int x)
{
assert(ps);
assert(pos < ps->size);
ps->a[pos] = x;
}
int main()
{
SL s;
int arr[] = { 1, 2, 3, 4, 5 }; // 创建一个整数数组
int len = sizeof(arr) / sizeof(arr[0]); // 计算数组的长度
SLInit(&s, arr, len);
//顺序表每个位置的值++
for(size_t i = 0;i<s.size; i++)
{
s.a[i]++; // 将顺序表中每个位置的值加1
}
cout << "传值返回++:";
for (size_t i = 0; i < s.size; ++i)
{
cout << s.a[i] << " "; // 输出顺序表中每个元素的值
}
cout << endl;//换行
}
总结:由此看出,可以利用引用直接修改返回对象的值
引用与指针的区别
使用方法的不同
- 初始化:①指针可以在定义时初始化也可以在之后赋值。②引用必须在定义时初始化
int a = 10;
int* p;
p = &a
\\\\\\\\\\\\\\\\\\\\\\\\
int a = 10;
int& b = a;
对内存影响不同
指针
- 指针是一个变量,它本身占用内存来存储地址。
- 指针有独立的内存地址,用于存储被指向对象的地址。
引用
- 引用本身不占用内存(或者说它的内存开销可以忽略不计),因为它只是一个别名。
- 引用没有独立的内存地址,它使用的是被引用对象的地址。
引用无法改变指向对象:引用一旦初始化后不能改变引用的对象
int a = 10;
int d = 5;
int&b = a;
b = d;//思考:是d变成b的别名,还是d赋值给b
//这里是d赋值给b
注意:a,b的地址没有发生改变,b的指向没有发生改变
标签:ps,变量,int,C++,引用,返回值,ret From: https://blog.csdn.net/F091500/article/details/143804089