• 读书笔记_Effective_C++_条款五:了解C++默默编写并调用哪些函数


     

    一个常见的面试题就是问“一个空类占几个字节”,想当然的是0个字节,但事实上类要区分不同的对象,比如:

    1 EmptyClass obj1;
    2 EmptyClass obj2;

    即便是空类,也要能识别obj1和obj2的不同,所以空类仍然要占字节数,一般占一个字节。

    还有一个针对空类的问题是“一个空类里面有什么”,就是想问编译器为这个空类自动生成了哪些成员函数。

    很容易想到的是生成了默认的构造函数和析构函数,事实上还有拷贝构造函数和赋值运算符,所以,总共生成了四个成员函数。具体地说,就是你表面上写了

    1 Class EmptyClass
    2 {
    3 };

     但实际编译器为你加了四个成员函数,所以看起来像这样:

     1 Class EmtpyClass
     2 {
     3 public:
     4 
     5 // 构造函数
     6 EmtpyClass(){}
     7 
     8  
     9 
    10 // 析构函数
    11 ~EmptyClass(){}
    12 
    13  
    14 
    15 // 拷贝构造函数
    16 EmptyClass(const EmptyClass& obj)
    17 {
    18 19 }
    20 
    21  
    22 
    23 // 赋值运算符重载
    24 EmptyClass& operator= (const EmptyClass& obj)
    25 {
    26 27 }
    28 
    29 };
    30 
    31  

    拷贝构造函数和赋值运算符的函数体内容由成员变量决定,假设有成员变量var1和var2,那么拷贝构造函数和赋值运算法的函数体像这样:

     1 EmptyClass(const EmptyClass& obj):var1(obj.var1), var2(obj.var2){}
     2 
     3  
     4 
     5 EmptyClass& operator= (const EmptyClass& obj)
     6 {
     7          var1 = obj.var1;
     8          var2 = obj.var2;
     9          return *this;
    10 }

    赋值运算符要返回自身*this,是因为考虑到可以出现连等的情况,比如obj1 = obj2 = obj3,另外,这里都使用了自身类的引用,即EmptyClass&,这里的引用是必须要加的,这是因为:

    (1) 引用修饰形参时,可以避免实参对形参的拷贝,一方面可以节省空间和时间资源,更为重要的是若实参对形参拷贝了,又会调用一次拷贝构造函数,这样拷贝构造函数就会一遍又一遍的被调用,造成无穷递归。

    (2) 引用修饰返回值时,可以使返回的对象原地修改。比如(a=b) ++,这样返回的a对象还可以进行自增操作,如果不加引用,则因为生成的是原对象的拷贝,所以这样的自增操作并不使a本体自增。

    对初学者而言,还要注意区分什么时候调用的是赋值运算符,什么时候调用的是拷贝构造函数。比如:

    EmptyClass a(b); // 调用的是拷贝构造函数
    EmptyClass a = b; // 调用的是拷贝构造函数
    1 EmptyClass a;
    2 a = b; // 调用的是赋值运算符

    这里注意一下第二个和第三个例子,同样是等号,但却调用了不同的成员函数,重要的区别就要看是不是在这句话中新产生一个对象,第二个例子新产生一个对象,所以调用的是拷贝构造,第三个例子a在“=”前已经诞生了,所以调用的是赋值运算符。

    本书中还讲到了一个特殊的情况,就是成员变量是const的,或者是引用,比如:

    1 class SampleClass
    2 {
    3 private:
    4          const int var1;
    5          double& var2;
    6 };

    这时候编译器会报错,告诉你无法提供合适的构造函数,因为对于const变量以及reference,需要在声明的时候初始化,而编译器提供的默认构造函数显然无法做到这点。可以改成下面这样:

     1 class SampleClass
     2 {
     3 private:
     4          const int var1;
     5          double& var2;
     6 
     7 public:
     8          SampleClass(const int a = 0, double b = 0):var1(a), var2(b){}
     9 
    10 };

    编译器不会报错了,但是如果像这样:

    1 SampleClass obj1;
    2 SampleClass obj2;
    3 obj2 = obj1;

    编译器会提示“operator =”函数在“SampleClass”中不可用,这说明编译器同样没有为SampleClass生成赋值运算符,因为var1和var2在初始化后,值就不能再改变了。但:

    1 SampleClass obj1;
    2 SampleClass obj2(obj1);

    却是可以编译通过的,这是因为编译器可以生成默认的拷贝构造函数:

    SampleClass(const SampleClass& s): var1(s.var1), var2(s.var2){}

    这种生成方式并不会破坏const和reference的特性。

    综上,编译器总是尽量地去生成这四个成员函数,但如果成员变量出现了const和reference,则编译器会拒绝生成默认的构造函数和赋值运算符重载函数。

  • 相关阅读:
    EasyNetQ使用(三)【Publish与Subcribe】
    EasyNetQ使用(二)【连接RabbitMQ,SSL连接,Logging】
    EasyNetQ使用(一)【介绍】
    .net core使用EasyNetQ做EventBus
    .NET Core微服务之开源项目CAP的初步使用
    CAP 介绍及使用【视频】
    .NET Core 事件总线,分布式事务解决方案:CAP
    微服务中的异步消息通讯
    使用 Metrics.net + influxdb + grafana 搭建项目自动化监控和预警方案
    编写高效的jQuery代码
  • 原文地址:https://www.cnblogs.com/jerry19880126/p/2964366.html
Copyright © 2020-2023  润新知