编译环境:VS 2013
一、C 语言传参
1. 传值
void swap(int left, int right)
{
int temp = left;
left = right;
right = temp;
}
这个函数很简单,就是一个交换两数的函数,我们编译运行:
咦?我明明通过函数交换了啊,怎么值还是没变呢?
原来是因为,仅仅在这个函数内部完成了两数的交换,调用者本身并没有发生交换,我们可以调用内存查看
通过监视和内存,我们发现,left 和 right 的值确实交换了
swap 函数执行完毕,我们再来看看 main 函数的 a,b
我们看到了,main 函数内的 a,b 的值并未交换,细心的我们发现,left 的地址和 a 的地址不同,right 的地址和 b 的地址不同,为什么呢?
因为:
- C 语言,在函数调用过程中,会生成一份临时变量,最终把实参的值传给临时变量,即形参是实参的一份临时拷贝,修改形参的值并不会改变实参的值。
这样做,避免了函数调用的副作用,但也使得无法在函数内部改变实参的值。- 如果想通过形参改变实参的值,只能通过指针传递
2. 传址
void swap(int *left, int *right)
{
int temp = *left;
*left = *right;
*right = temp;
}
我们编译运行之:
果然,两数交换了!
我们再调试,调用监视查看一下:
我们看到,left 接收的是 a 的地址,解引用就是变量 a,同样 right 也一样,这样就达到了交换两数的目的。
指针确实可以解决问题
在 C++ 中我们采用另一种方式,也可以解决这个问题,那就是左值引用
二、引用
1. 引用的概念
引用(reference) 是为对象起了另外一个名字,不是新定义一个变量,而是给已存在变量取了一个别名。
怎样证明我们没有定义一个新变量呢?
我们知道,定义变量就要开辟内存,如果没有开辟内存,那就没有定义新变量,我们调试查看一下就知道了:
#include <iostream>
#include <stdlib.h>
int main(void)
{
int a = 10;
int &refa = a;
std::cout << a << std::endl;
std::cout << refa << std::endl;
system("pause");
return 0;
}
我们看到,变量 a 和 refa 的地址相同,确实没有开辟新内存!!!
编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间
类型& 引用变量名(对象名) = 引用实体;
这里的类型必须和引用实体是同种类型的。
2. 引用的特性
- 引用在定义时,必须初始化
- 引用一旦引用一个实体(对象),就不能再引用其他实体了
引用相当于绑定,和初始值绑定在一起- 一个变量可以有多个引用
- 不能定义引用的引用
因为引用本身不是一个对象
3. const 的引用
在说 const 的引用之前,我们先来区别一下 C 语言和 C++ 的 const 限定符的不同。
我们说如果不希望某个变量的值被修改,可以加 const 限定符限定。
在 C 语言中:
- const 修饰的常变量不可以定义数组
C 中常变量和变量的唯一区别是常变量不能做左值
左值:既可以出现在赋值符(等号)左边作为可以被修改的变量(或表达式),也可以出现在等号右边的变量(或表达式)
右值:只能出现在等号右边的变量(或表达式)- 在 C 中可以通过指针修改 const 常变量:
int b = 20;
const int a = 10;
int *p = &a;
*p = 20;
- const 修饰指针
const int * p; // p 所指向的变量不能被修改,p可以修改
int const * p; // p 所指向的变量不能被修改,p可以修改
int * const p; // p 所指向的变量可以被修改,p 不能被修改指向
const int * const p; // p 自身和所指向的变量都不能被修改
const 修饰的是离自己最近的变量
在 C++ 中
- 常变量可以定义数组,可以作为数组下标。
- const 修饰一个变量,这个变量就是一个常量,不能被修改,指针也不行
const int * p; // p 所指向的变量不能被修改,p可以修改
int const * p; // p 所指向的变量不能被修改,p可以修改
int * const p; // p 只能指向一个可以被修改变量,但 p 不能修改指向
const int * const p; // p 自身和所指向的变量都不能被修改
这一点和 C 语言貌似一样
const 的引用
- 可以把引用绑定到 const 对象上,常量的引用
const int a = 1024;
const int &refa = a;
这里的 refa 是 a 的一个引用,同样 refa 不能被修改- 不能让一个非常量引用指向一个常量对象
这也再次说明引用的类型必须与其所引用对象的类型一致
但有两个例外:
- 在初始化常量引用时允许用任意表达式作为初始值,只要该表达式的结构能转换成引用的类型即可。
int i = 10;
const int &r1 = i; // 允许将 const int& 绑定到普通 int 对象上
const int &r2 = 10; // r2 是一个常量引用
const int &r3 = r1 * 2; // r3 是一个常量引用
我们再做个试验:
double dval = 3.14;
const int ri = dval;
std::cout << ri << std::endl;
这段代码的执行结果是 3 ,数据丢失了
- 允许一个常量引用绑定非常量对象、字面值,甚至一般表达式
4. 数组引用
int a[5] = { 0, 1, 2, 3, 4 };
int(&b)[5] = a;
数组是可以引用的
但是
int &b[5] = a;
这种写法是不对的!!!!!!
编译器报错是:
error C2440: “初始化”: 无法从“int [5]”转换为“int *[5]”
5. 引用的使用场景
- 作为函数形参
#include <iostream>
#include <stdlib.h>
void swap(int &left, int &right)
{
int temp = left;
left = right;
right = temp;
}
int main(void)
{
int a = 10;
int b = 20;
std::cout << "a = " << a << " " << "b = " << b << std::endl;
swap(a, b);
std::cout << "a = " << a << " " << "b = " << b << std::endl;
system("pause");
return 0;
}
编译执行:
果然达到了交换的目的
- 作为函数返回值
#include <iostream>
#include <stdlib.h>
int& TestRefReturn(int &a)
{
a += 10;
return a;
}
int main(void)
{
int a = 10;
std::cout << "a = " << a << std::endl;
TestRefReturn(a);
std::cout << "a = " << a << std::endl;
system("pause");
return 0;
}
编译执行:
我们再来看看下面这段代码输出什么:
int& Add(int a, int b)
{
int c = a + b;
return c;
}
int main(void)
{
int &ret = Add(10, 20);
Add(100, 200);
std::cout << "ret = " << ret << std::endl;
system("pause");
return 0;
}
30?我们编译运行:
300?出乎意料!!!
为什么呢?
因为我们返回了栈空间上的引用!!!
6. 引用和指针的区别
其实我们发现,这个引用和指针貌似很像,接下来,我们来看看他们有哪些相同点,又有那些不同点:
-
相同点
底层实现方式相同,都是按照指针的方式实现的
-
不同点
引用 | 指针 |
---|---|
在定义时必须初始化 | 没有要求 |
初始化之后,不能再指向其他对象 | 可以在任何时候指向任何一个同类型对象 |
没有 NULL 引用 | 有 NULL 指针 |
sizeof 求得的大小为引用类型的大小 | 32位系统 4 个字节 |
引用自加改变变量内容 | 指针自加改变了指针的指向 |
没有引用的引用 | 但有指针的指针 |
编译器寻址 | 手动寻址 |
引用比指针相对安全