C++相关知识总结(持续更新)

命名空间在C++中的作用      

  1. 避免命名冲突: 命名空间可以帮助在大型项目中避免命名冲突,通过将相关的变量、函数、类、对象等组织在同一个命名空间内,可以避免不同部分的代码中出现同名的名称冲突。

  2. 逻辑组织: 命名空间可以对相关的代码进行逻辑上的分组和组织,提高代码的可读性和维护性。通过命名空间,可以清晰地了解哪些代码属于某一个模块、库或者功能单元。

  3. 作用域控制: 命名空间提供了作用域控制的功能,可以帮助限制变量、函数、类等的作用域,从而减小名称的影响范围。

  4. 隐藏实现细节: 通过命名空间可以将某些实现细节隐藏在命名空间内部,只暴露需要对外公开的接口,从而提高了代码的封装性和安全性。

总之,命名空间在 C++ 中是一种重要的组织和控制结构,它可以帮助避免命名冲突、组织代码、控制作用域,并提高代码的可维护性和封装性。虽然类可以区分不同的方法,但命名空间的作用并不仅仅是为了区分函数或方法,而是提供了更加广泛的命名和作用域控制功能。

不同的命名空间、不同的类中所定义的成员重名的问题

        

#include <iostream>
using namespace std;

namespace FirstNamespace {
    void display() {
        cout << "Display function in FirstNamespace" << endl;
    }
}

namespace SecondNamespace {
    void display() {
        cout << "Display function in SecondNamespace" << endl;
    }
}

class MyClass1 {
public:
    void display() {
        cout << "Display function in MyClass1" << endl;
    }
};

class MyClass2 {
public:
    void display() {
        cout << "Display function in MyClass2" << endl;
    }
};

int main() {
    FirstNamespace::display(); // 调用 FirstNamespace 中的 display 函数
    SecondNamespace::display(); // 调用 SecondNamespace 中的 display 函数

    MyClass1 obj1;
    obj1.display(); // 调用 MyClass1 中的 display 函数

    MyClass2 obj2;
    obj2.display(); // 调用 MyClass2 中的 display 函数

    return 0;
}

在上面的示例中,我们演示了在不同命名空间和类中定义的同名函数的调用,它们不会相互冲突,因为它们存在于不同的作用域内。

计算程序运行时两处的时间间隔

 

                auto start = std::chrono::steady_clock::now();
                //此处是两个时间点中间运行的代码段
                auto end = std::chrono::steady_clock::now();
                auto diff = end - start;
                std::chrono::milliseconds diff_ms = std::chrono::duration_cast<std::chrono::milliseconds>(diff);
                //原本获取的时间都是纳秒级,此处转换成毫秒级

等待毫秒级的时间

std::cout << "开始等待..." << std::endl;

// 等待500毫秒
std::this_thread::sleep_for(std::chrono::milliseconds(500));
                
std::cout << "等待结束" << std::endl;

程序以错误状态退出

        1. 在main主程序中,执行到了return 1,则表示程序以某种错误状态退出

        2. 在main其中函数内部执行时,可以通过执行exit(1)来使程序以异常状态退出

socket通信中的阻塞与非阻塞

        

        在Socket通信中,阻塞是指程序在执行输入/输出操作时被暂时挂起,直到特定的条件被满足为止。对于套接字(socket),阻塞与非阻塞是两种常见的工作模式。

        在阻塞模式下,当程序执行套接字的读取或写入操作时,如果数据并未准备就绪或无法立即发送,程序会暂时挂起(即被阻塞),直到满足特定条件为止。例如,当调用recv函数来接收数据时,在阻塞模式下,如果没有数据可读,程序将暂时挂起直到有数据到达。类似地,当调用send函数发送数据时,如果发送缓冲区已满,程序将暂时挂起直到有足够的空间可以发送数据。

        相反,在非阻塞模式下,当程序执行套接字的读取或写入操作时,如果无法立即执行相应的操作,函数会立刻返回,而不会使程序暂时挂起。通过检查返回值,程序可以确定操作是否可以立即执行,或者需要稍后再次尝试。

        要注意的是,默认情况下套接字通常是阻塞的,如果需要使用非阻塞模式,需要使用相应的系统调用来设置套接字为非阻塞模式。选择使用阻塞或非阻塞模式取决于你的应用程序设计和需求,每种模式都有各自的优缺点。

