C++ 整理

C++的数据类型

C++是一种静态类型语言,它支持以下基本数据类型:

  1. 整型 (int):表示整数,可分为有符号整型(int)和无符号整型(unsigned int),不同类型占用的存储空间大小不同。
  2. 字符型 (char):表示一个字符,占用一个字节的存储空间。
  3. 布尔型 (bool):表示真或假,占用一个字节的存储空间。
  4. 浮点型 (float, double):表示实数,可分为单精度浮点数(float)和双精度浮点数(double),占用的存储空间大小不同。
  5. 空类型 (void):表示没有值,通常用于函数返回值类型或指针类型。

除了基本数据类型,C++还支持以下扩展数据类型:

  1. 枚举类型 (enum):表示一组具有名字的整数值,通常用于程序中需要使用固定值的情况。
  2. 数组类型 (array):表示一组具有相同数据类型的变量集合,可以使用下标操作访问其中的元素。
  3. 结构体类型 (struct):表示一组不同数据类型的变量集合,可以使用成员访问操作符访问其中的元素。
  4. 联合类型 (union):表示一组不同数据类型的变量集合,但是只能同时存储其中的一个元素,占用的存储空间大小取决于最大的成员类型大小。

此外,C++还支持指针类型 (pointer),引用类型 (reference)等,这些数据类型在C++的程序设计中具有重要的作用。

c++内存结构与管理

C++是一种直接操作内存的语言,因此需要程序员具备一定的内存管理知识。下面是C++内存结构和内存管理的一些具体内容:

  1. 内存结构

C++程序的内存可以分为以下几个部分:

  • 栈内存(Stack Memory):由编译器自动分配和释放,存储局部变量、函数参数等。栈内存空间大小有限,一般几MB到几十MB不等。
  • 堆内存(Heap Memory):由程序员手动申请和释放,存储动态分配的内存空间。堆内存空间大小较大,可以申请GB级别的内存空间。
  • 静态内存(Static Memory):存储全局变量和静态变量,程序在运行期间一直存在,直到程序退出才会释放。
  • 代码内存(Code Memory):存储可执行代码,由操作系统负责加载到内存中运行。
  1. 内存管理

C++程序员需要手动管理堆内存的申请和释放,否则会造成内存泄漏或者内存溢出等问题。具体的内存管理包括以下内容:

  • 动态内存申请:使用new运算符申请内存空间,并返回指向该空间的指针。例如:int *p = new int;
  • 动态内存释放:使用delete运算符释放申请的内存空间,例如:delete p;。需要注意的是,如果使用new[]运算符申请了数组类型的内存空间,需要使用delete[]运算符释放空间,例如:delete[] arr;。
  • 内存泄漏:如果程序没有正确释放申请的内存空间,就会造成内存泄漏,即申请的内存空间无法被回收,导致程序的内存使用越来越大。为了避免内存泄漏,需要在程序中明确释放申请的内存空间。
  • 空指针异常:如果使用了指针变量,但是没有初始化或者初始化为null指针,就会造成空指针异常,即访问不存在的内存空间,导致程序崩溃。为了避免空指针异常,需要在使用指针变量之前进行初始化,并进行有效性检查。

除了以上内存管理的内容外,C++程序员还需要掌握内存分配器(Memory Allocator)的使用,如STL中的malloc和free函数,以及Boost库中的Memory Pool等高级内存管理技术,以提高程序的内存使用效率和性能。

预处理指令有哪些。使用场景是什么

C++预处理指令是指在编译代码之前由预处理器(preprocessor)处理的特殊指令,它们以#字符开头。预处理器是C++编译器的一部分,用于对代码进行文本替换、宏定义、条件编译等操作,从而在编译时生成最终的源代码。下面是一些常用的预处理指令及其使用场景:

  1. #include

用于包含头文件,将其他文件中的代码复制到当前文件中。头文件中通常包含了函数声明、宏定义和常量等代码,可以提供给当前文件使用。例如:#include 表示包含标准输入输出流库的头文件iostream。

  1. #define

用于宏定义,可以用来定义常量、函数、数据类型等。例如:#define PI 3.14定义了一个名为PI的常量,其值为3.14。

  1. #ifdef,#ifndef,#endif

