前言  从大一上接触C++,到大一下接触ACM,到现在大三下,我自以为对C++有了很深的理解,其实不然,我不清楚的地方还特别多,准备趁此空闲时间重学C++。
const 与指针  这是这篇博文的重点,常常我们会碰到多种声明
1 2 3 4 const  char * const  a = new  char [10 ];const  char * a = new  char [10 ];char * const  a = new  char [10 ];char * a = new  char [10 ];
 他们有什么共性与不同呢?下面的程序演示了区别,注释的地方是非法操作会报错。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include  <iostream>  using  namespace  std;int  main ()    const  char * const  a = new  char [10 ];   const  char * b = new  char [10 ];   char * const  c = new  char [10 ];   char * d = new  char [10 ];   char * e = new  char [10 ];            b = e;   c[0 ] = 'e' ;      d[0 ] = 'e' ;   d = e;   delete [] a, b, c, d, e;   return  0 ; } 
 下面解释为啥会出现这种情况,我们注意到const关键字,指的是不可修改的意思,对于b而言,const 修饰char*,表面char*不可修改即指针指向的内容不可修改,对于c而言const修饰c,表示c这个指针本身不可修改。
enum back  这是这篇博文的重点,enum back 是一个很实用的编程技术,很多人都会用到它,更进一步,enum back技术是模版元编程的基本技术
1 2 3 4 5 6 7 8 #include  <iostream>  using  namespace  std;class  my_class  {  enum  { size = 10  };   int  data[size]; }; int  main ()  
 这里其实我们也可以用static const size = 10;来实现,但是这不影响enum是一个好方法,enum不会导致额外的内存分配。
const 修饰返回值  如果有必要,尽量使用const修饰返回值
1 2 3 4 5 6 #include  <iostream>  using  namespace  std;const  int  sum (int  a, int  b)  return  a + b; }int  main ()  return  0 ; }
有什么好处?  如果你不小心把==写成了=,下面的代码会报错。当然也有肯定是好处多余坏处
1 2 3 4 5 6 7 8 9 10 #include  <iostream>  using  namespace  std;const  int  sum (int  a, int  b)  return  a + b; }int  main ()    if  (sum (1 , 2 ) = 3 ) {     printf ("hello world!" );   } } 
const 能够重载成员函数  为什么要重载一遍const? 目前笔者也不太懂,只知道const能够让c++代码更加高效。下面的代码解释了如何使用const重载成员函数,大概是这样的,const对象调用成员函数的时候会调用const版,普通的对象调用普通版。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include  <iostream>  using  namespace  std;class  my_class  {  int  x = 1 , y = 2 ;  public :   const  int & get ()  const       std::cout << "x"  << std::endl;     return  x;   }      int & get ()       std::cout << "y"  << std::endl;     return  y;   } }; void  f (my_class cls)  get (); }void  f2 (const  my_class cls)  get (); }int  main ()    my_class cls;   f (cls);   f2 (cls); } 
重载带来的代码翻倍该如何处理?  大多数情况下,我们不会写上面的代码,那个太蠢了,没人会这样做,通常const版与普通版函数得到的结果是相同的。仅仅多了一个const标记,如果我们对这样相同功能的函数写两份一样的代码,是很不值得的。我们可以这样处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include  <iostream>  using  namespace  std;class  my_class  {  int  x = 1 , y = 2 ;  public :   const  int & get ()  const       std::cout << "const"  << std::endl;     return  x;   }      int & get ()       std::cout << "normal"  << std::endl;     return  const_cast <int &>(         (static_cast <const  my_class&>(*this )).get ()       );   } }; void  f (my_class cls)  get (); }void  f2 (const  my_class cls)  get (); }int  main ()    my_class cls;   f (cls);   f2 (cls); } 
对象初始化  基本数据类型这里就不说了,直接讲类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 #include  <iostream>  using  namespace  std;class  node  {  int  x;  public :   node () {}   node (int  x_) { x = x_; } }; class  my_class  {  node a, b, c, d;  public :   my_class (node a_, node b_, node c_, node d_) {     a = a_;     b = b_;     c = c_;     d = d_;   } }; int  main ()  <!---more-->  这样实现没有问题,但是效率较低,c++标准保证类的构造函数调用之前初始化先调用成员的构造函数。这样以来,my_class里面的abcd都被先初始化再赋值了,通常我们使用冒号来构造他们。 ```cpp #include  <iostream>  using  namespace  std;class  node  {  int  x;  public :   node () {}   node (int  x_) : x (x_) {} }; class  my_class  {  node a, b, c, d;  public :   my_class (node a_, node b_, node c_, node d_) : a (a_), b (b_), c (c_), d (d_) {} }; int  main ()  ## 小细节  c++标准规定了这里的构造顺序是与声明顺序为序的,而不是冒号后面的顺序。 # 不同编译单元的非局部静态变量顺序问题  先看代码,这是一个.h ```cpp #include  <iostream>  using  namespace  std;class  my_class  {};extern  my_class mls;
 注意到有一个extern my_class mls;如果我们有多个编译单元,每个都extern一些对象,这些对象初始化的顺序,c++没有规定,所以可能导致他们随机的初始化,但是如果这些对象之间有要求有顺序,怎么办?你乱序初始化可能会出错的。这时候我们可以使用单例模式来保证正确的顺序。
