首页 > 编程语言 >C++学习笔记-Cherno C++系列

C++学习笔记-Cherno C++系列

时间:2024-11-23 12:56:00浏览次数:7  
标签:std Cherno const cout int 笔记 C++ include

21-23.【Cherno C++】C++中的静态(static)

  • static变量只在编译单元内部链接
    • 静态变量的作用域只在单个文件内
    • 建议:在非特殊情况下,永远使用static定义全局变量以限制作用域
  1. 全局变量重复定义
/* a.cpp */
int g_Variable = 5;
/* main.cpp */
#include <iostream>  
int g_Variable=10;  
int main(){  
    std::cout<<s_Variable<<std::endl;  
}
  1. 使用static限制a.cpp中全局变量的作用域
/* a.cpp */
static int s_Variable = 5;
/* main.cpp */
#include <iostream>  
int s_Variable=10;  
int main(){  
    std::cout<<s_Variable<<std::endl;  
}
  1. 还可以使用external关键词在main.cpp中定义外部变量
/* a.cpp */
int s_Variable = 5;
/* main.cpp */
#include <iostream>  
extern int s_Variable;  
int main(){  
    std::cout<<s_Variable<<std::endl;  
}
  1. 如果外部变量被标记为static,则main.cpp无法链接外部变量
/* a.cpp */
static int s_Variable = 5;
/* main.cpp */
#include <iostream>  
extern int s_Variable;  
int main(){  
    std::cout<<s_Variable<<std::endl;  
}
  • 类和结构体中的静态变量和静态方法
    • 用来描述类或结构体自身的属性
    • 静态方法不能访问非静态变量
#include <iostream>

struct Entity
{
    int x, y;

    void Print() const {
        std::cout << x << ", " << y << std::endl;
    }
};

struct Entity_Static
{
    static int x, y;

    static void Print() {
        std::cout << x << ", " << y << std::endl;
    }
};

// 对于静态变量,如果要通过实例访问需要事先初始化,该初始化语句不能写在函数体内
int Entity_Static::x;
int Entity_Static::y;

int main() {
    Entity e{};
    e.y = 2;
    e.x = 3;
    Entity e1{5, 6};
    e.Print();  //3,2
    e1.Print(); //5,6

    Entity_Static es1;
    es1.x = 2;
    es1.y = 5;

    Entity_Static es2;
    es2.x = 6;
    es2.y = 8;
    es1.Print(); //6,8
    es2.Print(); //6,8
    /* 对于静态变量和方法,上述的引用没有任何意义,可以直接使用类或结构体名称引用 */
    Entity_Static::x = 1;
    Entity_Static::y = 2;
    Entity_Static::Print();
}
/* 静态方法不能访问非静态变量 */
# include <iostream>  
  
struct Entity  
{  
    int x, y;  
	// Invalid use of member 'x' in static member function
    static void Print() {  
        std::cout << x << "," << y << std::endl;  
    }  
};
/* 静态方法不能访问非静态变量的原因在于:静态方法找不到非静态变量的实例 */
/* 通过以下方法可以实现静态方法访问非静态变量 */
#include <iostream>

struct Entity
{
    int x, y;

    static void Print(const Entity& e) {
        std::cout << e.x << ", " << e.y << std::endl;
    }
};
  • 局部作用域中的static
    • 延长局部变量的生存周期【其生存周期基本上相当于整个程序的生存期】
#include <iostream>

void FunctionStatic() {
    static int i = 10;
    i++;
    std::cout << i << ", ";
}

void Function() {
    int i = 10;
    i++;
    std::cout << i << ", ";
}

int main() {
    /* 静态变量i在函数FunctionStatic执行完毕后不会销毁 */
    FunctionStatic();
    FunctionStatic();
    FunctionStatic();
    FunctionStatic();
    FunctionStatic();
    std::cout << std::endl;
    /* 非静态变量i在函数Function执行完毕后会销毁 */
    Function();
    Function();
    Function();
    Function();
    Function();
    std::cout << std::endl;
}
  • 局部静态(Local Static)的应用
/* 单例模式 */
/* 老方法:使用类或结构体 */
#include <iostream>  
#include <string>  
  
class Singleton  
{  
private:  
    std::string name = "noName.";  
    static Singleton* s_Instance;  
public:  
    static Singleton& Get() { return *s_Instance; }  
  
    void Hello() { std::cout << "Hello " << name << std::endl; }  
};  
  
Singleton* Singleton::s_Instance = nullptr;  
  
int main() {  
    Singleton::Get().Hello();  
    return 0;  
}
/* 新方法:使用局部静态(Local Static) */
#include <iostream>
#include <string>

class Singleton
{
private:
    std::string name = "noName.";
public:
    static Singleton& Get() {
        static Singleton instance;
        return instance;
    }

    void Hello() { std::cout << "Hello " << name << std::endl; }
};

int main() {
    Singleton::Get().Hello();
    return 0;
}

44.【Cherno C++】C++的智能指针

需要包含头文件memory

  • std::unique_ptr
    • 不能复制unique_ptr,指向同一区域的unique_ptr只能有一个
    • 使用std::make_unique<Entity>()对unique_ptr进行安全的初始化
#include <iostream>
#include <memory>

class Entity
{
public:
    Entity() {
        std::cout << "Created Entity!" << std::endl;
    }

    ~Entity() {
        std::cout << "Destroyed Entity!" << std::endl;
    }

    void Print() {
        std::cout << "Print Successful." << std::endl;
    }
};

int main() {
    // std::unique_ptr
    /* 只能显式调用 */
    /* 不能使用 std::unique_ptr<Entity> entity = new Entity();
     * 这种隐式调用方式 */
    std::unique_ptr<Entity> entity(new Entity());
    entity->Print();

    // 更加安全的调用方式
    /* 能够处理构造函数的调用异常 */
    std::unique_ptr<Entity> entity1 = std::make_unique<Entity>();
    entity1->Print();
    // unique_prt不能被复制,不能写成如下形式。
    // std::unique_ptr<Entity> entity0 = entity1;
}
  • std::shared_ptr
    • 可以复制,通过引用计数追踪shared_ptr被引用了多少次
    • 使用std::make_shared<Entity>()对shared_ptr进行安全的初始化
#include <iostream>  
#include <memory>  
  
class Entity  
{  
public:  
    Entity() {  
        std::cout << "Created Entity!" << std::endl;  
    }  
  