select和setsockopt对于socket超时设置的区别

        

        使用 select 和 setsockopt 对套接字进行超时设置有一些区别。

  1. select 函数:

    • select 函数允许程序等待多个套接字或文件描述符中的任何一个变得可读、可写或发生异常,并设置一个超时时间。
    • 它的主要作用是进行I/O多路复用,并且可以在超时后从阻塞态中返回,以处理其他任务或进行超时处理。
    • 适用于需要同时监视多个套接字或文件描述符的情况,可用于非阻塞的I/O操作。
  2. setsockopt 函数:

    • setsockopt 函数用于设置套接字选项,其中包括连接超时、接收超时和发送超时等选项。
    • 最常用的是 SO_RCVTIMEO 和 SO_SNDTIMEO 选项,分别用于设置接收和发送超时。
    • 这些选项通常用于在进行阻塞式I/O操作时设置超时时间,以确保在超时条件下从阻塞态中返回。

总的来说,select 主要用于等待多个套接字的I/O事件并设置超时,而 setsockopt 则用于针对单个套接字进行超时设置,以便于限制其在读取或写入时的阻塞时间。

线程与进程的区别

        进程是资源分配的最小单位,线程是程序执行的最小单位

  1. 定义:

    • 进程:是程序执行时的实例,包括程序的代码、数据以及一些系统资源。
    • 线程:是进程中的执行单元,一个进程可以包含多个线程,它们共享进程的资源。
  2. 资源分配:

    • 进程:每个进程有独立的内存空间、文件描述符等资源,各个进程间相互独立。
    • 线程:线程共享进程的内存空间、文件句柄等资源,因此线程间通信比进程间通信更加方便。
  3. 调度:

    • 进程:是系统进行资源分配和调度的一个独立单位,不同进程间切换需要较大的开销。
    • 线程:是系统进行调度和执行的基本单位,线程间的切换开销较小。
  4. 创建和销毁:

    • 进程:创建和销毁进程的开销相对较大,通常需要比较复杂的操作。
    • 线程:创建和销毁线程的开销相对较小,可以更加轻便地实现。
  5. 并发:

    • 进程:进程是独立并发执行的,各个进程间相互独立。
    • 线程:线程是在同一进程中并发执行的,线程间可以共享进程的资源。

总结

  • 进程是程序执行时的实例,具有独立的内存空间和资源,各个进程间相互独立。
  • 线程是进程中的执行单元,共享进程的资源,可以更轻量地实现并发执行和共享资源。

在实际开发中,通常会根据任务的不同使用进程和线程来实现并发和并行执行,以充分利用计算资源。

注:main函数是程序的主线程

匿名函数

完整形式:

        完整形式的Lambda函数包括以下几个部分:

  1. 捕获列表(capture list):指定Lambda函数能够捕获的外部变量。
  2. 形参列表:Lambda函数的参数列表。
  3. 可选的返回类型说明:指定Lambda函数的返回类型。
  4. Lambda函数体:包含Lambda函数实际执行的代码。

以下是Lambda函数的完整形式示例:

[capture list](parameters) -> return_type {
    // Lambda函数体
    // 可执行多条语句
}

省略形式 :

        省略形式的Lambda函数通常省略了捕获列表和返回类型说明,只包含形参列表和Lambda函数体。省略形式的Lambda函数会自动进行捕获变量的推导,并根据返回语句自动推导返回类型。

以下是Lambda函数的省略形式示例:

[](parameters) {
    // Lambda函数体
    // 可执行多条语句
}

        在实际使用中,可以根据情况选择适合的Lambda函数形式。省略形式一般用于简单的场景,而完整形式则更加灵活,能够明确指定Lambda函数的捕获列表和返回类型。

为什么要使用匿名函数

  1. 方便传递:可以方便地将匿名函数作为参数传递给标准算法、自定义算法、回调函数等。这使得代码更加简洁,避免定义独立的函数或者函数对象。

  2. 简化代码:对于一次性使用或者只在一个小范围内有效的函数逻辑,使用匿名函数可以减少不必要的命名和定义,使得代码更加紧凑和易读。

  3. 减少传统函数指针的使用:传统函数指针的使用可能会引入语法冗长或者隐晦,而匿名函数可以更加直观和自含义,提高了代码的可读性。

  4. 便于函数式编程:匿名函数是函数式编程的基本构造单元,它们允许开发人员更直接地表达函数接口、处理集合、映射和过滤数据等。

  5. 避免数据耦合:匿名函数可以通过捕获外部变量的方式解耦代码,而传统函数可能需要额外的参数来传递外部状态。

