C++干货

项目实用的C++知识
之前看到的,不过自己整理了下。方便以后查找。

如下:

允许重载:函数名相同,参数类型不同,参数个数不同,即可重载,使用见下面代码:

  #include <iostream>
  using std::cout;
  void func(int a) { cout << "func " << a << "\n"; }
  void func(int a, int b) { cout << "func " << a << " " << b << "\n"; }
  void func(float a) { cout << "func " << a << "\n"; }
  int main()
  {
     func(1);
     func(1, 2);
     func(1.1f);
     return 0;
 }

namespace:namespace是什么?其实就相当于为变量和函数划个范围.如果不加namespace会有什么问题:

// namespace.cc
#include <iostream>

void func() {
    std::cout << "n1 func" << "\n";
}

void func() {
    std::cout << "n2 func" << "\n";
}
// redefinition of 'func'
int main() {
    func(); 
    return 0;
}

结果大家可能都知道,这样的代码编译会报错:redefinition of ‘func’;一般规定一个程序内不允许同一个强符号有多个定义,那怎么解决?可使用namespace:

// namespace.cc
#include <iostream>

namespace n1 {

void func() {
    std::cout << "n1 func"
              << "\n";
}

}  // namespace n1

namespace n2 {

void func() {
    std::cout << "n2 func"
              << "\n";
}
}  // namespace n2

// redefinition of 'func'

int main() {
    n1::func();
    n2::func();
    return 0;
}

这样编译成功,加入namespace后其实对编译器来说就是两个不同的函数,因为生成的是两个不同的符号
注意:不要过多using namespace xxx,例如using namespace std;可能就容易引起符号冲突问题,可以使用using std::cout这种方式。

class & struct:

很多面试官可能都会问class和struct的区别?具体答案我这里就不贴出来了,大家自己搜索答案就可,或者留言问我,它俩在C++里其实没什么区别,就是默认权限不同而已。
这里需要掌握几个知识点:

  • 如何定义一个类
  • 构造函数的定义及使用
  • 析构函数的定义及使用
  • 拷贝构造函数的定义及使用
  • 移动构造函数的定义及使用
  • 赋值构造函数的定义及使用
  • 移动赋值函数的定义及使用
    下面贴出一段示例代码:
// class.cc
#include <iostream>
#include <memory>

using std::cout;

class TClass {
   public:
    TClass() {
        cout << "构造函数1" << "\n";
    }

    TClass(int a) : a_(a) {
        cout << "构造函数2" << "\n";
        data_ = new int[10];
    }

    TClass(const TClass &a) : a_(a.a_) {
        cout << "拷贝构造函数" << "\n";
        data_ = new int[10];

        memcpy(data_, a.data_, 10 * sizeof(int));
    }

    TClass(TClass &&a) : a_(a.a_), data_(a.data_) {
        a.data_ = nullptr;
        cout << "移动构造函数" << "\n";
    }

    TClass &operator=(TClass &a) {
        a_ = a.a_;
        if (data_) delete[] data_;
        data_ = new int[10];
        memcpy(data_, a.data_, 10 * sizeof(int));

        cout << "赋值构造函数" << "\n";
        return *this;
    }

    TClass &operator=(TClass &&a) {
        a_ = a.a_;
        if (data_) delete[] data_;
        data_ = a.data_;
        a.data_ = nullptr;
        cout << "移动赋值函数" << "\n";
        return *this;
    }

    ~TClass() {
        cout << "析构函数" << "\n";
        if (data_) delete[] data_;
    }

    void func() { cout << "a " << a_ << " \n"; }

   private:
    int a_;
    int *data_;
};

int main() {
    TClass a(1);              //构造函数2
    TClass b(a);              //拷贝构造函数
    TClass c(std::move(a));   //移动构造函数
    c = b;                    //赋值构造函数
    c = std::move(b);         //移动赋值函数
    TClass d = c;             //拷贝构造函数
    TClass e = std::move(d);  //移动构造函数
    a.func();
    b.func();
    return 0;
}

模板

