C++面试题大华/网顺总结

面试无疑是对自己能力的一种检测,通过面试过程中出现的问题,知道自己的不足,从而提高。下面将对一些面试题的总结

试题

首先考验通配符的使用

int _tmain(int argc, _TCHAR* argv[])
{
    int a  = 22;
    int b = 333;
    printf("%3d %3d",a,b);  //打印的内容
    //暂停,直到任意键按下
    system("pause");
    return 0;
}

上面会打印出什么?
首先看看通配符的的意义

%d              十进制有符号整数 
%u              十进制无符号整数 
%f              浮点数 
%s              字符串 
%c              单个字符 
%p              指针的值 
%e              指数形式的浮点数 
%x, %X          无符号以十六进制表示的整数 
%0              无符号以八进制表示的整数 
%g              自动选择合适的表示法 

%3d 表示输出3位整型数, 不够3位右对齐。
所以是 空格22 333

第二段程序

    class A
    {};
    class B :virtual public A{};

    printf("A.sizeof[%d]B.sizeof[%d]",sizeof(A),sizeof(B));

A是空类,其大小为1;B不是空类,其大小为4.因为含有指向虚基类的指针。
打印的结果是A.sizeof[1]B.sizeof[4]

其中有考察到一个空类的大小不是0,和虚函数表所占用的空间大小。
所谓类的实例化就是在内存中分配一块地址.(空类同样可以被实例化),每个实例在内存中都有一个独一无二的地址,为了达到这个目的,编译器往往会给一个空类隐含的加一个字节,这样空类在实例化后在内存得到了独一无二的地址.因为如果空类不隐含加一个字节的话,则空类无所谓实例化了(因为类的实例化就是在内存中分配一块地址)。

知识点

  • 在gdb中使用 thread apply all bt 查看所用线程堆栈信息
  • inux下查看对象文件依赖的动态库的工具ldd,ldd可以列出一个对象文件所依赖的所有的动态库。ldd不是可执行文件,而是一个shell脚本。

运算符重载相关,题目如下
具体考察的就是运算符重载,构造函数,拷贝构造函数,的执行顺序,


//题目:如下为类型CMyString的声明,请为该类型添加赋值运算符函数。

class CMyString

{

public:

CMyString(char *pData=NULL);//构造函数

CMyString();//析构函数

CMyString(const CMyString& r);//拷贝构造函数

private:

char* m_pData;//数据域,字符指针

};

构造函数

CMyString()      // 构造函数,p指向堆中分配的一空间  
{
    m_pdata = new char(125);
    //m_pdata = "helllo";此处在写例子的时候,大小越界了,导致析构有问题。char的默认大小是-127~127,所以使用"helllo";出错了。
    printf("默认构造函数\n");

}

拷贝构造函数

CMyString(const A& r)
{
    m_pdata = new char(100);    // 为新对象重新动态分配空间  
    memcpy(m_pdata, r.m_pdata, strlen(r.m_pdata));
    printf("copy构造函数\n");
}

析构函数

~CMyString()     // 析构函数,释放动态分配的空间  
{
    if (m_pdata != NULL)
    {
        delete m_pdata;
        m_pdata = NULL;  //释放后初始化为NULL
        printf("析构函数\n");
    }
}

运算符重载

CMyString & CMyString :: operator =(const CMystring & str)
{
    if (this != &str)
    {

        CMystring str_Temp(str);//创建的对象出了if自动析构
         char *pTemp= str_Temp.m_pData;
        str_Temp.m_pData = m_pData;
        m_pData = pTemp;
    }
    return *this;
}

测试程序

int _tmain(int argc, _TCHAR* argv[])
{
    CMyString aa;
    CMyString dd;
    dd = aa;            //运算符重载
    CMyString bb = aa;  // 拷贝构造复制对象  
    CMyString cc(aa);   // 拷贝构造复制对象  

    system("pause");
    return 0;
}