用于条件编译,可以根据指定的条件编译一部分代码。例如:#ifdef _DEBUG #include #endif表示如果宏_DEBUG已经定义,则包含头文件cassert,否则不包含。

  1. #if,#elif,#else

用于条件编译,可以根据指定的条件编译一部分代码。例如:#if (x > 0) cout << "x is positive" << endl; #elif (x < 0) cout << "x is negative" << endl; #else cout << "x is zero" << endl; #endif表示根据变量x的值输出不同的消息。

  1. #pragma

用于向编译器发送特定的命令或指令,可以用来控制编译器的行为或优化代码。例如:#pragma once表示指示编译器只包含当前文件一次,避免重复包含。

预处理指令可以提高代码的可读性和灵活性,可以根据不同的平台、编译器或者操作系统等定义不同的宏,从而实现跨平台编译或者编译不同版本的代码。同时也可以使用条件编译实现不同的调试或者发布版本,提高代码的可维护性和适应性。

Typedef别名

在C++中,typedef是一种定义类型别名的方法,它可以将一个类型名定义为另一个类型的别名,从而方便程序员使用。以下是常见的typedef别名及其使用场景:

  1. typedef int INT;

这条语句将int类型定义为INT的别名,使用INT可以代替int,例如:INT a = 10;。

  1. typedef char* PCHAR;

这条语句将char_类型定义为PCHAR的别名,使用PCHAR可以代替char_,例如:PCHAR str = "Hello World!";。

  1. typedef struct tagStudent { char name[20]; int age; } STUDENT;

这条语句将struct tagStudent类型定义为STUDENT的别名,使用STUDENT可以代替struct tagStudent,例如:STUDENT stu = { "Tom", 18 };。

  1. typedef void (*FUNC_PTR)(int);

这条语句将void ()(int)类型定义为FUNC_PTR的别名,使用FUNC_PTR可以代替void ()(int),例如:FUNC_PTR pFunc = func;。

  1. typedef std::vector<int> IntVec;

这条语句将std::vector<int>类型定义为IntVec的别名,使用IntVec可以代替std::vector<int>,例如:IntVec vec; vec.push_back(1);。

typedef别名的使用场景主要有以下几个方面:

  1. 提高代码的可读性和可维护性,使用别名可以使代码更加易懂,避免使用复杂或难以理解的类型名称。

  2. 简化代码编写,使用别名可以简化代码编写,提高效率。

  3. 便于跨平台编译,使用别名可以在不同的编译器或平台上保持一致的类型名称,避免因不同编译器或平台的差异导致的编译错误。

  4. 便于类型变更和维护,使用别名可以使类型的变更更加方便和安全,减少代码的修改和维护成本。

c++中结构体与共用体

在C++中,结构体(struct)和共用体(union)都是用户自定义的数据类型,可以用于定义复杂的数据结构。

结构体是一种数据类型,它可以包含多个不同的数据类型,用于描述一个对象或实体的多个属性。结构体中的每个属性称为成员变量,可以是基本数据类型或其他结构体。结构体中的成员变量按照定义的顺序依次排列,每个成员变量都可以通过结构体变量名和成员名来访问。

共用体是一种特殊的结构体,它只能同时存储一个成员变量的值。共用体的成员变量共享同一块内存空间,因此只能访问其中一个成员变量,访问时需要指定成员变量的名称。共用体通常用于节省内存或在不同的数据类型之间进行转换。

以下是结构体和共用体的使用场景:

  1. 结构体通常用于描述具有多个属性的对象或实体,例如:学生、汽车、房屋等。结构体可以将多个属性封装在一起,方便进行数据操作和管理。

  2. 共用体通常用于需要在不同的数据类型之间进行转换的场景,例如:网络通信、文件读写等。共用体可以将不同类型的数据存储在同一块内存空间中,从而避免了数据类型转换时的数据拷贝和类型转换。

  3. 结构体和共用体都可以用于定义复杂的数据结构,例如:树、图等。通过定义结构体和共用体,可以将不同的数据类型组合在一起,构成复杂的数据结构,方便数据的操作和管理。

总之,结构体和共用体都是C++中非常重要的用户自定义数据类型,它们的使用场景非常广泛,可以帮助程序员更加方便和灵活地进行数据操作和管理。

