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

3.read-only的const。如果你突然冒出一句看似很高深的话但又不解释一般都是装逼,就像前面提到过const准确的应该理解为一个read-only的变量而不是一个常量,那么常量和变量的区别到底是什么呢?按照c语言的定义,比如,1.5是一个浮点数常量,”roger”这是一个字符串常量,而在C语言中(在C++语言中扩展了const的定义,不能完全使用这种理解方式),const并不是一个常量,你可以定义一个.c文件,然后使用如下的定义:

const int Length=100;
int arr[Length];

     进行编译,编译器会给出类似这样的错误信息“expected constant expression”,说明编译器不认为const定义的是常量,而如果使用int arr[1]就可以通过编译。还有一个地方也可以进行证明,在c语言中,case语句里必须是常量,如果const定义的真的被认为是常量的话,那么在switch的case语句中就可以使用它,但是事实并不是这样的,在编译器中做这样的尝试之后,会给出类似如下的错误信息:“case expression not constant”。

4.类里面的const。C++作为一种面向对象的语言,可以定义类、成员函数等等是其区别于C语言的一个重大特点,const关键字在类中的使用也是有各种陷阱和容易混淆的地方的,为了配合下面的说明,我们定义一个类Mobile。

class Mobile
{
public:
    Mobile(){electricity=0;};
    void SetElectricity(int value){electricity=value;};
    int GetElectricity()
    {
        cout<<"non-const"<<endl;
        return electricity;
    };
private:
       int electricity;//电量
};

     在这个类中,有一个设置电量的成员函数和一个获得电量的成员函数。但是目前这个类有个问题就是可以在Get函数中修改所要获取的值,虽然在有些情况下是可以的,但是在本类中我们的本意是完整无误获取电量,所以需要确保这一点,我们必须使用const,为了区别于返回一个const int的值的函数,C++采用的是在函数后面添加const表示该函数不能修改成员变量。为了展示这两种不同的摆放方式有什么不同,添加两个成员函数如下:

const int GetElectricity()
{
     cout<<"const1"<<endl;
     return electricity;
};

int GetElectricity() const
{
     cout<<"const2"<<endl;
     return electricity;
};

     这时候点击编译,一定会产生类似如下的错误信息“'GetElectricity' : redefinition; different type modifiers”,c++中,返回值不同不能区别函数的重载与否。所以编译器会认为这是两个一样的函数,所以如果这样定义一个函数其作用是返回值不可被修改。在删去该函数之后,编译通过,也就是说,在类中,可以使用const实现对于成员函数的重载,但是想要达到这一目的摆放的位置很重要。既然是重载,那么就应该在调用中体现出来,C++中是采用如下的方法调用这两个函数的,对于const的对象调用const的函数,而对于非const的对象,调用普通的函数,可以通过以下的测试代码证明:

Mobile m;
m.SetElectricity(100);
const Mobile m1;
 