首先如果我们不写构造函数,拷贝构造函数,析构函数,系统都会为我们建立一个默认的。
而且析构函数只有一个,不能够重载。
关于拷贝构造函数的知识
参考 C++构造函数/析构函数/拷贝构造函数/深拷贝浅拷贝解析
- 浅拷贝:所谓浅拷贝,指的是在对象复制时,只是对对象中的数据成员进行简单的赋值,上面的例子都是属于浅拷贝的情况,默认拷贝构造函数执行的也是浅拷贝。大多情况下“浅拷贝”已经能很好地工作了,但是一旦对象存在了动态成员,那么浅拷贝就会出问题了。
- 深拷贝 在“深拷贝”的情况下,对于对象中动态成员,就不能仅仅简单地赋值了,而应该重新动态分配空间。
- 总结
拷贝构造函数中分为深拷贝和浅拷贝,一般情况下浅拷贝已经满足需求,但是当存在动态成员时,浅拷贝就不能满足需求了。比如一个对象中有指针成员,只是通过简单的浅拷贝,只能够让复制的对象指向同一片区域,而不是创建一片同样大小的区域。这就需要通过深拷贝来解决。

浅拷贝测试
此处我们去掉拷贝构造函数

    CMyString aa;
    CMyString dd;
    dd = aa;            //dd对象已经实例化了,不需要构造,此时只是将a赋值给bbb,只会调用赋值函数运算符重载
    CMyString bb = aa;  // 拷贝构造复制对象  
    CMyString cc(aa);   // 拷贝构造复制对象  

这里写图片描述
从监视处可以看到,aa ,bb,cc,dd,的地址都是相同的。
dd = aa 此处进行的是浅拷贝,只是对指针的拷贝,对内容没有拷贝。他们指向的都是同一块内存地址。地址都是 0x00ae12c0

  • 所以在本例子中,浅拷贝 在析构的时候,会对统一地址进行四次的析构,会出异常。
  • 另外aa ,bb,cc,dd指向的同一块内存,任何一方的变动都会影响到另一方。很不安全
  • 另外当delete aa .m_data, aa.m_data内存被释放后.,bb,cc,dd所指的空间不能在被利用了,产生指针悬挂。
  • 深拷贝采用了在堆内存中申请新的空间来存储数据,这样每个可以避免指针悬挂。

下面我们添加上拷贝构造函数

    m_pdata = new char(100);    // 为新对象重新动态分配空间  
    memcpy(m_pdata, r.m_pdata, strlen(r.m_pdata));
    printf("copy构造函数\n");

这里写图片描述

析构执行效果
这里写图片描述

析构的顺序是 cc,bb,dd,aa
这里写图片描述

拷贝构造函数何时会被调用?

  • 用类的一个已知的对象去初始化该类的另一个对象时。(初始化时用”A=B”,也可以用A(B)的形式。)例如 CMyString bb = aa; CMyString cc(aa);
  • 函数的形参是类的对象,调用函数进行形参和实参的结合时。
    void myTestFunc(CMyString ex)
    {
    }
    //因为 ex 没有被初始化, 所以 CMyString ex = aa 继续调用拷贝构造函数
    //在上面的基础上调用,回调用拷贝构造函数
    dd.myTestFunc(aa);
  • 函数的返回值是类的对象,函数执行完返回调用者。
    myTestFunc2()
    {
    return *this;
    }//在上面的基础上调用,回调用拷贝构造函数
    ee = dd.myTestFunc2();

    拷贝构造函数的参数使用引用的作用
    首先我们看 输出 CMyString bb = aa; //(1) 拷贝构造复制对象
    其实调用的是 bb.CMyString (aa) // (2)

我们假如拷贝构造函数参数不是引用类型的话,执行过程如下

  • 首先会执行CMyString ex = aa 上面的(1)引用的是 (CMyString &ex = aa
    由于ex 没有被初始化,所以会继续调用拷贝构造函数,上面的(2)。那么又会触发拷贝构造函数,就这下永远的递归下去。

总结
拷贝构造函数的参数使用引用类型不是为了减少一次内存拷贝, 而是避免拷贝构造函数无限制的递归下去。
参考 拷贝构造函数的参数为什么必须使用引用类型

猜你喜欢

转载自blog.csdn.net/osean_li/article/details/80866916