,这里引用也和取地址使用了同⼀个符号&,大家注意使用方法角度区分就可以。int a = 0;//引⽤:b和c是a的别名int& b = a;int& c = a;//也可以给别名b取别名,d。">
赞
踩
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。比如:水浒传中李逵,宋江叫"铁牛",江湖上人称"黑旋风";林冲,外号豹子头;
类型& 引用别名 = 引用对象;
C++中为了避免引入太多的运算符,会复用C语言的⼀些符号,比如前面的<< 和 >>,这里引用也和取地址使用了同⼀个符号&,大家注意使用方法角度区分就可以。
int a = 0;
// 引⽤:b和c是a的别名
int& b = a;
int& c = a;
// 也可以给别名b取别名,d相当于还是a的别名
int& d = b;
这串代码在底层的角度是这样的,这有一块空间,给它取了一个名字叫做a,又给它取了一个名字叫b,又给它取了一个名字叫c,给b取了一个名字叫d,其实还是这块空间,相当于一块空间有多个名字。,它们的地址都是一样的。
对指针变量取别名:
int main()
{
int a = 0;
int* p1 = &a;
int*& p2 = p1;
int e = 10;
p2 = &e;
cout << *p2 << endl;
cout << *p1 << endl;
cout << a << endl;
return 0;
}
引用的作用:
做参数
typedef struct ListNode
{
int val;
struct ListNode* next;
}LTNode, *PNode;
int main()
{
PNode plist = NULL;
ListPushBack(plist, 1);
return 0;
}
这里的*PNode是指把typedef struct ListNode* 定义为PNode。
这里的d的地址没变,说明d还是a的别名,d没有指向e,只是把e的值赋给了d。
当我们实现链表的时候,我们要删除一个结点,但这时的地址之间是相互关联的,因为引用不能改变指向,所以就不可能完成。所以C++的指针引用不能完全替代指针。
减少拷贝的案例:
传值传参会生成一个拷贝,传值返回也会生成一个拷贝。C++中是这样规定的,在红线这里进行一个传值返回,返回这个对象的时候, 他不会引用这个对象做函数调用的返回值,不会返回要返回的东西,它会生成一个临时对象,把这的值给临时对象,再用临时对象做这个整个表达式的返回值。STTop(st1)+=1;这是的加1就是加到临时对象上面。临时对象具有常性。
那临时对象是什么呢?
临时变量通常是指编译器在栈里面临时开一块空间存储中间值的这种,也有可能是用寄存器去存。
这里如何使用STTop(st1)+=1呢?只需要采用引用返回就可以了。传值返回就是返回rs.a[rs.top - 1]的拷贝,传引用返回就是返回他的别名,也就是这里的2。也就a数组指向的top-1这里的数值。这时候就把被引用对象给改变了。
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;
}// 栈顶
void STPush(ST& rs, STDataType x)
{
// 满了, 扩容
if (rs.top == rs.capacity)
{
printf("扩容\n");
int newcapacity = rs.capacity == 0 ? 4 : rs.capacity * 2;
STDataType* tmp = (STDataType*)realloc(rs.a, newcapacity *
sizeof(STDataType));
if (tmp == NULL)
{
perror("realloc fail");
return;
}
rs.a = tmp;
rs.capacity = newcapacity;
}
rs.a[rs.top] = x;
rs.top++;
}
int& STTop(ST& rs)
{
assert(rs.top > 0);return rs.a[rs.top - 1];
}
int main()
{
ST st1;
STInit(st1);
STPush(st1, 1);
STPush(st1, 2);
cout << STTop(st1) << endl;
//修改栈顶的数据呢
(STTop(st1)) += 1;
cout << STTop(st1) << endl;
return 0;
}
其实指针也可以做到:
并不是任何场景都能用引用返回(后面结合类和对象讲),比如说:
C++设计引用不是为了替代指针,而是为了简化指针有时候要解决的问题。
ret是局部变量,这里类似于野引用。
int* fun( )
{
int ret = 10;
return *ret;
}
这里的指针就越界了,指针越界不一定报错。
在vs中设置了抽查位置,这两个位置不分配给别人,给两个固定的值,在程序运行结束时看这两个位置的值有没有发生改变,没有被修改就说明没有越界。
这里是把x的这块空间拷贝给给y,x不能修改这块空间。
下面这个也是经典的权限放大:
下图这个是权限的缩小:
p1对于a的权限是只读不能写的,p1拷贝给给p2,p2又变成可读可写的了,p2的权限被放大,p5指向b的权限是可读可写的,p6指向p5也是可读可写的(这里不存在权限放大,因为const修饰的是p5本身不是指向的内容)。
a * 3这里存在一个临时变量,临时变量具有常性,这里加个const就行了。
这里的d给给rd其实也不是直接给过去的,中间也会产生一个临时变量来存储中间的结果。d给给中间的临时对象,这个临时对象是int类型,临时对象再给给rd。
类型转换会产生临时对象,d给了临时对象,这里的临时对象给给rd。也就是说rd引用了临时对象。
那上面这些到底有什么用呢?
这里的传值传参就不说了,对于引用传参
void fun(int& rx)如果变量 rx 不改变形参,建议前面加 const,void fun(const int& rx),加const的好处是什么呢?传参就非常宽泛。
const引用的价值是什么?
1.可以引用const对象
2.可以引用普通对象
3.可以引用临时对象
野引用的案例:
从底层汇编的角度看,引用也是用指针实现的。
可执行程序是一个文件,它会以一个进程的角度来进行运行(可执行程序会生成一个进程),进程才会给它分配内存,进程会把可执行程序的那个指令加载到内存的里面。
10000*100是指10000行指令*100行Add的指令总和的指令,10000+100是指10000行指令+100行Add的指令总和的指令。内联展开会导致一个问题:代码膨胀,代码膨胀会导致可执行程序变大,可执行程序变大,加载到进程也会变大,加载到内存导致内存变大。(可执行程序就是安装包)
// F.h# include <iostream>using namespace std;inline void f ( int i);// F.cpp# include "F.h"void f ( int i){cout << i << endl;}// text.cpp# include "F.h"int main (){
// 链接错误:⽆法解析的外部符号 "void __cdecl f(int)" (?f@@YAXH@Z)f ( 10 );return 0 ;}//那要怎么做呢?把// F.cpp这个文件中的f(int i)给注释掉# include "F.h"//void f ( int i)//{//cout << i << endl;//}在// F.h中定义inline void f ( int i){cout << i << endl;}
// 实现⼀个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))// 为什么不能加分号 ?int main(){int ret = Add(1,2);//这里是将a替换成1,b替换成2;//int ret = Add(1,2);;//在这种场景下不会有问题cout << Add(1,2) << endl;//如果加分号在这种情况下就会报错,还有下面这种if(Add(1,2)){//...}cout << ret << endl;}// 为什么要加外⾯的括号 ?//有下面这种情况int main(){int ret = Add(1,2);//这里是将a替换成1,b替换成2;//int ret = Add(1,2);;cout << Add(1,2)*3 << endl;//cout << (1) + (2) * 3 << endl;}// 为什么要加里面的括号 ?int main(){int x =1,y = 2;Add(x & y ,x | y); // -> (x & y + x | y)}
#ifndef NULL
#ifdef __cplusplus
#define NULL 0//C++
#else
#define NULL ((void *)0)//C语言
#endif
#endif
把0转换为(void*)0,也不会匹配,void*到int*也需要转换的。
• C++11中引入nullptr,nullptr是一个特殊的关键字,nullptr是⼀种特殊类型的字面量,它可以转换 成任意其他类型的指针类型。使用nullptr定义空指针可以避免类型转换的问题,因为nullptr只能被 隐式地转换为指针类型,而不能被转换为整数类型。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。