C++中空类详解

1、空类,声明时编译器不会生成任何成员函数
对于空类,编译器不会生成任何的成员函数,只会生成1个字节的占位符。

    有时可能会以为编译器会为空类生成默认构造函数等,事实上是不会的,编译器只会在需要的时候生成6个成员函数:一个缺省的构造函数、一个拷贝构造函数、一个析构函数、一个赋值运算符、一对取址运算符和一个this指针。

代码:

#include <iostream>
using namespace std;
class A
{
 
};
class B
{
	virtual bool compare(int a,int b) = 0;
};
class C:public A,public B
{
 
};
class D:public A,public B
{
	virtual bool compare(int a, int b) = 0;
};
class E :virtual A, virtual B
{
 
};
class F :virtual A, virtual B
{
	virtual bool compare(int a, int b) = 0;
};
int main()
{
	cout << "A zize:" << sizeof(A) << endl;
	cout << "B zize:" << sizeof(B) << endl;
	cout << "C zize:" << sizeof(C) << endl;
	cout << "D zize:" << sizeof(D) << endl;
	cout << "E zize:" << sizeof(E) << endl;
	cout << "F zize:" << sizeof(F) << endl;
	system("pause");
	return 0;
}

分析:

类A是空类,但空类同样可以被实例化,而每个实例在内存中都有一个独一无二的地址,为了达到这个目的,编译器往往会给一个空类隐含的加一个字节,这样空类在实例化后在内存得到了独一无二的地址,所以sizeof(A)的大小为1。

类B里面因有一个纯虚函数,故有一个指向虚函数的指针(vptr),32位系统分配给指针的大小为4个字节,所以sizeof(B)的大小为4。类C继承于A和B,编译器取消A的占位符,保留一虚函数表,故大小为4。类D继承于A和B,派生类基类共享一个虚表,故大小为4。类E虚继承A和B,含有一个指向基类的指针(vftr)和一个指向虚函数的指针。类F虚继承A和B,含有一个指向基类的指针(vftr)和一个指向虚函数的指针。

2、空类,定义时会生成6个成员函数

class Empty
{
};

等价于:

class Empty
{
  public:
    Empty();                            //缺省构造函数
    Empty(const Empty &rhs);            //拷贝构造函数
    ~Empty();                           //析构函数 
    Empty& operator=(const Empty &rhs); //赋值运算符
    Empty* operator&();                 //取址运算符
    const Empty* operator&() const;     //取址运算符(const版本)
};

使用时的调用情况:

Empty *e = new Empty();    //缺省构造函数
delete e;                  //析构函数
Empty e1;                  //缺省构造函数                               
Empty e2(e1);              //拷贝构造函数
e2 = e1;                   //赋值运算符
Empty *pe1 = &e1;          //取址运算符(非const)
const Empty *pe2 = &e2;    //取址运算符(const)

C++编译器对这些函数的实现:

inline Empty::Empty()                          //缺省构造函数
{
}
inline Empty::~Empty()                         //析构函数
{
}
inline Empty *Empty::operator&()               //取址运算符(非const)
{
  return this; 
}           
inline const Empty *Empty::operator&() const    //取址运算符(const)
{
  return this;
}
inline Empty::Empty(const Empty &rhs)           //拷贝构造函数
{
  //对类的非静态数据成员进行以"成员为单位"逐一拷贝构造
  //固定类型的对象拷贝构造是从源对象到目标对象的"逐位"拷贝
}
 
inline Empty& Empty::operator=(const Empty &rhs) //赋值运算符
{
  //对类的非静态数据成员进行以"成员为单位"逐一赋值
  //固定类型的对象赋值是从源对象到目标对象的"逐位"赋值。

}


转载https://blog.csdn.net/ox0080/article/details/84162776
拷贝构造函数和赋值运算符

  在默认情况下(用户没有定义,但是也没有显示的删除),编译器会自动隐式生成一个拷贝构造函数和赋值运算符,但用户可以使用delete来指定不生成拷贝构造函数和赋值运算符,这样的对象就不能通过值传递,也不能进行赋值运算