    ~Entity() {  
        std::cout << "Destroyed Entity!" << std::endl;  
    }  
  
    void Print() {  
        std::cout << "Hello" << std::endl;  
    }  
};  
  
int main() {  
    // std::shared_ptr  
 std::shared_ptr<Entity> entity;  
    {  
        auto sharedEntity = std::make_shared<Entity>();  
        sharedEntity->Print();  
        // shared_ptr 可以复制  
 entity = sharedEntity;  
    }  
    // 由于sharedEntity在函数作用域外仍然有引用,  
 // 所以sharedEntity在程序执行结束后才释放  
 entity->Print();  
}
  • std::weak_ptr
    • 通常和shared_ptr连用
      • shared_ptr赋值给shared_ptr会增加shared_ptr的引用次数
      • shared_ptr赋值给weak_ptr不会增加引用次数
    • 注意:weak_ptr不会检测自己是否还有效
#include <iostream>
#include <memory>

class Entity
{
public:
    Entity() {
        std::cout << "Created Entity!" << std::endl;
    }

    ~Entity() {
        std::cout << "Destroyed Entity!" << std::endl;
    }

    void Print() {
        std::cout << "Hello" << std::endl;
    }
};

int main() {
    // std::weak_ptr
    std::weak_ptr<Entity> entity;
    {
        auto sharedEntity = std::make_shared<Entity>();
        sharedEntity->Print();
        // shared_ptr 可以复制
        entity = sharedEntity;
    }
    // weak_ptr指向的shared_ptr在作用域外已经释放,
    // 此处的weak_ptr变量entity指向了无效内存区域
    //entity->Print();
}

为什么能够返回unique_ptr?

编译器对return的解析如下:

  1. 如果能够move,那么就调用move构造(移动构造函数)。
  2. 如果无法move,那就调用copy构造(复制构造函数)。
  3. 如果无法copy,那就报错(不能move也不能copy,说明该对象无法从函数体内部返回)。

显然:

  1. unique_ptr<T>具有移动构造函数,可以从函数体内部返回。
  2. 由于是移动构造,当前函数体外的_nodes和函数体内的_nodes是同一个对象。

45.【Cherno C++】C++复制与复制构造函数

  • 浅复制 与 深复制
#include <iostream>
#include <cstring>

struct test2Struct
{
    int x{};
};

class StringManually
{
private:
    char* m_Buffer;
    unsigned int m_Size;
public:
    // 去除explicit,允许进行隐式转换
    StringManually(const char* string)
            : m_Size(strlen(string)) {
        // m_Size+1 字符串要以 \0 符号结尾以终结输出
        m_Buffer = new char[m_Size + 1];
        memcpy(m_Buffer, string, m_Size);
        // 手动添加终止符
        m_Buffer[m_Size] = '\0';
    }

    StringManually(const StringManually& other)
            : m_Size(other.m_Size) {
        m_Buffer = new char[m_Size + 1];
        memcpy(m_Buffer, other.m_Buffer, m_Size);
        m_Buffer[m_Size] = '\0';
    }

    ~StringManually() {
        delete[] m_Buffer;
    }

    // 重载运算符[]
    char& operator[](const unsigned char index) const {
        return m_Buffer[index];
    }
};

std::ostream& operator<<(std::ostream& stream, const StringManually& string) {
    stream << string.m_Buffer;
    return stream;
}

// 隐式转换 char* => StringManually
void test1() {
    StringManually string = "No explicit in StringManually.";
    std::cout << string << std::endl;
}

/* 没有复制构造函数的情况下,下列复制方式均为浅复制 */
// 浅复制 【a和b指向同一内存区域,b.x的值改变导致a.x的值改变】
void test2() {
    auto a = new test2Struct;
    auto b = a;
    a->x = 5;
    b->x = 10;
    std::cout << a->x << std::endl;
}

// 浅复制 【a和b指向同一内存区域,导致两次内存释放】
void test3() {
    StringManually a = "li";
    StringManually b = a;
    // 两次内存释放
    std::cout << b << std::endl;
}

// 深复制 【复制构造函数】
void test4() {
    StringManually string = "Cherno";
    StringManually second(string);
    second[2] = 'a';
    std::cout << string << std::endl;
    std::cout << second << std::endl;
}

int main() {
    test1();
    test2();
    test4();
    test3();
    return 0;
}

46.【Cherno C++】C++的箭头操作符与点操作符

  • 箭头操作符用于获取指针指向的对象(类、结构体)中的成员
  • 点操作符直接获取对象(类、结构体)中的成员
#include <iostream>
#include <string>
#include <utility>

class Entity
{
private:
    int m_Id{};
    std::string m_Name{};

public:
    Entity(const int& id, std::string name)
            : m_Id(id), m_Name(std::move(name)) {};

    Entity(const Entity& other) = delete;

    ~Entity() = default;

    void Print() const {
        std::cout << "Hello, " << m_Name
                  << ", your Id is " << m_Id << std::endl;
    }
};

int main() {
    // 创建指针,该指针引用在堆上创建的Entity类实例
    auto* entityHeap = new Entity(200, "li");
    // 使用 -> 操作符访问
    entityHeap->Print();
    // 解引用后可以使用 . 操作符访问
    /* 注意优先级,点操作符优先级高于解引用 */
    (*entityHeap).Print();
}
  • 使用箭头操作符获取内存中某个成员变量的偏移量
/* 这就是魔法 */
#include <iostream>

struct Vector3
{
    float x, z, y;
};

int main() {
    /* 1. 将0(nullptr)转换为一个Vector3的指针,表示地址从0开始偏移
     * 2. 用箭头操作符访问Vector3中的成员
     * 3. 用取址操作符&,获取这个x的地址,此时x的地址就是偏移后的地址
     * 4. 将该地址转换为long long*/
    auto offset_x = (long long) &((Vector3*) nullptr)->x;
    auto offset_y = (long long) &((Vector3*) nullptr)->y;
    auto offset_z = (long long) &((Vector3*) nullptr)->z;
    std::cout << offset_x << ", " << offset_y << ", " << offset_z << std::endl;
}

47.【Cherno C++】C++的动态数组

  • std::vector(坑爹的名字)
    • 在C++中的意思是动态数组(Dynamic Array List)
    • 注意:和数学中的向量没有任何关系。
#include <iostream>
#include <vector>

struct Vector3
{
    float x, y, z;
};