模板主要分为函数模板和类模板,下面有示例代码:

// template.cc
#include <iostream>

using std::cout;
template <typename T>
T max(T a, T b) {  // 函数模板
    return a > b ? a : b;
}

template <typename T>
struct Vec {  // 类模板
    Vec(T a) : a_(a) {}

    void func() { cout << "func value " << a_ << "\n"; }
    T a_;
};

int main() {
    cout << "max(1, 2) " << max(1, 2) << "\n";
    cout << "max(1.1f, 2.2f) " << max(1.1f, 2.2f) << "\n";
    cout << "max(1.10, 2.20) " << max(1.10, 2.20) << "\n";

    Vec<int> vi(1);
    vi.func();

    Vec<float> vf(1.1f);
    vf.func();
    return 0;
}

引用:C++里多了个引用的概念,很多人面试应该有被问到过引用和指针有什么区别吧?

关于指针和引用,在这里强烈推荐大家阅读《面试系列之指针和引用的使用场景》这篇文章,通俗易懂的纯干货型文章。

大家可以记住一个关键点就是引用追求从一而终,它的指向永远不会变,而指针是个善变的东西,它的指向随时可以改变,一般开发中会使用const &方式进行参数传递,省去不必要的对象拷贝:

多态:这是C++语言所支持的或者说面向对象的一个重要特性,直接看代码:

    // polymorphic.cc
  
    #include <iostream>
    using std::cout;
    struct Base {
      Base() {
          cout << "base construct" << "\n";
      }
  
      virtual ~Base() {
          cout << "base destruct" << "\n";
      }
  
      void FuncA() {}
  
      virtual void FuncB() { cout << "Base FuncB \n"; }
  
      int a;
      int b;
    };
    
     struct Derive1 : public Base {
      Derive1() { cout << "Derive1 construct \n"; }
      ~Derive1() { cout << "Derive1 destruct \n"; }
      void FuncB() override { cout << "Derive1 FuncB \n"; }
  };
  
    struct Derive2 : public Base {
      Derive2() { cout << "Derive2 construct \n"; }
      ~Derive2() { cout << "Derive2 destruct \n"; }
      void FuncB() override { cout << "Derive2 FuncB \n"; }
    };
  
  
    int main() {
      {
          Derive1 d1;
          d1.FuncB();
      }
  
      cout << "======= \n";
      {
          Derive1 d1;
          d1.FuncB();
      
          Base &b1 = d1;
          b1.FuncB();
      }
      
      cout << "======= \n";
      {
          Derive2 d2;
          d2.FuncB();
      
          Base &b2 = d2;
          b2.FuncB();
      }
      
      cout << "======= \n";
      {
          Base b;
          b.FuncB();
      }
      
      cout << "======= \n";
      {
          Base *b = new Derive1();
          b->FuncB();
          delete b;
      }
      
      return 0;
  }
  

这里简单介绍了实现多态的两种方式:通过指针或者通过引用。

注意:

  • 构造函数不能是虚函数
  • 基类析构函数最好是虚函数
  • 尽量不要使用多继承

new和delete

C++内存申请和释放会使用new和delete关键字,而基本不会使用C语言中的malloc和free,可看下面的示例代码:

 // new_delete.cc
 
 #include <iostream>
 
 using std::cout;
 
 struct A {
     int a_;
 };
 
 int main() {
     A* a1 = new A;
     delete a1;
 
     A* a2 = new A[10];
     delete[] a2;
     return 0;
 }

注意:

  • new和delete要配对使用
  • new[]和delete[]要配对使用
  • maloc和free要配对使用
  • 类型转换:既然使用了C++语言,在类型转换方面就一定要使用C++风格,这比C语言风格的强制类型转换更安全。

    • static_cast
    float f = 1.0f;int a = static_cast<int>(f);
    
    • const_cast
    const char* cc = "hello world\n";char* c = const_cast<char*>(cc);
    
    • dynamic_cast
    struct Base {};struct Derive : public Base {};void func() {    Base* base = new Derive;    Derive* derive = dynamic_cast<Derive*>(base);}
    
    • reinterpret_cast
    A *a = new A;void* d = reinterpret_cast<void*>(a);
    

