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

二、指针的好基友的&

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,指针可以。

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏转载gongluck的CSDN博客

python笔记:#008#变量的命名

变量的命名 目标 标识符和关键字 变量的命名规则 0.1 标识符和关键字 1.1 标识符 标示符就是程序员定义的 变量名、函数名 名字 需要有 见名知义 的...

3714
来自专栏xingoo, 一个梦想做发明家的程序员

【面试虐菜】—— JAVA面试题(1)

今天参加笔试,里面有设计模式,和一些基础题! 印象最深的是:什么不是Object的函数,我蒙的finalize,哎,无知! 还问了,接口与抽象类的不同,还...

2079
来自专栏追不上乌龟的兔子

为什么Python中的None is None is None == True

最近在StackOverflow上看到了一个问题,为什么Python中的None is None is None返回True,看到大家的讨论后对Python中的...

8967
来自专栏数据结构与算法

1245 最小的N个和

1245 最小的N个和 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 钻石 Diamond 题目描述 Description ...

2745
来自专栏程序员同行者

python 函数进阶

函数根据 有没有参数 以及 有没有返回值,可以 相互组合,一共有 4 种 组合形式

1193
来自专栏十月梦想

php字符串基本操作

字符串查找strstr(查找目标字符串,查找关键词),stristr(查找目标字符串,查找关键词)

671
来自专栏我和PYTHON有个约会

15. 程序编程进阶:函数的返回值

函数中代码块执行的结果,如果我们后面的代码中需要用到,就需要函数返回我们执行的结果,就是需要返回值;

1142
来自专栏青玉伏案

在Objective-C中浅谈面向对象

  接触面向对象也有一段时间了,当时是通过C++学习的OOP,后来又接触到了PHP和Java。每种OOP的语言在面向对象上或多或少都会有不同的地方,现在在学习O...

1856
来自专栏IT可乐

Java数据结构和算法(六)——前缀、中缀、后缀表达式

  前面我们介绍了三种数据结构,第一种数组主要用作数据存储,但是后面的两种栈和队列我们说主要作为程序功能实现的辅助工具,其中在介绍栈时我们知道栈可以用来做单词逆...

2559
来自专栏有趣的Python

0-浙大攻略计划-专业课-c语言入门(慕课网)

C语言入门 -> Linux C语言编程基本原理与实践 -> Linux C语言指针与内存 -> Linux C语言结构体

2792

扫码关注云+社区

领取腾讯云代金券