面试无疑是对自己能力的一种检测,通过面试过程中出现的问题,知道自己的不足,从而提高。下面将对一些面试题的总结
试题
首先考验通配符的使用
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)
dd.myTestFunc(aa);
{
}
//因为 ex 没有被初始化, 所以 CMyString ex = aa 继续调用拷贝构造函数
//在上面的基础上调用,回调用拷贝构造函数
- 函数的返回值是类的对象,函数执行完返回调用者。
myTestFunc2()
ee = dd.myTestFunc2();
{
return *this;
}//在上面的基础上调用,回调用拷贝构造函数
拷贝构造函数的参数使用引用的作用
首先我们看 输出CMyString bb = aa; //(1) 拷贝构造复制对象
其实调用的是bb.CMyString (aa)
// (2)
我们假如拷贝构造函数参数不是引用类型的话,执行过程如下
- 首先会执行
CMyString ex = aa
上面的(1)引用的是 (CMyString &ex = aa
)
由于ex 没有被初始化,所以会继续调用拷贝构造函数,上面的(2)。那么又会触发拷贝构造函数,就这下永远的递归下去。
总结
拷贝构造函数的参数使用引用类型不是为了减少一次内存拷贝, 而是避免拷贝构造函数无限制的递归下去。
参考 拷贝构造函数的参数为什么必须使用引用类型