C++11常用新特性

C++的重大变革肯定是C++11啦,C++11标准引入了很多有用的新特性,这仿佛打开了新世界的大门,让C++开发者开发效率大幅提高,下面我会列出C++11常用的新特性,并附上简单的实例代码:

auto & decltype:用于类型推导
auto用于推导变量类型,decltype用于推导表达式返回值类型
// auto_decltype.cc

int main() {
    auto a = 10;  // 10是int型,可以自动推导出a是int

    int x = 0;
    decltype(x) y;      // y是int类型
    decltype(x + y) z;  // z是int类型

    return 0;
}

std::function:用于封装一个函数,功能类似于函数指针,但却比函数指针方便的多。

// function.cc

#include <functional>
#include <iostream>

using std::cout;

void printNum(int i) { cout << "print num " << i << "\n"; }

typedef void (*FuncPtr)(int i);

int main() {
    FuncPtr ptr = printNum;
    ptr(2);

    void (*ptr2)(int) = printNum;
    ptr2(3);

    std::function<void(int)> func = printNum;
    func(1);

    return 0;
}
lambda表达式:随时可方便定义的一个匿名函数。
// lambda.cc
#include <iostream>
using std::cout;

int main() {
    auto func = [](int a) -> int { return a + 1; };
    int b = func(2);
    cout << b << "\n";
    return 0;
}

**注意:**lambda表达式的变量捕获方式分为值捕获和引用捕获

int a = 0;
auto f1 = [=](){ return a; }; // 值捕获a
cout << f1() << endl;

auto f2 = [=]() { return a++; }; // 修改按值捕获的外部变量,error
auto f3 = [=]() mutable { return a++; };
std::function和std::bind使得我们平时编程过程中封装函数更加的方便,而lambda表达式将这种方便发挥到了极致,可以在需要的时间就地定义匿名函数,不再需要定义类或者函数等,在自定义STL规则时候也非常方便,让代码更简洁,更灵活,开发效率也更高。
std::thread:C++11中使用std::thread创建线程,使用非常的方便,直接看代码:
   void func() { std::cout << "new thread \n"; }
   int main() {
       std::thread t(func);
       if (t.joinable()) {
           t.join(); // 或者t.detach(); 
       }
       return 0;
   }
   ```

   **注意:**使用std::thread一定要记得join或者detach。

 #### RAII:既然使用C++,那一定要理解RAII风格(利用对象生命周期管理资源),继续往下看:如果不使用RAII风格,加锁解锁我们怎么办?

   ```c++
   // 不使用RAII
   void func() {
       std::mutex mutex;
       mutex.lock();
       if (xxx) {
           mutex.unlock();
           return;
       }
       if (xxxx) {
           mutex.unlock();
           return;
       }
       ...
       mutex.unlock();
   }
   ```
   而如果使用RAII呢:
   ```c++
   void func() {
       std::unique_lock<std::mutex> lock(mutex);
       if (xxx) return;
       if (xxxx) return;
       ...
   }
   ```
   是不是方便了很多,这里介绍了std::unique_lock的使用,还有一种锁是std::lock_guard,使用方式相同,至于它们之间有什么区别,我这里卖个关子,大家可以自行查找哈,锻炼一下自己的搜索能力。
   智能指针也是典型的RAII风格,如果不使用智能指针管理内存是这样:

   ```c++
   void func() {
       A* a = new A;
       if (xxx) {
           delete a;
           return;
       }
       if (xxxx) {
           delete a;
           return;
       }
       ...
       delete a;
   }
   ```
   而如果使用智能指针是这样:

   ```c++
   void func() {
       std::shared_ptr<A> sp = std::make_shared<A>(); // unique_ptr类似
       std::shared_ptr<A> sp = std::shared_ptr<A>(new A); // 尽可能使用make_shared或者make_unique(C++14)
       if (xxx) return;
       if (xxxx) return;
       ...
   }

