带领你打开C++的神秘之门--完结篇(中)

简介: 带领你打开C++的神秘之门--完结篇

2.2 使用场景:


做参数:


引用和指针在简单传参(后续有复杂的自定义对象传参,效果更加明显)是的对比.



做返回值:


(1)我们之前的一般返回是这样的.


//普通返回
int test()
{
  static int a = 0;
  a += 5;
  return a;
}
int main()
{
  int& c = test();
  cout << c << endl;
  return 0;
}


(2)改用引用作为返回值后:


//引用返回
int& test()//注意看返回值的类型
{
  static int a = 0;
  a += 5;
  return a;//返回的是a的别名
}
int main()
{
  int& c = test();//此时c是作为别名接收返回值
  cout << c << endl;
  return 0;
}


图解:



引用作为返回值的写法减少了拷贝,所以明显效率更高.


那以后我们都用引用作为返回值吗?(友情提示引用虽好,可不要贪杯哦!);


我们看一下下面的情况:


//引用返回
int& test()
{
  int a = 0;
  a += 5;
  return a;
}
int add(int a, int b)
{
  int sum = a + b;
  return sum;
}
int main()
{
  int& c = test();
  add(22, 44);
  cout << c << endl;
  return 0;
}


结果:


66


c 是函数test()的返回值,我们打印的是c,为啥结果确实add(22,44)的返回值,是巧合吗?


这就要考虑所在环境了,还是画图比较好理解,上图解!



如果引用做返回值时,返回的空间是被系统收回的,那就很危险.


引用作为返回值的时候,可以修改返回值,或者获取返回值,而不是获取返回值的拷贝(临时变量).


(1)普通返回:


//普通返回
int test()
{
  static int a = 0;
  a += 5;
  return a;
}
int main()
{
  int c = test();
  c =  95;//对c不会影响a,因为它只是a的拷贝返回
  cout << test() << endl;//调用了两次,所以结果为10
  return 0;
}


运行结果:


10


(2)引用返回:


//引用返回
int& test()
{
  static int a = 0;
  a += 5;
  return a;
}
int add(int a, int b)
{
  int sum = a + b;
  return a + b;
}
int main()
{
  int& c = test();
  c =  95;//对c修改,就是对a修改.
  cout << test() << endl;
  return 0;
}


运行结果:100


可以通过调试发现,test返回的是a的别名,所以c是a的别名的别名.



小结:


1.引用做参数基本上所有场景都可以.


 (1)方便做输出型参数(例如:swap()函数),可以用传引用改变实参.


 (2)传参是以别名的形式,中间没有拷贝,可以提高效率.


2.引用做返回值时需要特别注意,如果出了作用域,对象空间被系统收回了,就不能用引用返回.其它情况建议用引用返回,可以减少拷贝,提高效率.


 (1)同样,可以减少拷贝,提高效率.


 (2)可以修改返回值,或者是获取返回值.(后续会遇到这种情况).


2.3 常引用?


(1)权限放大:


 从只读–>可读,可写,权限放大会报错.因为不安全.


  //情况1
  const int a = 6;
  //错误写法
  int& ra = a; //该语句编译时会报错,因为a变量具有常性,而ra是可读可写,权限不能放大.
  //正确写法:
  const int& ra = a;//权限的平移
  //情况2
  //错误写法
  int& b = 5; // 该语句编译时会出错,b为常量
  //正确写法
  const int& b = 5;//权限的平移
  //情况3
  double d = 13.14;
  //错误写法
  int& rd = d; // 这里会发生隐式类型转换,而产生的临时变量具有常性.
  //正确写法
  const int& rd = d;
  //情况4
int Test_Const()
{
    int x = 2, y = 3;
  int sum = x + y;
  return sum;
}
int main()
{
  //错误写法
  int& ret=Test_Const();//返回值是临时变量(因为函数栈帧被销毁了,需要借助寄存器,或者别的产生临时变量返回)的拷贝,临时变量具有常性.
  //正确写法
  const int& ret = Test_Const();
  return 0;
}


情况3的特别说明:隐式转换(操作数两边数据类型不同时,要保证数据两边的类型不变,需要借助临时变量).



(2)权限缩小:


  从可读可写–>只读,权限缩小,更加安全.


  int& Test_Const()
{
  int x = 2, y = 3;
  int sum = x + y;
  return sum;
}
int main()
{
  //权限平移
  int& ret = Test_Const();//注意Test_Const函数的返回值是sum的别名,并不是具有常性的临时变量,所以这里不会报错
  //缩小
  const int& ret = Test_Const();//从可读可写-->只读,权限缩小,更加安全,不会报错.
  return 0;
}


小结:


 权限可以平移和缩小,但是不可以放大,使用时需要注意.


2.4 从底层探究引用和指针.


示例代码:


int main()
{
  int a = 4;
  //从语法上看,引用不开空间,而是用别名直接对a操作
  int& b = a;
  b = 5;
  //从语法看,指针需要开空间存储a的地址,然后通过地址去找a
  int* p = &a;
  *p=20;
  return 0;
}


我们通过调试窗口,打开反汇编窗口,观察汇编代码:



我们发现,在底层引用和指针的实现逻辑是一样的.都需要开空间,但是在语法上,我们依旧认为它是没有开空间的.


就好比:


