C++ 学习5

C++ 学习5

    • std::vector中的emplace_back和push_back
    • std::variant
    • std::remove、erase
    • std::string::erase
    • std::filesystem
    • const和constexpr的区别
    • coreDump和异常的区别
    • boost::asio::ip::address_v4、network_v4
    • std::unique_lock和std::lock_guard
    • C++中的静态成员变量
    • C++中的单体类
  • 实战
    • 使用状态模式实现一个状态机

std::vector中的emplace_back和push_back

参考:
https://blog.csdn.net/u013834525/article/details/104047635
https://zhuanlan.zhihu.com/p/610294692

在大多数情况下,emplace_back比push_back更高效,因为它可以避免不必要的拷贝和移动操作。emplace_back会在vector的末尾直接构造一个新元素,而push_back则是先创建一个元素,然后再将其拷贝或移动到vector的末尾。

  1. 当vector中存储的元素类型没有定义构造函数时,只能使用push_back。

  2. 当vector中存储的元素类型是基本类型(如int、float等)时,使用emplace_back和push_back的效率相同。

  3. 当vector中存储的元素类型是有状态的对象(如std::unique_ptr)时,使用emplace_back可能会导致内存泄漏或其他问题,因为emplace_back会在vector中直接构造一个新元素,而不是先创建一个元素,然后再将其拷贝或移动到vector的末尾。在这种情况下,应该使用push_back。

emplace_back的最大优势是它可以直接在vector的内存中去构建对象,不用在外面构造完了再copy或者move进去!

使用建议:

  1. 左值用push_back
  2. 右值用emplace_back
  3. 局部变量尽量使用emplace_backin-place构建,不要先构建再拷贝或移动。

std::variant

参考:https://blog.csdn.net/hang_ning/article/details/123826220
https://blog.csdn.net/janeqi1987/article/details/100568096
总结:std::variant代表了类型安全的union类型,与union类似,std::variant的一个实例要么存储了可选类型中的一个值,要么没有存储。但与union比,其优势是可以判断当前真实的存储类型并且可以存储结构体这种复杂的数据结构。
推荐使用std::get_if或者std::holds_alternative来判断std::variant变量保存的具体类型:

#include 

using MyType value = std::variant<int, double, bool, std::string>;
MyType value = "hello, world";
if (std::holds_alternative<std::string>(va)) {
	std::cout << "holds std::string" << std::endl;
} else {
	std::cout << "Not holds std::string" << std::endl;
}
auto* v = std::get_if<std::string>(&value);
if (v){
   std::cout<< *v <<std::end;  //"hello, world"
}

注意:上面的代码是在运行期进行类型判断效率较低;应当尽量使用 std::visit 方法来访问,实现在编译期推断:

#include "overloaded.h"

  std::variant<int, string> value = 123// 使用std::is_same_v判断类型
  std::visit(
	  [&](auto &&arg) {
	  	using T = std::decay_t<decltype(arg)>; // 类型退化,去掉类型中的const 以及 &
	  	if constexpr(std::is_same_v<T, int>) {
	  		cout << "int: " << arg << '\n';
	  	} else if constexpr(std::is_same_v<T, std::string>) {
	  		cout<< "str: "<< arg <<'\n';
	  	}
  }, value);

  // 使用std::is_convertible_v判断类型
  std::visit(
	  [&](const auto &&arg) {
	  	if constexpr (std::is_convertible_v<decltype(arg), int>) {
	  		cout << "int: " << arg << '\n'; // 注意:这里如果arg是个结构体,可以直接使用arg.member的方式访问成员
	  	} else if constexpr (std::is_convertible_v<decltype(arg), std::string>) {
	  		cout<< "str.size(): "<< arg.size() <<'\n';
	  	}
  }, value);

  // 使用std::visit和Overloaded访问方式一
  std::visit(overloaded{
	   void operator()(int i) { cout << "int: " << i << '\n'; }
	   void operator()(const std::string& s) { cout << "str: " << s << '\n'; }
	   },value);

  // 使用std::visit和Overloaded访问方式二
  std::visit(Overloaded{
           [&out](const int &val) { out << "GatewayParam{" << val << "} "; },
           [&out](const string &val) { out << "ServiceParam{" << val << "} "; },
  		 },
  value );