// 重载运算符<<
std::ostream& operator<<(std::ostream& stream, const Vector3& vector3) {
    stream << vector3.x << ", " << vector3.y << ", " << vector3.z << std::endl;
    return stream;
}

int main() {
    std::vector<Vector3> vertices{};
    // 使用push_back()向vector中添加东西,
    /* 根据类的特性可以使用构造函数,也可以直接使用初始化列表。*/
    vertices.push_back({1, 2, 3});
    vertices.push_back({4, 5, 6});
    vertices.push_back({7, 8, 9});
    // C++17新特性,范围for循环,等价于C#中的foreach(element in Elements)
    for (const auto& element: vertices) {
        std::cout << element << std::endl;
    }
    // 从vector中移除元素
    /* 注意:此处不能直接传入元素索引,需要用迭代器(iterator)
     * 可以从头删除【vertices.begin()】,也可以从末尾删除【vertices.end()】 */
    vertices.erase(vertices.begin() + 1);
}

48.【Cherno C++】C++的stdvector使用优化

#include <iostream>
#include <vector>

struct Vector3
{
    float m_x, m_y, m_z;

    Vector3(float x, float y, float z)
            : m_x(x), m_y(y), m_z(z) {}

    Vector3(const Vector3& other)
            : m_x(other.m_x), m_y(other.m_y), m_z(other.m_z) {
        std::cout << "Copied!" << std::endl;
    }

    ~Vector3() = default;
};

int main() {
    // 6次复制,每次赋值时vector调整了两次大小
    auto* vertices = new std::vector<Vector3>{};
    vertices->push_back(Vector3(1, 2, 3));
    vertices->push_back(Vector3(4, 5, 6));
    vertices->push_back(Vector3(7, 8, 9));
    delete vertices;
    std::cout << std::endl;

    // 优化1:每次赋值vector只调整一次大小
    /* 创建vertices2 */
    auto* vertices2 = new std::vector<Vector3>;
    /* 让vector的容量一次增加3 */
    vertices2->reserve(3);
    /* 只需要3次复制 */
    vertices2->push_back(Vector3(1, 2, 3));
    vertices2->push_back(Vector3(4, 5, 6));
    vertices2->push_back(Vector3(7, 8, 9));
    delete vertices2;
    std::cout << std::endl;

    // 优化2:在优化1的基础上使用emplace_back替代push_back
    /* emplace_back只传递用于构造Vector3实例的参数,而不是传递已经构建的对象,从而省去了复制操作(不会调用复制构造函数) */
    auto* vertices3 = new std::vector<Vector3>;
    vertices3->reserve(3);
    vertices3->emplace_back(1, 2, 3);
    vertices3->emplace_back(4, 5, 6);
    vertices3->emplace_back(7, 8, 9);
    delete vertices3;
    /* 在栈上创建vector实例效果相同 */
    std::vector<Vector3> vertices4;
    vertices4.reserve(3);
    vertices4.emplace_back(1, 2, 3);
    vertices4.emplace_back(4, 5, 6);
    vertices4.emplace_back(7, 8, 9);
}

54.【Cherno C++】C++中的栈和堆

#include <iostream>

int main() {
    // 在栈上分配int
    int value = 10;
    int array[] = {1, 2, 3, 4, 5};
    std::cout << value << std::endl;
    for (auto& element: array) std::cout << element << ", ";
    std::cout << std::endl;
    
    // 在堆上分配int
    auto* hValue = new int{5};
    auto* hArray = new int[]{1, 2, 3, 4, 5};
    std::cout << *hValue << std::endl;
    for (int i = 0; i < 5; i++) std::cout << hArray[i] << ", ";
    delete hValue;
    delete[] hArray;
}
  • 注意:在堆上分配内存有额外开销,尽量在栈上分配内存,如果在堆上分配内存则尽量进行堆内存预分配。

58.【Cherno C++】C++的函数指针(原始函数指针)

  • 使用auto或直接使用函数指针对应的类型名称定义
#include <iostream>
#include <string>

void Hello() {
    std::cout << "hello." << std::endl;
}

void HelloWithInput(const std::string& name, const int& age) {
    std::cout << "Hello " << name << ", your age is " << age << ". " << std::endl;
}

int main() {
    // 定义了一个指向Hello函数的指针HelloPtr,第二个等式左边的类型是auto推测出的实际类型
    auto HelloPtrWithAuto = &Hello;
    void (* HelloPtr)() =&Hello;
    // 通过函数指针调用Hello函数
    HelloPtrWithAuto();
    HelloPtr();
    // 定义一个带有形参的函数指针,下面第一个等式左边的类型是auto推测出的实际类型
    void (* HelloWithInputPtr)(const std::string&, const int&) = &HelloWithInput;
    auto HelloWithInputPtrWithAuto = &HelloWithInput;
    HelloWithInputPtr("li", 25);
    HelloWithInputPtrWithAuto("li", 25);
}
  • 使用typedef声明函数指针
#include <iostream>  
#include <string>  
  
void Hello(const std::string& name, const int& age) {  
    std::cout << "Hello " << name << ", your age is " << age << ". " << std::endl;  
}  
  
int main() {  
    typedef void(* HelloFunction)(const std::string&, const int&);  
    HelloFunction function = &Hello;  
    function("li",25);  
}
  • 函数指针的实际应用(lambda表达式)
#include <iostream>
#include <vector>

void PrintValue(const int& value) {
    std::cout << "Value: " << value << std::endl;
}

void ForEach(const std::vector<int>& values, void(* func)(const int&)) {
    for (const int& value: values) {
        func(value);
    }
}

int main() {
    std::vector<int> values = {1, 5, 4, 2, 3};
    ForEach(values, PrintValue);
}
#include <iostream>
#include <vector>
// 在形参中声明了一个函数指针
void ForEach(const std::vector<int>& values, void(* func)(const int&)) {
    for (const int& value: values) {
        func(value);
    }
}

int main() {
    std::vector<int> values = {1, 5, 4, 2, 3};
    // lambda表达式
    ForEach(values, [](const int& value) {
        std::cout << "Value: " << value << std::endl;
    });
}

59.【Cherno C++】C++的lambda表达式