又方便了很多吧,unique_ptr和shared_ptr的区别本文也不介绍,文章最后我会列出学习资料,在学习资料里可以找到答案。

原子操作:使用std::atomic可达到原子效果,使用方法:
 std::atomic<int> ai;
 ai++;
 ai.store(100);
 int a = ai.load();
enum class:带有作用域的枚举类型,可用于完全替代enum,为什么要使用enum class呢?我们先看一段直接使用enum的代码:
    enum AColor {
        kRed,
        kGreen,
        kBlue
    };
    enum BColor {
        kWhite,
        kBlack,
        kYellow
    };  
    int main() {
        if (kRed == kWhite) {
            cout << "red == white" << endl;
        }
        return 0;
    }

不带作用域的枚举类型可以自动转换成整形,且不同的枚举可以相互比较,代码中的kRed居然可以和kWhite比较,这都是潜在的难以调试的bug,而这种完全可以通过有作用域的枚举来规避。所以出现了enum class:

    enum class AColor {
        kRed,
        kGreen,
        kBlue
    };
    
    enum class BColor {
        kWhite,
        kBlack,
        kYellow
    };
    
    int main() {
        if (AColor::kRed == BColor::kWhite) { // 编译失败
            cout << "red == white" << endl;
        }
        return 0;
    }

使用enum class可以在编译层面就规避掉一些难以调试的bug,使代码健壮性更高。

condition_variable:条件变量
    std::condition_variable cv;
    std::mutex mutex_;
    void func1() {
        std::unique_lock<std::mutex> lock(mutex_);
        --count;
        if (count == 0) cv.notify_all();
    }
    void func2() {
        std::unique_lock<std::mutex> lock(mutex_);
        while (count) {
            cv.wait(lock);
        }
    }

**注意:**不要直接用wait,而要记得使用wait(mutex, cond)或者while(cond) {wait(mutex);}

nullptr:表示空指针可以使用nullptr,不要使用NULL
   void func(char*) {
       cout << "char*";
   }
   void func(int) {
       cout << "int";
   }
   
   int main() {
        func(NULL); // 编译失败 error: call of overloaded ‘func(NULL)’ is ambiguous
       func(nullptr); // char*
       return 0;
   }
chrono:时间相关函数可以考虑使用chrono库,例如休眠某个时间段:
    void customSleep() {
        std::this_thread::sleep_for(std::chrono::milliseconds(5));
        std::this_thread::sleep_for(std::chrono::seconds(5));
    }

使用chrono可以精确的指定时间单位,可以明确的之道休眠的是几毫秒还是几秒,非常方便。
chrono中包含三种时钟:
- steady_clock:单调时钟,只会增加,常用语记录程序耗时
- system_clock:系统时钟,会随系统时间改动而变化
- high_resolution_clock:当前系统最高精度的时钟,通常就是steady_clock
拿计时举例:

    void customSleep() {
        std::this_thread::sleep_for(std::chrono::milliseconds(5));
        std::this_thread::sleep_for(std::chrono::seconds(5));
    }
    
    int main() {
        std::chrono::time_point<std::chrono::high_resolution_clock> begin = std::chrono::high_resolution_clock::now();
        customSleep();
        auto end = std::chrono::high_resolution_clock::now();
        auto diff = end - begin;
        long long diffCount = std::chrono::duration_cast<std::chrono::milliseconds>(diff).count();
        cout << diffCount << "\n";
    
        return 0;
    }