注意:上面使用std::visit + Overloaded时有限制条件:必须把每种数据类型的处理函数都写出来;如果你只想处理其中一种参数,且对运行性能没有极致要求,那么还是在运行时进行判断吧。否则会报如下的编译失败:

type_traits:2957:11: error: no type named ‘type’ in ‘struct xxx

其它方法:
C++ 学习5_第1张图片

std::remove、erase

https://blog.csdn.net/yedawei_1/article/details/108754282
std::remove()是C++ STL中的一个算法,用于从容器中删除元素。它不会真正地删除元素,而是将要删除的元素移动到容器的末尾,并返回一个指向新的逻辑结尾的迭代器。这个迭代器之前的所有元素都是未被删除的,而之后的所有元素都是已被删除的。要真正地删除这些元素,可以使用容器的erase()函数。

#include 
#include 
#include 

int main() {
    std::vector<int> v = {4, 1, 2, 4, 3, 4, 5};
    auto new_end = std::remove(v.begin(), v.end(), 4);
    v.erase(new_end, v.end());
    for (auto i : v) {
        std::cout << i << " ";
    }
    std::cout << std::endl;
}

输出:

1 2 3 5

std::string::erase

http://www.manongjc.com/detail/30-ozpmqwwlzpuodgn.html
可以实现删除单个字符、一段连续的字符、或者所有特定的字符。

比如,一个字符串格式如下:开头部分为"-----Begin-----\n",结尾部分为"-----End-----\n",中间除了正常内容外还有很多换行符’\n’,去掉开头部分、结尾部分、中间所有的\n:

    std::string_view beginPart = "-----Begin-----\n";
    std::string_view endPart = "-----End-----\n";

    // 去掉BEGIN部分
    std::size_t beginPos = s.find(beginPart);
    if (beginPos != std::string::npos) {
        s.erase(0, beginPart.length());
    }

    // 去掉End部分
    std::size_t endPos = s.find(endPart);
    if (endPos != std::string::npos) {
        s.erase(endPos, endPart.length());
    }

    // 去掉所有的换行符
    s.erase(std::remove(s.begin(), s.end(), '\n'), s.end());

std::filesystem

https://blog.csdn.net/chenyijun/article/details/109263942
std::filesystem文件系统库提供了文件系统、路径、常规文件、目录等等相关组件进行操作的相关功能。