![[Cpp中lambda语句的组成部分.png]]

  1. 捕获子句(C++规范中称为 引导)
    • [=] 通过值捕获lambda作用域内的所有变量
    • [&] 通过引用捕获lambda作用域内所有的变量
    • [&a]通过引用捕获lambda作用域内的变量a
    • [a] 通过值捕获lambda作用域内的变量a
    • [ ] 表示不捕获lambda作用域内的任何变量
  2. 参数列表(也称为lambda声明符)【非必须】
    • 在C++14标准之后,可以使用auto关键字作为类型说明符
    • 如果不需要参数列表,即不需要向lambda表达式传入参数,则可省略空括号
  3. 可变规范(mutable关键词)【非必须】
    • 通常,lambda 的函数调用运算符是按值常数量(const by value),使用mutable关键字会取消按值传递的外部变量在lambda表达式主体中的const属性,从而使lambda表达式的主体可以修改通过值捕获的变量。
    • 总结:捕获子句按值捕获时,传入lambda表达式的副本只读不可修改,需要mutable修饰以更改外部变量在lambda表达式中的副本
  4. 异常规范(throw())【非必须】
    • 现代C++建议使用noexcept关键词替代throw
    • 与普通函数一样,如果lambda表达式使用noexcept声明了异常规范并且lambda主体引发异常,编译器将生成警告。
  5. 尾随返回类型【非必须】
    • 用于指定lambda表达式的返回类型,可以使用auto关键词自动推导返回类型,如果返回类型为确定为void则不要使用auto
  6. lambda表达式主体
    • 在C++14标准之后,可以在lambda表达式主体中引入新变量

注:可以将lambda表达式声明为constexpr,或在常量表达式中使用它,在常量表达式中允许对每个捕获或引入的数据成员进行初始化。

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

using namespace std;

template<typename T>
void Print(const string& string, const T& data) {
    cout << string;

    for (const auto& element: data) {
        cout << element << " ";
    }

    cout << endl;
}

void fillVector(vector<int>& v) {
    // nextValue为静态全局变量,作用域在其所在的.cpp文件内,函数fillVector执行完毕后nextValue不会被销毁
    static int nextValue = 1;
    // 为容器的各个元素赋值,其第三个参数gen需要为lambda表达式或函数
    // 注意:该方法不是线程安全的,应尽量避免使用,这里只用作演示
    generate(v.begin(), v.end(), [] { return nextValue++; });
}

int main() {
    const int elementCount = 9;
    // 初始化一个vector<int>变量,初始值有9个1
    vector<int> vertices(elementCount, 1);
    // 定义两个变量x,y初始化为1
    int x{1}, y{1};
    // 计算两个元素的和,并将结果按顺序存放在vertices中
    generate_n(vertices.begin() + 2,
               elementCount - 2,
               [=]() mutable noexcept -> int { //x,y按照const int形式复制进lambda域中,使用mutable让lambda域中的x,y可被修改
                   int n = x + y;
                   // 使用mutable修饰后,现在lambda域中的x,y可被修改
                   x = y;
                   y = n;
                   return n;
               });
    Print("vector vertices after call to generate_n() with lambda: ", vertices);

    // lambda表达式,只改变在lambda副本内的x,y的值,不会改变作用域之外的x,y的值。
    cout << "x: " << x << " y: " << y << endl;

    // Fill the vector with a sequence of numbers
    fillVector(vertices);
    Print("vector vertices after 1st call to fillVector(): ", vertices);
    // Fill the vector with the next sequence of numbers
    fillVector(vertices);
    Print("vector vertices after 2nd call to fillVector(): ", vertices);
}
#include <iostream>
#include <vector>
#include <functional>

// 原始指针
void ForEachRaw(const std::vector<int>& values, void(* func)(const int&)) {
    for (const int& value: values) {
        func(value);
    }
}

// 使用std::function
void ForEach(const std::vector<int>& values, const std::function<void(int)>& func) {
    for (const int& value: values) {
        func(value);
    }
}

int main() {
    std::vector<int> values = {1, 5, 4, 2, 3};
    // lambda表达式
    ForEachRaw(values, [](const int& value) {
        std::cout << "Value: " << value << std::endl;
    });
    int a = 5;
    // [=]通过值传递所有变量,[&]通过引用传递所有变量,
    // [&a]通过引用传递变量a,[a]通过值传递变量a
    auto lambda = [&](const int& value) {
        std::cout << "Value: " << a << std::endl;
    }; // values有5个元素,func(value)执行了5次,每次都输出a=5,即a被输出了5次
    ForEach(values, lambda);
}
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>

// foreach接收一个vector<int>类型的参数和一个返回值为void,参数类型为const int&的函数
void ForEach(const std::vector<int>& values, const std::function<void(const int&)>& func) {
    for (const int& value: values) func(value);
}

int main() {
    std::vector<int> values = {1, 5, 4, 2, 3};

    auto lambda = [](const int& value) {
        std::cout << value << " ";
    };
    ForEach(values, lambda);
    std::cout << std::endl;
    auto lambda1 = [](const int& value) {
        return value > 3;
    };
    // 在数组中搜索第一个满足条件的值并输出
    auto it = std::find_if(values.begin(), values.end(), lambda1);
    std::cout << *it << std::endl;

    // lambda特性完全使用
    int a{0};
    std::string name{"li"};
    auto lambda2 = [a, name](const int& value)mutable -> std::tuple<int&, std::string&> {
        a += value;
        name = name.append(" Sir. ");
        return {a, name};
    };
    auto[newA, newName]=lambda2(10);
    std::cout << newA << ", " << newName << std::endl;
    newA=100;
    newName="liu";
    std::cout << newA << ", " << newName << std::endl;
}

62.【Cherno C++】C++的线程

#include <iostream>
#include <thread>

static bool sg_Finished = false;
static int sg_times = 0;

void GetThreadId() {
    std::cout << "Started thread Id: " << std::this_thread::get_id() << std::endl;
}

void DoWork(const int& sleepTime_secends) {
    GetThreadId();
    while (!sg_Finished) {
        std::cout << "Working..." << std::endl;
        sg_times++;
        std::this_thread::sleep_for(std::chrono::seconds(sleepTime_secends));
    }
}

void output(const int& times) {
    std::cout << "Finished. Print: " << times << " times." << std::endl;
}

int main() {
    // thread类型接收的是一个函数指针
    std::thread worker(DoWork,1);
    std::cin.get();
    sg_Finished = true;
    // 等待该线程工作完成
    /* 该功能在C#等现代编程语言中叫做wait或wait for exit */
    worker.join();
    output(sg_times);
    GetThreadId();
}

63.【Cherno C++】C++的计时

#include <iostream>
#include <chrono>
#include <thread>

