C++基础知识之"引用"

fengjingtu

引用概念

在C++中新增加了引用的概念
引用可以看作一个已定义变量的别名
引用的语法:Type& name = var;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void quote_demo1()
{
int a = 10; //分配4字节的内存,为内存空间取名为a。
int &b = a;
int *p = &a;

printf("&a=%d,&b=%d,p=%d\n", &a, &b, p);//三个变量相关的是同一块内存
printf("a=%d,b=%d,*p=%d\n", a, b, *p);
a = 11; //直接赋值
printf("a=%d,b=%d,*p=%d\n", a, b, *p);
*p = 12; //指针间接赋值
printf("a=%d,b=%d,*p=%d\n", a, b, *p);
b = 13; //引用间接赋值
printf("a=%d,b=%d,*p=%d\n", a, b, *p);
return;

}

引用是C++的概念

属于C++编译器对C的扩展,我们不能用C的语言考虑。

1
2
3
4
5
6
7
int main()
{
int a = 0;
int &b = a;
b = 11;
return 0;
}

引用做函数参数

普通引用在声明时必须用其它的变量进行初始化,因为他是别人的别名。

引用作为函数参数声明时不进行初始化,他是实参的别名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
struct Teacher
{
char name[64];
int age;
};
void quote_demo2(Teacher &t)//此处的t就是调用时候的t1
{
t.age = 43;
printf("age=%d\n", t.age);
return;
}
void quote_demo2_2(Teacher t)//t1是实参,t是形参
{
t.age = 45;
printf("age=%d\n", t.age);
return;
}
int test()
{
Teacher t1;
t1.age = 42;
quote_demo2(t1);
printf("age=%d\n", t1.age);
quote_demo2_2(t1);
printf("age=%d\n", t1.age);
return 0;
}

引用的意义

引用作为其它变量的别名而存在,因此在一些场合可以代替指针

引用相对于指针来说具有更好的可读性和实用性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//引用的意义:增加代码可读性,代替指针
void quote_demo3(int &a, int &b)//等效于下面的,使用,调用都更简便
{
int c;
c = a;
a = b;
b = c;
}
void quote_demo3_2(int *a, int *b)
{
int c;
c = *a;
*a = *b;
*b = c;
}
int test()
{
int a = 10;
int b = 11;
printf("a=%d,b=%d\n", a, b);
quote_demo3(a, b);
printf("a=%d,b=%d\n", a, b);
quote_demo3_2(&a, &b);
printf("a=%d,b=%d\n", a, b);
return 0;
}

引用本质

引用在C++中的内部实现是一个常指针

Type& name
Type* const name

C++编译器在编译过程中使用常指针作为引用的内部实现,因此引用所占用的空间大小与指针相同。
从使用的角度,引用会让人误会其只是一个别名,没有自己的存储空间。这是C++为了实用性而做出的细节隐藏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

//引用的本质:内部实现的一个常指针
void quote_demo4(int &a)
{
a = 5;
}
void quote_demo4_2(int *const a)
{
*a = 6;
}
int main4()
{
int a = 4;
quote_demo4(a);
printf("a=%d\n", a);
quote_demo4_2(&a);
printf("a=%d\n", a);
//C++编译器在内部,将引用的操作,改成了上面常指针的操作。
//使用引用时候,只当它是变量本身,分析奇怪语法现象的时候才考虑他的本质。
return 0;

}

结论:
引用在实现上,只不过是把:间接赋值成立的三个条件的后两步和二为一
当实参传给形参引用的时候,只不过是c++编译器帮我们程序员手工取了一个实参地址,传给了形参引用(常量指针)
当我们使用引用语法的时,我们不去关心编译器引用是怎么做的
当我们分析奇怪的语法现象的时,我们才去考虑c++编译器是怎么做的

函数返回值是引用(引用当左值)

C++引用使用时的难点:

当函数返回值为引用时,若返回栈变量,不能成为其它引用的初始值,不能作为左值使用
若返回静态变量或全局变量,可以成为其他引用的初始值,即可作为右值使用,也可作为左值使用

C++链式编程中,经常用到引用,运算符重载问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118

//函数返回值是引用
//关键在于使用什么变量来接收
int quote_demo5()
{
//static int a=5;
int a = 5;
return a;
}
int & quote_demo5_2()//基础类型a返回的时候,也会有一个副本
{
//static int a=5;
int a = 5;
return a;
}

int * quote_demo5_3()
{
//static int a=5;
int a = 5;
return &a;
}