STL
使用C++有几个能不用STL标准库的,这里列出了常用的STL,并贴出使用代码。
std::vector:可以理解为动态可自动扩容的数组,使用vector需要掌握几个常用函数的使用
- resize
- reserve
- capacity
- clear
- swap
- at
   void func() {
       std::vector<int> vec;
       std::cout << "vector size " << vec.size() << "\n";          // vector size 0
       std::cout << "vector capacity " << vec.capacity() << "\n";  // vector capacity 0
       vec.push_back(1);
       vec.emplace_back(2);
       vec.push_back(3);
       std::cout << "====================== \n";
       std::cout << "vector size " << vec.size() << "\n";          // vector size 3
       std::cout << "vector capacity " << vec.capacity() << "\n";  // vector capacity 4
       vec.reserve(200);
       std::cout << "====================== \n";
       std::cout << "vector size " << vec.size() << "\n";          // vector size 3
       std::cout << "vector capacity " << vec.capacity() << "\n";  // vector capacity 200
       vec.resize(20);
       std::cout << "====================== \n";
       std::cout << "vector size " << vec.size() << "\n";          // vector size 20
       std::cout << "vector capacity " << vec.capacity() << "\n";  // vector capacity 200
       vec.clear();
       std::cout << "====================== \n";
       std::cout << "vector size " << vec.size() << "\n";          // vector size 0
       std::cout << "vector capacity " << vec.capacity() << "\n";  // vector capacity 200
       std::vector<int>().swap(vec);
       std::cout << "====================== \n";
       std::cout << "vector size " << vec.size() << "\n";          // vector size 0
       std::cout << "vector capacity " << vec.capacity() << "\n";  // vector capacity 0
   }

**注意:**for循环中erase某一个节点时要处理好迭代器的指向问题

   void erase(std::vector<int> &vec, int a) {
       for (auto iter = vec.begin(); iter != vec.end();) { // 正确
           if (*iter == a) {
               iter = vec.erase(iter);
           } else {
               ++iter;
           }
       }
   
       for (auto iter = vec.begin(); iter != vec.end(); ++iter) {  // error
           if (*iter == a) {
               vec.erase(iter);
           }
       }
   }
remove函数不会真正的删除某个元素,它只会把某个要删除的元素移动到容器尾部,真正要删除还需使用erase,需要remove要和erase搭配使用。
   bool isOdd(int i) { return i & 1; }
   
   void print(const std::vector<int>& vec) {
       for (const auto& i : vec) {
           std::cout << i << ' ';
       }
       std::cout << std::endl;
   }    
   int main() {
       std::vector<int> v = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
       print(v);
   
       std::remove(v.begin(), v.end(), 5);  // error
       print(v);
   
       v.erase(std::remove(v.begin(), v.end(), 5), v.end());
       print(v);
   
       v.erase(std::remove_if(v.begin(), v.end(), isOdd), v.end());
       print(v);
   }
std::array:开发时可以考虑用std::array代替普通数组,因为它有获取长度,遍历,边界检查等功能
   int main() {
       std::array<int, 10> array;
       // int array[10];
       array.at(10) = 20;  // terminating with uncaught exception of type std::out_of_range: array::at
       std::cout << "hello " << array.at(10) << "\n";
       return 0;
   }
std::map & std::unordered_map:都是key-value级别的字典,使用方式相同,一个使用树实现,一个使用哈希表实现,可根据需求选择具体使用哪种字典。
  int main() {
      std::map<int, std::string> map;
      map[1] = std::string("hello");
      std::cout << map[1] << "\n";
      return 0;
  }
std::list:链表
   int main() {
       std::list<int> list{1, 2, 3, 2};
       list.sort();
       // std::sort(list.begin(), list.end());
       for (auto i : list) {
           std::cout << i << " ";
       }
       std::cout << "\n";
       return 0;
   }

**注意:**list的排序需要使用list.sort(),不能使用std::sort

std::tuple:我个人经常使用,tuple像结构体一样,也可以理解为pair的延伸
  struct T {
      int a_;
      int b_;
      int c_;
      T(int a, int b, int c) : a_(a), b_(b), c_(c) {}
  };
  
  int main() {
      T a(1, 2, 3);
      std::cout << "a " << a.a_ << " b " << a.b_ << " c " << a.c_ << "\n";
      std::tuple<int, int, int> tuple = std::make_tuple(2, 3, 4);
      std::cout << "a " << std::get<0>(tuple) << " b " << std::get<1>(tuple) << " c " << std::get<2>(tuple) << "\n";
      return 0;
  }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

guangod

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值