void TimeCount() {
    // 记录当前时间
    auto start = std::chrono::high_resolution_clock::now();
    // 更加友好的定时时间设置
    using namespace std::literals::chrono_literals;
    std::this_thread::sleep_for(1s);
    auto end = std::chrono::high_resolution_clock::now();
    std::chrono::duration<float> duration = end - start;
    std::cout << duration.count() << std::endl;
}

int main() {
    TimeCount();
}
  • 定义一个Timer类,实现在其作用域内自动计时
#include <iostream>
#include <chrono>
#include <thread>

struct Timer
{
    std::chrono::time_point<std::chrono::system_clock, std::chrono::duration<long long, std::nano>> m_start, m_end;
    std::chrono::duration<float> m_duration{};

    Timer() {
        m_start = std::chrono::high_resolution_clock::now();
    }

    ~Timer() {
        m_end = std::chrono::high_resolution_clock::now();
        m_duration = m_end - m_start;
        std::cout << "Timer took " << m_duration.count() << " seconds." << std::endl;
    }
};

int main() {
    Timer timer;
    // 更加友好的定时时间设置
    using namespace std::literals::chrono_literals;
    std::this_thread::sleep_for(1s);
}

65.【Cherno C++】C++标准库std::sort

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

int main() {
    std::vector<int> values1 = {3, 5, 1, 4, 2};
    std::vector<int> values2(values1);
    std::sort(values1.begin(), values1.end(), std::greater<int>());
    for (auto value: values1) std::cout << value << " ";
    std::cout << std::endl;
    auto lambda = [](int a, int b) -> bool { return a < b; };
    std::sort(values2.begin(), values2.end(), lambda);
    for (auto value: values2) std::cout << value << " ";
    std::cout << std::endl;
}

66.【Cherno C++】C++类型双关

  • 对于Java或C#,它们的类型系统很难绕开,但在C++中可以通过指针直接访问内存的方式绕开类型系统。【仅作演示用,如非必要不应该绕开类型系统】
#include <iostream>

int main() {
    int a = 50;
    double value = a;
    std::cout << value << std::endl;
    // 类型双关,将int的值当作double处理
    /* 仅作演示用,可能占用了其他程序的4个字节 */
    value = *(double*) &a;
    std::cout << value << std::endl;
}
#include <iostream>

struct Entity
{
    int m_x{}, m_y{};
};

int main() {
    Entity e = {5, 8};
    // 将Entity当作数组处理
    int* position = (int*) &e;
    std::cout << position[0] << ", " << position[1] << std::endl;
    // 手动间接寻址*(-_-)*
    int* positionOf_y = (int*) ((char*) &e + 4);
    std::cout << *positionOf_y << std::endl;
}

73.【Cherno C++】C++的dynamic_cast

  • 类似于CSharp中的关键字as
    • 如果可以进行类型转换则执行类型转换并返回
    • 如果不能进行转换则返回null
  1. CSharp代码如下:
namespace ConsoleApp1;
class Program
{
    public static void Main(string[] args)
    {
        var myclass2= new MyClass2(2019);
        var myclass1=new MyClass1(24,"li");
        MyClass2? reverseMyClass1ToMyClass2 = myclass1 as MyClass2;
        CheckReverse(reverseMyClass1ToMyClass2);
        var reverseMyClass2ToMyClass1 = myclass2 as MyClass1;
        CheckReverse(reverseMyClass2ToMyClass1);
    }
    private static void CheckReverse(object? reverseMyClass1ToMyClass2)
    {
        if (reverseMyClass1ToMyClass2 == null) { Console.WriteLine("reversedVar is null."); }
        else { Console.WriteLine("reversedVar is Not null."); }
    }
}
class MyClass1
{
    internal int? Age { get; set; }
    internal string? Name { get; set; }
    public MyClass1()
    {
        Age = null;
        Name = null;
    }
    public MyClass1(int age,string name)
    {
        Age = age;
        Name = name;
    }
}
class MyClass2 : MyClass1
{
    internal int? Id { get; set; }
    public MyClass2()
    {
        Id = null;
    }
    public MyClass2(int id){ Id = id; }
}
  1. Cpp代码如下:
  • 使用dynamic_cast的前提是编译器存储了“运行时类型信息(Run-Time Type Information)”,类似于C#等托管型语言的“运行时(Runtime)”
#include <iostream>  
  
class Entity  
{  
public:  
    virtual void PrintName() {}  
};  
  
class Player : public Entity  
{  
};  
  
class Enemy : public Entity  
{  
};  
  
int main() {  
    Player* player = new Player();  
    // 隐式转换  
 Entity* actuallyPlayer = player;  
    Entity* actuallyEnemy = new Enemy();  
  
    // 隐式转换后再转换回来会出问题,认不得e是Player还是Enemy  
 //Player* p = actuallyPlayer;  
 // 解决办法1:显式类型转换,依靠人为判断,容易引来风险  
 Player* p = (Player*) actuallyPlayer;  
    /* 可以人为将actuallyPlayer转换为Enemy */  
 Enemy* e1 = (Enemy*) actuallyPlayer;  
  
    // 解决办法2:dynamic_cast,要求待转换的变量类型e存在多态  
 Player* p1 = dynamic_cast<Player*>(actuallyPlayer);  
    /* 将actuallyEnemy转换为Player时会返回nullptr,表示转换失败 */ Player* p2 = dynamic_cast<Player*>(actuallyEnemy);  
  
    return 0;  
}

74.【Cherno C++】C++的基准测试

#include <iostream>
#include <chrono>

class Timer
{
public:
    Timer() {
        m_StartTimePoint = std::chrono::high_resolution_clock::now();
    }

    ~Timer() {
        Stop();
    }

private:
    std::chrono::time_point<std::chrono::high_resolution_clock> m_StartTimePoint;

    void Stop() {
        auto endTimePoint = std::chrono::high_resolution_clock::now();
        auto start = std::chrono::time_point_cast<std::chrono::nanoseconds>(
                m_StartTimePoint).time_since_epoch().count();
        auto end = std::chrono::time_point_cast<std::chrono::nanoseconds>(endTimePoint).time_since_epoch().count();
        auto nanoTime = end - start;
        std::cout << nanoTime << std::endl;
    }
};

