TA的每日心情 开心 半小时前
签到天数: 227 天
[LV.7]常住居民III
管理员
积分 27565
帖子摘要: 文章目录 1 引用概念2 引用特性3 使用场景4 常引用5 传值与传引用的效率比较5.1 值和引用作为函数参数的性能比较5.2 值和引用作为返回值类型的性能比较 6 引用和指针的区别 1 引用概念......4 } P: q# w! O% l- G/ U, R3 V
/ N5 S/ o+ M( h0 x 大家好,欢迎来到Java吧(www.java8.com),交流、学习Java技术、获取Java资源无任何套路,今天说一说:“【C++入门】引用详解(引用的特性、引用的使用、引用与指针的区别)” 8 G( W# L+ O2 j# L
+ B5 h) n0 Y; H. l5 [ 6 l8 y! b* w1 S5 o$ b' X) G. f
4 \$ B. l7 @" V4 a, g9 x$ x/ c
8 @# ?' { T+ l
& R( z" U/ q. Y' ]* d' n7 K2 X
6 g, ?; ~% {3 O- s
$ ? }- n! I7 I( F 4 p; b8 w- P5 x6 M% a
文章目录% A: d" P/ @! A) h- ?$ A4 P
1 引用概念 2 引用特性 3 使用场景 4 常引用 5 传值与传引用的效率比较 5.1 值和引用作为函数参数的性能比较 5.2 值和引用作为返回值类型的性能比较 + K8 T# a }/ M+ O9 F
6 引用和指针的区别 4 l% U, Q8 G. C2 s
+ Y! v- F/ F J + A+ _* L; ]( K8 A5 \7 K
1 引用概念
* F O' s9 O" g1 l" j' n
& N# v8 ^' Q* \/ _" ?. B 引用不是新定义一个变量而是给已存在的变量取了一个别名编译器不会为引用变量开辟内存空间它和它引用的变量共用一块内存空间。
; g u& [& j7 P" k4 p7 x 格式引用类型& 引用变量名对象名 = 引用实体 5 G# O/ _+ P& l4 p' f
注意 引用类型和引用实体是同种类型的。; _. u0 I. O" ]9 E7 Q
0 Z* x5 Q0 O+ s0 p% U n 1 k- _) _4 y: x
引用示例
) r2 i% ?. A6 R8 I9 v
) f* d. W+ K( d/ h% Z 1 m5 ?( n: Q( L8 \, f) W" S
! H- F- A1 |5 r% p2 t+ W
9 [+ q, H$ v1 @0 w0 A 2 引用特性
( {% b& K* w, F$ |0 Q [ol]引用在定义时必须初始化
4 R7 L A/ w* k 示例
- f& Z# I9 ~) C$ k% T/ d w- L
* }. |$ Y& e! j- o+ x8 Y
- f. y4 D/ P1 {0 s8 T, g; T 一个变量可以有多个引用也可以对引用再进行引用或者说可以为变量取多个别名也可以为别名再取别名
7 p3 H- i& C e' N 示例
! C* c# b q& Q% ]$ x 7 l. C' v: r2 p+ X
! q% w! F$ A$ |. U; |3 {" w6 j 引用一旦引用一个实体就不能再引用其它实体 [/ol] 0 }* p+ O2 K D! x# R4 c5 `' M
; i; N$ A# A+ h) y/ g& F
3 J- O; H2 ]) F, B) s 1 i4 Q2 u% s2 G8 B& l K/ b5 }
3 使用场景
- y: s) j- q$ R! I& s" d- g [ol]引用做参数 [/ol] ; p( R. c1 G0 u( S& d
void Swap(int& num1, int& num2)
6 t+ F7 D9 {& ]; m/ P {
int temp = num1;
8 S. `* f1 B3 t5 d3 |5 r" `; n num1 = num2;
8 j) D( j0 T3 h5 Q; j& s$ } num2 = temp;
}
复制代码
6 P. c! P! l6 N
: x& n' t7 Q- q2 `6 L* n 引用做返回值 [/ol] 9 j1 Q, X% W5 j( U" j4 Q8 \
前言传值返回
: i! B0 q9 w- a T4 ]- I5 A5 E4 \ . S8 J( y+ e! R7 i
当用值作为返回值时不是直接将变量返回而是返回变量的一份临时拷贝临时变量。考虑到对于函数中的局部变量在调用完函数时即被销毁此时需要返回的变量也会被销毁空间不再属于该变量因此选择在销毁前将变量值拷贝到一个临时变量中这个临时变量一般由寄存器4/8个字节大小充当如eax如果返回值过大则会选择在上层栈帧中创建临时变量再将临时变量作为返回值返回。
( T/ f' S) i) a- q 4 i$ D8 h2 F% N, t% R
; N$ ?+ n+ Y5 x5 r/ H
那如果作为返回值的变量出作用域后不会被销毁那还会产生临时变量吗事实上编译器不管需要返回的变量出作用域后会不会被销毁在返回时都会产生临时变量。 9 g. F; X9 k) x& |* W
; z' M/ q- Q) e8 B+ O* U. K
7 Q) ]( d5 B) h) G6 Q) ^
& q& A) `. C+ ]" d' n 8 x1 B% H; v& C- R- s
传引用返回 / i& S$ n! J& W0 J- Y+ q+ B$ H6 t+ {; c
( e/ t% h# Q1 L
那既然需要返回的变量都不会被销毁将其再拷贝到临时变量中是不是麻烦了些因此在下面我们以引用作为返回值但这不是说就不会创建临时变量了临时变量依然会被创建但这个临时变量相当于返回变量的一个别名因为该变量出作用域后不会被销毁因此我们依然可以进行访问此时临时变量与返回变量共用一块空间相比于传值返回中间就减少了拷贝的过程返回时同样将临时变量赋值给接收的变量即可可以认为传引用返回就相当于将变量直接返回了。
: V. S8 t& n+ q0 x. R; ^
4 T z! w6 a* k b , }% w1 a4 @" X' {2 b
0 Y8 P! z8 P* M2 a8 s( N1 m
下面再看一段示例代码
! e1 B0 P1 u& R7 g5 Y6 l) i int& Add(int a, int b)
' ^1 o' s" d4 l" m3 {( t {
1 Y- r. a2 f+ n$ K6 H# Q int c = a + b;
: ]4 w7 h9 `, _ E/ q return c;
2 R- k/ e+ O( |! T0 L9 e }
! l" O6 W2 L1 B6 f/ ]" j2 J( X int main()
; e/ E7 R' r. K& Z {
% }8 d- P1 @+ s' D int& ret = Add(1, 2);
Add(3, 4);
cout
+ e8 a3 x8 t# R+ g7 X1 I; h6 V9 }/ Z 示例输出结果
, ~4 [' D6 M/ {, b! M ^/ [ [img]/295380211e044714a055a86bee97be3c.png[/img]
8 h) T) W& @, F, g$ F6 O1 M
4 U5 \; L5 \5 O# Z4 F [b]说明对于以出了作用域即被销毁的变量作为返回值的如果使用传引用返回实际上是无意义的因为局部变量的空间已被销毁不再属于该变量再通过传引用返回的别名去访问该空间时其值是不确定的无法确保得到的还是正确结果。[/b]
2 H* A7 x! X7 D( T, C& M* @1 a
[img]/8d0e2362b3814b1ea6094fb032a52fe7.png[/img]
- u2 ]8 W1 p% K. q
# L2 ~6 A6 O! f \$ l4 E9 D: r/ O* k( e
0 d( z" g- s* A [b]
[b]注意如果函数返回时出了函数作用域如果返回对象还在空间还没还给系统如静态变量、全局变量、上一层栈帧中的变量、malloc开辟的等等则可以使用引用返回否则必须使用传值返回。[/b]
/ L& t5 p3 P j) n c6 x [/b]
[u][*]传引用返回还可修改返回值。
! R' y" y# Q' w$ n 示例代码[/u]
[code]#define N 10
+ L) m% v/ Q- U typedef struct Array {
& X( z# r# a+ W5 r4 | int a[N];
! G* D* ]$ S" b+ I9 ~9 Z int size;
3 T' ]9 t8 k! b9 \/ K8 T }AY;
//查找数组中某个下标位置的元素
0 ]8 |# Y5 T3 V1 P2 n1 u; E int& PosAt(AY& ay, int i) {
assert(i
5 R. _0 Q$ X1 q! X1 v0 ? 示例输出结果
" {% ?: t- ?: D7 ?8 N: G% H# ?
[img]/17e2e59d104a4d93a20bf8bbc6305bd6.png[/img]
! C5 t3 ?1 ?) Y, j; I 4 常引用
1 R# x) ~2 h- W3 |( i [b]
[b]指针和引用在赋值和初始化时权限可以保持或缩小但不能放大。[/b]
+ b3 D( I$ `# G7 G* { [/b]
# D$ B$ t9 Y- d 示例代码
: A$ @% ]$ H/ t; I! B: ?! I# p
[code]//传值返回
int Count() {
6 ~7 u) t) y6 ] X int n = 0;
n++;
9 _' F1 B7 _+ t% E8 v# z return n;
+ W, C, l! [4 |$ L0 N3 N7 ` }
void TestConstRef() {
//示例1
# {9 o6 Z" N6 i, U$ c$ W/ o' N$ H const int a = 10;//const修饰的变量具有常属性不可修改
/ h; b7 j9 r! _# ]) H W //错误语法权限被放大。ra引用与a共用一块空间而a具有常属性
//如果ra引用不具有常属性则修改ra的值时则违背了a的常属性
//int& ra = a;//编译时会报错
const int& ra = a;//正确语法
//示例2
" h9 |) R$ p2 } //int& b = 10;//错误语法10为常量引用权限放大
. x1 C% l) j3 F const int& b = 10;//正确语法使引用变量b也具有常属性
//示例3
//Count函数是传值返回的返回的是临时变量临时变量具有常属性
! g, D4 @- `/ W1 j //int& ret = Count();//编译报错
8 Y4 a& f: O) w4 ?& \ const int& ret = Count();//正确语法ret为返回的临时变量的常引用
. v9 R0 x/ v: Q$ x M; g4 n int ret2 = Count();//正确语法将返回的临时变量赋值给ret2
//示例4
double d = 3.14;
//int& rd = d;//编译报错引用类型与引用实体不是同一种类型
//解释
//在进行类型转换无论是强制类型转换还是自动类型提升时都会产生临时变量
//如有int i = 0; (double)i其并不是直接将int型变量i转换为double型
//而是通过一个临时变量将i转化为double如果是double d = i;则是再将临时变量赋值给d
" g I+ ^$ L/ F" ~& |
//下句代码则表示rd为double型变量d强制转换为int型变量时产生的 临时变量 的常引用
' E# H. O! u9 U: m- ?- e5 T4 e //但注意rd本质上并不是d的引用二者也不是共用一个空间
//d仍是double型变量rd则为int型
const int& rd = d;//编译通过
printf("d = %f\nrd = %d\nd的地址为%p\nrd的地址为%p\n", d, rd, &d, &rd);
}
int main() {
TestConstRef();
return 0;
}
' a8 |& i( m$ m% j' `* M 复制代码
6 D6 ~1 H# Y! T. ^
- V: U3 m! N0 C5 v' x 1 Q3 c. x$ ~, r& U
+ [) M' H( A( L1 C: z 5 传值与传引用的效率比较 5 `' o, N2 q- a7 ?) R) ^6 n
$ @: K/ y' E0 {8 A1 h9 y' D
以值作为参数或者返回值类型在传参和返回期间函数不会直接传递实参或者将变量本身直接返回而是传递实参或者返回变量的一份临时拷贝因此用值作为参数或返回值类型效率是非常低下的尤其是当参数或者返回值类型非常大时效率就更低了。
1 b) I h# Z' }2 x! |" m- C 1 v- \) z1 e' \5 r# N
2 a3 H9 ]# W" G# n
下面用两段测试代码来比较传值与传引用的效率
! J/ l3 m& [* j6 O
7 \1 _8 D2 E7 y% x- D, n 5.1 值和引用作为函数参数的性能比较 - g- w/ @5 B9 t7 e" P+ ~6 r- \
测试代码; p6 I0 M4 G+ v. R+ C
# O9 `9 F$ B, o; i3 {) y. L2 A5 i #include [i]
8 S% z5 w7 S# F1 u using namespace std;
4 Y6 a# g9 R6 p7 Z* M% z #include
. r' N+ K' X, ~# x2 D! @ struct A { int a[100000]; };
5 [' U" j5 S0 t: Z& N void TestFunc1(A a) {}
; N1 h; f5 T" a/ f4 u8 [ A6 Q4 @ void TestFunc2(A& a) {}
void TestRefAndValue()
8 ] _9 u0 r/ L( J7 x( q {
A a;
// 以值作为函数参数
size_t begin1 = clock();
9 l+ J! E$ J. _9 A3 j( ` for (size_t i = 0; i
1 K! R7 P9 U# t8 v0 T( ~; ] 测试结果
* j Q q8 I6 d8 ^2 `3 [4 K5 Z [img]/f224be09a82d4d1abdf3c5bc084d084d.png[/img]
分析从测试结果来看值作为参数的函数运行时间要比引用作为参数的函数运行时间多得多以引用作为参数能很好提升程序运行效率。
4 f, {" ~: }9 e, x9 a+ k" i0 ]
- [" Y/ y" K/ \7 L [size=5]5.2 值和引用作为返回值类型的性能比较[/size]
测试代码
1 S: z, @. z5 |( [7 t- V# A: o
' H2 O2 e- r z7 A! W3 ~$ X [code]#include [i]
- Z1 T% f" W, c* g using namespace std;
1 ]/ }7 i* m/ N4 ?- P( k/ ] #include
9 Z1 L z3 C/ O3 B; E struct A { int a[10000]; };
2 \& F; A# ?% D2 D! z A a;
// 值返回
7 L- \: x4 A% ? A TestFunc1() { return a; }
// 引用返回
A& TestFunc2() { return a; }
% `+ t" Q5 k" B. g void TestReturnByRefOrValue()
{
; r$ Y5 f# c$ s( V) S5 ^ // 以值作为函数的返回值类型
size_t begin1 = clock();
for (size_t i = 0; i
6 D* Y" m/ ~1 { 测试结果
[img]/313208fbe306450091e11f3460c7b2c8.png[/img]
. j" L2 F; t4 Y9 R% g6 [) [0 z 分析从测试结果来看值作为返回值类型的函数运行时间要比引用作为返回值类型的函数运行时间多得多以引用作为返回值类型能很好提升程序运行效率。
' w8 h# \; C' x+ p7 N5 v! b5 v7 g
6 引用和指针的区别
- J6 ~; F8 y. ]# m2 d0 \- | [u][*]在 [code]语法概念上 复制代码
引用就是一个别名没有独立空间和其引用实体共用一块空间。而指针是有开辟独立空间的空间中存放的是指向的变量的空间地址。7 f+ o! q% R# f! Z
示例代码
; j) u' k% R+ E% O) M int main()
2 W7 G! S% D5 N1 \# W {
" m1 I2 }, |2 G% D: P int a = 10;
int& ra = a;//引用a
int* pa = &a;//指向a
cout
示例输出结果
6 L# ]2 N: s) e$ h9 B* Q w Z) {
[img]/52b0034359af40f1935cb383deaedcc3.png[/img]
9 v$ s% W4 s+ @ [u][*]引用在 [code]底层实现上 复制代码
实际是有空间的因为引用是按照指针方式来实现的。7 L( j8 i- m7 L9 g9 S
示例代码 5 y- J) Q, |1 I; T- C- L
int main()
2 X7 P, j7 h: q0 z {
int a = 10;
+ D6 a+ U3 C5 G8 R4 e int& ra = a;
/ i O) g4 O" B: ?- \ ra = 20;
int* pa = &a;
*pa = 20;
1 c; _. |6 m+ E, T# H return 0;
}
1 _4 p- H9 p/ s! g/ m 复制代码
/ S! |$ l" c5 }3 T 进入调试通过反汇编查看引用和指针的汇编代码对比发现**在汇编代码中引用和指针的实现相同都是开辟了独立的空间将指向变量的地址赋值给了引用变量或指针变量。**
4 e2 r, r+ n, [ : I @; Y, H- }" g/ G3 j; r
$ A9 a# k: g D( }9 U! a; k, b/ h ! `( v3 k5 \' ?( m9 }3 E: x& `3 d
引用和指针的不同点 5 M$ l( I: l+ D- Q! P+ X
8 N: R# \* Y- E4 k. g [ol]引用在概念上是定义一个变量的别名指针则是存储一个变量的地址。 引用在定义时必须初始化指针则没有要求。 引用在初始化时引用一个实体后就不能再引用其它实体而指针可以在任何时候指向任何一个同类型实体。 没有NULL引用但有NULL指针。 在复制代码
中的含义不同引用结果为引用类型的大小但指针始终是地址空间所占字节个数32位平台下占4个字节。 引用自加即引用的实体增加1指针自加即指针向后偏移一个所指向实体类型的大小。 有多级指针但没有多级引用。 访问实体方式不同指针需要显示解引用引用是由编译器自己处理。 引用比指针使用起来相对安全。不会有类似空指针等的问题。[/ol] 1 P, X9 n! p0 k$ ]6 l# N
' _7 j0 x, W h$ X- t i- U
以上是我对C++中引用相关的一些学习记录总结如有错误希望大家帮忙指正也欢迎大家给予建议和讨论谢谢- t w# l0 o" }* Z
* W" E7 E5 x* R$ P2 S. E+ Z7 C, S
' C+ P. @$ N' Q0 |2 Y4 ^% m ! Q6 D, [0 ~, B% O A' [6 h
本文来源csdn,由Java吧转载发布,观点不代表Java吧的立场,转载请标明来源出处:https://www.java8.com