理论知识:引用变量(之后会谈和引用声明概念上的区别和本质上的相同)
引用变量最先出现在函数探幽中,是C++中新增的一种复合类型。引用是已定义的变量的别名(另一个名称),
引用变量的主要用途是用作函数的形参。通过将引用变量用作参数,函数将使用原始数据,而不是副本。
目录
创建引用声明
C++赋予取地址符另外一个意义,将其用来声明引用。
int rats;
int & rodents = rats;
引用数组
int arr[10] = {10};
int ( &pArr ) [10] = arr;//引用
pArr[0] = 10;//访问内容
上述表明,他们指向相同的值和内存单元。
int rats = 12;
int & pra = rats;
qDebug()<<"rats的值:"<<rats;
qDebug()<<"rats的地址:"<<&rats;
qDebug()<<"pra的值:"<<pra;
qDebug()<<"pra的地址:"<<&pra;
显然,引用之后,两个变量指向同样的内存单元,那么不妨再加一句进行验证:
int rats = 12;
int & pra = rats;
pra++;//++++++++++++++++++++++++++++++++++++++++++++
qDebug()<<"rats的值:"<<rats;
qDebug()<<"rats的地址:"<<&rats;
qDebug()<<"pra的值:"<<pra;
qDebug()<<"pra的地址:"<<&pra;
以上内容,显示了引用是如何工作的。
那么我们可以看出,引用非常像指针,那么我们来大致看看二者的区别:
int rats = 101;
int & pr = rats;
int * prs;
prs = &rats;
说明:
- 指针可以先声明,再赋值,完全没有问题,但是,引用不可以,必须在引用的时候就进行赋值操作,否则报错。
- 引用更像是const指针,一旦赋值,就会和这个变量关联起来,并将一直效忠于它。
- 指针可以在程序中进行重新的赋值,可以指向其他内存地址,但是引用不可以,一旦在程序中对引用进行二次赋值,原始变量也会跟着改动。引用更像是原始变量的一种别称。
那么此处就可以说明一个问题:
c++中的引用与指针的区别
★ 相同点:
1. 都是地址的概念;
指针指向一块内存,它的内容是所指内存的地址;引用是某块内存的别名。
★ 区别:
1. 指针是一个实体,而引用仅是个别名;
2. 引用使用时无需解引用(*),指针需要解引用;
3. 引用只能在定义时被初始化一次,之后不可变;指针可变;
4. 引用没有 const,指针有 const,const 的指针不可变;
5. 引用不能为空,指针可以为空;
6. “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身(所指向的变量或对象的地址)的大小;
typeid(T) == typeid(T&) 恒为真,sizeof(T) == sizeof(T&) 恒为真,但是当引用作为成员时,其占用空间与指针相同(没找到标准的规定)。
7. 指针和引用的自增(++)运算意义不一样;
下面我们看一到题目,来体会一下二者差别,看看引用为啥是“别名”
std::string str1("trend");
std::string str2("micro");
std::string& strs = str1;
std::string* ptrs = &str1;
strs = str2;
ptrs = &str2;
strs = micro, * ptrs = micro
strs = str2并不是把引用指向str2。
由于strs相当于str1, 该句会调用赋值构造函数将str2赋值给str1
这是非常关键的。
将引用用作函数参数(引用最为本质的用法):
C++按值传递,按值传递导致被调用函数使用调用程序的值的拷贝。
C语言也允许避开按值传递的限制,采用按指针传递的方式。
//按值传递
void sneey(int x);
int main()
{
int times = 20;
sneezy(times);
}
void sneezy(int x)
{
.....
}
//按引用传递
void grumpy(int &x);
int main()
{
int times = 20;
sneezy(times);
}
void grumpy(int &x)
{
.....
}
那么说了这么多,引用到底应该如何用?适用范围又是哪儿些呢?
C++之所以提出引用,主要还是为了函数在传递大量参数时,提高代码的效率所使用的手段
所以常规的基本数值类型,还是按值传递即可。
而数据较大的数据类型,比如类(对象)和结构,可以使用按引用传递,会提高代码的效率。
将引用声明用于结构
.........(头文件)
struct free
{
int made;
int attempts;
float percent;
};
void display(const free & ft);
free & accumulate(free & target,const free & source);
int main()
{
free one = {"one",1,1};
free temp = {"temp",2,3};
free dup = {"dup",2,3};
accumulate(temp,one);//表达式1
display(temp);
display(accumulate(temp,one));//表达式2
//表达式1和表达式2等价
accumulate(temp,one) = dup;
}
void display(const free & ft)
{
cout<<ft.made<<.....
.............
}
free & accumulate(free & target,const free & source)
{
target.attempts + = source.attempts;
target.made += source.made;
return target;
}
我们可以看到,程序段中有一个非常特殊的函数
free & accumulate(free & trget,const free & source);
返回值是引用,参数分别是引用于const引用
那么这么做到底有什么好处呢?
传统的按值传递和return,都是将数据复制到一个临时内存中,然后在传递给形式参数或者return回去。
如果数据量小,影响并不明显,如果数据量大,那么会影响程序的响应速度。
但是如果我们按照引用传递并且在合适的时候返回引用,那么将提高程序运行的速度
但是有时候我们仍然需要使用传统的返回机制
为了保持某些变量不被修改,及时是结构体或者类,依旧使用传统的返回机制。
程序中还有一条语句:
accumulate(temp,one) = dup;
在C++中,等式的左边需要是可以修改的内存块,而等式的右边包括字面值(10)和表达式(X+Y)
上述内容完全符合C++的要求,accumulate( )返回值是引用,完全是可以修改的内存块。
如果我们更改一部分内容:
const free & accumulate(free & trget,const free & source);
比起原函数,增加了const部分
表明返回值引用,是不可改变的,那么上述语句:
accumulate(temp,one) = dup;
就是错误的,因为等式的左边是不可修改的。
将引用声明用于类对象
将类传递给函数时,C++通常的做法是使用引用。
下面的程序将展示如下:
........
//预处理头文件
string version1(const string & s1,const string & s2);//ok
const string & version1(string & s1,const string & s2);//有影响
const string & version1(string & s1,const string & s2);//内存泄漏
int main()
{
string input;
string copy;
string result;
result = version1(input,"***");
result = version2(input,"***");
result = version3(input,"***");
return 0;
}
string version1(const string & s1,const string & s2)
{
string temp;
temp = s2+s1+s2;
return temp;
}
const string & version1(string & s1,const string & s2)
{
s1 = s2+s1+s2;
return s1;
}
const string & version1(string & s1,const string & s2)
{
string temp;
temp = s2+s1+s2;
return temp;
}
三个函数:
第一个函数,属于传统的返回机制,后两个函数都是返回引用。
第一个函数没有问题,非常稳定。
第二个函数虽然可以工作,但是返回的是引用值,会改变s1的原始数据。
第三个函数错误!会引起内存泄漏,毕竟temp是局部变量,函数又需要返回引用,二者矛盾。
如果返回值是引用,那么函数内的return 后面的变量,一定不能是局部变量,应该是全局变量,因为局部变量超出作用域后
自动释放内存,引用的被引用量都没有了,何谈引用。
上述介绍完毕,具有普遍意义,但是不具备特殊性,请根据工程要求自取。
下面说明一下引用声明和引用变量的差异
- 二者其实都是引用,那么引用变量是在一个源文件中的概念,而引用声明是相对于定义声明产生的,是不同源文件之间共享数据的一种方式。
- 引用声明需要关键字extern,而引用变量只需要 & 运算符即可。
- 定义引用声明和引用变量的形式有所区别
//引用声明:
twofile1.cpp
/////////////
#include<iostream>
int tom = 3;//静态存储持续性,外部链接性
twofile2.cpp
////////////
#include<iostream>
extern int tom//引用声明
//引用变量:
int re = 3;
int & pr = re;
总结:
关于const:将引用参数声明为常量数据的引用的理由有三个
- 使用const可以避免无意中修改数据的编辑错误
- 使用const使函数能够处理const和非const实参,否则将只能接受非const数据
- 使用const引用使函数能够正确生成并使用临时变量
那么,何时使用引用参数呢?总结一下:
使用引用参数的主要原因有两个
- 程序员能够修改调用函数中的数据对象
- 通过传递引用而不是按值传递,可以提高程序的运行速度
对于使用传递的值而不作修改的函数
- 如果数据对象小,如内置数据类型或小型结构,则按值传递
- 如果数据对象是数组,则使用指针,因为这是唯一的选择,并将指针声明为指向const的指针
- 如果数据对象是较大的结构,则使用const指针或const引用,以提高程序的效率
- 如果对象是类对象,则使用const引用
对于修改调用函数中的数据的函数
- 如果数据对象是数组,那就指针
- 如果数据对象是结构,则使用引用或者指针
- 如果数据对象是类对象,则使用引用
使用引用时的注意事项
- 引用必须指向一块合法内存空间
- 不要返回局部变量的引用
引用的本质
int a = 10;
int * const aRef = &a;
int a = 10;
int & aRef = a;
//程序会自动转换为 int * const aRef = &a;这也就是为什么引用一定要在定义的时候初始化的原因
aRef = 20;
qDebug()<<"aRef:"<<aRef;
qDebug()<<"a:"<<a;
指针常量:关键字顺序为 * 、 const(和中文顺序一致), 例如 int * const a,表示指针a是一个常量,初始化后不可更改指向(永远指向某个对象),但是指向的对象的值可以修改,如*a=10;
sizeof()和引用
void main()
{
int a[10]={0};
testyinyong(a);
}
void testyinyong(int (&a)[10])
{
qDebug()<<sizeof(a);
}
务必注意引用 参数的格式: int (&a)[10];
输出:
sizeof求数组大小的时候,等于数组元素个数*每个数组元素的大小,计算字符串的时候要算是字符串结束符'\0',与strlen不同的就是strlen不计算'\0',但是当数组是函数的形参时会将降为指针。但是引用就另当别论了。
题目:
char array[] = "abcdefg";
printf("%d\n", sizeof(array)); (1)
char *p = "abcdefg";
printf("%d\n", sizeof(p)); (2)
void func(char p[10])
{
printf("%d\n", sizeof(p)); (3)
}
void func(char (& p)[10])
{
printf("%d\n", sizeof(p)); (4)
}
int main(void)
{
printf("%d\n", sizeof(char[2])); (5)
printf("%d\n", sizeof(char &)); (6)
return 0;
}
8 4 4 10 2 1
第四个,传递引用,并计算sizeof,因为是引用,不同于指针,所以大小不会因为变成了函数参数就折损为普通指针
所以还是10
应用场合
.h
typedef struct Test
{
int M_age;
}T;
.cpp
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
T * p = nullptr;
allowspace(&p);
qDebug()<<"M_age"<<p->M_age;
}
void MainWindow::allowspace(T ** p)
{
*p = (T *)malloc(sizeof(T));
(*p)->M_age = 20;
}
以上代码使用了二级指针,有点绕
可以使用引用指针进行代替
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
T * p = nullptr;
allowSpaceRef(p);
qDebug()<<"M_age"<<p->M_age;
}
void MainWindow::allowSpaceRef(T * & p)
{
p = (T *)malloc(sizeof(T));
p->M_age = 40;
}
好理解很多