1 2 3 4 5 6 7 8 9 10 11 #include  <iostream>  using  namespace  std;class  my_class  { public :   my_class& singleton ()   {     static  my_class mls;     return  mls;   } }; 
结语  不要乱写类的构造函数,少写非局部静态变量。
编译器默默作出的贡献  在我们写类的时候,我们可以不写构造函数、拷贝构造函数、赋值操作、析构函数,编译器就为我们作出这一切。
带引用成员变量的类  我们考虑这样一个类,他有一个成员变量是一个引用类型。
1 2 3 4 5 6 7 8 #include  <iostream>  using  namespace  std;class  my_class  {  int & a; }; int  main ()  
 这个类会报错。因为你缺少对a的初始化,现在有两种选择,第一种方案是用一个变量给他赋值
1 2 3 4 5 6 7 8 9 #include  <iostream>  using  namespace  std;int  hello = 0 ;class  my_class  {  int & a = hello; }; int  main ()  
 或者使用构造函数来给他赋值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include  <iostream>  using  namespace  std;class  my_class  {  int & a;  public :   my_class (int & a) : a (a) {} }; int  main ()    int  x = 1 , y = 2 ;   my_class m1 (x)  ;   my_class m2 (y)  ;    } 
 另一方面,这里的m1=m2,这个赋值操作又不被允许了,原因是c++中没有让一个引用变成另一个引用这样的操作,所以我们必须自己实现赋值函数。
构造函数或者赋值函数  在应用中我们可能会碰到不允许使用拷贝这样的操作,我们实现这个约束有两种方案。第一是声明这个函数,然后不实现他。这样的话能够实现这功能,但是报错的时候编译器不会报错
1 2 3 4 5 6 7 8 9 10 11 12 13 #include  <iostream>  using  namespace  std;class  my_class  { public :   my_class () {}   my_class (const  my_class& rhs); }; int  main ()    my_class m;   my_class m2 (m)  ; } 
然后链接器重锤出击。
1 2 3 4 5 Undefined symbols for architecture x86_64:   "my_class::my_class(my_class const&)", referenced from:       _main in cc9GRPax.o ld: symbol(s) not found for architecture x86_64 collect2: error: ld returned 1 exit status 
 我也觉得这样有点坑爹。
 正确的做法应该是将这些不希望被使用的函数显示定义为私有函数。这样的话在编译期就会被发现,然后报错。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include  <iostream>  using  namespace  std;class  my_class  {  my_class (const  my_class& rhs) {}  public :   my_class () {} }; int  main ()    my_class m;   my_class m2 (m)  ; } 
virtual函数  没有什么可说的,他就是为一个类添加了一个成员变量,每当你调用virtual函数的时候,会变成调用一个新的函数,在这个函数里面有一个局部的函数指针数组,根据编译器添加成员变量来决定接下来调用哪一个函数。于是就实现了多态。
无故添加virtual的后果  如果你对一个不需要virtual的类添加了virtual函数,那么这个类的大小将扩大32位,如果你这个类本身就只有64位大小,那么他将因为你无故添加的virtual增大50%的体积。
operator=的陷阱  定义赋值函数难吗?难,真的特别难,如果你能看出下面的代码中赋值函数的问题,那你就懂为什么难了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include  <iostream>  using  namespace  std;class  my_class  {  int  *p;  public :   my_class &operator =(const  my_class &rhs) {     delete  p;     p = new  int (*rhs.p);     return *this ;   } }; int  main ()  
 这里的问题其实很明显,这个赋值不支持自我赋值。解决方案可以说在最前面特判掉自我赋值,或者是先拷贝最后再delete,又或者是用拷贝构造函数拷贝一份,然后swap来实现。