void path_use()
{
    fs::path currentPath = fs::current_path();
    //获取makefile路径  append operator/ 添加元素到带目录分隔符的路径
    fs::path makefilePath = currentPath / "Makefile.Debug"; // 路径拼接
    std::cout << "Makefile path      = " << makefilePath.string() << std::endl;
    //分解操作
    //root_name 返回路径的根名
    std::cout << "root_name          = " << currentPath.root_name() << std::endl;
    //root_directory 返回路径的根目录
    std::cout << "root_directory     = " << currentPath.root_directory() << std::endl;
    //root_path 返回路径的根路径
    std::cout << "root_path          = " << currentPath.root_path() << std::endl;
    //relative_path 返回相对根路径的路径
    std::cout << "relative_path      = " << currentPath.relative_path() << std::endl;
    //parent_path 返回亲路径的路径
    std::cout << "parent_path        = " << currentPath.parent_path() << std::endl;
    //filename 返回文件名路径组分
    std::cout << "filename           = " << currentPath.filename() << std::endl;
    //stem 返回主干路径组分
    std::cout << "stem               = " << currentPath.stem() << std::endl;
    //extension 返回文件扩展名路径组分
    std::cout << "extension          = " << currentPath.extension() << std::endl;
    std::cout << "extension          = " << makefilePath.extension() << std::endl;
 
    //查询操作
    //empty 检查路径是否为空
    std::cout << "empty              = " << currentPath.empty() << std::endl;
    //检查对应路径元素是否非空
    std::cout << "has_root_path      = " << currentPath.has_root_path() << std::endl;
    std::cout << "has_root_name      = " << currentPath.has_root_name() << std::endl;
    std::cout << "has_root_directory = " << currentPath.has_root_directory() << std::endl;
    std::cout << "has_relative_path  = " << currentPath.has_relative_path() << std::endl;
    std::cout << "has_parent_path    = " << currentPath.has_parent_path() << std::endl;
    std::cout << "has_filename       = " << currentPath.has_filename() << std::endl;
    std::cout << "has_stem           = " << currentPath.has_stem() << std::endl;
    std::cout << "has_extension      = " << currentPath.has_extension() << std::endl;
 
    //检查 root_path() 是否唯一标识文件系统位置
    std::cout << "is_absolute        = " << currentPath.is_absolute() << std::endl;
    std::cout << "is_relative        = " << currentPath.is_relative() << std::endl;
}

const和constexpr的区别

const和constexpr都是C++中的关键字,但它们的用途不同。const用于定义常量,而constexpr用于定义常量表达式。

const关键字可以用于修饰变量、函数参数、函数返回值等,表示该变量或函数返回值不可修改。

const int a = 10;
const int *p = &a;

constexpr关键字可以用于定义常量表达式,即在编译时就能计算出结果的表达式

constexpr int factorial(int n) {
    return n <= 1 ? 1 : (n * factorial(n - 1));
}

coreDump和异常的区别

C++中core dump和异常的区别是,core dump是程序在被操作系统杀掉以后,保留的一份内存快照,一般有Segmentation Fault和Abort等。
异常是指程序在运行时发生了错误,但是程序可以通过catch语句捕获并处理这个错误。

C++ core dump调试及崩溃监控
https://blog.csdn.net/hello2mao/article/details/79258471
https://blog.csdn.net/Bruce_0712/article/details/72824957
(1) 保存core dump文件

ulimit -c unlimited

(2) 利用core文件定位、调试问题

gdb 可执行程序 core

(3) 动态库的core dump调试,在CMakeList.txt中添加:

