C++ 基础学习5

这篇具有很好参考价值的文章主要介绍了C++ 基础学习5。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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 <variant>

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

std::remove、erase

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

#include <iostream>
#include <algorithm>
#include <vector>

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.h>     // ::signal
::signal(SIGSEGV, &my_signal_handler);
::signal(SIGABRT, &my_signal_handler);

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

#include <signal.h>     // ::signal, ::raise
#include <boost/stacktrace.hpp>

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 <iostream>
#include <boost/asio.hpp>

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 <iostream>
#include <boost/asio.hpp>

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 <iostream> 
#include <thread> // std::thread
#include <mutex> // std::mutex, std::unique_lock
#include <vector>

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;
}

自己改良版:文章来源地址https://www.toymoban.com/news/detail-456183.html

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++ 基础学习5的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C++中的vector类模拟实现

    目录 vector模拟实现 vector类设计 vector类构造函数 vector类根据个数构造函数 vector类根据迭代器区间构造函数 vector类拷贝构造函数 vector类赋值运算符重载函数 vector类析构函数 vector类获取有效数据个数函数 vector类获取容量大小函数 vector类begin()函数 vector类end()函数 vector类reser

    2024年04月13日
    浏览(38)
  • 【C++】:C++中的STL序列式容器vector源码剖析

    vector定于与stl_vector.h头文件中 例如: vector的数据结构非常简单:一个线性连续空间 下面介绍vector的3个数据结构: start:表示目前使用空间的头 finish:表示目前使用空间的尾 end_of_storage:表示目前可用空间的尾 说明:为了降低空间配置时的速度成本,vector实际配置的大小可

    2024年01月22日
    浏览(52)
  • C++中的vector使用详解及重要部分底层实现

           本篇文章会对vector的语法使用进行详解。同时,还会对重要难点部分的底层实现进行讲解。其中有vector的 迭代器失效 和 深拷贝 问题。希望本篇文章的内容会对你有所帮助。 目录 一、vector 简单概述 1、1 C语言中数组的不便 1、2 C++中的动态数组容器vector  二、vector的

    2024年02月12日
    浏览(47)
  • 【C++】中的vector使用详解及重要部分底层实现

           本篇文章会对vector的语法使用进行详解。同时,还会对重要难点部分的底层实现进行讲解。其中有vector的 迭代器失效 和 深拷贝 问题。希望本篇文章的内容会对你有所帮助。 目录 一、vector 简单概述 1、1 C语言中数组的不便 1、2 C++中的动态数组容器vector  二、vector的

    2024年02月15日
    浏览(37)
  • 【C++ STL】vector基础知识

    2023年05月29日
    浏览(51)
  • 探索C++中的动态数组:实现自己的Vector容器

    🎉个人名片 : 🐼作者简介:一名乐于分享在学习道路上收获的大二在校生 🙈个人主页🎉:GOTXX 🐼个人WeChat:ILXOXVJE 🐼本文由GOTXX原创,首发CSDN🎉🎉🎉 🐵系列专栏:零基础学习C语言----- 数据结构的学习之路----C++的学习之路 🐓每日一句:如果没有特别幸运,那就请特

    2024年03月16日
    浏览(48)
  • 在C++中,如何把vector中的某一项删除

    要在C++中从vector中删除某个元素,可以使用vector的erase()函数。erase()函数接受一个迭代器参数,该参数指向要删除的元素。以下是一个简单的例子: 在上面的例子中,我们使用erase()函数删除了vector中的第三个元素,即数字3。在调用erase()函数时,我们使用迭代器指向要删除的

    2024年02月16日
    浏览(46)
  • C++学习vector

    1,把list的相关函数都实现出来(未完) 2, 运行结果:

    2024年02月10日
    浏览(47)
  • C++之map的emplace与pair插入键值对用例(一百七十四)

    简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏: Audio工程师进阶系列 【 原创干货持续更新中…… 】🚀 人生格言: 人生从来没有捷径,只有行动才是治疗恐惧和懒惰的唯一良药. 更多原创,欢迎关注:An

    2024年02月12日
    浏览(44)
  • C++学习——vector类的使用

    目录 vector类的介绍: vector类的构造函数:   operator=   operator [ ]    begin end   size resize   capacity reserve   push_back pop_back   insert erase   vector是C++标准模板库中的部分内容,中文偶尔译作“容器”,但并不准确。它是一个多功能的,能够操作多种数据结构和算法的模板类和函数

    2024年02月09日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包