指针、智能指针、方法指针 具体使用。及其定义和使用场景

指针、智能指针和函数指针都是C++中非常重要的数据类型,它们各自有着不同的定义和使用场景,下面分别进行介绍:

  1. 指针

指针是一种特殊的数据类型,它可以存储内存地址。通过指针,程序可以直接访问内存中的数据。指针的定义方式为:类型名 *指针变量名;例如:int *p;定义了一个名为p的整型指针变量。

指针的主要使用场景包括:

  • 动态内存分配和释放
  • 数组访问
  • 传递函数参数
  1. 智能指针

智能指针是一种特殊的指针,它可以自动管理内存的释放。智能指针通常用于动态分配的内存,可以避免程序员忘记释放内存所带来的问题。C++11中提供了两种智能指针:shared_ptr和unique_ptr。

shared_ptr是一种引用计数的智能指针,它会自动记录指向的对象的引用次数,当引用次数为0时,自动释放内存。shared_ptr的定义方式为:shared_ptr<类型>指针变量名;例如:shared_ptr p;定义了一个名为p的整型智能指针变量。

unique_ptr是一种独占所有权的智能指针,它可以确保只有一个指针变量指向对象,并且当指针变量离开作用域时,自动释放内存。unique_ptr的定义方式为:unique_ptr<类型>指针变量名;例如:unique_ptr p;定义了一个名为p的整型独占智能指针变量。

智能指针的主要使用场景包括:

  • 动态内存分配和释放
  • 避免内存泄漏
  1. 函数指针

函数指针是指向函数的指针变量,可以将函数名作为参数传递给函数指针,从而实现函数的动态调用。函数指针的定义方式为:返回类型 (*指针变量名)(参数列表);例如:int (*p)(int, int);定义了一个名为p的指向返回值为int,参数列表为int和int的函数指针变量。

函数指针的主要使用场景包括:

  • 回调函数
  • 函数动态调用
  • 多态实现

总之,指针、智能指针和函数指针都是C++中非常重要的数据类型,它们各自有着不同的定义和使用场景。程序员需要根据具体的需求来选择适合的指针类型,从而实现代码的高效和灵活。

具体使用

下面分别以指针、智能指针和函数指针为例,举例说明它们的具体使用场景和用法:

  1. 指针

指针的一个常见使用场景是动态内存分配和释放。例如,程序需要在运行时动态创建一个数组来存储数据,可以使用new运算符分配内存,然后使用指针来访问这个数组:

int size = 10;
int *arr = new int[size];
for (int i = 0; i < size; i++) {
    arr[i] = i;
}
delete[] arr; // 释放内存

指针的另一个常见使用场景是传递函数参数。例如,程序需要交换两个变量的值,可以使用指针作为函数参数来实现:

void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}
int main() {
    int a = 1, b = 2;
    swap(&a, &b);
    return 0;
}
  1. 智能指针

智能指针的一个常见使用场景是动态内存分配和释放。例如,程序需要动态创建一个对象并使用,可以使用智能指针来管理内存,从而避免内存泄漏:

class MyClass {
public:
    MyClass() { cout << "MyClass constructor" << endl; }
    ~MyClass() { cout << "MyClass destructor" << endl; }
};
int main() {
    shared_ptr<MyClass> p(new MyClass());
    return 0;
}

智能指针的另一个常见使用场景是避免空指针的问题。例如,程序需要调用一个函数并返回一个指针,但是有可能函数返回的是空指针,这时可以使用智能指针来判断指针是否为空:

unique_ptr<int> create_int() {
    int *p = new int(123);
    return unique_ptr<int>(p);
}
int main() {
    unique_ptr<int> p = create_int();
    if (p) {
        cout << *p << endl;
    } else {
        cout << "null pointer" << endl;
    }
    return 0;
}
  1. 函数指针

函数指针的一个常见使用场景是回调函数。例如,程序需要实现一个排序算法,可以通过函数指针来指定排序的方式:

