首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >初级程序员面试不靠谱指南(三)

初级程序员面试不靠谱指南(三)

作者头像
一心一怿
发布2018-04-16 14:40:17
6940
发布2018-04-16 14:40:17
举报
文章被收录于专栏:take time, save timetake time, save time

二、指针的好基友的&

1.&的意义。说&是指针的好基友其实不恰当,因为&这个符号在C/C++不止有一种含义,但是因为其经常会和指针一起出现在被问的问题列表上,所以,在大部分情况下,它们是好基友,那么&符号一共有哪些涵义呢?这一般都是初级筛选的题目,这种题目的意义在于快速的筛选掉那些根本什么也不会的人。答案很简单,主要有三个地方会用到这个符号,第一个取变量的地址,比如在int *pointer=&i;时,这是这个符号是出现在等号的右边(也就是右值),第二个表示引用,这个概念会是本节的重点,出现在int &ref=i;这种类型的等式之中,在等号的左边,第三种是一种运算符,标示两个位相与(1&1=1,1&0=0,0&1=0,0&0=0)。

2.什么是“引用”。引用这个词可以理解为精确无误的转述或者表达别人曾经说过的话或者写过的文字,说白了完全就是别人的东西,那么怎么理解引用这个东西呢?按照很多经典书籍上所叙述的,引用就可以看做是变量的一个别名(alias),可以理解成换了另外一个符号表示这个变量。在编译器编译你的程序的时候,会将你在程序里定义的所有符号放在一个叫做符号表的物体之中,然后按照一定的规律给符号表中的内容分配内存。比如你定义了一个int i=3;那么这个i就会被放在符号表中,然后会给这个符号表示的内容分配一个内存单元,其中的内容是3,再按照某种深刻的方式将这个分配的内存和这个符号联系起来,这就完成了一个变量的定义(编译原理只记得个大概了,有错误的话请指正)。对比指针和引用的使用方式,多少有点类似,那么先从下面的一个小例子初窥一下指针和引用在这个方面有什么不同:

 int i=0;
 int *pointer_i=&i;
 int &ref_i=i;
 
 cout<<"address of i:"<<&i<<endl;
 cout<<"address of ref_i:"<<&ref_i<<endl;
 cout<<"address of pointer_i:"<<&pointer_i<<endl;

      这里面出现了太多&符号,参考1里面的内容,主要有两个含义,一个是取变量地址,比如cout里面那些,pointer的那句,一个是引用,分清楚这些应该并不是什么难事,运行这段程序,可以看到输出如下:

      可以看到ref_i和i的地址是一样的,pointer的地址不一样,说明了编译器给pointer分配了一个内存单元,而ref_i没有,这里就是它们的第一个不同点,编译器会给指针分配一个内存单元,而对于引用不会,所以说ref_i所表示都不能称之为一个变量或什么的,它仅仅是i的另一个符号表示而已。它和i一样,一举一动都是操作的同一个内容,完成的同一件事情。那么设计这样一个概念的作用是什么呢?在上面这段程序中使用两个符号表示一个变量的意义明显不是要一个做另一个的备胎,其作用主要体现在涉及到函数的时候(包括成员函数),下面一个例子真的是烂大街了。

void swap(int& i, int& j)
{
  int tmp = i;
  i = j;
  j = tmp;
}

      如果你看过100页的关于C++的书,你会像背诵一样说出上面代码的意义,它可以完成两个数的交换,如果不使用&,就不可能得到正确的结果。为了稍微深刻一点的展示这个概念,可以采用如下的代码:

void swap1(int& i, int& j)
{
  cout<<"1:address1:"<<&i<<endl;
  cout<<"1:address2:"<<&j<<endl;
  int tmp = i;
  i = j;
  j = tmp;
}

void swap2(int i, int j)
{
  cout<<"2:address1:"<<&i<<endl;
  cout<<"2:address2:"<<&j<<endl;
  int tmp = i;
  i = j;
  j = tmp;
}

void main()
{
 int a=7,b=10;
  
 cout<<"address of a:"<<&a<<endl;
 cout<<"address of b:"<<&b<<endl;
 
 swap1(a,b);
 swap2(a,b);
} 

     目的是为了查看在函数调用的时候各个变量的具体情况,其执行结果如下图所示:

     可以看到在swap1中,传入两个参数的地址就是main函数中两个变量的地址,而swap2的两个参数地址是新的,和原始变量的地址没有任何关系(而且还很远,有兴趣的话这里也可以继续研究下去,但是我想一起放在函数的时候再写),也就是说无论怎么操作,它并没有改变原来的变量的值,它改变的是在另外某一个地方,我们这个传入参数的值所赋予的两个新的变量的值(这就是按值传递),和我们想改变的两个内存单元里面保存的值没有关系,为了能够简洁点的表示出这个概念,一般都会用一个词“副本”。而swap1,我们是将地址传给了调用函数(按地址传递),直接改变的我们想要改变的内存单元的值,也就是可以调用者想要达到的目的。有一点需要强调的是,在C语言中,不能使用类似swap1(int &i,int &j)这样的形式,因为C语言中没有按址传递的概念,它通过传递指针实现按址传递,但是其本质还是传递的值,只不过传递的是一个地址的值,这样说好像有些逻辑不通,不过这个问题不大,因为有的书上也认为C语言有按址传递,我想表达的主要意思是,在C语言中,不能使用swap1(int &i,int &j)。希望下面的例子能多少表达一点我想说的意思。