int main() {
    int value = 0;
    {
        Timer timer;
        for (int i = 0; i < 1000000; i++)value += 2;
    }
    std::cout << value << std::endl;
}
  • 注意:代码需要在release模式下测试才有意义
    • 在Debug模式下:$2001600ns=2001.6\mu s=2.0016ms$
    • 在Release模式下:$0ns$
      • 该循环被跳过,$i$直接赋值为$2000000$

75.【Cherno C++】C++的结构化绑定

  • C++17标准新功能【适用于有多个返回值的情况】
#include <iostream>
#include <tuple>
#include <string>

std::tuple<std::string, int> CreatePerson() {
    return {"li", 25};
}

template<class T1, class T2>
void output(const T1& reference1, const T2& reference2) {
    std::cout << reference1 << ", " << reference2 << std::endl;
}

int main() {
    auto person = CreatePerson();
    // 在C++17之前,访问tuple中元素非常麻烦,尤其是代码多的时候
    /* 0,1分别表示name和age,但代码量大时很难将索引与名称一一对应 */
    auto name = std::get<0>(person);
    auto age = std::get<1>(person);

    /* 还可以使用std::tie,但name1和age1仍然需要事先定义 */
    std::string name1{};
    int age1{};
    std::tie(name1, age1) = CreatePerson();

    // C++17新特性,结构化绑定【语法糖】
    auto[name2, age2] = CreatePerson();

    // 结果输出
    output(name, age);
    output(name1, age1);
    output(name2, age2);
    return 0;
}

76.【Cherno C++】如何处理OPTIONAL数据

  • C++17标准新功能std::optional
    • 处理可存在可不存在的数据【类似于C#中的可空类型】
#include <iostream>
#include <string>
#include <fstream>
#include <optional>

/* 很难知道文件是否被打开,返回空字符串不能确定是没找到还是文件本身为空 */
std::string ReadFileAsString(const std::string& filepath) {
    std::ifstream stream(filepath);
    if (stream) {
        std::string result{};
        // read file[此处省略读文件的代码]
        stream.close();
        /* 读取成功返回结果 */
        return result;
    }
    // 如果没有成功打开文件,则返回默认字符串【空字符串】
    return {};
}

/* 需要额外的引用参数 */
std::string ReadFileAsString(const std::string& filepath, bool& isSuccess) {
    std::ifstream stream(filepath);
    if (stream) {
        std::string result{};
        stream.close();
        isSuccess = true;
        return result;
    }
    isSuccess = false;
    return {};
}

/* C++17新特性,std::optional,如果没有数据返回null,如果有数据返回该数据 */
std::optional<std::string> ReadFileAsStringOptional(const std::string& filepath) {
    std::ifstream stream(filepath);
    if (stream) {
        std::string result{};
        stream.close();
        return result;
    }
    return {};
}

int main() {
    auto data = ReadFileAsStringOptional("data.txt");
    if (data.has_value()) std::cout << "File read successfully" << std::endl;
    else std::cout << "File read failed." << std::endl;

    /* 可以使用std::optional设置默认参数,如果用户设置该参数则使用用户值,如果没设置则使用默认参数 */
    std::optional<int> count{};
    int c = count.value_or(100);
    std::cout << c << std::endl;
    c = 10;
    std::cout << c << std::endl;
}

77.【Cherno C++】单一变量存放多种类型的数据

  • C++17标准新功能std::variant
    • 不用担心所处理数据的确切数据类型
      • 类似于C#中的dynamic,但是Cpp中的std::variant是半自动的。
        • 在使用之前需要手动给定一个类型表
        • 在类型表内的类型才能随意切换
        • 在读取时还需要使用std::get<T>()手动执行类型转换
#include <iostream>
#include <variant>
#include <string>

int main() {
    std::variant<std::string, int> data;
    data = "li";
    std::cout << std::get<std::string>(data) << std::endl;
    data = 25;
    std::cout << std::get<int>(data) << std::endl;
    /* 在直接使用std::get<T>()的情况下,如果数据类型错误会引发异常 */
    /* 使用std::get_if<T>替代 */
    if (auto stringValue = std::get_if<std::string>(&data)) {
        std::string& v = *stringValue;
        // do something with string v.
    } else if (auto intValue = std::get_if<int>(&data)) {
        int& v = *intValue;
        // do something with int v.
    }
    
    return 0;
}

78.【Cherno C++】如何存储任意类型的数据

  • C++17标准新功能std::any
    • 类似于std::variant,但不需要在定义时指定类型
    • 在使用std::any_cast<T>转换变量时仍然要确保类型一致
    • 注:避免使用std::any,使用std::optional或std::variant替代
#include <iostream>
#include <any>
int main() {
    std::any data;
    data = 2;
    std::cout << std::any_cast<int>(data) << std::endl;
    data = "test";
    auto string = std::any_cast<const char*>(data);
    std::cout << string << std::endl;
}

82.【Cherno C++】C++的单例模式

  • 通过Get()访问任意数量的非静态成员方法
#include <iostream>

class Singleton
{
public:
    // 单例类不能被复制,所以删除复制构造函数以避免复制
    Singleton(const Singleton&) = delete;

    static Singleton& Get() {
        return s_Instance;
    };

    void Function() {
        m_Member += 2.3f;
        std::cout << m_Member << std::endl;
    }

    void Function1() {
        m_Member -= 1.4f;
        std::cout << m_Member << std::endl;
    }

private:
    Singleton() = default;

    static Singleton s_Instance;
    float m_Member{};
};

Singleton Singleton::s_Instance;

int main() {
    Singleton::Get().Function();
    Singleton::Get().Function1();
}

82.1. 补充内容:随机数生成方法

  1. linux下直接调用std::random_device,然而windows不行,windows下的随机数周期明显,不是真正的随机数
#include <iostream>  
#include <random>  
#include <vector>  
  
int main() {  
    std::random_device rd;  
    std::vector<unsigned int> randomNumbers{};  
    int numbersOfRandomNumbers = 10;  
    randomNumbers.reserve(numbersOfRandomNumbers);  
    for (int i = 0; i < numbersOfRandomNumbers; i++) randomNumbers.push_back(rd());  
    for (auto& element: randomNumbers) std::cout << element << ", ";  
    std::cout << std::endl;  
    randomNumbers.clear();  
    for (int i = 0; i < numbersOfRandomNumbers; i++) randomNumbers.push_back(rd());  
    for (auto& element: randomNumbers) std::cout << element << ", ";  
    std::cout << std::endl;  
}
  1. 在windows和linux中获取真随机数的通用方法
