The Top 20 Cpp Tips of All Time (C++)

The Top 20 Cpp Tips of All Time

By Danny Kalev
Code style, memory management, performance enhancement, object-oriented design, and STL.

  1. 作为一条准则:使用代替<iostream.h>,除非你处理了那些只与<iostream.h>兼容的遗留代码。

    在功能方面,包括模板化的IO类,它同时支持窄字符和宽字符;而<iostream.h>却只支持以char为导向的流。第三,在C++的iostream接口标准规格在许多微妙的方面发生了变化。所以,的接口与实现与<iostream.h>存在着一定得差异。最后,,组件声明于std命名空间中,而<iostream.h>组件是全局性的。

  2. 右值的引用限制

    对于下方例子, 如果不确定i在f内会发生什么变化, 而i又是不能随意篡改, 那么用const修饰, 防止引用带来不必要的意外.

    void f(const int &i);
    int main(){
        f(2); //OK
    }
    
  3. 逗号分割表达式

    The if condition contains three expressions separated by commas. C++ ensures that each of the expressions is evaluated and its side effects take place. However, the value of an entire comma-separated expression is only the result of the rightmost expression.

    所有语句都被执行,但是整条表达式的值由最后一个决定.

  4. 在程序启动前调用函数

    The easiest way to achieve this is by calling these functions from a constructor of a global object.

    放到全局对象的构造函数中.

  5. 处理复杂的函数指针语法

    利用typedef层层构造:

    //original
    void (*p[10])(void(*)()); //这是一个大小为10的传入参数为空参空返回值函数指针的函数指针的数组.
    
    // prettify
    
    typedef void(*pfv)();
    typedef void(*pf_taking_pfv)(pfv);
    pf_taking_pfv p[10];
    
    
  6. 类的成员指针

  • 指向数据成员的指针

    在星号前加上类名::

    class A{
      public:
        int num;
        int x;
    };
    
    int A::*pmi = & A::num;
    
    A a1, a2;
    int n = a1.*pmi; // copy num to n
    a1.*pmi = 5;     // assign 5 to a1.num
    a2.*pmi = 6;     // assign 6 to a2.num
    
  • 指向函数成员的指针

    class A{
        public:
        int func();
    };
    
    int (A::*pmf) ();
    
    pmf = & A::func;
    
    A a;
    
    (a.*pmf)(); // invoke a.func()
    
    A *pa = &a;
    (pa->*pmf)() // calls pa->func()
    
  1. 内存碎片

    为了避免Memory fragments, 需要尽量减少使用动态内存, 分配内存是分配大内存快, 不要为单一对象分配内存, 一次一个对象数组.

  2. delete和delete[]区分

    int *p = new int[10];
    delete[] p;
    
    int *pi = new int;
    delete pi;
    
  3. 类成员对齐

    struct A{
        bool a;
        int  b;
        bool c;
    }; // sizeof(A) = 12
    
    struct A_mini{
    	 bool a;
    	 bool c;
    	 int  b;
    };   // sizeof(A_mini) = 8
    
  4. 前缀和后缀操作符

    前缀先改变操作数再使用其值, 后缀反之.
    在不使用值时, 推荐使用前缀提高效率, 后缀需要预先建立临时对象.

    /*disassembly of the expression: m=n++;*/
    mov ecx, [ebp-0x04] /*store n's value in ecx register*/
    mov [ebp-0x08], ecx /*assign value in ecx to m*/
    inc dword ptr [ebp-0x04] /*increment n*/
    
    /*disassembly of the expression: m=++n;*/
    inc dword ptr [ebp-0x04] /*increment n;*/
    mov eax, [ebp-0x04] /*store n's value in eax register*/
    mov [ebp-0x08], eax /*assign value in eax to m*/
    
  5. 消除临时对象

    Complex x, y, z;
    x = y + z; // create a temporary object `y+z`
    
    // More efficient way
    
    Complex y,z;
    Complex x = y +z; // initialization instead of assignment
    
    // Or like this
    
    x = y;
    x +=z;
    
  6. 没有虚析构函数的类继承是危险的

    如果类的析构函数非虚, 那么只能当中实体类, 无法成为基类.
    因为如果继承这些类, 会造成在删除对象时候, cpp不会调用整个析构链.

    class A
    {
    public:
      ~A() // non virtual
      {
      // ...
      }
    };
    
    class B: public A /* 不好; A 没有虚析构函数*/
    {
    public:
      ~B()
      {
      // ...
      }
    };
    
    int main()
    {
     A * p = new B; /*貌似没什么问题*/
     delete p; /*问题出现, B的析构未被调用*/
    }
    
  7. 将嵌套声明为包含类的友元

    当作为其包含类的友元来声明一个嵌套类时,你应当将友元声明放于嵌套类声明的后面,而不是前面,放在前面时,
    编译器还未知class B.

    class A 
    {
    private:
     int i;
    public:
     class B /*先定义嵌套类*/
     {
      public:
      B(A & a) { a.i=0;}; 
     };
     friend class B;/*友元声明*/ 
    }
    
  8. 好用的STL术语

    • Container
    • Genericity
    • Algorithm
    • Adaptor
    • O(h) Big Oh Notation
    • Iterator
  9. 模板的定义位置

    通常情况下, 在.h文件内声明函数和类, 而将它们的定义放到一个单独的.cpp文件中.
    但是在使用模板的时候, 这样是不可行的. 原因: 实例化模板的时候, 编译器必须得到模板确切的定义,
    而不是仅仅它的声明.
    最好的办法是全部放在.h文件内.
    第二种办法是使用export关键字,

    // output.h - 声明头文件
    template<class T> void output (const T& t);
    
    // out.cpp - 定义代码文件
    #include <****>
    export template<class T> void output (const T& t) {std::cerr << t;}
    
    //main.cpp:用户代码文件
    
    #include "output.h"
    void main() // 使用output()
    {
        output(4);
        output("Hello");
    }
    
  10. 函数对象的标准基类

