• C++你可能不知道地方


    c++中编译器替我们完成了许多事情,我们可能不知道,但也可能习以为常。下面详细介绍
     
    一、初始化与初始赋值
    首先说说类的初始化与初始赋值之前的区别,这也许里面可能有我们不知道的事情。  
    其实类初始化与初始赋值还是有区别的。
    复制代码
     1         class People{
     2         public:
     3                 People(std::string name,int age,int height);
     4         private:
     5                 std::string m_sName;
     6                 int m_iAge;
     7                 int m_iHeight;
     8         }
     9         //赋值
    10         People::People(std::string name,int age,int height)
    11         {
    12                 m_sName=name;
    13                 m_iAge=age;
    14                 m_iHeight=height;
    15         }
    16         //初始化列表
    17         People::People(std::string name,int age,int height)
    18         :m_sName(name),m_iAge(age),m_iHeight(height)
    19         {}
    复制代码
     C++规定,对象的成员变量初始化动作发生在进入构造函数本体之前。在构造函数内成员变量赋值都不是初始化,而是赋值。
     赋值时首先调用默认构造函数为m_sName,m_iAge,m_iHeight赋初始值,然后在立刻调用赋值操作符进行赋新值。
     成员初始列表是将各个成员变量实参都作为复制构造函数的实参。
     所以看出赋值相对于初始化,多了一步就是使用赋值操作符进行赋值。所以初始化的效率比赋值的效率高多了。但是对于内置类型,它们效率是一样的。
     
    二、空类
      想想你如果声明一个空类,C++编译器会对它做什么呢?编译器就会为它声明一个复制构造函数,赋值操作符和一个析构函数,以及默认构造函数。所有这些函数都是public而且inline函数。
    编译器写的赋值构造函数和赋值操作符,只是单纯地将来源对象的每个non-static变量拷贝到目标对象,具体是进行位拷贝。
         如果声明了一个构造函数,编译器是不会创建默认构造函数。
      如果不希望类支持拷贝构造函数与赋值操作符怎么办?不声明?按照上面说明编译器会自动帮你生成。那么可以将它们声明为private,这样阻止编译器自动生成拷贝构造函数(public)。private成功阻止他人使用,但是这并不安全。因为类成员函数以及友元函数还是可以调用private的拷贝构造函数和赋值操作符。
         如果只在private下声明拷贝函数和赋值操作符,在有人通过类成员函数去以及member函数去调用它,会获得一个连接错误。那么这里能不能将错误在编译的时候体现出来呢?这里只用将拷贝函数声明为private,并且不在自身,就可以办到了。显然继承一个拷贝函数和赋值操作符为private的基类就办到了,基类如下:
    复制代码
    1         class NonCopyable{
    2         protected:
    3                  NonCopyable (){}
    4                 ~  NonCopyable (){}
    5         private:
    6              NonCopyable (const  NonCopyable &);
    7              NonCopyable & operater=(const  NonCopyable &);
    8         };
    复制代码
      原因是类成员函数或者友元函数尝试拷贝对象,编译器便会尝试生成一个复制构造函数与赋值操作符,并会调用基类的对应函数,但是会被拒绝,因为基类这些函数是private。
     
    3、++函数
      下面说说“*++"与"++*"中你不知道的事情,c++规定后缀形式自加函数有一个int类型参数,当函数被调用时,便其一传递一个0作为int参数的值传递给该函数,而前缀形式自己函数,类型参数没有要求,所以这样就能区分一个++函数是前缀形式与后缀形式了,具体代码如下:
    复制代码
     1 class UPInt{
     2 public
     3     UPInt& operator++( ) ;                      //++ 前缀
     4     const UPInt operator++( int );          //++后缀
     5     UPInt& operator --( );                       // --前缀
     6     const UPInt operator --( int )           //--后缀
     7     UPInt& operator +=( int );               //
     8     ...
     9 };
    10 
    11 UPInt & UPInt::operator++( )
    12 {
    13     *this += 1;
    14     return *this;
    15 }
    16 
    17 const UPInt UPInt :: operator++( int )
    18 {
    19     UPInt oldValue = *this;
    20     ++(*this);
    21     return oldValue;
    22 }
    复制代码
    后缀函数使用返回参数类型const,是为了避免下面代码生效
    1 UPInt i;
    2 i++++;
    这个时候第一次调用++返回cosnt对象,并再次调用然后这个函数是non-const成员函数,所以const对象无法调用这个函数,那么i++++就无法生效了。
    这里说说效率问题,我们可以看到后缀++函数建立一个临时对象以作为它返回值,这个临时对象经过构造并在最后被析构。而前缀++函数没有这样的临时变量,并且没有那样的操作。所以如果我们在程序中使用前缀++效率会更加高一些,没有了临时变量的构造与析构的动作。
     
    4.虚析构函数
    带有多态性质的base class应该声明一个virtual析构函数。
    为什么这么说呢?看下面例子
            class base
            { ... }
            class derived:public base
            {... }
            
            base * p= new derived;
         假设这里基类的析构函数不是virtual,当使用完p指针,我们删除它的时候,想想会发生什么,因为基类的析构函数是non-virtual所以不会发生多态直接调用基类析构函数,仅仅删除继承类中基类那部分内容,那么继承类对象其他内存没有被销毁,从而资源泄漏。
        如果将其声明为virtual,那么就会发生多态,调用的是指向继承类的指针,那么就会销毁的是整个继承类象。
     
    5.传递方式用引用
     缺省情况下c++以值传递方式传递对象至函数。函数参数都是以实际实参的复件为初值,而调用端所获得的是函数返回值的一个附件。这些复件都是由拷贝构造函数产出。看如下例子
    复制代码
     1         class Person{
     2         public:
     3             Person();
     4             virtual ~Person();
     5             ...
     6         private:
     7             std::string name;
     8             std::string address;
     9         }
    10         
    11         class Student:public Person{
    12         public:
    13             Student();
    14             ~Student();
    15             ...
    16         private:
    17             std::string schoolName;
    18             std::string schoolAddress;
    19         };
    复制代码
     那么如果有一个函数验证是否为学生
    1   bool validateStudent(Student s);
    2   Student plato;
    3   bool platoIsOK=validateStudent(plato);
      分析这3行代码,编译器到底做了什么?首先调用Student的copy构造函数,然后以plato为蓝本将s初始化,当validateStudent返回被销毁,所以成本为"一次Student copy构造函数调用,加上一次Student析构函数调用"。
          Student对象内部有两个string对象,所以构造了两个string对象。Student继承自Person对象,里面又有两个string对象。所以by value方式传递一个Student对象,总体成本是"六次构造函数和六次析构函数"!
          以by reference方式传递参数也可避免对象切割问题。当一个derived class对象以by value方式传递并被视为一个base class对象,base class的copy构造函数会被调用,造成像derived class对象全被切割掉了,仅仅留下base class对象。看如下代码通过传递引用参数完成多态
    复制代码
            class Window{
            public:
                ...
                std::string name() const;
                virtual void display() const;
            };
            class WindowWithScrollBars:public Window{
            public:
                ...
                virtual void display() const;
            };
            
            //传入Windos类型,调用其display函数
            //传入WindowWithScrollBars类型,调用其display函数
            //体现多态
            void printNameAndDispaly(const Window& w)
            {
                std::cout<<w.name();
                w.display();
            }    
    复制代码
    窥视c++编译器的底层,reference往往以指针实现出来,因此pass by reference真正传递的是指针。如果对象属于内置型,pass by value往往比pass by reference 效率高些。



    知识是一点一点积累起来的                  --小风

    分类: C++
  • 相关阅读:
    SharePoint 2013 APP 开发示例 (六)服务端跨域访问 Web Service (REST API)
    麦咖啡导致电脑不能上网
    SharePoint 2013 Central Admin 不能打开
    SharePoint 2013 APP 开发示例 (五)跨域访问 Web Service (REST API)
    SharePoint 2013 APP 开发示例 系列
    synthesize(合成) keyword in IOS
    Git Cmd
    简单的正则匹配
    Dropbox
    SQL Server Replication
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/2744706.html
Copyright © 2020-2023  润新知