怎么做网站编辑,网络公司经营范围网站建设,域名到期与网站打不开,销售系统浅析C的指针与引用 文章目录 浅析C的指针与引用一、对比引用与指针二、引用左值引用右值引用引用折叠 三、指针与引用的性能差距总结 一、对比引用与指针
总论#xff1a;
引用指针必须初始化可以不初始化不能为空可以为空不能更换目标可以更换目标 引用必须初始化#xff…浅析C的指针与引用 文章目录 浅析C的指针与引用一、对比引用与指针二、引用左值引用右值引用引用折叠 三、指针与引用的性能差距总结 一、对比引用与指针
总论
引用指针必须初始化可以不初始化不能为空可以为空不能更换目标可以更换目标 引用必须初始化而指针可以不初始化。 我们在定义一个引用的时候必须为其指定一个初始值但是指针却不需要。
int r; //不合法没有初始化引用
int *p; //合法但p为野指针使用需要小心引用不能为空而指针可以为空。 由于引用不能为空所以我们在使用引用的时候不需要测试其合法性而在使用指针的时候需要首先判断指针是否为空指针否则可能会引起程序崩溃。
void test_p(int* p)
{if(p ! null_ptr) //对p所指对象赋值时需先判断p是否为空指针*p 3;return;
}
void test_r(int r)
{r 3; //由于引用不能为空所以此处无需判断r的有效性就可以对r直接赋值return;
}引用不能更换目标 指针可以随时改变指向但是引用只能指向初始化时指向的对象无法改变。
int a 1;
int b 2;int r a; //初始化引用r指向变量a
int *p a; //初始化指针p指向变量ap b; //指针p指向了变量b
r b; //引用r依然指向a但a的值变成了b二、引用
左值引用
常规引用一般表示对象的身份。
右值引用
右值引用就是必须绑定到右值一个临时对象、将要销毁的对象的引用一般表示对象的值。 右值引用可实现转移语义Move Sementics和精确传递Perfect Forwarding它的主要目的有两个方面 消除两个对象交互时不必要的对象拷贝节省运算存储资源提高效率。能够更简洁明确地定义泛型函数。 引用折叠 X 、X 、X 可折叠成 XX 可折叠成 X 例如当我们有一个模板函数参数声明为 T并且传递给它一个左值时类型 T 会被推导为左值引用类型。这就是所谓的万能引用它可以根据传入的参数类型自动调整为左值引用或右值引用。
这里有一个简单的例子来说明引用折叠
templatetypename T
void forward(T arg) {// arg 可以是左值引用或右值引用
}int main() {int x 10;forward(x); // x 是左值T 被推导为 intforward(10); // 10 是右值T 被推导为 int
}在这个例子中forward 函数可以接受任何类型的参数无论是左值还是右值而不需要为每种情况编写重载函数。这就是引用折叠和右值引用在现代C编程中的妙用。
C的引用在减少了程序员自由度的同时提升了内存操作的安全性和语义的优美性。比如引用强制要求必须初始化可以让我们在使用引用的时候不用再去判断引用是否为空让代码更加简洁优美避免了指针满天飞的情形。除了这种场景之外引用还用于如下两个场景 引用型参数 一般我们使用const reference参数作为只读形参这种情况下既可以避免参数拷贝还可以获得与传值参数一样的调用方式。
void test(const vectorint data)
{//...
}
int main()
{vectorint data{1,2,3,4,5,6,7,8};test(data);
}引用型返回值 C提供了重载运算符的功能我们在重载某些操作符的时候使用引用型返回值可以获得跟该操作符原来语法相同的调用方式保持了操作符语义的一致性。一个例子就是operator []操作符这个操作符一般需要返回一个引用对象才能正确的被修改。这就是我们常用的链式调用
vectorint v(10);
v[5] 10; //[]操作符返回引用然后vector对应元素才能被修改//如果[]操作符不返回引用而是指针的话赋值语句则需要这样写
*v[5] 10; //这种书写方式完全不符合我们对[]调用的认知容易产生误解三、指针与引用的性能差距
指针与引用之间有没有性能差距呢这种问题就需要进入汇编层面去看一下。我们先写一个test1函数参数传递使用指针
void test1(int* p)
{*p 3; //此处应该首先判断p是否为空为了测试的需要此处我们没加。return;
}该代码段对应的汇编代码如下
(gdb) disassemble
Dump of assembler code for function test1(int*):0x0000000000400886 0: push %rbp0x0000000000400887 1: mov %rsp,%rbp0x000000000040088a 4: mov %rdi,-0x8(%rbp)0x000000000040088e 8: mov -0x8(%rbp),%rax0x0000000000400892 12: movl $0x3,(%rax)0x0000000000400898 18: nop0x0000000000400899 19: pop %rbp0x000000000040089a 20: retq
End of assembler dump.
上述代码1、2行是参数调用保存现场操作第3行是参数传递函数调用第一个参数一般放在rdi寄存器此行代码把rdi寄存器值指针p的值写入栈中第4行是把栈中p的值写入rax寄存器第5行是把立即数3写入到rax寄存器值所指向的内存中此处要注意(%rax)两边的括号这个括号并并不是可有可无的(%rax)和%rax完全是两种意义(%rax)代表rax寄存器中值所代表地址部分的内存即相当于C代码中的*p而%rax代表rax寄存器相当于C代码中的p值所以汇编这里使用了(%rax)而不是%rax。
我们再写出参数传递使用引用的C代码段test2
void test2(int r)
{r 3; //赋值前无需判断reference是否为空return;
}这段代码对应的汇编代码如下
(gdb) disassemble
Dump of assembler code for function test2(int):0x000000000040089b 0: push %rbp0x000000000040089c 1: mov %rsp,%rbp0x000000000040089f 4: mov %rdi,-0x8(%rbp)0x00000000004008a3 8: mov -0x8(%rbp),%rax0x00000000004008a7 12: movl $0x3,(%rax)0x00000000004008ad 18: nop0x00000000004008ae 19: pop %rbp0x00000000004008af 20: retq
End of assembler dump.
我们发现test2对应的汇编代码和test1对应的汇编代码完全相同这说明C编译器在编译程序的时候将指针和引用编译成了完全一样的机器码。所以C中的引用只是C对指针操作的一个“语法糖”在底层实现时C编译器实现这两种操作的方法完全相同。 总结
C中引入了引用操作在对引用的使用加了更多限制条件的情况下保证了引用使用的安全性和便捷性还可以保持代码的优雅性。在适合的情况使用适合的操作引用的使用可以一定程度避免“指针满天飞”的情况对于提升程序稳定性也有一定的积极意义。最后指针与引用底层实现都是一样的不用担心两者的性能差距。