void bubble_sort(int *arr, int n, bool (*cmp)(int, int)) {
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j < n - i - 1; j++) {
            if (cmp(arr[j], arr[j + 1])) {
                swap(arr[j], arr[j + 1]);
            }
        }
    }
}
bool ascending(int a, int b) {
    return a < b;
}
bool descending(int a, int b) {
    return a > b;
}
int main() {
    int arr[] = { 3, 1, 4, 1, 5, 9, 6, 5, 3 }; 
    int n = sizeof(arr) / sizeof(int); 
    bubble\_sort(arr, n, ascending);
     for (int i = 0; i < n; i++) { 
     cout << arr\[i\] << " "; 
     } 
     cout << endl;
     bubble\_sort(arr, n, descending); 
     for (int i = 0; i < n; i++) { 
     cout << arr\[i\] << " "; 
     } 
     cout << endl; return 0; 
     }

上面的代码定义了两个比较函数ascending和descending,然后将它们作为函数指针传递给bubble_sort函数,从而实现升序和降序两种排序方式。

c++中并发编程

在C++中,可以通过多线程来实现并发编程。C++标准库提供了一个线程库<thread>,其中包含了创建和管理线程的类和函数。

通过创建多个线程,可以在不同的线程中执行不同的任务,从而实现并发编程。可以使用std::thread类创建一个新线程,将需要在新线程中执行的函数作为参数传递给std::thread类的构造函数。

例如,下面的代码展示了如何使用std::thread类来创建一个新线程并在其中执行一个函数:

#include <iostream>
#include <thread>

void myFunction() {
    std::cout << "This is myFunction" << std::endl;
}

int main() {
    std::thread myThread(myFunction);

    myThread.join();

    return 0;
}

在上面的代码中,myFunction函数会在新线程中执行。myThread.join()会等待新线程执行完毕,然后程序才会继续往下执行。

另外,C++11还提供了一个原子类型std::atomic,它可以保证在多线程并发操作时的原子性,从而避免了多线程竞争导致的数据不一致问题。C++11还提供了一个互斥量std::mutex和一个条件变量std::condition_variable,可以用来实现线程同步和互斥操作。

除了C++标准库提供的多线程机制之外,还可以使用第三方库,比如Boost和Poco等,它们提供了更丰富的多线程功能,例如线程池、定时器、消息队列等,可以更方便地进行并发编程。

下面是一些具体实现实例:

  1. 使用std::thread创建多个线程并执行不同的函数
#include <iostream>
#include <thread>
#include <chrono>

void function1() {
    std::cout << "Thread 1 is running" << std::endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    std::cout << "Thread 1 finished" << std::endl;
}

void function2() {
    std::cout << "Thread 2 is running" << std::endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(2000));
    std::cout << "Thread 2 finished" << std::endl;
}

int main() {
    std::thread t1(function1);
    std::thread t2(function2);

    std::cout << "Main thread is running" << std::endl;

    t1.join();
    t2.join();

    std::cout << "All threads finished" << std::endl;

    return 0;
}

在上面的代码中,我们创建了两个线程,分别执行function1function2函数。std::this_thread::sleep_for函数可以让线程休眠指定的时间。t1.join()t2.join()语句会等待线程t1t2执行完毕后,程序才会继续往下执行。

  1. 使用std::atomic保证多线程操作的原子性
#include <iostream>
#include <thread>
#include <atomic>

std::atomic<int> counter(0);

void increment() {
    for (int i = 0; i < 10000; ++i) {
        ++counter;
    }
}

void decrement() {
    for (int i = 0; i < 10000; ++i) {
        --counter;
    }
}

int main() {
    std::thread t1(increment);
    std::thread t2(decrement);

    t1.join();
    t2.join();

    std::cout << "Counter = " << counter << std::endl;

    return 0;
}

在上面的代码中,我们创建了两个线程t1t2,分别执行incrementdecrement函数。counter是一个std::atomic<int>类型的变量,可以保证多线程操作时的原子性,从而避免了多线程竞争导致的数据不一致问题。

  1. 使用std::mutexstd::condition_variable实现线程同步和互斥操作
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>

std::mutex mutex;
std::condition_variable cv;
bool ready = false;

void worker_thread() {
    std::unique_lock<std::mutex> lock(mutex);
    while (!ready) {
        cv.wait(lock);
    }
    std::cout << "Worker thread is running" << std::endl;
}

