项目实用的C++知识
之前看到的,不过自己整理了下。方便以后查找。
#include
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.cc
#include
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
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的区别?具体答案我这里就不贴出来了,大家自己搜索答案就可,或者留言问我,它俩在C++里其实没什么区别,就是默认权限不同而已。
这里需要掌握几个知识点:
// class.cc
#include
#include
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
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;
}
关于指针和引用,在这里强烈推荐大家阅读《面试系列之指针和引用的使用场景》这篇文章,通俗易懂的纯干货型文章。
大家可以记住一个关键点就是引用追求从一而终,它的指向永远不会变,而指针是个善变的东西,它的指向随时可以改变,一般开发中会使用const &方式进行参数传递,省去不必要的对象拷贝:
// polymorphic.cc
#include
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;
}
这里简单介绍了实现多态的两种方式:通过指针或者通过引用。
注意:
C++内存申请和释放会使用new和delete关键字,而基本不会使用C语言中的malloc和free,可看下面的示例代码:
// new_delete.cc
#include
using std::cout;
struct A {
int a_;
};
int main() {
A* a1 = new A;
delete a1;
A* a2 = new A[10];
delete[] a2;
return 0;
}
注意:
类型转换:既然使用了C++语言,在类型转换方面就一定要使用C++风格,这比C语言风格的强制类型转换更安全。
float f = 1.0f;int a = static_cast(f);
const char* cc = "hello world\n";char* c = const_cast(cc);
struct Base {};struct Derive : public Base {};void func() { Base* base = new Derive; Derive* derive = dynamic_cast(base);}
A *a = new A;void* d = reinterpret_cast(a);
C++的重大变革肯定是C++11啦,C++11标准引入了很多有用的新特性,这仿佛打开了新世界的大门,让C++开发者开发效率大幅提高,下面我会列出C++11常用的新特性,并附上简单的实例代码:
// 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
#include
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.cc
#include
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++; };
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<int> ai;
ai++;
ai.store(100);
int a = ai.load();
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,使代码健壮性更高。
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);}
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;
}
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 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(diff).count();
cout << diffCount << "\n";
return 0;
}
使用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);
}
}
}
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);
}
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;
}
int main() {
std::map<int, std::string> map;
map[1] = std::string("hello");
std::cout << map[1] << "\n";
return 0;
}
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
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;
}