int quote_demo5_4()
{
int a = 5;
//static int &b=a;
int &b = a;
return b;
}
int * quote_demo5_5()
{
int a = 5;
//static int *b=&a;
int *b = &a;
return b;
}
int & quote_demo5_6()
{
int a = 5;
//static int &b=a;
int &b = a;
return b;
}
int main5()
{
int a;
//以下情况根据release和debug的编译形式不同而不同
//如果将要返回变量全部设置为static,则都为5.

a = quote_demo5();
printf("a=%d\n", a);//release:5 debug:5
a = quote_demo5_2();
printf("a=%d\n", a);//release:0 debug:5
a = quote_demo5_4();
printf("a=%d\n", a);//release:5 debug:5
a = quote_demo5_6();
printf("a=%d\n\n", a);//release:0 debug:5

a = 0;
int &b = a;
b = quote_demo5();
printf("a=%d\n", b);//release:5 debug:5
b = quote_demo5_2();
printf("a=%d\n", b);//release:0 debug:5
b = quote_demo5_4();
printf("a=%d\n", b);//release:5 debug:5
b = quote_demo5_6();
printf("a=%d\n\n", b);//release:0 debug:5

a = 0;
int *p = &a;
*p = quote_demo5();
printf("a=%d\n", *p);//release:5 debug:5
*p = quote_demo5_2();
printf("a=%d\n", *p);//release:0 debug:5
p = quote_demo5_3();
printf("a=%d\n", *p);//release:0 debug:5
*p = quote_demo5_4();
printf("a=%d\n", *p);//release:5 debug:5
p = quote_demo5_5();
printf("a=%d\n", *p);//release:0 debug:5
*p = quote_demo5_6();
printf("a=%d\n\n", *p);//release:0 debug:5

return 0;
}

//函数返回值是引用,返回的是形参

int & qoute_demo6(int *p)
{
*p = 100;
return *p;
}
int qoute_demo6_2(int *p)
{
*p = 101;
return *p;
}
int main6()
{
int a = 10;

a = qoute_demo6(&a);
printf("%d\n", a);

a = qoute_demo6_2(&a);
printf("%d\n", a);

int &b = qoute_demo6(&a);
printf("%d\n", b);

int c = qoute_demo6_2(&a);
printf("%d\n", c);

return 0;
}//如果返回值是一个非基础类型,会涉及copy构造函数和操作符重载等问题,后面说明。

指针引用

指针引用,与c元的二级指针结合思考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
 //指针引用,与c元的二级指针结合思考
struct Teacher2
{
char name[64];
int age;
};
int qoute_demo7(Teacher2* &myp)
{
myp = (Teacher2 *)malloc(sizeof(Teacher2));
memset(myp, 0, sizeof(Teacher2));
myp->age = 31;
return 0;
}//指针的引用而已
int qoute_demo7_2(Teacher2 **myp)
{
Teacher2 *p = (Teacher2 *)malloc(sizeof(Teacher2));
if (p == NULL)
{
return -1;
}
memset(p, 0, sizeof(Teacher2));
p->age = 30;
*myp = p;
return 0;
}
int main7()
{
Teacher2 *t = NULL;
qoute_demo7(t);
printf("%d\n", t->age);
qoute_demo7_2(&t);
printf("%d\n", t->age);

return 0;
}

常引用

使用变量初始化const引用

在C++中可以声明const引用

const Type& name = var;

const引用让变量拥有只读属性 ,只可以修改变量本身和指针,但是不能修改const引用。

1
2
3
4
5
6
7
8
9
10
11

//常引用
//1使用变量初始化const引用
//const 让变量拥有只读属性。
void qoute_demo8_1()
{
int a = 10;
const int &b = a;//const int * const b
// b=11; //error.
return;
}
使用字面量常量初始化const引用

如果用字面量常量初始化引用,不加const会报错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//2使用字面量初始化const引用
//使用字面量初始化,const引用会分配内存空间
void qoute_demo8_2()
{
const int a = 10;//字符表
printf("a=%d\n", a);
//int &b=19;//error
// printf("b=%d\n",b);
const int &c = 19;//分配内存空间给c;
printf("c=%d\n", c);
return;
}
/*int main0000(int argc,char**argv){
int a1[4]={1,2,3,4};
int*ptr=(int*)(&a1+1);
printf("%d",*(ptr-1));
int a=0xabcd1234 ;
char b=((char*)&a)[0];
printf("%x\n",b);
}*/

结论:

Const & int e 相当于 const int const e
普通引用 相当于 int
const e1
当使用常量(字面量)对const引用进行初始化时,C++编译器会为常量值分配空间,并将引用名作为这段空间的别名
使用字面量对const引用初始化后,将生成一个只读变量