int main() {
    std::thread worker(worker_thread);

    std::cout << "Main thread is running" << std::endl;

    {
        std::lock_guard<std::mutex> lock(mutex);
        ready = true;
    }
    cv.notify_one();

    worker.join();

    std::cout << "All threads finished" << std::endl;

    return 0;
}

std::lock_guard<std::mutex> lock(mutex); 这句的作用是什么

std::lock_guard<std::mutex>是一个RAII风格的互斥锁保护类,用于保护一个临界区,以确保同一时间只有一个线程可以访问该临界区。

lock_guard在构造函数中获取互斥锁,在析构函数中释放互斥锁。因此,使用std::lock_guard<std::mutex>定义的对象,在作用域结束时,会自动释放互斥锁,避免了手动释放互斥锁的麻烦。

在上面的例子中,我们使用std::lock_guard<std::mutex>来保护ready变量的读写操作,防止多线程访问时出现数据竞争的问题。当lock_guard对象的生命周期结束时,互斥锁会自动释放,从而避免了程序员手动释放锁的操作。

函数、虛函数、纯虛函数与析构函数

函数是指一组执行特定任务的代码,可以接受参数并返回值。在C++中,函数可以用于封装重复使用的代码块,提高代码的可读性和可维护性。

虚函数是在基类中声明的,用virtual关键字修饰的成员函数。虚函数可在派生类中重写(override)或重载(overload),实现运行时多态性。使用虚函数可以让基类指针或引用调用派生类的成员函数,实现动态绑定。

纯虚函数是在基类中声明的,没有函数体的虚函数,即在函数声明语句后面加上=0。派生类必须重写所有纯虚函数,否则该派生类也是抽象类。使用纯虚函数可以实现接口(interface),即只声明不定义成员函数。

析构函数是类的特殊成员函数,用于在对象销毁时执行清理工作,例如释放内存、关闭文件等。析构函数的命名规则为~类名(),不接受参数,不返回值。析构函数在对象销毁时自动调用,不需要显式调用。

运用场景:

  • 函数:封装代码块,提高代码的可读性和可维护性。
  • 虚函数:实现多态性,让基类指针或引用调用派生类的成员函数。
  • 纯虚函数:实现接口(interface),只声明不定义成员函数。
  • 析构函数:执行对象销毁时的清理工作,例如释放内存、关闭文件等。

下面是一个简单的例子,展示了函数、虚函数、纯虚函数和析构函数的使用场景:

#include <iostream>
#include <string>

using namespace std;

// 基类
class Animal {
public:
    // 普通成员函数
    void eat(const string& food) {
        cout << "Animal eats " << food << endl;
    }
    // 虚函数
    virtual void move() {
        cout << "Animal moves" << endl;
    }
    // 纯虚函数
    virtual void sound() = 0;
    // 析构函数
    virtual ~Animal() {
        cout << "Animal is destroyed" << endl;
    }
};

// 派生类1
class Dog : public Animal {
public:
    // 重写虚函数
    void move() override {
        cout << "Dog runs" << endl;
    }
    // 实现纯虚函数
    void sound() override {
        cout << "Dog barks" << endl;
    }
    // 析构函数
    ~Dog() {
        cout << "Dog is destroyed" << endl;
    }
};

// 派生类2
class Cat : public Animal {
public:
    // 重写虚函数
    void move() override {
        cout << "Cat climbs" << endl;
    }
    // 实现纯虚函数
    void sound() override {
        cout << "Cat meows" << endl;
    }
    // 析构函数
    ~Cat() {
        cout << "Cat is destroyed" << endl;
    }
};

int main() {
    Animal* ptr_animal = new Dog(); // 派生类对象可以赋给基类指针
    ptr_animal->eat("meat"); // 调用基类函数
    ptr_animal->move(); // 调用派生类函数
    ptr_animal->sound(); // 调用派生类函数
    delete ptr_animal; // 调用派生类析构函数

    ptr_animal = new Cat(); // 派生类对象可以赋给基类指针
    ptr_animal->eat("fish"); // 调用基类函数
    ptr_animal->move(); // 调用派生类函数
    ptr_animal->sound(); // 调用派生类函数
    delete ptr_animal; // 调用派生类析构函数

    return 0;
}

