命名空间在C++中的作用
-
避免命名冲突: 命名空间可以帮助在大型项目中避免命名冲突,通过将相关的变量、函数、类、对象等组织在同一个命名空间内,可以避免不同部分的代码中出现同名的名称冲突。
-
逻辑组织: 命名空间可以对相关的代码进行逻辑上的分组和组织,提高代码的可读性和维护性。通过命名空间,可以清晰地了解哪些代码属于某一个模块、库或者功能单元。
-
作用域控制: 命名空间提供了作用域控制的功能,可以帮助限制变量、函数、类等的作用域,从而减小名称的影响范围。
-
隐藏实现细节: 通过命名空间可以将某些实现细节隐藏在命名空间内部,只暴露需要对外公开的接口,从而提高了代码的封装性和安全性。
总之,命名空间在 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
对套接字进行超时设置有一些区别。
-
select
函数:select
函数允许程序等待多个套接字或文件描述符中的任何一个变得可读、可写或发生异常,并设置一个超时时间。- 它的主要作用是进行I/O多路复用,并且可以在超时后从阻塞态中返回,以处理其他任务或进行超时处理。
- 适用于需要同时监视多个套接字或文件描述符的情况,可用于非阻塞的I/O操作。
-
setsockopt
函数:setsockopt
函数用于设置套接字选项,其中包括连接超时、接收超时和发送超时等选项。- 最常用的是
SO_RCVTIMEO
和SO_SNDTIMEO
选项,分别用于设置接收和发送超时。 - 这些选项通常用于在进行阻塞式I/O操作时设置超时时间,以确保在超时条件下从阻塞态中返回。
总的来说,select
主要用于等待多个套接字的I/O事件并设置超时,而 setsockopt
则用于针对单个套接字进行超时设置,以便于限制其在读取或写入时的阻塞时间。
线程与进程的区别
进程是资源分配的最小单位,线程是程序执行的最小单位
-
定义:
- 进程:是程序执行时的实例,包括程序的代码、数据以及一些系统资源。
- 线程:是进程中的执行单元,一个进程可以包含多个线程,它们共享进程的资源。
-
资源分配:
- 进程:每个进程有独立的内存空间、文件描述符等资源,各个进程间相互独立。
- 线程:线程共享进程的内存空间、文件句柄等资源,因此线程间通信比进程间通信更加方便。
-
调度:
- 进程:是系统进行资源分配和调度的一个独立单位,不同进程间切换需要较大的开销。
- 线程:是系统进行调度和执行的基本单位,线程间的切换开销较小。
-
创建和销毁:
- 进程:创建和销毁进程的开销相对较大,通常需要比较复杂的操作。
- 线程:创建和销毁线程的开销相对较小,可以更加轻便地实现。
-
并发:
- 进程:进程是独立并发执行的,各个进程间相互独立。
- 线程:线程是在同一进程中并发执行的,线程间可以共享进程的资源。
总结
- 进程是程序执行时的实例,具有独立的内存空间和资源,各个进程间相互独立。
- 线程是进程中的执行单元,共享进程的资源,可以更轻量地实现并发执行和共享资源。
在实际开发中,通常会根据任务的不同使用进程和线程来实现并发和并行执行,以充分利用计算资源。
注:main函数是程序的主线程
匿名函数
完整形式:
完整形式的Lambda函数包括以下几个部分:
- 捕获列表(capture list):指定Lambda函数能够捕获的外部变量。
- 形参列表:Lambda函数的参数列表。
- 可选的返回类型说明:指定Lambda函数的返回类型。
- Lambda函数体:包含Lambda函数实际执行的代码。
以下是Lambda函数的完整形式示例:
[capture list](parameters) -> return_type {
// Lambda函数体
// 可执行多条语句
}
省略形式 :
省略形式的Lambda函数通常省略了捕获列表和返回类型说明,只包含形参列表和Lambda函数体。省略形式的Lambda函数会自动进行捕获变量的推导,并根据返回语句自动推导返回类型。
以下是Lambda函数的省略形式示例:
[](parameters) {
// Lambda函数体
// 可执行多条语句
}
在实际使用中,可以根据情况选择适合的Lambda函数形式。省略形式一般用于简单的场景,而完整形式则更加灵活,能够明确指定Lambda函数的捕获列表和返回类型。
为什么要使用匿名函数
-
方便传递:可以方便地将匿名函数作为参数传递给标准算法、自定义算法、回调函数等。这使得代码更加简洁,避免定义独立的函数或者函数对象。
-
简化代码:对于一次性使用或者只在一个小范围内有效的函数逻辑,使用匿名函数可以减少不必要的命名和定义,使得代码更加紧凑和易读。
-
减少传统函数指针的使用:传统函数指针的使用可能会引入语法冗长或者隐晦,而匿名函数可以更加直观和自含义,提高了代码的可读性。
-
便于函数式编程:匿名函数是函数式编程的基本构造单元,它们允许开发人员更直接地表达函数接口、处理集合、映射和过滤数据等。
-
避免数据耦合:匿名函数可以通过捕获外部变量的方式解耦代码,而传统函数可能需要额外的参数来传递外部状态。
创建线程
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()创建进程后,之后的代码父进程和子进程都会执行,父进程和子进程是并发执行的,它们的执行顺序取决于操作系统的调度策略。