智能指针与引用计数型智能指针  这里指的分别是auto_ptr<T> 和shared_ptr<T>
智能指针  智能指针是一个模版类,他以一个类作为模版,当智能指针被析构的时候,他会去调用他保存的对象的析构函数。这样就达到了自动析构的效果,但是如果将一个智能指针赋值给另外一个智能指针的时候,如果不做处理就可能会导致智能指针指向的区域被多次析构函数,于是智能指针的解决方案是赋值对象会被设置为null。
引用计数型智能指针  引用计数型智能指针采取了引用计数的方案来解决上诉问题,当引用数为0的时候才对指向的空间进行析构。
智能指针不经意间的内存泄漏 1 2 3 4 5 6 7 8 #include  <iostream>  #include  <memory>  using  namespace  std;int  f ()  return  1 ; }int  g (auto_ptr<int > p, int  x)  return  1 ; }int  main ()  g (auto_ptr <int >(new  int (2 )), f ()); }
 上诉代码不会发生内存泄漏,但是若f函数会抛出异常,则可能发生。
不要返回引用  为了防止拷贝构造函数导致的额外开销,我们往往把函数的参数设为const &,我也曾一直想如果返回值也是const &,会不会更快
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include  <iostream>  #include  <vector>  using  namespace  std;vector<int >& f (int  n)   {   vector<int > res (100 ,0 )  ;   res[0 ]=n;   return  res; } int  main ()    vector<int > a = f (10 );   a[0 ] = 1 ; } 
 显然是错误的做法。你怎么可以想返回一个局部变量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include  <iostream>  #include  <vector>  using  namespace  std;vector<int >& f (int  n)   {   static  vector<int > res (100 ,0 )    res[0 ]=n;   return  res; } int  main ()    vector<int > a = f (10 );   a[0 ] = 1 ; } 
 在大多数情况下这确实是正确的做法。然而下面这个操作,
1 int  main ()  f (0 ) == f (1 )); }
 我不想解释为什么输出是1
全特化和偏特化  这两个东西是针对模版而言的,比方说你定义了一个模版类,但是想对其中的某一个特殊的类做一些优化,这时候就需要这两个东西了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 #include  <iostream>  #include  <vector>  using  namespace  std;template  <class  T , class  S >class  node  { public :   void  print ()  puts ("模版" ); } }; template  <class  S >class  node <int , S> { public :   void  print ()  puts ("偏特化" ); } }; template  <>class  node <int , int > { public :   void  print ()  puts ("全特化" ); } }; template  <class  T , class  S >void  f (T a, S b)    puts ("函数模版" ); }; template  <class  S >void  f (int  a, S b)    puts ("函数偏特化" ); }; template  <>void  f (int  a, int  b)    puts ("函数全特化" ); }; int  main ()    node<double , double > n1;   node<int , double > n2;   node<int , int > n3;   n1.print ();   n2.print ();   n3.print ();   f (1.0 ,1.0 );   f (1 ,1.0 );   f (1 ,1 ); } 
 这个程序的输出是
1 2 3 4 5 6 模版 偏特化 全特化 函数模版 函数偏特化 函数全特化 
降低编译依存关系  很多大型c++项目如果编译的依存关系太复杂,则很有可能稍微修改一行代码就导致整个项目重新编译,这是很不友好的。
第一种方法是使用handle class 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #pragma  once namespace  data_structure {template  <class  T >class  handle  { private :   T* ptr;         int * count;    public :      handle (T* ptr) : ptr (ptr), count (new  int (1 )) {}      handle (const  handle<T>& rhs) : ptr (rhs.ptr), count (&++*rhs.count) {}      const  handle<T>& operator =(const  handle<T>& rhs) {     if  (--*rhs.count == 0 ) delete  ptr, count;     ptr = rhs.ptr;     count = &++*rhs.count;     return  *this ;   }   ~handle () {     if  (--*count == 0 ) delete  ptr, count;   }   T& operator *() { return  *ptr; }   T* operator ->() { return  ptr; }   const  T& operator *() const  { return  *ptr; }   const  T* operator ->() const  { return  ptr; } }; }   
 这就是一个简单的handle类,当然这个类并不能降低依存关系,因为他是一个模版类,所有的模版类都不能够被分离编译。但我们可以对专用的类构造一个专用的handle,即可实现分离编译。