在这个例子中,Animal是一个基类,DogCat是两个派生类,都继承了AnimalAnimal中有一个普通成员函数eat(),一个虚函数move(),一个纯虚函数sound(),以及一个析构函数~Animal()DogCat都重写了move()sound(),并实现了各自的析构函数。在main()函数中,我们创建了一个Dog对象和一个Cat对象,并通过基类指针调用它们的成员函数,包括eat()move()sound()。注意,派生类对象可以赋给基类指针,这样就可以通过基类指针调用

c++ 初始化列表

C++的初始化列表是用于初始化成员变量的一种方式,可以在构造函数中通过初始化列表来对类的成员变量进行初始化。

下面是一个简单的例子:

c++

class Person {
public:
  Person(int age, std::string name) : age_(age), name_(name) {}
private:
  int age_;
  std::string name_;
};

在这个例子中,构造函数使用初始化列表来对age_name_成员变量进行初始化。注意,成员变量的初始化顺序与它们在类中的声明顺序一致,而不是在初始化列表中的顺序。

初始化列表的使用场景包括:

  1. 初始化成员变量,尤其是const和引用类型的成员变量,因为它们在构造函数中无法被赋值。
  2. 在构造函数中调用基类的构造函数。
  3. 初始化非静态成员对象,如std::mutex,这些对象的构造函数可能会抛出异常,所以最好在初始化列表中初始化它们。

需要注意的是,如果成员变量没有被初始化列表初始化,它们将使用默认构造函数进行初始化,如果没有默认构造函数,会导致编译错误。同时,初始化列表不能对static成员变量进行初始化。

C++指针类型有哪些

C++中指针类型有以下几种:

  1. 普通指针:指向数据类型的指针,使用*解引用操作符来访问指向的值。

    c++

    int value = 42;
    int *p = &value;
    *p = 24;
    std::cout << value << std::endl; // 输出24
    
  2. 常量指针:指向常量数据类型的指针,无法通过指针修改指向的值。

    c++

    const int value = 42;
    const int *p = &value;
    // *p = 24; // 编译错误
    
  3. 指向常量的指针:指向可变数据类型的常量指针,无法通过指针修改指向的值。

    c++

    int value = 42;
    int *const p = &value;
    *p = 24;
    // p = nullptr; // 编译错误
    
  4. 常量指针常量:指向常量数据类型的常量指针,无法通过指针修改指向的值和指针本身的值。

    c++

    const int value = 42;
    const int *const p = &value;
    // *p = 24; // 编译错误
    // p = nullptr; // 编译错误
    

指针类型的使用场景包括:

  1. 传递函数参数时,可以使用指针来避免拷贝大型数据结构,提高效率。
  2. 动态内存分配时,使用指针来管理内存,可以灵活地分配和释放内存空间。
  3. 在函数中返回多个值时,可以通过指针来返回多个值。
  4. 在操作数组和字符串时,使用指针可以遍历和修改数组和字符串的元素。
  5. 在实现数据结构和算法时,使用指针可以方便地访问和修改数据结构的内部元素。

c++ 中利用 mutex、condition_variable、 lock_guard

在C++中,mutexcondition_variablelock_guard是用于多线程编程的重要工具,主要用于实现线程同步和互斥。下面分别介绍一下它们的具体用法:

  1. mutex:互斥锁,用于保护共享资源的访问,防止多个线程同时访问同一个资源而导致数据出现错误。使用std::mutex定义互斥锁,然后使用lock()unlock()方法进行加锁和解锁。示例代码如下:
#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx; // 定义互斥锁

void print(int i)
{
    mtx.lock(); // 加锁
    std::cout << "Thread " << i << " is running" << std::endl;
    mtx.unlock(); // 解锁
}

int main()
{
    std::thread t1(print, 1);
    std::thread t2(print, 2);
    t1.join();
    t2.join();
    return 0;
}
  1. condition_variable:条件变量,用于线程之间的通信和同步。可以通过wait()方法等待另一个线程发出通知,也可以通过notify_one()notify_all()方法通知其他线程继续执行。使用std::condition_variable定义条件变量。示例代码如下:
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>

std::mutex mtx;
std::condition_variable cv;
bool ready = false;

void worker()
{
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock, []{ return ready; });
    std::cout << "Worker is running" << std::endl;
}

