Cpp记录10.4k

更新于 2015/7/9 10:03:12

名称空间

  • 匿名的名称空间只在本文件中可见。

    //file1.cpp
    namespace
    {
        int challenge = 0;
        void test()
        {
            ...
    }
    
  • 名称空间的创建

一般来说,声明实现是要分离的,而对于一个名称空间来说,不能再某个名称空间中声明了,却在另一个名称空间里定义,所以我们一般在头文件中声明它,并在实现文件中定义它们

// file1.h
namespace mix_define{

        void interF(int args_1);
        double goRound(int args_1);
        ...
}


//file1.cpp
namespace mix_define{

        void interF(int args_1)
        {
            //Something happen...
        }

        double goRound(int args_1)
        {
            //Something happen...
        }
}
  • 使用名称空间的时候,最好避免全部倒入,容易造成名称污染。

    using namespace std; //不建议这么做
    
    using std::cout;
    using std::cin; //最好如此或者,更好的做法是:
    
    std::cout << "In the Real Code" << std::endl;
    //但是这么做会让代码显得十分臃肿,所以取舍在于自己。
    
  • 类设计中特殊的函数

一个类,当它拥有这样的构造函数时:

class Wushxin{

public:
        Wushxin() {}
        Wushxin(double heigth);
...
private:
        double my_heth;
...
};

我们可以这么使用它:

int main()
{
    Wushxin FirstCopy;
    FirstCopy = 178.6; //这样的赋值是成立的
                       //首先调用Wushxin(double heigth),将176.8传入,
                       //将生成一个临时的Wushxin类型的对象,再拷贝进FirstCopy对象中。
    double heigth = FirstCopy; // 可行吗?,答案是不可行
...

但是,如果要让他成立,自然也有方法:

很少使用的特殊函数:

class Wushxin{
...
    operator double() const{
            return my_heth;
    }
...
}; 

如此定义一个特殊的转换函数之后,我们就能无错的编译通过并运行上述代码了。

const

  • 在C++中,const默认是内部链接,即只能在本文件中可见,如果想让其他文件也能使用,则应该添加extern说明符

    extern const int sizes = 100;
    

    代表着,编译器给它分配了内存空间,使得其他文件能够找到它

  • 一个const的临时变量,总能将自己的地址传递给函数:

    • 这句话的意义在于,我们可以让一个函数返回值的过程中,所创建的临时变量,被林一个函数以const &接收,并保证它的正确性和效率。

    • 一个函数的返回值所创建的临时对象(编译器创建它的目的是为了保存返回值),只能被const的参数列表接收,这是因为任何对临时变量进行修改的操作都是无意义的,因为最后临时变量都是要被销毁的(在表达式之后)。

    • 一个临时变量是不能够被取地址的,而且编译器总是把临时类对象当成常量。

  • 一个const对象,必须在创立的时候就初始化它,所以选择在构造函数里的参数列表中对const进行初始化是一个最好的选择。

    MyClass(int args):mem_int(args) {} 
    

引用

  • 引用经常被用在函数的参数列表以及返回值类型。
  • 当使用引用作为参数的时候,如非必要,我们要尽最大限度的让参数是 const &

    • 如果我们按值传递(Pass By value),那么我们就需要一个构造函数和一个析构函数的调用,而如果是常量引用,我们紧紧需要将这个对象的地址压入函数建立的栈中。

auto (C++11)

  • auto 并不能自动的添加 const 属性,只能人为选择是否添加
  • auto 可以与 decltype 配合使用,来推断函数的返回值,在C++14中可以只用 auto ,但对于 C++11 而言:

    auto TestFunc(int arg_1, int arg_2) -> decltype(arg_1+arg_2) 
    {
        return arg_1+arg_2;
    }
    

    在代码中,auto的作用就是占一个位置,类型由 -> decltype(x+y) 提供,与Lambdas的语法类似。

copy-construction

  • 对于一个类而言,一个拷贝构造函数是十分有必要的,它能有效的防止你的类对象在传递的时候不出错,否则,由于C++要与C语言进行兼容,在按值传递(Pass By Value)故采用的是位拷贝,这会导致并不是真正的重新构造一个新的临时对象,但是在退出函数的时候,却会调用析构函数,相当于一不小心把传递进来的这个对象给销毁了!而我们并没有想让它被改变,所以我们当初才采用按值传递。
  • 这一点十分重要。
  • 拷贝构造函数,通常是这样的:

    ClassDefineByMe(const ClassDefineByMe & PassObj);
    //ClassDefineByMe是自己定义的类型
    
  • 总之,拷贝构造函数是为了解决按值传递这个问题的,如果你不会用到按值传递,那么可以关闭它(=deleteC++11 OR private itC++98)。或者干脆一些,不显式定义,交给编译器去自动生成。

operator= 重载

  • 当我们在表达式中使用 = 时,对于内建类型,自然是很简单的拷贝,对于C++的容器而言,自有重载好的 = 进行拷贝操作。
  • 对于自己定义的类而言,当我们:

    ClDefByMe tmp1 = 1;
    ClDefByMe tmp2 = tmp1;
    tmp1 = tmp2;
    

    第一句调用匹配的构造函数或者默认构造函数,第二句调用拷贝构造函数(而不是 = ),第三句才是使用重载后的=(假设类中重载了它)

  • 所以对于初始化以及赋值而言,为了清晰明了,我们在初始化定义一个新的对象的时候,时空直接初始化的语法即:

    ClDefByMe tmp(1);
    
  • 对于已经创建好的对象进行赋值的时候,使用=

    tmp2 = tmp;
    

    这样就能很清楚的看出使用的是=的功能。

指向类成员的指针

  • 实际上,因为类的实际对象,一直到运行时才会有具体的地址,但是我们如果对某个还没有生成的对象中的成员取地址呢?
  • 可以,但那并不是真正的地址,而是取到偏移量,但是表面上看着像取了地址一般,所以由此引发了两种新的怪异语法糖: ->*.*
  • 也就是说,对一个类成员成员,无论是数据成员还是成员函数,都是一样的。就是语法有些怪异。

    class ClDefByMe{
    public:
        ClDefByMe():count(0),pubIntMem(0){}
        ClDefByMe(int PreKey):count(PreKey),pubIntMem(0){}
        void getCounts(){
            std::cout << "The count is " << count << std::endl;
        }
        int pubIntMem;
    private:    
        int count;
    };
    

    这是类的实现,类中有一个 int 私有数据成员, int 公有数据成员,和一个成员函数

    int main()
    {
        ClDefByMe testObj, *pToObj = &testObj;
        //我们可以这么用
        int ClDefByMe::*pToInt = &ClDefByMe::pubIntMem;
        void (ClDefByMe::*pToFunc) = &ClDefByMe::getCounts;
        //可以直接初始化,也可以声明完后再初始化。
        testObj.*pToInt = 10; //现在数据成员pubIntMem的值为10
        (testObj.*pToFunc)(); //调用。
        //pToObj->*pToInt = 10;
        //(pToObj->*pToFunc)();
        return 0;
    }
    

    其中域解析操作符 :: 一定要有,其次函数前方的取地址符号&也一定要有。虽然函数名即使函数地址,但是此处并非真正的取函数地址,而是偏移量。

运算符重载

  • 用于自定义类的运算符重载
  • 不能重载一切内建类型的运算符,不能重载不存在的运算符(如 ** ),

重载方式1: 友元函数形式

  • .

    • 特点:
      • 可以在类的外部直接操作类的私有成员
      • 但是不能使用 this 指针
      • 类外实现时,可以不带域解析操作符,而一般类成员函数却要
      • 使用所重载的运算符时,可以任意调换运算符两边的操作数,只需要有对应的重载函数即可。
    • 声明:

      class ClDefByMe{
      public:
          //...构造函数 和 接口
          //新增接口:
          ClDefByMe* address(){ return this; }
      
          //部分一元运算符<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
          friend ClDefByMe* 
                  operator&(const ClDefByMe&); //重载取地址运算符
          friend const ClDefByMe&
                  operator++(ClDefByMe&); //重载前缀递增操作符
          friend const ClDefByMe
                  operator++(ClDefByMe&, int); //重载后缀形式
          //递减运算符类似。
          //一元运算符<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
      
          //部分二元运算符<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
          friend const ClDefByMe
                  operator+(const ClDefByMe&, const ClDefByMe&);
          friend const ClDefByMe
                  operator<<(const ClDefByMe&, int);
      
          friend const ClDefByMe
                  operator&(const ClDefByMe&, const ClDefByMe&);
          friend ClDefByMe&
                  operator&=(ClDefByMe&, const ClDefByMe&);
          //二元运算符<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
      
      private:
          //...数据成员
      };
      
    • 类外部实现:

      //部分一元运算符<<<<<<<<<<<<<记得不必写friend<<<<<<<<<<<<<<
      ClDefByMe* operator&(const ClDefByMe& GetAddress)
      {
          return GetAddress.address();
      }//此处并没有用到域解析操作符,因为是友元函数
      const ClDefByMe& operator++(const ClDefByMe& PrePlus)
      {
          ++PrePlus.count;
          return PrePlus;
      }
      const ClDefByMe operator++(ClDefByMe& PostPlus, int)
      {//int可以看成一个永远不会被使用的标志,值由编译器提供。
          ClDefByMe tmpRet(PostPlus.getCounts());
          ++PostPlus.counts; //此处使用前缀后缀都无所谓,推荐前缀
          return tmpRet; //使用这个返回值的表达式结束后,这个对象立刻被销毁,不用担心内存问题
      }
      //一元运算符<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
      
      //二元运算符<<<<<<<<<<<<<&在此处是二元运算符与<<<<<<<<<<<<<
      const ClDefByMe operator+(const ClDefByMe& addObjLeft, const ClDefByMe& addObjRight)
      {
          return ClDefByMe(addObjLeft.count + addObjRight.count);
      }
      
      const ClDefByMe operator&(const ClDefByMe& andObjLetf, const ClDefByMe& andObjRight)
      {
          return ClDefByMe(andObjLeft.count & andObjRight.count);
      }
      ClDefByMe& operator&=(ClDefByMe& andassiLeft, const ClDefByMe& adnassiRight)
      {//此处注意,Left是非const,因为根据语法 x &= y 中的x是需要拥有被改变的权限的,
       //并且返回值设为非const也是为了这个权限考虑
          //在这种永久改变操作对象的实现中,我们要注意一点就是,
          //如果运算符两边的操作数是同一个的情况
          if(&andassiLeft == &andassiRight) //判断是否相同
          {
              andassiLeft.count = andassiRight.count & andassiRight.count;
              return andassiLeft;    
          }
          andassiLeft.count &= andssiLeft.count;
          return andassiLeft;
      }
      
      const ClDefByMe operator<<(const ClDefByMe& lmoveLeft, int moveBits)
      {
          return ClDefByMe(lmoveLeft.count << moveBits);
      }//注意这不是重载输出流的运算符,而是移位运算符
      //二元运算符<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
      

重载方式2:成员函数形式

  • .

    • 特点:
      • 可以使用this指针
      • 与普通的成员函数一眼
      • 当所重载的运算符左边不是该类的对象时重载失效。
      • 成员函数形式的重载,相当于运算符左边的操作数被默认定下(即对象本身)
    • 声明:

      class ClDefByMe{
      public:
          //...构造函数 和 接口
      
          //部分一元运算符<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
          const ClDefByMe& operator+(); //一元运算符 取正
          const ClDefByMe& operator--(); //前缀递减
          const ClDefByMe  operator--(int); //后缀递减
          //一元运算符<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
      
          //部分二元运算符<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
          const ClDefByMe operator-(const ClDefByMe&);  //二元运算符 减法
          const ClDefByMe operator>>(int); //二元运算符 右移
          ClDefByMe&      operator&=(const ClDefByMe &); //组合运算
          //二元运算符<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
      private:
          //...
      };
      
    • 类外部实现:

      //部分一元运算符<<<<<<<使用域解析操作符<<<<<<<<<<<<<<<<<<<<
      const ClDefByMe ClDefByMe::operator+()
      {
          return ClDefByMe(counts); //也可以返回 *this,不过类型要变为
                                    // const ClDefByMe &
      }
      const ClDefByMe& ClDefByMe::operator--()
      {
          --counts;     //因为是成员函数,可以直接操作私有成员
          return *this;
      }
      const ClDefByMe ClDefByMe::operator--(int)
      {
          return ClDefByMe(counts--);
      }
      //一元运算符<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
      
      //部分二元运算符<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
      const ClDefByMe ClDefByMe::operator+(const ClDefByMe& addArgRight)
      {
          return ClDefByMe(count+addArgRight.count);//直接操作私有成员    
      }
      const ClDefByMe operator>>(int moveBits)
      {
          return ClDefByMe(count >> moveBits);
      }
      
      ClDefByMe& operator&=(const ClDefByMe & andassiRight)
      {
          if(this == &andassiRight)
          {
              count = andassiRight.count & andassiRight.count;
              return *this;
          }
          count &= andassiRight.count;
          return *this;
      }
      //二元运算符<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
      

某些特殊的操作符重载

//某些操作实现包含在<iostream>中
friend ostream & 
    operator<<(ostream &, const ClDefByMe &);//类中声明 重载输出
ostream & operator<<(ostream & streams, const ClDefByMe & outRight)
{
    streams << outRight.count << endl;
    return streams; //没有return其实也行,但是就无法嵌套输出了。
}

此处,传递的参数和返回值必须是引用(对于流),这样在函数里的改变才能影响到外部。

输入也是一样。

  • 总结

    1. 对于一元运算符而言,成员函数形式总是比较适合的。
    2. 一些正常的数学运算,将传递的参数都置为 常量引用(const &)
    3. 对比成员函数形式和友元函数形式的重载,后者可能将具有更高的灵活性,例如类型转换,比如表达式: a+1,两种形式的重载都合法,但是换一个写法 1+a,成员函数形式的写法一定会报错,但是友元函数的写法只要有对应的构造函数进行类型转换(将1构造成对应的类型)就能通过编译。因为对于成员函数的实现而言,其左操作数的类型必须是完全匹配的。
    4. 所有赋值运算符这一类运算符(如=, +=, &=, …)的重载,需要改变其左边的操作数,
      • 对于友元函数形式的,就需要将左操作数声明为非 const 参数,否则就在函数参数列表中把它们都声明为 const
      • 对于成员函数形式的,就需要将函数声明为 const 即可,其余不需考虑。
    5. 返回值类型:

      • 对于返回临时对象,需要让返回值类型为 const value.
      • 对于返回非临时对象的,需要让返回值类型为 const value reference.
      • 如果是改变左值的运算符,那么就去掉返回值中的const,其余结构不变,何时改变,就是所有赋值一类的运算符。
      • 但临时对象总是默认为 const,所以如果返回临时对象,但是不在返回值地方声明为 const,它依然是 const
      • 如果没有必要,请不要把(如果是版本新的编译器就无所谓了,会帮你优化):

        return ClDefByMe(addObjLeft.count + addObjRight.count);
        

        写成

        ClDefByMe tmp(addObjLeft.count + addObjRight.count);
        return tmp;
        

        这涉及到编译器的操作,一般情况下后面这种写法会多出一个操作,也就是调用拷贝构造函数,如果你的类恰好没定义,而这个类的拷贝又需要自己定义的拷贝构造函数,那就 Boom!

    6. 推荐的重载写法:
      • 推荐写成 友元函数形式 的运算符: 所有二元运算符
      • 必须写成 成员函数形式 的运算符: =, (), [], ->, ->*
      • 推荐写成 成员函数形式 的运算符: 所有赋值相关的运算符,如:
        +=, /=, *=, >>=, ...
      • 推荐写成 *成员函数形式 * 的运算符: 所有一元运算符
    7. 不可以重载的运算符:
      • **(不存在), .(点运算符), .*

转载注明: www.wushxin.top/2015/06/21/Cpp记录10.4k.html