老婆饼里面没有老婆,红烧狮子头里面没有狮子,娃娃菜里面没有娃娃.


  1. 引用语法概念上定义一个变量的别名,而指针是存储一个变量地址。


  1. 引用在定义时必须初始化,否则编译器不知道是谁的“别名”,而指针没有规定,只不过我们习惯性初始化为NULL而已.


  1. 引用在初始化时引用一个实体后,就不能再引用其他实体(上面有讲到),而指针可以在任何时候指向任何一个同类型实体,并不受限制.


  1. 没有NULL引用,但有NULL指针


  1. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)


  1. 对引用的赋值,就是对引用实体进行修改,而指针+1或者赋值则是对实体的地址编号操作,并不会影响实体.


  1. 不存在多级引用,但是可以多个引用可以引用一个实体,即一个实体有多个别名.指针可以有多级指针.


  1. 访问实体方式不同,指针需要显式解引用,引用编译器会自己转换处理.


  1. 安全性:引用比指针使用起来相对更安全 ,好歹不存在空指针吧!😂😂😂


三、重新认识一下auto关键字


3.1 auto关键字的介绍


在c语言中:


 auto是C语言的一个关键字,关键字主要用于声明变量的生存期为自动,这个关键字不怎么多写,因为所有的局部变量默认就是auto的。


int a=0;//默认就是自动生存期
//等价于下面的
auto int a=0;//写成这样也太麻烦了,我们一般直接省略不写.


C语言中提供了存储auto,register,extern,static说明的四种存储类别。四种存储类别说明符有两种存储期:自动存储期和静态存储期。


自动存储期:


auto和register。自动变量指在局部创建该变量,然后出了局部的作用域,该变量的声明周期就结束了,还给操作系统了.


静态存储周期:


extern,static


C++赋予了auto新的“生命”:


 C++11标准中,标准委员会赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型


指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得.


🌰栗子


#include <stdlib.h>
# include <iostream>
using std::cout;
using std::cin;
using std::endl;
int main()
{
  int a = 3;
  double b = 4.1;
  auto c = a + b;//会自动推导出c的类型
  cout << c << endl;
  cout << typeid(c).name() << endl;//typeid(c).name()会打印变量c的类型,后续会介绍,这里了解一下就行.
  return 0;
}


 这样看似乎auto关键字的作用不是很大,我们可以直接写double c,那是因为没有遇到复杂的场景,试着看一下下面这段代码(我们暂时不需要看懂代码的作用,只需要关注类型名即可).


#include <string>
#include <map>
int main()
{
  std::map<std::string, std::string> m{ 
    { "name", "名字" },
  { "age", "年龄" },
  {"sex","性别"} };
  std::map<std::string, std::string>::iterator it = m.begin();
  while (it != m.end())
  {
    //....
  }
  return 0;
}


由于在工程中很多情况下,我们不能展开头文件,难免会遇到这样的类型:(上面代码的it的类型)


std::map<std::string, std::string>::iterator


使用宏替换可以吗?


可以是可以,但是宏替换的缺点你是否能接受?


  1. 没有安全检查,直接进行替换.


  1. 可读性很差,也不方便调试.


那是否有小伙伴想到使用typedef进行类型重定义呢?


其实typedef也是有缺点的.


例如:


typedef char* pchar;
int main()
{
  char arr[] = "cjn";
  //const pchar p1;//此语句报错,这里是指向不可改变的常量指针,定义时需要初始化
  const pchar p1=arr;
  *p1 = 'x';//指向不可以改变,但指向的内容可以.
  printf("%s\n", arr);
  char* parr =arr;//定义一个字符指针
  const pchar* p2=&parr;//指向字符指针的地址
  **p2 ='a';//解引用后的指针与p1类型一样,指向的内容可以改
  //*p2 = NULL;//但是指针指向不能改
  printf("%s\n", arr);
  return 0;
}


const pchar等价于char* const表示指针的指向 不能改变.


因为此处类型pchar相当于是类型char* 的别名,const pchar表示const修饰的是char* ,即char*指针的的指向不能被修改,但是其指向的内容可以修改.


const pchar*的类型等价于 char * const* .


typedef有的场景很容易让我们误解类型.

目录
相关文章
|
1月前
|
Java 编译器 C++
【C++】 | 类和对象完结篇
【C++】 | 类和对象完结篇
|
10月前
|
安全 编译器 程序员
带领你打开C++的神秘之门--完结篇(下)
带领你打开C++的神秘之门--完结篇
62 0
|
10月前
|
编译器 Linux C语言
带领你打开C++的神秘之门--完结篇(上)
带领你打开C++的神秘之门--完结篇
95 0
|
10月前
|
编译器 程序员 C语言
带领你打开C++神秘之门--入门篇
带领你打开C++神秘之门--入门篇
38 0
|
11月前
|
Java 编译器 C++
【C++】类和对象(完结篇)(二)
【C++】类和对象(完结篇)
71 0
|
11月前
|
存储 编译器 C++
【C++】类和对象(完结篇)
【C++】类和对象(完结篇)
50 0
|
编译器 C++
<C++>运算符重载完结,详解赋值,关系,函数调用运算符
<C++>运算符重载完结,详解赋值,关系,函数调用运算符
109 0
<C++>运算符重载完结,详解赋值,关系,函数调用运算符
|
5天前
|
存储 编译器 C语言
c++的学习之路:5、类和对象(1)
c++的学习之路:5、类和对象(1)
21 0
|
5天前
|
C++
c++的学习之路:7、类和对象(3)
c++的学习之路:7、类和对象(3)
19 0