第二种方法是使用interface class  这里不提供代码了,简单说就是使用基类制造存虚函数作为接口,实现多态。
分离模版类中的模版无关函数  如果你有一个矩阵模版,模版中包含了行数和列数,而里面有一个类似于矩阵求逆的操作,虽然他与行列有关,但是因为这个函数非常的长,另一方面又有客户定义了许多矩阵,11的、2 2的、23的、3 2的等等,然后你的代码就会开始膨胀,这非常不友好,我们最好的做法是,定义一个基类,让基类传入行列参数去实现这些代码。这样我们的矩阵模版就不必将求逆这种很长很长的代码放进去了,直接继承就可以。
模版元编程    这种编程方式已经被证明具有图灵完备性了,即他能完成所有的计算工作。
模版元求阶乘 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include  <iostream>  using  namespace  std;template  <int  n>struct  node  {  enum  { value = n * node<n - 1 >::value }; }; template  <>struct  node <0 > {  enum  { value = 1  }; }; int  main ()   cout<<node<10 >::value<<endl; } 
模版元筛素数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include  <iostream>  using  namespace  std;template  <int  n, int  i>struct  is_prime  {  enum  { value = (n % i) && is_prime<n, i - 1 >::value }; }; template  <int  n>struct  is_prime <n, 1 > {  enum  { value = 1  }; }; int  main ()    printf ("%d %d\n" , 2 , is_prime<2 , 2  - 1 >::value);   printf ("%d %d\n" , 3 , is_prime<3 , 3  - 1 >::value);   printf ("%d %d\n" , 4 , is_prime<4 , 4  - 1 >::value);   printf ("%d %d\n" , 5 , is_prime<5 , 5  - 1 >::value);   printf ("%d %d\n" , 6 , is_prime<6 , 6  - 1 >::value);   printf ("%d %d\n" , 7 , is_prime<7 , 7  - 1 >::value); } 
gcd和lcm  有兴趣的读者可以去实现这两个东西,这里我就不提供代码了。
policies设计  这个设计目前对我而言,还有点深,先留个坑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 #include  <iostream>  #include  <tr1/memory>  using  std::cin;using  std::cout;using  std::endl;using  std::tr1::shared_ptr;template  <class  T >class  CreatorNew  { public :   CreatorNew () { cout << "Create CreatorNew Obj ! "  << endl; }   ~CreatorNew () { cout << "Destroy CreatorNew Obj ! "  << endl; }   shared_ptr<T> CreateObj ()   {     cout << "Create with new operator !"  << endl;     return  shared_ptr <T>(new  T ());   } }; template  <class  T >class  CreatorStatic  { public :   CreatorStatic () { cout << "Create CreatorStatic Obj ! "  << endl; }   ~CreatorStatic () { cout << "Destroy CreatorStatic Obj ! "  << endl; }   T& CreateObj ()   {     cout << "Create with static obj !"  << endl;     static  T _t ;     return  _t ;   } }; template  <template  <class > class  CreationPolicy >class  WidgetManager  : public  CreationPolicy<WidgetManager<CreationPolicy> > { public :   WidgetManager () { cout << "Create WidgetManager Obj !"  << endl; }   ~WidgetManager () { cout << "Destroy WidgetManager Obj !"  << endl; } }; int  main (int  argc, char ** argv)    cout << "------------- Create WidgetManager Object ! ------------"  << endl;   WidgetManager<CreatorNew> a_wid;   WidgetManager<CreatorStatic> b_wid;   cout << endl        << "-- Create WidgetManager Object With CreateObj Method (New) ! --"         << endl;   a_wid.CreateObj ();   cout << endl        << "-- Create WidgetManager Object With CreateObj Method (Static) ! --"         << endl;   b_wid.CreateObj ();   cout << endl        << "------------ Destroy WidgetManager Object ! ------------"  << endl;   return  0 ; } 
policies class 的析构函数  先说结论,不要使用public继承,上诉代码是错误的,第二policies类不要使用虚析构函数,并且为虚构函数设为protect。
policy 组合  当我们在设计一个智能指针的时候,我们能够想到有两个方向:是否支持多线程,是否进行指针检查,这两个功能是正交的,这就实现了policy的组装
定制指针  当我们设计智能指针的时候,我们不一定必须是传统指针,我们可以抽象指针为迭代器,缺省设置为一个既包含指针又包含引用的类。
静态断言检查器  最前面给了一个基于构造长度为0的数组的断言检查,我的编译器似乎很强大,允许我这样操作了。。。。我们就忽略他吧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 namespace  program_check {template  <bool >struct  CompiledTimeError ;template  <>struct  CompiledTimeError <true > {};template  <bool >struct  CompiledTimeCheck  {CompiledTimeCheck (...){};}; template  <>struct  CompiledTimeCheck <false > {};}   #define  STATIC_CHECK_1(expr) program_check::CompiledTimeError<(expr) != 0> () #define  STATIC_CHECK(expr,msg) \ (program_check::CompiledTimeError<(expr) != 0> (), "msg" ) int  main (int  argc, char ** argv)  STATIC_CHECK (false ,abssf );} 
int2type  int2type是一种技术,他把int映射为一个类型,从而能够让他对函数去实现重载,下面的程序就是一个很好的例子,注意我们的主函数里面用的是int2type<2>如果把2换成1,是无法编译的,因为int没有clone这个函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 namespace  trick {template  <int  v>struct  int2type  {  enum  { value = v }; }; }   using  namespace  trick;template  <class  T >class  node  {  T *p;  public :   void  f (T x, int2type<1 >)  clone (); }   void  f (T x, int2type<2 >)     void  f (T x, int2type<3 >)   }; int  main ()    node<int > a;   a.f (1 , int2type <2 >()); } 
type2type  这种技术类似与int2type,他用来解决函数不能偏特化的问题,当然现在的编译器似乎已经支持这个功能了。
1 2 3 4 template  <class  T >struct  type2type  {  typedef  T orignal_type; }; 
 有了这个代码,我们能模拟出偏特化,甚至函数返回值的重载,而且这个类型不占任何空间。