m.GetElectricity();
m1.GetElectricity();

     运行结果如下:

    

     可以看到它们的调用的函数各不相同,可以按照这个思路从编译器的角度理解一下为什么m1调用Set会产生错误,其产生错误的原因绝对不是“从逻辑上一个const的值不能被修改”,编译器完全不知道什么叫做逻辑,其判断方式是在class里面根本找不到const标示的重载函数,所以其判断为错误,我觉得写程序要时刻铭记编译器的思考方式和人的思考方式是不同的。总之,在类中,可以使用const限制成员函数对成员变量的修改,并且可以实现成员函数的重载。

      下一个问题可能稍微有些颠覆性,怎样看待一个类的const?也就是说比如m1,我们是应该认为在外部(也就是调用方)不能修改其值且在其内部也不能修改其值,或者是在外部不能修改其值但是在内部可以修改,只是让外部不要发现呢?啊,我描述的有点绕,那么我换个方式描述一下试试看。比如想象你是调用方,你并不知道这个类内部是怎样实现的,你只要能够检查到m1的内容不能随意被修改就可以认为其符合const的约定,看起来这么说在逻辑上符合对于const的要求。还有一种方法就比较严格,你要求类内部要严格自律,不是只是让外部看起来是不能被修改的,同时内部也是一丝一毫不能被修改。这两种都可以说的通,但是编译器采取的是哪种呢?机器不像人,没有拥有感情,这既是好处也是坏处,在设计的时候,其采用的是上面第二种方式,但是,所有人类都是一样,总不喜欢一直按照规矩走下去(或者说人们在制定规矩的时候都是从片面的地方,并不能想出一个全面完美的规矩),有时候可能真的需要在标示了const的成员函数中修改某一个成员变量的值,于是C++为了解决这个问题,提出了一个和const相反的关键词,mutable,只要用该关键字标示的成员变量就可以在const函数中被修改,比如将上面类添加一个新的成员函数ispoweroff,标示为mutable,用来断定是否没电了,虽然在Get里面设计这样一个内容不合逻辑,先将就着看看吧。初始为false,在标记有const的成员函数中添加如下语句:

if(electricity==0) ispoweroff=true;

      使用上面一样的代码测试该程序,可以发现并没有报错,说明即使是const标记的函数仍然通过了检测,此时const的对象同样不能调用非const的函数,说明至少在外部不能察觉出const的对象内容被修改,听起来有点像中国常用走后门技术,但是这也是经常会被问到的一个问题。

5.其他有关const的内容。

     const还有一些可能会被问到的问题,我印象中最多的一个是#define和const定义一个内容有什么不同,#define定义的是一个宏,定义的是一个常量,只是简单的进行替换,所以其并不能进行类型的检查,但是const定义不一定是常量(在C语言中),#define定义的内容在C语言中可以使用在数组大小,case语句中,但是const定义不能。另外,const定义的变量可以更节省内存,因为const定义的内容不会在每次赋值给某个变量时都会重新分配内存,而#define定义每次都会分配一个内存。

     如何将const的强制转换成为非const,c++使const_cast<type>(expression)可以将非const的值转化成为const的值,但是这个转换指的是const指针转换成为非const指针,比如下面的代码就是错误的:

const int b=0;
int a=1;
a=const_cast<int>(b);

     在给出正确的代码之前,在这个地方可以继续考察下自己有没有搞清楚上一篇说的const int*和int *const之间的差别,可以思考一下,下面哪个赋值是可以通过的?   

//代码1
int i=0;
int *const  b=&i;
int* a;
a=b;
//代码2
int i=0;
int const *b=&i;
int* a;
a=b;

      不能的那一组采用a=const_cast<int*>(b)就能完成转换。如果想把非const对象转换成为const对象,可以使用static_cast。

      还有一个常见的就是关于iterator的,在C++中你可以定义如下两种iterator:

const vector<int>::iterator it1=...;
vector<int>::const_iterator it2=...;

      这也是经常会被问到的一个地方,也就是上面两个的差别,这个正好和指针那个相反,也就是第一个不能修改其指向,但是可以修改其指向的内容(不能++操作等等),第二个是不能修改其指向的内容,但是可以修改其指向(可以参与迭代),可以参考一下上一篇的内容(初级程序员面试不靠谱指南(一)),具体原因,到写STL的时候再详细说明。

     下一个我曾经遇到的问题是,为什么一般要用const修饰operator*(或者在重载操作符其余的操作符定义中)?这个设计我个人觉得很巧妙,由于const不能作为左值,所以如果定义了两个对象a,b,那么(a*b)=c这种情况就不会通过编译。

      另外关于一些const的比较重要内容就是和&相关的一些,也就是我下一篇要写的内容,关于从const的内容,我推荐可以看看下面这篇文章:

      http://www.cprogramming.com/tutorial/const_correctness.html

 

posted on 2013-05-19 20:11  一心一怿  阅读(1646)  评论(4编辑  收藏  举报

导航