void master()
{
    std::this_thread::sleep_for(std::chrono::seconds(2));
    {
        std::lock_guard<std::mutex> lock(mtx);
        ready = true;
    }
    cv.notify_one();
}

int main()
{
    std::thread t1(worker);
    std::thread t2(master);
    t1.join();
    t2.join();
    return 0;
}
  1. lock_guard:锁保护器,用于在作用域内自动锁定互斥锁,并在作用域结束时自动解锁。使用std::lock_guard定义锁保护器。示例代码如下:

c++

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;

void print(int i)
{
    std::lock_guard<std::mutex> lock(mtx); // 加锁
    std::cout << "Thread " << i << " is running" << std::endl;
} // 解锁

int main()
{
    std::thread t1(print, 1);
    std::thread t2(print, 2);
    t1.join();
    t2.join();
    return 0;
}

std::lock_guardstd::mutex lock(mtx); 可以替代 mtx.lock(); // 加锁 mtx.unlock();

可以使用 std::lock_guard<std::mutex> 来替代 mtx.lock();mtx.unlock(); 操作,其会在构造函数中自动加锁,析构函数中自动解锁,确保在出现异常等情况时也能正确释放锁资源,更加安全和方便。例如:

std::mutex mtx;

// 使用 std::lock_guard 来加锁和解锁互斥量 mtx
void foo() {
    std::lock_guard<std::mutex> lock(mtx);
    // 临界区代码
}

foo 函数的执行期间,互斥量 mtx 将会被自动加锁,函数执行完后会自动解锁。这种方式比较简单,同时也避免了忘记解锁的问题。

指针常量 和 常量指针的区别

指针常量和常量指针都是指针类型的修饰符,它们的区别在于指针所指向的内容(也就是指针指向的内存地址中的值)是否可以被修改。

指针常量是指针本身是一个常量,也就是指针本身的值不能被修改,但是可以通过指针修改指向的内容。例如:

c++

int a = 10;
int b = 20;
int *const p = &a;  // 定义一个指针常量 p,指向变量 a,p 的值不能被修改
*p = 30;  // 可以修改指针 p 所指向的变量 a 的值
// p = &b;  // 编译错误,无法修改指针常量 p 的值

常量指针是指指针所指向的内容是一个常量,也就是指针所指向的变量的值不能被修改,但是指针本身的值可以被修改。例如:

int a = 10;
int b = 20;
const int *p = &a;  // 定义一个常量指针 p,指向变量 a,不能通过 p 修改 a 的值
// *p = 30;  // 编译错误,无法通过常量指针 p 修改 a 的值
p = &b;  // 可以修改指针 p 所指向的地址,指向变量 b

需要注意的是,指针常量和常量指针都可以用 const 关键字进行修饰,从而增加代码的可读性和安全性。例如,指向常量的指针可以这样定义:

const int *p = &a;  // 指向常量的指针,不能通过 p 修改 a 的值

常量指针也可以这样定义:

int a = 10;
int b = 20;
int *const p = &a;  // 指针常量,p 的值不能被修改
const int *const q = &a;  // 常量指针常量,既不能通过 q 修改 a 的值,也不能修改 q 的值

c++中指针拷贝和对象拷贝

在C++中,指针拷贝和对象拷贝有以下不同:

  1. 指针拷贝:将一个指针变量的值(即指针所指的地址)赋给另一个指针变量,此时两个指针变量指向的是同一个地址,它们共享同一块内存空间。例如:
int a = 10;
int *p1 = &a;
int *p2 = p1; // 指针拷贝,p2和p1指向同一个地址
  1. 对象拷贝:将一个对象的值复制给另一个对象,此时两个对象独立存在,它们分别占据不同的内存空间。对象拷贝可以通过拷贝构造函数或者赋值运算符来实现。例如:
class Person {
public:
    int age;
    string name;
    // 拷贝构造函数
    Person(const Person &p) {
        age = p.age;
        name = p.name;
    }
    // 赋值运算符
    Person &operator=(const Person &p) {
        if (this != &p) {
            age = p.age;
            name = p.name;
        }
        return *this;
    }
};

Person p1;
p1.age = 20;
p1.name = "Tom";

Person p2 = p1; // 对象拷贝,p2和p1是两个独立的对象

