C++入门篇(二)

简介: C++入门篇(二)

一、引用


1.1 什么是引用?


引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。简单来说就是,比如张三,在学校的外号可能叫小张,但是本质上张三和小张都是同一个人,小张只不过是张三的别名而已。

如何定义引用:


类型& 引用变量名(对象名) = 引用实体


例如:int a=10; int& b=a;


那么b就是a的引用。b和a都是指向同一块空间,也就是说,改变a的同时也会改变b,相反,改变b也会改变a。


注意:引用类型必须和引用实体是同种类型的。



1.2 引用的特性


  1. 引用在定义时必须初始化


  1. 一个变量可以有多个引用


  1. 引用一旦引用一个实体,不能再引用其他实体


int main()
{
  int a = 10;
  //int& b;//这种写法是错误的,引用在定义的时候必须初始化
  int& b = a;//这种写法才是正确的
  int x = 20;
  int& y = x;
  int& z = x;
  printf("%p\n", &x);
  printf("%p\n", &y);
  printf("%p\n", &z);
  return 0;
}




1.3 常引用




1.4 引用的使用场景


  1. 引用可以做参数


  1. 引用也可以做返回值


1、引用做参数:


相信大家对C语言的指针都已经相当的熟悉了,指针传参也是用得非常多了,但是应该在指针的使用上也吃过不少亏了,指针传参确实可以较少拷贝,提高效率,但是指针使用起来还是非常的繁琐的,传参的时候要取地址,访问的时候要解引用等等。正是基于这样的一些原因,C++才增加了引用,引用做参数既能做到减少拷贝,使用起来又比较简单,不容易出错。用引用做参数可以说是既拥有了指针的优点,又弥补了指针使用复杂的缺点,堪称完美。


下面我们就来看看如何用引用做参数:


//用指针做参数
void swap1(int* x, int* y)
{
  int tmp = *x;
  *x = *y;
  *y = tmp;
}
//用引用做参数
//引用传参的x是a的引用,y是b的引用
//也就是说x就是a,y就是b,所以在函数
//里面直接交换即可,改变x和y就是改变
//a和b,能达到交换a和b的结果
void swap2(int& x, int& y)
{
  int tmp = x;
  x = y;
  y = tmp;
}
int main()
{
  int a = 10;
  int b = 20;
  cout << "a=" << a << " b=" << b << endl;
  //用指针做参数
  swap1(&a, &b);
  cout << "a=" << a << " b=" << b << endl;
  //用引用做参数
  swap2(a, b);
  cout << "a=" << a << " b=" << b << endl;
  return 0;
}


引用可以作为输出型参数。


引用做参数,可以减少拷贝,提高效率。


2、引用做返回值(重点)


以下这段代码的输出结果是多少?


int& Add(int a, int b)
{
  int c = a + b;
  return c;
}
int main()
{
  int& ret = Add(10, 20);
  Add(5, 5);
  cout << "Add(10, 20) is :" << ret << endl;
  return 0;
}


有人说:“这还不简单吗?10+20当然等于30啊!”确实10+20=30是没错的,但是这里的运行结果真的是30吗?我们来看一下。



这是个什么情况?10+20=10?看来数学是体育老师教的实锤了哈哈哈。我们看一下到底是什么原因导致这个10+20会等于10。




特别需要注意:函数返回时,出了函数作用域,如果返回对象还在(还没还给操作系统),则可以使用引用返回,如果已经还给操作系统了,则必须使用传值返回。(切记)


1.5 传值和传引用效率比较


1.5.1 传值和传引用做参数的性能对比


以值作为参数或者返回值类型,在传参和传返回值期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。比如说返回值是一个很大的对象的时候,拷贝的消耗就太大了。


struct A 
{ 
  int a[10000] = { 0 };
};
void TestFunc1(A a) {}
void TestFunc2(A& a) {}
void TestRefAndValue()
{
  A a;
  // 以值作为函数参数
  size_t begin1 = clock();
  for (size_t i = 0; i < 100000; ++i)
  {
    TestFunc1(a);
  }
  size_t end1 = clock();
  // 以引用作为函数参数
  size_t begin2 = clock();
  for (size_t i = 0; i < 100000; ++i)
  {
    TestFunc2(a);
  }
  size_t end2 = clock();
  // 分别计算两个函数运行结束后的时间
  cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
  cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}