/* 加入std::mt19937 mt(rd());以加强windows下随机数的随机性 */
#include <iostream>
#include <random>
#include <vector>

int main() {
    std::random_device rd;
    // 增强随机性
    std::mt19937 mt(rd());
    std::vector<unsigned int> randomNumbers{};
    int numbersOfRandomNumbers = 10;
    randomNumbers.reserve(numbersOfRandomNumbers);
    for (int i = 0; i < numbersOfRandomNumbers; i++) randomNumbers.push_back(mt());
    for (auto& element: randomNumbers) std::cout << element << ", ";
    std::cout << std::endl;
    randomNumbers.clear();
    for (int i = 0; i < numbersOfRandomNumbers; i++) randomNumbers.push_back(mt());
    for (auto& element: randomNumbers) std::cout << element << ", ";
    std::cout << std::endl;
}

84.【Cherno C++】跟踪内存分配的简单方法

  • 重载new和delete操作符并使用智能指针
#include <iostream>
#include <memory>

struct AllocationMetrics
{
    uint32_t TotalAllocated = 0;
    uint32_t TotalFreed = 0;

    // [[nodiscard]] 如果返回的对象被调用者丢弃,则编译器将生成警告。
    [[nodiscard]] uint32_t CurrentUsage() const {
        return TotalAllocated - TotalFreed;
    }

    void PrintMemoryUsage() const {
        std::cout << "Memory Usage:" << this->CurrentUsage() << " bytes\n";
        std::cout << "TotalAllocated " << TotalAllocated << " bytes\n";
        std::cout << "TotalFreed " << TotalFreed << " bytes\n";
        std::cout << std::endl;
    }
};

static AllocationMetrics sg_AllocationMetrics;

void* operator new(std::size_t size) {
    sg_AllocationMetrics.TotalAllocated += size;
    return malloc(size);
}

void operator delete(void* memory, std::size_t size) {
    sg_AllocationMetrics.TotalFreed += size;
    free(memory);
}


struct Object
{
    int x, y, z;

    Object() = default;

    ~Object() = default;
};

int main() {
    sg_AllocationMetrics.PrintMemoryUsage();
    {
        //Allocating 12 bytes
        std::unique_ptr<Object> object = std::make_unique<Object>();
        sg_AllocationMetrics.PrintMemoryUsage();
    }//Freeing 12 bytes
    sg_AllocationMetrics.PrintMemoryUsage();
}

89.【Cherno C++】移动语义

#include <iostream>
#include <cstring>

class StringManually
{
public:
    StringManually() = default;

    explicit StringManually(const char* string) {
        printf("Created!\n");
        m_Size = strlen(string);
        m_Data = new char[m_Size];
        memcpy(m_Data, string, m_Size);
    }

    StringManually(const StringManually& other) {
        printf("Copied!\n");
        m_Size = other.m_Size;
        m_Data = new char[m_Size];
        memcpy(m_Data, other.m_Data, m_Size);
    }

    ~StringManually() {
        printf("Destroyed!\n");
        delete[] m_Data;
    }

    void Print() {
        for (uint32_t i = 0; i < m_Size; i++)printf("%c", m_Data[i]);
        printf("\n");
    }

private:
    char* m_Data{};
    uint32_t m_Size{};
};

class Entity
{
public:
    explicit Entity(const StringManually& name)
            : m_Name(name)/* 在此处复制了一次,name复制给Entity创建的StringManually实例m_Name */ {
    }

    void PrintName() {
        m_Name.Print();
    }

private:
    StringManually m_Name{};
};

int main() {
    // 有了一次不必要的复制过程,无法把StringManually("Che")这一实例直接传入Entity的m_Name中
    Entity entity(StringManually("Che"));
    entity.PrintName();
}
  • 使用移动构造函数代替复制构造函数
#include <iostream>
#include <cstring>

class StringManually
{
public:
    StringManually() = default;

    explicit StringManually(const char* string) {
        printf("Created!\n");
        m_Size = strlen(string);
        m_Data = new char[m_Size];
        memcpy(m_Data, string, m_Size);
    }

    // 禁止复制
    StringManually(const StringManually&) = delete;

    // 移动构造函数
    /* noexcept用于约定该函数不会抛出异常,如果抛出异常则直接终止程序,
     * 其作用在于阻止异常的传播和扩散 */
    StringManually(StringManually&& other) noexcept {
        printf("Moved!\n");
        m_Size = other.m_Size;
        m_Data = other.m_Data;
        // 在移动构造函数中要处理旧的StringManually实例other
        other.m_Size = 0;
        other.m_Data = nullptr;
    }

    ~StringManually() {
        printf("Destroyed!\n");
        delete[] m_Data;
    }

    void Print() {
        for (uint32_t i = 0; i < m_Size; i++)printf("%c", m_Data[i]);
        printf("\n");
    }

private:
    char* m_Data{};
    uint32_t m_Size{};
};

class Entity
{
public:
    // 使用右值引用构造函数替代传值的构造函数
    explicit Entity(StringManually&& name)
            : m_Name((StringManually&&) name) {
        /* 2. Entity实例在初始化过程中由m_Name接收了传入的右值,没有复制过程
         * 省去了不必要的复制操作。*/
        std::cout << "Entity Created with Entity(StringManually&& name)." << std::endl;
    }
    /* 3. StringManually("Che")创建了匿名StringManually实例经name传入Entity后失去作用,被析构
     * 注:name对应于移动构造函数中的StringManually(StringManually&& other) noexcept */

    void PrintName() {
        m_Name.Print();
    }

    ~Entity() {
        std::cout << "Entity Destroyed." << std::endl;
    }

private:
    StringManually m_Name{};
};

int main() {
    /* 1. StringManually("Che")创建了匿名StringManually实例 */
    Entity entity(StringManually("Che"));
    entity.PrintName();
    /* 4-5.在析构Entity的过程中析构StringManually m_Name */

    /* 总结:Reason for two destroys: 1 where the hollow object is destroyed + 1 where the actual heap memory is deallocated. */
    /* 总结:The first one is destroying object with size of 0 and data as null */
    /* 总结:两次销毁的原因:
     * 1. 空对象【StringManually(StringManually&& other)中的other】被销毁(虚假销毁,释放的内存是nullptr指向的内存)
     * 2. 实际堆内存被释放。 */
}

90. 构造函数、复制构造函数、移动构造函数总结