为了简化编写函数对象的过程, 标准库提供了两个类模板, 作为用户函数对象的基类:
std::unary_functionstd::binary_function. 两者都在<functional>文件中,
unary_function 接受一个参数, binary_function结婚搜两个.用法如下:

template < class Arg, class Res > struct 
unary_function 
{
 typedef Arg argument_type;
 typedef Res result_type;
};
template < class Arg, class Arg2, class Res > 
struct binary_function 
{
 typedef Arg first_argument_type;
 typedef Arg2 second_argument_type;
 typedef Res result_type;
};

template < class T > 
class is_vowel: public unary_function< T, bool >
{
public:
 bool operator ()(T t) const
 {
  if ((t=='a')||(t=='e')||(t=='i')||(t=='o')||(t=='u'))
   return true;
  return false;
 }
};
  1. STL容器内存储动态分配对象

    存储指针

    class Base {};
    class Derived : public Base{};
    
    std::vector <Base *> v;
    v.push_back(new Derived);
    v.push_back(new Base);
    
    // 删除必须如下
    
    delete v[0];
    delete v[1];
    
  2. 向量当数组

    使用表达式&v[0]或者*v.front(), 注意不要越界.

    void func(const int arr[], size_t length );
    int main()
    {
     vector <int> vi;
     //.. fill vi
     func(&vi[0], vi.size());
    }
    
  3. 动态多维数组和向量

    利用向量的嵌套来替代多维数组. 无需手动处理分配问题,内存泄漏问题.

  4. 不要在容器内存储auto_ptr

    在C++标准中,一个STL元素必须是可以“拷贝构造”和“赋值”。这个条款意味着,对于一个给定类,“拷贝”和“赋值”是其非常便利顺手的操作。特别是,当你将它复制到目标对象时,原始对象的状态是不会改变的。
     
    但是,这是不适用auto_ptr的。因为auto_ptr的从一个拷贝到另一个或赋值到另一个对象时会使得原始对象产生预期变动之外的变化。

    std::vector <auto_ptr <Foo> > vf;/*a vector of auto_ptr's*/
    // ..fill vf
    int g(){  
    	std::auto_ptr <Foo> temp=vf[0]; /*vf[0] becomes null*/
    }
    
原文地址:https://www.cnblogs.com/sonnet/p/15187464.html