int main()
{
  TestRefAndValue();
  return 0;
}



1.5.2 传值和传引用做返回值的性能对比


struct A 
{ 
  int a[10000]; 
};
A a;
// 值返回
A TestFunc1() { return a; }
// 引用返回
A& TestFunc2() { return a; }
void TestReturnByRefOrValue()
{
  // 以值作为函数的返回值类型
  size_t begin1 = clock();
  for (size_t i = 0; i < 100000; ++i)
  {
    TestFunc1();
  }
  size_t end1 = clock();
  // 以引用作为函数的返回值类型
  size_t begin2 = clock();
  for (size_t i = 0; i < 100000; ++i)
  {
    TestFunc2();
  }
  size_t end2 = clock();
  // 计算两个函数运算完成之后的时间
  cout << "TestFunc1 time:" << end1 - begin1 << endl;
  cout << "TestFunc2 time:" << end2 - begin2 << endl;
}
int main()
{
  TestReturnByRefOrValue();
  return 0;
}



通过上述代码的比较,明显看到传值和传引用做参数或者返回值的时候效率相差很大,显然传引用是更高效的,因为传引用可以减少拷贝。


1.6 引用和指针之间的区别


在语法层面上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。实际在底层实现上是有空间的,因为引用是按照指针方式来实现的。



引用和指针的不同点:


  1. 引用在概念上是变量的一个别名,指针变量是存储了这个变量的地址。


  1. 引用在定义是必须初始化,指针不一定要初始化。


  1. 引用一旦有了它引用的实体之后就不能修改,即不能再引用别的实体,但是指针可以先指针一个变量,后指向另一个变量。


  1. 引用不能有空引用,但是可以有空指针。


  1. 引用在sizeof下的大小是这个引用的实体的大小,但是指针是固定大小的,在32位平台下是4个字节,在64位下是8个字节。


  1. 引用的自加是引用的实体的值增加1,而指针自加即指针向后偏移一个类型的大小。


  1. 有多级指针,但是没有多级引用。


  1. 访问实体的方式不同,指针访问实体需要显示地解引用,引用访问实体由编译器处理。


  1. 引用比指针使用起来更安全,例如引用不会存在越界引用等等的问题。


相关文章
|
2月前
|
编译器 C++
C++入门12——详解多态1
C++入门12——详解多态1
40 2
C++入门12——详解多态1
|
2月前
|
编译器 C语言 C++
C++入门3——类与对象2-2(类的6个默认成员函数)
C++入门3——类与对象2-2(类的6个默认成员函数)
32 3
|
2月前
|
存储 编译器 C语言
C++入门2——类与对象1(类的定义和this指针)
C++入门2——类与对象1(类的定义和this指针)
33 2
|
2月前
|
C++
C++入门13——详解多态2
C++入门13——详解多态2
81 1
|
2月前
|
程序员 C语言 C++
C++入门5——C/C++动态内存管理(new与delete)
C++入门5——C/C++动态内存管理(new与delete)
72 1
|
2月前
|
编译器 C语言 C++
C++入门4——类与对象3-1(构造函数的类型转换和友元详解)
C++入门4——类与对象3-1(构造函数的类型转换和友元详解)
20 1
|
2月前
|
存储 编译器 C++
C++入门3——类与对象2-1(类的6个默认成员函数)
C++入门3——类与对象2-1(类的6个默认成员函数)
35 1
|
2月前
|
编译器 C语言 C++
C++入门6——模板(泛型编程、函数模板、类模板)
C++入门6——模板(泛型编程、函数模板、类模板)
47 0
C++入门6——模板(泛型编程、函数模板、类模板)
|
2月前
|
存储 安全 编译器
【C++打怪之路Lv1】-- 入门二级
【C++打怪之路Lv1】-- 入门二级
23 0
|
2月前
|
自然语言处理 编译器 C语言
【C++打怪之路Lv1】-- C++开篇(入门)
【C++打怪之路Lv1】-- C++开篇(入门)
28 0