注意:即使Test是模板类Test<T>,Test<T>的复制和移动构造函数仍然和非泛型的Test类相同。考虑如下示例:

template<typename T>
class A
{
public:
    A(const A& theOther) = default;

    A& operator=(const A& theOther) = default;

    A(A&& theOther) noexcept = default;

    A& operator=(A&& theOther) noexcept = default;

};

在上述示例中,即使A含有模板参数,但在类A的内部将A作为类型使用时,它也会被认为是完整的类型A<T>

#include <iostream>
#include <string>
#include <utility>

class Test
{
public:
    Test() {
        std::cout << "constructor" << std::endl;
        std::cout << GetStrings() << std::endl;
    };

    explicit Test(std::string strings) : _strings(std::move(strings)) {
        std::cout << "constructor" << std::endl;
        std::cout << GetStrings() << std::endl;
    }

    Test(const Test& others) : _strings(others._strings) {
        std::cout << "copy constructor" << std::endl;
        std::cout << GetStrings() << std::endl;
    };

    Test& operator=(const Test& others) = delete;

    Test(Test&& others) noexcept: _strings(std::move(others._strings)) {
        std::cout << "move constructor" << std::endl;
        std::cout << GetStrings() << std::endl;
    };

    Test& operator=(Test&& newEntity) = delete;

    ~Test() = default;

    std::string& GetStrings() {
        return _strings;
    }

    void ModifyStrings(const std::string& newString) {
        _strings = newString;
        std::cout << "string has been modified to " << _strings << std::endl;
    }

private:
    std::string _strings;
};

int main() {
    auto* test1 = new Test("test1");
    auto* test2 = new Test("test2");
    delete test2;
    test2 = new Test(*test1);
    test2->ModifyStrings("Test2");
    delete test2;
    test2 = new Test(std::move(*test1));
    test2->ModifyStrings("Test2");
    delete test2;

    Test test3("test3");
    Test test4(test3);
    test4.ModifyStrings("Test4");
    Test test5(std::move(test4));
    test5.ModifyStrings("Test5");
}

运行结果如下:

constructor
test1
constructor
test2
copy constructor
test1
string has been modified to Test2
move constructor
test1
string has been modified to Test2
constructor
test3
copy constructor
test3
string has been modified to Test4
move constructor
Test4
string has been modified to Test5

标签:std,Cherno,const,cout,int,笔记,C++,include
From: https://www.cnblogs.com/z7hwHqeY/p/18564321

相关文章

  • vscode的C++引用头文件总是报错,网上教程都试了还是没用,请来这里。
    本教程跟网上大部分教程大同小异。(节省时间:在编辑task.json文件时只需写头文件路径,一定不要写源文件路径即可,其余步奏跟其他人的相同)若成功解决问题,希望可以给小编一个赞其中一些操作看不懂的可以先看其他人的步奏,如:适合初学者!超详细的vscode的C++自定义头文件的配置!_vscod......
  • Sickos1.1 详细靶机思路 实操笔记
    Sickos1.1详细靶机思路实操笔记免责声明本博客提供的所有信息仅供学习和研究目的,旨在提高读者的网络安全意识和技术能力。请在合法合规的前提下使用本文中提供的任何技术、方法或工具。如果您选择使用本博客中的任何信息进行非法活动,您将独自承担全部法律责任。本博客明确表......
  • C++提高编程-STL
    STL初识容器算法迭代器初识vector存放内置数据类型#include<vector>#include<algorithm>voidmyPrint(intx){ cout<<x<<'';}voidtest01(){ //创建vector容器 vector<int>v; //向容器中插入数据 v.push_back(10); v.push_back(20); v.......
  • 笔记:最小斯坦纳树
    最小斯坦纳树定义摘自百度百科的定义:斯坦纳树问题是组合优化问题,与最小生成树相似,是最短网络的一种。最小生成树是在给定的点集和边中寻求最短网络使所有点连通。而最小斯坦纳树允许在给定点外增加额外的点,使生成的最短网络开销最小。实现例题:P6192【模板】最小斯坦纳树-......
  • C++游戏开发
    C++是一种常用的编程语言,广泛应用于游戏开发领域。在使用C++进行游戏开发时,可以利用C++的面向对象特性、高性能和可移植性来创建游戏。以下是C++游戏开发的详细步骤:设计游戏的概念和目标:在开始游戏开发之前,你需要明确游戏的概念和目标,包括游戏的类型、玩法、关卡设计等。这......
  • 【GESP】C++一级练习 luogu-B2060, 满足条件的数累加
    一级知识点循环和取余操作练习题,基础练习。题目题解详见:https://www.coderli.com/gesp-1-luogu-b2060/【GESP】C++一级练习luogu-B2060,满足条件的数累加|OneCoder一级知识点循环和取余操作练习题,基础练习。https://www.coderli.com/gesp-1-luogu-b2060/......
  • 【GESP】C++一级练习 luogu-B2058, 奥运奖牌计数
    一级知识点循环和求和练习。题目题解详见:https://www.coderli.com/gesp-1-luogu-b2058/https://www.coderli.com/gesp-1-luogu-b2058/https://www.coderli.com/gesp-1-luogu-b2058/2008 年北京奥运会,A国的运动员参与了 n 天的决赛项目 (1≤n≤100)。现在要统计一下A国......
  • 笔记:二维哈希
    二维哈希前置芝士哈希前缀和教程二维哈希板子P10474BeiJing2011Matrix矩阵哈希-洛谷代码#include<bits/stdc++.h>usingnamespacestd;typedefunsignedlonglongull;constullRowBase=131,ColBase=1313;constintmaxn=1005;intn,m,a,b;ul......
  • 宝宝的C++,小学生C++编程启蒙 书籍等
    1、宝宝的C++(2016-11)2、啊哈编程星球:一本书入门Python和C++(2019年09月)啊哈编程星球啊哈编程星球!编程学习从这开始~3、我的第一本算法书(修订版)--2024.24、聪明的算法(2022.07)--6到12岁小读者量身打造的前沿科学大揭秘系列科普书5、走进GoC的编程世界(......
  • 笔记:二分图
    概念二分图:又称作二部图,设\(G=(V,E)\)是一个无向图,如果顶点集\(V\)可分割为两个互不相交的子集\(A,B\),并且图中的每条边\((u,v)\)所关联的两个顶点\(u,v\)分别属于这两个顶点集\((u\inA,v\inB)\),则称图\(G\)为一个二分图。也就是说一个图被划分成了两个......