返回首页

C++中必须知道的问题(转)

时间:2013-03-14 09:32来源:摘录所得 作者:未知 点击:
把前端时间学习c++中遇到的一些问题跟大家分享一下 1.静态成员函数为什么不能声明为const 2.当一个父类对象以其子类对象初始化时会发生什么 3.一个类的对象所占内存是由什么决定的
  
把前端时间学习c++中遇到的一些问题跟大家分享一下

1.静态成员函数为什么不能声明为const
2.当一个父类对象以其子类对象初始化时会发生什么
3.一个类的对象所占内存是由什么决定的
4.构造函数初始化列表的作用
5.为什么最好不要在构造函数和析构函数中调用虚函数
6.虚函数表是怎么回事
7.虚函数指针vptr存在于一个对象的什么位置,其作用是什么
8.一个string对象的数据成员占多少内存
9.一个空类的对象占几个字节
10.通过指针存取一个对象的数据成员和通过对象直接存取其数据成员有什么区别
11.什么时候会调用拷贝构造函数,什么时候又会调用赋值运算符?
12.构造函数为什么不能声明为虚函数

答案:
1.普通成员函数声明为const是防止函数修改调用该成员函数的对象的数据成员,这个对象时通过this指针隐式传递给该函数的
,而静态成员函数没有this指针,所以就没有这项功能,也就不需要声明为const
2.子类对象的数据成员包含继承自父类的数据成员和子类本省的数据成员两部分,所以用子类对象赋值给父类对象时会发生切割行为,即初始化的父类只会存在子类对象的父类数据成员部分
3.一个类的对象所占内存是由其数据成员(包括继承自父类的数据成员和自己本身的数据成员两部分)加上其虚函数指针(如果存在虚函数)
4.使用初始化列表的构造函数显式的初始化类的成员;而没使用初始化列表的构造函数是对类的成员赋值,并没有进行显式的初始化
5. 实例化一个派生类对象时,首先调用父类构造函数,此时派生类部分的成员变量还没有初始化,如果虚函数下降到派生类级,会发生不可预知的行为
6.C++中当一个类中存在virtual函数(虚函数)或者它的父类中存在虚函数,那么编译器就会为这个类生成虚函数表(virtual table),虚函数表中存放着虚函数的地址,含有虚函数的类或者其子类的对象编译器都为其加上一个虚函数指针vptr,vptr在执行时指向对应的虚函数,从而实现多态
7.vptr一般的编译器会将其放在对象所占内存空间的最前面的部分,vptr的作用就是指向虚函数表存放的函数

8.要解答这个问题就要高清string类的实现,一个string对象的数据成员包括存放该string的地址的指针,还有该string的长度两部分,即:4+strlen(str)+1
9.一个空类为1个字节,可能会因为编译器不同而有所不同,这一个字节是为了定位这个空类的对象,反过来思考会比较容易理解,我们假设空类对象不占内存,那么系统怎么区分这个空类的若干个对象呢?因为普通类(非空类)的对象都占据一定的内存空间,所以系统可以唯一的确定他们并调用他们相应的成员函数,那么为什么是1个字节,而不是2个,3个或者更多呢,当然是1个字节就可以定位空类的对象了,多的话岂不是浪费
10.当要存取的数据成员是从虚基类继承过来时,通过指针不能确定这个指针具体指向哪个对象,存取只能延迟到执行期间,而通过对象存取,在编译期间就已经确定了该数据成员相对于该对象的便宜,所以用对象存取效率更高,如果不存在虚基类,那么两种方式完全一样
11.在一个新对象被创建时用另一个对象初始化该对象一定会调用拷贝构造函数,因为新对象的创建一定会调用构造函数,如果不存在新对象的创建那么就会调用赋值运算符
12.构造函数是分配内存的行为,虚函数需要通过虚函数表虚函数指针来调用,在构造函数之前,这些都还没有初始化,所以也就无从调用,所以构造函数不能为虚函数

对于有的网友的评论说第5题的答案有些问题,所以我在这里对第5题(为什么不要在构造函数和析构函数中调用希函数)给出了代码演示,希望您能通过这些断码能看出某些端倪:
下面的这段代码是用来说明为什么不要在构造函数中调用虚函数,注意是调用,就是函数调用
#include <iostream>
using namespace std;
class A
{
public:
A()
{
cout<<"A的构造函数"<<endl;
print();
}
virtual void print()
{
cout<<"A中的虚函数print()"<<endl;
}
virtual ~A()
{
cout<<"A的析构函数"<<endl;
}
};


class B:public A
{
public:
B()
{
cout<<"B的构造函数"<<endl;
print();
}
void print()
{
cout<<"B中的虚函数print()"<<endl;
}
virtual ~B()
{
cout<<"B的析构函数"<<endl;
}
};




int main()
{
 cout<<"************************"<<endl;
 A *pA = new B;
 pA->print();
 cout<<"************************"<<endl;
 delete pA;
 return 0;
}

下面贴出这段代码的输出,用的是微软的vc6.0,有兴趣的同学也可以在别的编译器如gcc下试试,我想答案应该也是一样的

看到程序的第二行输出了吧,我们在A的构造函数中调用了虚函数数print(),如果虚函数起作用那么应该调用B中的print(),输出“B中的虚函数print()”,原因就是new B首先会调用A的构造函数,A的构造函数执行时,B中的成员和vptr都还没有初始化,所以这时调用虚函数print()还调用的A自己的print()

再来看看为什么不要在析构函数中调用虚函数,这也是fera的问题,我在第5题的答案中确实没有回答,那是因为我觉得这个跟构造函数中为什么不要调用虚函数类似,既然有问题,那么下面给出代码演示:
#include <iostream>
using namespace std;
class A
{
public:
A()
{
cout<<"A的构造函数"<<endl;
print();
}
virtual void print()
{
cout<<"A中的虚函数print()"<<endl;
}
virtual ~A()
{
cout<<"A的析构函数"<<endl;
print();
}
};


class B:public A
{
public:
B()
{
cout<<"B的构造函数"<<endl;
print();
}


void print()
{
cout<<"B中的虚函数print()"<<endl;

}
/* virtual ~B()
{
cout<<"B的析构函数"<<endl;
print();
}
*/
};


int main()
{
 cout<<"************************"<<endl;
 A *pA = new B;
 cout<<"************************"<<endl;
 delete pA;
 return 0;
}

下面贴上这段断码的输出,有图有真相:

当delete pA执行时,先调用子类B的析构函数,这里避免混淆我注释了B的析构函数,然后再调用A的析构函数,如果A中的虚函数生效,那么肯定会调用B中的print()函数,因为A *pA = new B;显然根据输出的结果可以看出这里只调用了A中的虚函数print(),也就是说虚函数此时失效,因为如果虚函数起作用,那么会调用B的print()函数,而此时B的析构函数已经执行完毕,B中的成员已经被析构,所以不可能再调用他的任何函数,这就是虚函数在析构函数中失效的根本原因。
 
 
相关课程链接C++双平台就业班
 

 
------分隔线----------------------------

  • 李老师
  • 李老师
  • 胡老师
  • 胡老师
合作伙伴
  • 武汉工程大学合作培训机构

  • 国家信息技术紧缺人才培养工程(NITE)

  • ARM公司全球授权培训中心

  • 国内首家Symbian授权培训

  • 微软全球嵌入式合作伙伴

  • Altera全球合作培训机构

在线客服
  • 客服热线:
    139-8620-3604
    159-2752-9536



  • 官方微信
  •  咨询微信二维码