void test(int *u)
{
    printf("address of parameter itself:%p\r\n",&u);
    printf("address of parameter pointed:%p\r\n",&(*u));
}

void main()
{
    int i=100;
    printf("address of i:%p\r\n",&i);
        test(&i);     
} 

     执行结果如下,请仔细分别一下,2C和7C。

3.const和引用。const和引用结合也会有很多可能会咨询的问题,先从最简单的开始,下面的代码能不能通过编译?

int &b=7;

    你可以先试试,答案可能是不能通过编译,因为7是一个常量,自然需要一个常量的引用,所以正确的写法应该是const int& b=7,那么这个引用有没有被分配内存呢?如果你试着输出会发现其被分配了一个内存,你可能要说不是引用不会被分配内存吗?但是这里被分配内存的是这个常量'7',而不是引用b,所以这一点和前面所论述的并不矛盾。

    下面是一个经典问题了,在这里我也是完整的“引用”

//(1)
     int i = 42;
     const int &r = 42;
     int temp = r + i;
     int& r2 = temp;

//(2)
     int i = 42;
     const int &r = 42;
     int& r2 = r + i;

    上面两段代码哪一个是正确的?答案是第一个,r+i 会产生一个int类型临时变量所以在使用的时候要用const 引用,所以第二种用法,如果写成const int& r2 = r + i;就是对的。

    第三个问题就是在传递的时候为什么经常会遇到f(const A& a),A是一个struct或者class的名字,前面说过了按值传递是将传进来的值一个个复制到某一个地方的变量之中,如果这个类很庞大,自然在传递的过程中就会复制很多的内容,这样太浪费时间,所以如果采用的是引用的方式,仅仅是传入对象的一个别名,避免了复制所造成效率的浪费。那为什么要加一个const呢?从2中也可以看到,如果不采用const的话,传入的变量有会被改变,所以使用const可以保证不会被误操作而发生改变。但是内置数据类型(比如int)能用传值的尽量不要用传址,因为内置的数据类型没有构造析构的过程,这两种效率都差不多,而且你不按常理出牌,使用const和引用结合的方式,对于可读性会造成影响,别人会好奇什么这里需要使用这样一个东西。

4.返回值和引用。将引用作为返回值和3类似,主要目的是为了提高效率,这样做不会产生临时变量,不会调用构造函数,这是其优点,但是有一个特别值得注意的地方就是,不要返回临时变量的引用,我们以下面的例子来说明:

int& ReturnReference()
{
    int i=100;
    cout<<"function1---address i:"<<&i<<endl;
    return i;
}

void test()
{
    int x=0,y=0,z=0;
    cout<<"function2---address x:"<<&x<<endl;
    cout<<"function2---address y:"<<&y<<endl;
    cout<<"function2---address z:"<<&z<<endl;
}

void main()
{
 int &a=ReturnReference(); 
 cout<<"main---address a:"<<&a<<endl;
 test();
 cout<<"a is:"<<a<<endl; 
} 

   先来看看输出结果好了:

   可以看到,输出的a的值并不正确了,查看一下各个函数中的地址信息,a所得到的值正是第一个函数中返回的引用的值(地址相同),但是第二个函数调用以后,可以看到x也用了第一个函数中i的地址,此时a所表示的变量也在这个地址之中,所以,a变量的值被覆盖了,至于为什么不是0,这个问题在这里展开就太多了,你可以试试在你注释掉所有的cout和test语句中,就能得到正确的结果。其原因是函数里面的变量在函数结束之后(局部变量)就消失(析构)了,它原来的地址下一次仍然会被使用,从输出中也可以看到这一点,所以不要返回一个局部变量的引用。但是如果你将第一个函数的返回值保存到一个变量a中,那么无论调用test与否都是正确的,这是因为你已经又开辟了一个内存单元保存了返回的引用值,后面的修改也不会针对这个内存单元了。

5.指针和引用的区别。指针和引用的区别也是经常会被问到的一个问题,稍微总结一下好了:

   引用就是该变量自身,而指针只是指向该变量而已。

   当一个引用被创建的时候,它能再作为其他的引用,但是指针可以重新调整其指向的对象(但是这一点在有些编译器里面貌似已经是允许的了)。

   引用不能设置为NULL,指针可以。

   引用不能不初始化,指针在不考虑出问题的情况下至少是允许不初始化的。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2013-05-20 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档