创建线程

1. 一般情况,创建线程,线程中执行函数

在这个示例中,printNumbers函数将在一个新的线程中执行,而主线程将继续执行std::cout << "Main thread continues..."语句。同时,t.join()语句确保主线程等待子线程执行完毕后再结束。

#include <iostream>
#include <thread>

void printNumbers() {
    for (int i = 1; i <= 5; ++i) {
        std::cout << i << std::endl;
    }
}

int main() {
    // 创建一个新的线程并开始执行printNumbers函数
    std::thread t(printNumbers);

    // 主线程继续执行其他任务
    std::cout << "Main thread continues..." << std::endl;

    // 等待子线程执行完毕
    t.join();

    return 0;
}

2. 想在C++的std::thread中执行多条语句

可以传递一个lambda函数(匿名函数)作为线程的执行体 

#include <iostream>
#include <thread>

int main() {
    std::thread t([] {
        std::cout << "Thread is executing multiple statements" << std::endl;
        std::cout << "Statement 1" << std::endl;
        std::cout << "Statement 2" << std::endl;
    });

    t.join();

    return 0;
}

线程的join与detach

        

在C++的std::thread中,join()detach()是用来管理线程的两种重要方法。

1. join()

join() 方法用于等待线程结束,直到线程执行完毕,主线程才会继续执行。这也意味着 join() 方法会阻塞调用它的线程,直到被调用的线程执行完成。

下面是一个简单的示例,演示了使用 join() 方法等待线程执行完毕:

#include <iostream>
#include <thread>

void threadFunction() {
    // 一些耗时的操作
    std::this_thread::sleep_for(std::chrono::seconds(3));
    std::cout << "Thread function finished" << std::endl;
}

int main() {
    std::thread t(threadFunction);
    t.join();  // 等待线程执行完毕

    std::cout << "Main function finished" << std::endl;

    return 0;
}

在这个示例中,join() 方法会等待 threadFunction() 执行完毕,然后主线程才会继续执行。

2. detach()

detach() 方法用于将线程和调用线程(通常是主线程)分离,使得线程独立执行,主线程不再管理这个线程。一旦线程被分离,它将独立运行,直到线程执行结束。

下面是一个示例,演示了使用 detach() 方法将线程分离:

#include <iostream>
#include <thread>

void threadFunction() {
    // 一些耗时的操作
    std::this_thread::sleep_for(std::chrono::seconds(3));
    std::cout << "Thread function finished" << std::endl;
}

int main() {
    std::thread t(threadFunction);
    t.detach();  // 分离线程

    std::cout << "Main function finished" << std::endl;

    return 0;
}

在这个示例中,主线程使用 detach() 方法将线程分离,然后主线程继续执行,而线程将独立执行直到完成其任务。

总的来说,join() 方法用于等待线程执行完毕,而 detach() 方法用于将线程分离,使其独立执行。在使用这两种方法时,需要根据具体情况来决定如何管理线程的生命周期。

创建进程

        在C++中,可以使用fork()系统调用在Unix-like系统上创建进程。fork()系统调用会创建一个新的进程,称为子进程,该子进程是父进程的副本。以下是一个简单的示例,演示了使用fork()来创建子进程:

#include <iostream>
#include <unistd.h>

int main() {
    pid_t pid = fork();

    if (pid == -1) {
        // 创建进程失败
        std::cerr << "Failed to fork" << std::endl;
    } else if (pid == 0) {
        // 子进程
        std::cout << "This is the child process" << std::endl;
    } else {
        // 父进程
        std::cout << "This is the parent process, child pid = " << pid << std::endl;
    }

    return 0;
}

在这个示例中,调用fork()后,fork()会返回两次。在子进程中,fork()的返回值为0;而在父进程中,fork()的返回值为子进程的进程ID。通过检查返回值,就可以区分子进程和父进程,并执行不同的逻辑。

调用fork()创建进程后,之后的代码父进程和子进程都会执行,父进程和子进程是并发执行的,它们的执行顺序取决于操作系统的调度策略。

  • 9
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值