复制代码

 1 #include <iostream>
 2 
 3 using namespace std;
 4 class Person {
 5 public:
 6     Person():{}
 7     Person(const Person& p) = delete;
 8     Person& operator= (const Person& p) = delete;
 9 
10     void SetName( string  strName ) 
11     {
12         name = strName;
13     }
14     void SetAge(int nAge)
15     {
16         age = nAge;
17     }
18 private:
19     int age;
20     string name;
21 };
22 int main()
23 {
24     Person p1;
25     p1.SetName("air");
26     p1.SetAge(18);
27 
28     //Person p2(p1);   错误的
29     return 0;
30 }

复制代码

上面定义的类Person显式的删除了拷贝构造函数和赋值运算符,在需要调用拷贝构造函数或者赋值运算符的地方,会提示无法调用该函数,它是已删除的函数。

还有一点需要注意的是,拷贝构造函数必须以引用的方式传递参数,这是因为,在值传递给一个函数的时候,会调用拷贝构造函数生成函数的实参,如果拷贝构造函数的参数仍然是以值的方式,就会无限循环的调用下去,直到函数的栈溢出。

何时调用

  拷贝构造函数和赋值运算符的行为比较相似,都是将一个对象的值复制给另一个对象,但是其结果却有些不同,拷贝构造函数使用传入对象的值生成一个新的对象的实例,而赋值运算符是将对象的值复制给一个已经存在的实例。这种区别从两者的名字也能轻易的分辨出来,拷贝构造函数也是一种构造函数,那么它的功能就是创建一个新的对象实例;赋值运算符是执行某种运算,将一个对象的值复制给另一个对象(已经存在的)。调用的是拷贝构造函数还是赋值运算符,主要是看是否有新的对象实例产生,如果产生了新的对象实例,那调用的就是拷贝构造函数;如果没有,那就是对已有的对象赋值,调用的是赋值运算符。

调用拷贝构造函数主要有以下场景:

  1:对象作为函数的参数,以值传递的方式传给函数。

  2:对象作为函数的返回值,以值的方式从函数返回。

  3:使用一个对象给另一个对象初始化。

代码如下:
复制代码

 1 #include <iostream>
 2 
 3 using namespace  std;
 4 class Person
 5 {
 6 public:
 7     Person() {}
 8     Person( const Person& p )
 9     {
10         cout << "Copy Constructor" << endl;
11     }
12 
13     Person& operator=( const Person& p )
14     {
15         cout << "Assign" << endl;
16         return *this;
17     }
18 
19 private:
20     int age;
21     string name;
22 };
23 
24 void f( Person p )
25 {
26     return;
27 }
28 
29 Person f1( )
30 {
31     Person p;
32     return p;
33 }
34 
35 
36 int main()
37 {
38     Person p;
39     Person p1 = p;    //  1 
40     Person p2;  //
41     p2 = p;           // 2
42     f(p2);            // 3
43 
44     p2 = f1();        // 4
45 
46     Person p3 = f1(); // 5
47 
48     return 0;
49 }

复制代码

 

1:虽然使用了“=”,但是实际上使用对象p来创建了一个新的对象p1.也就是产生了新的对象,所以调用的是拷贝构造函数。

2:首先声明了一个对象p2,然后使用复制运算符"=",将p的值赋值给p2,显然是调用了赋值运算符,为一个已经存在的对象赋值。

3:以值传递的方式将对象p2传入函数f内,调用拷贝构造函数构建一个函数f可用的实参

4:这条语句拷贝构造函数和赋值运算符都调用了。函数f1以值的方式返回一个Person对象,在返回时会调用拷贝构造函数创建一个临时对象tmp作为返回值;返回后调用赋值运算符将临时对象tmp赋值给p2.

5:应该是首先调用拷贝构造函数创建临时对象;然后再调用拷贝构造函数使用刚才创建的临时对象创建新的对象p3,也就是会调用两次拷贝构造函数。不过,编译器也没有那么傻,应该是直接调用拷贝构造函数使用返回值创建了对象p3。
  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无敌秋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值