类型选择器  在泛型编程中,我们常常会碰到类型选择的问题,若一个类型配置有选择为是否多态,则我们可能需要通过这个bool的值来判断下一步是定义一个指针还是定义一个引用,这时候我们的类型选择器登场了
1 2 3 4 5 6 7 8 9 10 namespace  trick {template  <bool  c, class  T , class  S >struct  type_chose  {  typedef  T type; }; template  <class  T , class  S >struct  type_chose <false , T, S> {  typedef  S type; }; }   
 type_choose<false,int*,int&>::type就是int&,
 互斥锁与共享锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include  <bits/stdc++.h>  #include  <mutex>  #include  <shared_mutex>  #include  <thread>  using  namespace  std;void  f (int  id, int * _x, shared_mutex* _m)    int & x = *_x;   shared_mutex& m = *_m;   if  (id & 1 ) {     for  (int  i = 0 ; i < 3000 ; i++) {       unique_lock<shared_mutex> lock (m)  ;       x++;     }   } else  {     for  (int  i = 0 ; i < 3000 ; i++) {       shared_lock<shared_mutex> lock (m);       int  read = x;       assert (x == read);     }   } } int  main ()    int  x;   shared_mutex m;   thread a[10 ];   for  (int  i = 0 ; i < 10 ; i++) a[i] = thread (f, i, &x, &m);   for  (int  i = 0 ; i < 10 ; i++) a[i].join ();   cout << x << endl; } 
 递归锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include  <bits/stdc++.h>  #include  <mutex>  #include  <shared_mutex>  #include  <thread>  using  namespace  std;mutex m1; recursive_mutex m2; void  f (int  i)      unique_lock<recursive_mutex> lock (m2)  ;   if (i==0 ) return ;   else  f (i-1 ); } int  main ()    f (10 ); } 
 超时锁,用于一定时间内获取锁,超时递归锁,同理