需要注意的是,对象拷贝可能存在浅拷贝和深拷贝之分,如果对象包含指针类型的成员变量,浅拷贝会导致两个对象共享同一块内存空间,而深拷贝则会为每个对象分配独立的内存空间,避免共享带来的问题。

c++中 STL

STL(Standard Template Library)是 C++ 的一个标准库,提供了一组通用的模板类和函数,包括容器、算法和迭代器等。STL 的设计思想是基于泛型编程,即提供一些通用的模板类和函数,能够适用于各种数据类型。

STL 提供了许多容器类,包括 vector、list、map、set 等,这些容器类可以存储不同类型的数据,并提供了各种操作这些数据的方法。STL 还提供了许多算法,如排序、查找、合并等,这些算法可以用于不同类型的容器,提高了代码的重用性和可读性。STL 中还有迭代器,它提供了一种访问容器中元素的通用方式,可以在容器内部遍历元素。

使用 STL 可以提高 C++ 代码的效率、可读性和可维护性,同时还能够减少开发时间和代码量。由于 STL 是标准库的一部分,因此可以保证在不同的平台上都可以使用,并且具有较好的可移植性。

常见使用STL的例子

以下是一些常见的使用 STL 的例子:

  1. 使用 vector 存储一组数据,并通过迭代器遍历:
#include <iostream>
#include <vector>

int main() {
  std::vector<int> nums = {1, 2, 3, 4, 5};
  for (auto it = nums.begin(); it != nums.end(); ++it) {
    std::cout << *it << " ";
  }
  std::cout << std::endl;
  return 0;
}
  1. 使用 set 存储一组唯一的数据:
#include <iostream>
#include <set>

int main() {
  std::set<int> nums = {1, 2, 3, 4, 5, 5, 4, 3, 2, 1};
  for (auto it = nums.begin(); it != nums.end(); ++it) {
    std::cout << *it << " ";
  }
  std::cout << std::endl;
  return 0;
}
  1. 使用 map 存储一组键值对,并通过迭代器遍历:
#include <iostream>
#include <map>

int main() {
  std::map<std::string, int> nums = {{"one", 1}, {"two", 2}, {"three", 3}};
  for (auto it = nums.begin(); it != nums.end(); ++it) {
    std::cout << it->first << ": " << it->second << std::endl;
  }
  return 0;
}
  1. 使用算法库中的 sort 函数对 vector 进行排序:
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
  std::vector<int> nums = {5, 4, 3, 2, 1};
  std::sort(nums.begin(), nums.end());
  for (auto it = nums.begin(); it != nums.end(); ++it) {
    std::cout << *it << " ";
  }
  std::cout << std::endl;
  return 0;
}

c++中文件io

在 C++ 中,文件 I/O 是通过流 (stream) 来完成的。常见的文件 I/O 处理方式包括:

  1. 打开文件:使用标准库中的 std::ifstreamstd::ofstream 类来打开文件,分别用于读取和写入文件。文件打开后,可以进行读取和写入操作。

  2. 写入数据:使用流插入运算符 << 将数据写入文件中,如 fout << "Hello World!"

  3. 读取数据:使用流提取运算符 >> 从文件中读取数据,如 fin >> num

  4. 关闭文件:使用 close() 方法关闭文件,释放相关资源。

例如,下面的示例展示了如何使用 C++ 中的文件 I/O 处理方式读取文件中的整数并计算它们的和:

#include <iostream>
#include <fstream>
#include <string>

int main() {
    std::ifstream fin("data.txt"); // 打开文件
    int num, sum = 0;
    while (fin >> num) { // 读取文件中的整数并计算它们的和
        sum += num;
    }
    fin.close(); // 关闭文件
    std::cout << "The sum of integers in data.txt is: " << sum << std::endl;
    return 0;
}

上述代码中,使用 std::ifstream 类打开文件 data.txt,并使用 >> 运算符从文件中逐个读取整数并计算它们的和。最后使用 close() 方法关闭文件。

需要注意的是,在进行文件 I/O 操作时,需要确保文件的存在和正确性,并且需要避免文件打开失败、读写错误等异常情况的发生。

posted @ 2023-03-27 23:57  懒懒初阳  阅读(66)  评论(0编辑  收藏  举报