add_definitions(-D_DEBUG)
add_definitions(-DDEBUG)
add_definitions("$ENV{CXXFLAGS} -O0 -Wall -g -ggdb")
SET(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g -ggdb")

(4) 崩溃监控方案
向系统注册发生Segmentation Fault和Abort时的回调即可监控C++的崩溃:

#include      // ::signal
::signal(SIGSEGV, &my_signal_handler);
::signal(SIGABRT, &my_signal_handler);

在回调里就可采集崩溃栈等信息,可以使用boost库方便的记录backtrace等信息:

#include      // ::signal, ::raise
#include 

void my_signal_handler(int signum) {
    ::signal(signum, SIG_DFL);
    boost::stacktrace::safe_dump_to("./backtrace.dump");
    ::raise(SIGABRT);
}

怎样让 C++ 中 throw exception 产生的 coredump 带上栈?
https://cloud.tencent.com/developer/article/1839207

如果是对已有的二进制,或者已经在运行的进程:

gdb 里面输入:

catch throw

然后运行, gdb 就会在任何 throw 的时候暂停,即可看到 throw 时候的栈。

boost::asio::ip::address_v4、network_v4

https://zhuanlan.zhihu.com/p/179070263
官网:https://www.boost.org/doc/libs/1_66_0/doc/html/boost_asio/reference/ip__network_v4.html
https://www.boost.org/doc/libs/1_66_0/doc/html/boost_asio/reference/ip__address.html
boost::asio::ip::address_v4类表示IPv4地址。它提供了一些函数,如to_string()、is_loopback()、is_multicast(、to_uint()等。

#include 
#include 

int main() {
    boost::asio::ip::address_v4 addr = boost::asio::ip::address_v4::from_string("192.168.1.1");
    std::cout << addr.to_string() << std::endl; // 输出192.168.1.1
    std::cout << addr.to_uint() << std::endl; // 转换成整型
    addr = boost::asio::ip::address_v4(addr.to_uint() + 1); // 将原地址进行"+1"操作
    std::cout << addr.to_string() << std::endl; // 输出192.168.1.2
    return 0;
}

boost::asio::ip::network_v4类表示IPv4网络。它提供了一些函数,如network()、prefix_length()、is_subnet_of()等:

#include 
#include 

int main() {
    boost::asio::ip::network_v4 net = boost::asio::ip::make_network_v4(boost::asio::ip::address_v4::from_string("192.168.1.1"), 24);
    std::cout << net.prefix_length() << std::endl; // 输出24
    std::cout << net.network().to_string() << std::endl; // 输出网络号 192.168.1.0
    return 0;
}

std::unique_lock和std::lock_guard

https://www.cnblogs.com/xudong-bupt/p/9194394.html

std::lock_guard是一个简单的RAII类,它在构造函数中获取互斥锁,在析构函数中释放互斥锁。它的灵活性较差,因为它只能在构造函数中获取互斥锁,在析构函数中释放互斥锁,不能手动释放互斥锁或者在获取互斥锁之前进行其他操作。

std::unique_lock是一个更加灵活的RAII类,它可以在构造函数中获取互斥锁,也可以在析构函数中释放互斥锁,还可以手动释放互斥锁。此外,std::unique_lock还提供了一些其他的功能,例如延迟锁定、尝试锁定、递归锁定等。但是,由于std::unique_lock的构造函数和析构函数都不是noexcept的,因此它可能会抛出异常,需要在使用时进行异常处理。

在使用std::mutex的情况下,一个线程试图锁定其已拥有的互斥元是错误的,并且将导致未定义行为。但在某些情况下,我们需要线程多次获取同一个互斥元却无需担心死锁的问题,这时候就可以使用std::unique_lock。
1. std::unique_lock可以在构造函数中进行加锁,析构函数中进行解锁(也可以提前调用.unlock()进行解锁),可重复加锁。
2. unique_lock比lock_guard使用更加灵活,功能更加强大。
3. 使用unique_lock需要付出更多的时间、性能成本。

#include  
#include  // std::thread
#include  // std::mutex, std::unique_lock
#include 

std::mutex mtx; // mutex for critical section
std::once_flag flag;

void print_block (int n, char c) {
    //unique_lock有多组构造函数, 这里std::defer_lock不设置锁状态
    std::unique_lock<std::mutex> my_lock (mtx, std::defer_lock);
    //尝试加锁, 如果加锁成功则执行
    //(适合定时执行一个job的场景, 一个线程执行就可以, 可以用更新时间戳辅助)
    if(my_lock.try_lock()){
        for (int i=0; i<n; ++i)
            std::cout << c;
        std::cout << '\n';
    }

	my_lock.unlock(); // 可根据需要提前解锁
	// do other thing
}

C++中的静态成员变量

C++中的静态成员变量可以是private、protected或public的,取决于它们在类中的声明位置。

如果静态成员变量在类的私有部分声明,则它们只能被该类的成员函数访问,即它们是私有的。如果静态成员变量在类的保护部分声明,则它们可以被该类的成员函数和派生类的成员函数访问,即它们是受保护的。如果静态成员变量在类的公共部分声明,则它们可以被该类的成员函数、派生类的成员函数和类的对象直接访问,即它们是公共的。

需要注意的是,无论静态成员变量是私有、受保护还是公共的,它们都是该类的所有对象共享的,而不是每个对象都有自己的一份。

C++中的单体类

单体类(Singleton Class)是一种设计模式,它保证一个类只有一个实例,并提供一个全局访问点。

在C++中,实现单体类可以使用静态成员变量和静态成员函数。静态成员变量只会被初始化一次,因此可以用来存储单体类的唯一实例。静态成员函数可以提供全局访问点,通过该函数可以获取单体类的唯一实例。

以下是一个简单的单体类的示例代码:

class Singleton {
public:
    static Singleton& getInstance() {
        static Singleton instance;
        return instance;
    }

    void doSomething() {
        // ...
    }

private:
    Singleton() {}  // 构造函数私有化,防止外部创建实例
    Singleton(const Singleton&) = delete;  // 禁止拷贝构造函数
    Singleton& operator=(const Singleton&) = delete;  // 禁止赋值运算符

    // 静态成员变量,存储唯一实例
    static Singleton* instance;
};

// 静态成员变量是属于类的,而不是属于类的实例,因此可以在类的外部进行访问和修改;不过这句也可以不写。
Singleton* Singleton::instance = nullptr;

在上面的代码中,构造函数被私有化,防止外部创建实例。静态成员函数getInstance()提供了全局访问点,通过该函数可以获取单体类的唯一实例。静态成员变量instance存储唯一实例,使用static关键字保证只会被初始化一次。同时,拷贝构造函数和赋值运算符被禁止,防止通过拷贝或赋值创建新的实例。

实战

使用状态模式实现一个状态机

这种方法适用于状态机比较复杂的情况,具体实现方法如下:

class State {
public:
    virtual void handle() = 0;
};

class StateA : public State {
public:
    void handle() override {
        // 处理状态A的逻辑
    }
};

class StateB : public State {
public:
    void handle() override {
        // 处理状态B的逻辑
    }
};

class StateC : public State {
public:
    void handle() override {
        // 处理状态C的逻辑
    }
};

class StateMachine {
public:
    StateMachine() {
        currentState = new StateA();
    }

    void setState(State* state) {
        currentState = state;
    }

    void handle() {
        currentState->handle();
    }

private:
    State* currentState;
};

int main() {
    StateMachine stateMachine;
    stateMachine.handle(); // 处理状态A的逻辑
    stateMachine.setState(new StateB());
    stateMachine.handle(); // 处理状态B的逻辑
    stateMachine.setState(new StateC());
    stateMachine.handle(); // 处理状态C的逻辑
    stateMachine.setState(new StateA());
    stateMachine.handle(); // 处理状态A的逻辑
    return 0;
}

自己改良版:

enum class StateEnum {
    StateA,
    StateB,
    StateC,
};

class HandleState {
public:
    virtual StateEnum handle(int event) = 0; // 处理事件并返回新状态
};

class HandleStateA : public HandleState {
public:
    StateEnum handle(int event) override {
        // 处理状态A的逻辑
    }
};

class HandleStateB : public HandleState {
public:
    StateEnum handle(int event) override {
        // 处理状态B的逻辑
    }
};

class HandleStateC : public HandleState {
public:
    StateEnum handle(int event) override {
        // 处理状态C的逻辑
    }
};

class StateMachine {
public:	
	void start() {
		stateHandle.insert({ StateEnum::StateA, std::make_unique<HandleStateA>() });
        stateHandle.insert({ StateEnum::StateB, std::make_unique<HandleStateB>() });
        stateHandle.insert({ StateEnum::StateC, std::make_unique<HandleStateC>() });
	}

    void handle(int event) {
        currentState = stateHandle[currentState]->handle(event);
    }

private:
    StateEnum currentState;
    std::unordered_map<StateEnum, std::unique_ptr<HandleState>> stateHandle; // 状态机的状态迁移处理表格
};

你可能感兴趣的:(c++,学习,开发语言)