老式C++(C++98)的实现
#include <iostream>
#include <queue>
#include <pthread.h>
#include <unistd.h>
std::queue<int> buffer; // 使用队列作为缓冲区
const unsigned int BUFFER_SIZE = 10; // 缓冲区的最大容量
// 声明互斥锁和条件变量,用于线程同步
pthread_mutex_t mutex;
pthread_cond_t not_full;
pthread_cond_t not_empty;
// 生产者线程函数
void* producer(void* arg) {
int id = *((int*)arg); // 获取生产者ID
int count = 0; // 计数器,用于标记生产的产品编号
while (true) {
sleep(2); // 每2秒生产一个产品
pthread_mutex_lock(&mutex); // 锁定互斥锁以访问共享资源
while (buffer.size() == BUFFER_SIZE) { // 如果缓冲区满,等待
pthread_cond_wait(¬_full, &mutex);
}
buffer.push(++count); // 生产产品并加入缓冲区
std::cout << "Producer " << id << " produced item " << count << std::endl;
pthread_cond_signal(¬_empty); // 通知消费者可以消费
pthread_mutex_unlock(&mutex); // 解锁互斥锁
}
return NULL;
}
// 消费者线程函数
void* consumer(void* arg) {
int id = *((int*)arg); // 获取消费者ID
while (true) {
sleep(1.5); // 每1.5秒消费一个产品
pthread_mutex_lock(&mutex); // 锁定互斥锁以访问共享资源
while (buffer.empty()) { // 如果缓冲区为空,等待
pthread_cond_wait(¬_empty, &mutex);
}
int item = buffer.front(); // 获取缓冲区中的产品
buffer.pop(); // 从缓冲区移除产品
std::cout << "Consumer " << id << " consumed item " << item << std::endl;
pthread_cond_signal(¬_full); // 通知生产者可以生产
pthread_mutex_unlock(&mutex); // 解锁互斥锁
}
return NULL;
}
int main() {
// 初始化互斥锁和条件变量
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(¬_full, NULL);
pthread_cond_init(¬_empty, NULL);
pthread_t producers[3]; // 创建3个生产者线程
pthread_t consumers[2]; // 创建2个消费者线程
int producer_ids[3] = {1, 2, 3}; // 生产者ID
int consumer_ids[2] = {1, 2}; // 消费者ID
// 创建生产者线程
for (int i = 0; i < 3; ++i) {
pthread_create(&producers[i], NULL, producer, &producer_ids[i]);
}
// 创建消费者线程
for (int i = 0; i < 2; ++i) {
pthread_create(&consumers[i], NULL, consumer, &consumer_ids[i]);
}
// 等待所有生产者线程结束
for (int i = 0; i < 3; ++i) {
pthread_join(producers[i], NULL);
}
// 等待所有消费者线程结束
for (int i = 0; i < 2; ++i) {
pthread_join(consumers[i], NULL);
}
// 销毁互斥锁和条件变量
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(¬_full);
pthread_cond_destroy(¬_empty);
return 0;
}
现代C++(C++11)的实现
#include <iostream>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>
std::queue<int> buffer; // 使用队列作为缓冲区
const unsigned int BUFFER_SIZE = 10; // 缓冲区的最大容量
std::mutex mtx; // 互斥锁,用于保护共享资源
std::condition_variable not_full; // 条件变量,用于通知生产者缓冲区未满
std::condition_variable not_empty; // 条件变量,用于通知消费者缓冲区不为空
// 生产者函数
void producer(int id) {
int count = 0; // 计数器,用于标记生产的产品编号
while (true) {
std::this_thread::sleep_for(std::chrono::seconds(2)); // 每2秒生产一个产品
std::unique_lock<std::mutex> lock(mtx); // 自动管理互斥锁的加锁和解锁
// 如果缓冲区已满,等待not_full条件变量
not_full.wait(lock, [] { return buffer.size() < BUFFER_SIZE; });
buffer.push(++count); // 生产产品并加入缓冲区
std::cout << "Producer " << id << " produced item " << count << std::endl;
not_empty.notify_one(); // 通知消费者可以消费
}
}
// 消费者函数
void consumer(int id) {
while (true) {
std::this_thread::sleep_for(std::chrono::milliseconds(1500)); // 每1.5秒消费一个产品
std::unique_lock<std::mutex> lock(mtx); // 自动管理互斥锁的加锁和解锁
// 如果缓冲区为空,等待not_empty条件变量
not_empty.wait(lock, [] { return !buffer.empty(); });
int item = buffer.front(); // 获取缓冲区中的产品
buffer.pop(); // 从缓冲区移除产品
std::cout << "Consumer " << id << " consumed item " << item << std::endl;
not_full.notify_one(); // 通知生产者可以生产
}
}
int main() {
std::thread producers[3]; // 创建3个生产者线程
std::thread consumers[2]; // 创建2个消费者线程
// 启动生产者线程
for (int i = 0; i < 3; ++i) {
producers[i] = std::thread(producer, i + 1);
}
// 启动消费者线程
for (int i = 0; i < 2; ++i) {
consumers[i] = std::thread(consumer, i + 1);
}
// 等待所有生产者线程结束
for (int i = 0; i < 3; ++i) {
producers[i].join();
}
// 等待所有消费者线程结束
for (int i = 0; i < 2; ++i) {
consumers[i].join();
}
return 0;
}
代码说明
1. 生产者和消费者模型
- 生产者: 负责生成数据并将其放入共享缓冲区中。这里的生产者每2秒生产一个项目。
- 消费者: 负责从共享缓冲区中取出数据进行处理。这里的消费者每1.5秒消费一个项目。
2. 共享缓冲区
std::queue<int>: 使用队列作为共享缓冲区,用于存放生产者生成的产品。- 缓冲区大小限制: 设定了缓冲区的最大容量为10,以防止缓冲区溢出。
3. 同步机制
- 互斥锁 (
std::mutex): 确保生产者和消费者不会同时访问缓冲区,避免竞争条件。 - 条件变量 (
std::condition_variable): 用于生产者和消费者之间的通信。例如,当缓冲区已满时,生产者会等待;当缓冲区为空时,消费者会等待。
4. 现代C++的优势
- 更简洁的语法: 使用
std::thread、std::mutex和std::condition_variable可以更安全、更简洁地管理多线程和同步操作。 - RAII原则: 现代C++利用RAII(资源获取即初始化)管理资源,如锁的自动释放,减少了手动管理的错误风险。
这个模型展示了如何在老式C++和现代C++中实现生产者-消费者模型。现代C++的实现更简洁、安全,推荐在实际开发中使用。 单例模式(Singleton Pattern)是一种设计模式,它确保一个类只有一个实例,并提供一个全局访问点。单例模式在很多场景下使用,比如管理全局资源、配置管理等。
在C++中,实现单例模式有多种方式,下面分别介绍在老式C++(C++98)和现代C++(C++11及之后)中的实现方法。
老式C++(C++98)实现单例模式
#include <iostream>
class Singleton {
private:
static Singleton* instance; // 指向单例实例的静态指针
// 私有构造函数,防止外部实例化
Singleton() {
std::cout << "Singleton instance created" << std::endl;
}
// 禁止拷贝构造和赋值操作
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
public:
// 静态方法,提供全局访问点
static Singleton* getInstance() {
if (instance == NULL) {
instance = new Singleton(); // 延迟初始化(懒汉模式)
}
return instance;
}
void showMessage() {
std::cout << "Hello from Singleton" << std::endl;
}
};
// 初始化静态成员
Singleton* Singleton::instance = NULL;
int main() {
// 通过getInstance()方法获取单例实例
Singleton* s1 = Singleton::getInstance();
s1->showMessage();
// 尝试获取另一个实例,实际上是相同的实例
Singleton* s2 = Singleton::getInstance();
s2->showMessage();
return 0;
}
代码说明:
- 私有构造函数:防止类的外部实例化。
- 静态指针
instance:用于保存单例的唯一实例。 getInstance()方法:提供全局访问点,通过该方法可以获取单例实例。如果实例不存在,创建它(延迟初始化)。- 拷贝构造和赋值操作符私有化:防止通过拷贝构造或赋值创建新的实例。
现代C++(C++11)实现单例模式
在C++11中,单例模式可以更简洁和安全地实现。通过使用std::mutex保证线程安全,或者使用std::call_once来确保只初始化一次实例。同时,C++11引入的局部静态变量在多线程环境下的初始化是线程安全的。
#include <iostream>
#include <mutex>
class Singleton {
private:
// 私有构造函数,防止外部实例化
Singleton() {
std::cout << "Singleton instance created" << std::endl;
}
// 禁止拷贝构造和赋值操作
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
public:
// 静态方法,提供全局访问点
static Singleton& getInstance() {
static Singleton instance; // 局部静态变量,线程安全并且懒汉模式
return instance;
}
void showMessage() {
std::cout << "Hello from Singleton" << std::endl;
}
};
int main() {
// 通过getInstance()方法获取单例实例
Singleton& s1 = Singleton::getInstance();
s1.showMessage();
// 尝试获取另一个实例,实际上是相同的实例
Singleton& s2 = Singleton::getInstance();
s2.showMessage();
return 0;
}
代码说明:
- 局部静态变量:
static Singleton instance在getInstance()中定义。根据C++11标准,局部静态变量的初始化是线程安全的,且只会在第一次访问时创建。 - 删除拷贝构造和赋值操作符:防止通过拷贝或赋值创建新的实例。
- 引用返回:
getInstance()返回的是单例实例的引用,而不是指针,避免了对指针操作的潜在错误。
两种实现的比较
-
线程安全性:
- 老式C++:如果在多线程环境下使用,需要手动添加锁(
mutex)来确保线程安全。 - 现代C++:利用局部静态变量的特性,确保了在多线程环境下实例的创建是安全的,不需要额外的同步机制。
- 老式C++:如果在多线程环境下使用,需要手动添加锁(
-
代码简洁性:
- 现代C++:代码更简洁,因为C++11本身提供了更强的语言特性,如删除拷贝构造函数、局部静态变量的线程安全初始化等。
-
内存管理:
- 老式C++:需要手动管理单例对象的内存,如在程序结束时可能需要手动销毁对象(未展示)。
- 现代C++:通过局部静态变量,自动管理单例对象的生命周期,程序结束时会自动清理。
总结
在现代C++中,建议使用局部静态变量的方式实现单例模式,这种方式不仅简洁,而且在多线程环境下安全。如果需要在老式C++中实现单例模式,则需要更复杂的代码来确保线程安全,并且需要手动管理内存。
在C++中,可以使用模板来编写一个通用的函数,用于比较不同数据类型的大小。模板使得函数可以适用于任意的数据类型,而不需要为每种类型单独编写代码。
下面是一个简单的模板函数,用于比较两个值的大小,并返回较大的那个值:
模板函数实现
#include <iostream>
// 模板声明,typename T 表示 T 是一个类型参数
template <typename T>
T getMax(T a, T b) {
return (a > b) ? a : b;
}
int main() {
// 使用模板函数比较两个整数
int a = 10, b = 20;
std::cout << "Max of " << a << " and " << b << " is " << getMax(a, b) << std::endl;
// 使用模板函数比较两个浮点数
double x = 3.14, y = 2.71;
std::cout << "Max of " << x << " and " << y << " is " << getMax(x, y) << std::endl;
// 使用模板函数比较两个字符
char c1 = 'a', c2 = 'z';
std::cout << "Max of " << c1 << " and " << c2 << " is " << getMax(c1, c2) << std::endl;
return 0;
}
代码解释
-
模板声明:
template <typename T>template <typename T>是模板声明,表示T是一个类型参数。T可以是任意类型,如int、double、char等。- 你也可以使用
template <class T>,typename和class在这里是等价的。
-
getMax函数:T getMax(T a, T b) { return (a > b) ? a : b; }getMax是一个模板函数,接受两个类型为T的参数,返回类型为T。- 通过
(a > b) ? a : b语句来比较两个值的大小,并返回较大的值。
-
使用模板函数:
- 在
main函数中,我们演示了如何使用getMax模板函数来比较不同数据类型的值,如int、double和char。
- 在
运行结果
运行上述代码,输出结果将如下所示:
Max of 10 and 20 is 20
Max of 3.14 and 2.71 is 3.14
Max of a and z is z
适用场景
- 通用性:模板函数可以适用于任意类型的数据,而无需为每种数据类型单独编写函数,这大大提高了代码的通用性和复用性。
- 类型安全:C++模板在编译期进行类型检查,因此能够在使用时捕获类型错误。
总结
通过模板,C++能够实现强大且通用的功能,适用于多种数据类型。在需要处理不同数据类型但执行相同逻辑操作的情况下,使用模板是一个非常有效的方法。 多态(Polymorphism)是面向对象编程的一个重要特性,它允许我们通过一个接口(基类指针或引用)来操作不同类型的对象。C++通过虚函数实现运行时多态。以下是一个较为复杂的多态性例子,展示了如何使用继承和虚函数来实现多态行为。
问题场景
假设我们要开发一个绘图应用程序,需要处理不同形状的对象(如圆形、矩形和三角形)。每个形状都可以绘制,并且计算其面积。通过使用多态性,我们可以通过一个统一的接口来操作这些不同的形状对象,而无需了解它们的具体类型。
代码实现
#include <iostream>
#include <vector>
#include <cmath>
// 基类: Shape
class Shape {
public:
// 虚析构函数,确保子类的析构函数被调用
virtual ~Shape() {}
// 纯虚函数,要求子类必须实现
virtual void draw() const = 0; // 绘制形状
virtual double area() const = 0; // 计算形状的面积
};
// 派生类: Circle
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) {}
// 实现纯虚函数
void draw() const override {
std::cout << "Drawing a Circle with radius: " << radius << std::endl;
}
double area() const override {
return M_PI * radius * radius; // 圆的面积公式
}
};
// 派生类: Rectangle
class Rectangle : public Shape {
private:
double width, height;
public:
Rectangle(double w, double h) : width(w), height(h) {}
// 实现纯虚函数
void draw() const override {
std::cout << "Drawing a Rectangle with width: " << width << " and height: " << height << std::endl;
}
double area() const override {
return width * height; // 矩形的面积公式
}
};
// 派生类: Triangle
class Triangle : public Shape {
private:
double base, height;
public:
Triangle(double b, double h) : base(b), height(h) {}
// 实现纯虚函数
void draw() const override {
std::cout << "Drawing a Triangle with base: " << base << " and height: " << height << std::endl;
}
double area() const override {
return 0.5 * base * height; // 三角形的面积公式
}
};
// 客户端代码
int main() {
// 创建形状对象的指针,并将它们存储在一个容器中
std::vector<Shape*> shapes;
shapes.push_back(new Circle(5.0));
shapes.push_back(new Rectangle(4.0, 6.0));
shapes.push_back(new Triangle(4.0, 3.0));
// 遍历形状容器,并通过基类指针调用多态方法
for (Shape* shape : shapes) {
shape->draw(); // 多态调用,绘制不同形状
std::cout << "Area: " << shape->area() << std::endl; // 多态调用,计算并输出面积
}
// 清理内存
for (Shape* shape : shapes) {
delete shape; // 删除形状对象,调用适当的析构函数
}
return 0;
}
代码解释
-
基类
Shape:- 这是一个抽象基类,定义了两个纯虚函数
draw()和area(),分别用于绘制形状和计算面积。 - 它还包含一个虚析构函数,以确保派生类的析构函数能够正确调用,避免内存泄漏。
- 这是一个抽象基类,定义了两个纯虚函数
-
派生类
Circle,Rectangle,Triangle:- 每个派生类都继承自
Shape,并实现了draw()和area()方法。 - 例如,
Circle类实现了draw()方法来绘制圆,并实现area()方法来计算圆的面积。
- 每个派生类都继承自
-
客户端代码:
- 使用
std::vector<Shape*>存储不同的形状对象。 - 通过
Shape*指针调用多态方法draw()和area(),即使这些方法在运行时会调用不同的派生类实现。 - 最后,通过
delete清理内存,确保不会发生内存泄漏。
- 使用
输出结果
程序运行时,将输出如下内容:
Drawing a Circle with radius: 5
Area: 78.5398
Drawing a Rectangle with width: 4 and height: 6
Area: 24
Drawing a Triangle with base: 4 and height: 3
Area: 6
多态的优点
- 扩展性:如果以后需要添加新的形状类型(例如椭圆或多边形),只需继承
Shape并实现draw()和area(),无需修改现有代码。 - 代码复用:通过使用基类指针或引用操作对象,可以编写更通用的代码,减少重复。
- 接口统一:所有的形状类都遵循
Shape接口,这使得代码易于理解和维护。
总结
这个例子展示了如何使用继承和虚函数来实现多态。通过多态性,程序可以在不考虑对象具体类型的情况下,调用对象的正确方法。这使得代码更加灵活、易于扩展,并且符合面向对象编程的设计原则。
在Java中,ThreadLocal是一个非常有用的工具,用于为每个线程提供独立的变量副本,这样每个线程都可以独立修改自己的副本,而不会影响其他线程。在C++中,虽然没有直接等同于Java ThreadLocal的类,但我们可以使用线程局部存储(Thread-Local Storage, TLS)来实现类似的功能。
C++11引入了关键字thread_local,用于声明线程局部变量。每个线程都会有自己的变量副本,这些副本在不同线程之间互不干扰。
示例:在C++中实现类似Java ThreadLocal的功能
以下是一个示例,展示如何在C++中使用thread_local来实现类似Java ThreadLocal的功能:
#include <iostream>
#include <thread>
#include <mutex>
// 使用 thread_local 关键字声明线程局部变量
thread_local int threadLocalVariable = 0;
// 全局互斥锁,用于控制输出流的并发访问
std::mutex cout_mutex;
void threadFunction(int id) {
// 每个线程都可以独立地修改 threadLocalVariable 的值
threadLocalVariable = id;
// 使用互斥锁确保输出不会被多个线程混杂
{
std::lock_guard<std::mutex> lock(cout_mutex);
std::cout << "Thread ID: " << id << " has threadLocalVariable value: " << threadLocalVariable << std::endl;
}
// 模拟一些复杂操作,导致 threadLocalVariable 的值发生变化
threadLocalVariable += 10;
{
std::lock_guard<std::mutex> lock(cout_mutex);
std::cout << "Thread ID: " << id << " after modification, threadLocalVariable value: " << threadLocalVariable << std::endl;
}
}
int main() {
std::thread t1(threadFunction, 1);
std::thread t2(threadFunction, 2);
std::thread t3(threadFunction, 3);
t1.join();
t2.join();
t3.join();
return 0;
}
代码解析
-
thread_local关键字:- 关键字
thread_local用于声明线程局部变量。threadLocalVariable在每个线程中都有一个独立的副本。 - 不同线程对
threadLocalVariable的修改互不影响。
- 关键字
-
threadFunction函数:- 每个线程运行此函数,首先将
threadLocalVariable的值设置为传入的线程ID。 - 然后,模拟对
threadLocalVariable进行某些操作(如加10),并再次输出该变量的值。
- 每个线程运行此函数,首先将
-
线程创建与执行:
- 在
main函数中,创建了三个线程,每个线程都有独立的threadLocalVariable变量。 - 通过
std::mutex和std::lock_guard来控制对std::cout的访问,防止多个线程同时输出导致混乱。
- 在
运行结果
运行此程序时,每个线程会独立输出其threadLocalVariable的值,结果类似如下:
Thread ID: 1 has threadLocalVariable value: 1
Thread ID: 2 has threadLocalVariable value: 2
Thread ID: 3 has threadLocalVariable value: 3
Thread ID: 1 after modification, threadLocalVariable value: 11
Thread ID: 2 after modification, threadLocalVariable value: 12
Thread ID: 3 after modification, threadLocalVariable value: 13
特性和应用场景
- 线程局部存储:
thread_local提供了一个简单的方式来为每个线程存储独立的变量,类似于Java中的ThreadLocal。 - 应用场景:
- 每个线程需要独立维护的状态,例如日志记录上下文、数据库连接、随机数生成器等。
- 在线程间共享一些共享数据时,避免数据竞争而需要隔离的情况下。
注意事项
- 生命周期:
thread_local变量的生命周期从线程开始到线程结束。每个线程都有自己独立的变量实例。 - 静态与动态:
thread_local变量可以是静态的,也可以是动态分配的,具体视使用场景而定。
总结
虽然C++没有像Java的ThreadLocal那样的专门类,但thread_local关键字提供了同样的功能。它使得每个线程都有自己独立的变量副本,适用于需要在多线程程序中保持线程独立状态的场景。
面向对象编程(OOP)是编程范式的一种,它基于“对象”的概念,通过类和对象来组织代码。面向对象编程有以下四个主要特性:
- 封装(Encapsulation):
- 封装是将对象的状态(属性)和行为(方法)封装在一起,并且隐藏对象的内部实现细节。外部只能通过对象公开的方法来访问和修改对象的状态。
- 继承(Inheritance):
- 继承是指一个类(子类)可以继承另一个类(父类)的属性和方法,从而实现代码重用和扩展。子类可以拥有父类的所有特性,并且可以扩展或修改这些特性。
- 多态(Polymorphism):
- 多态允许不同的类对同一方法做出不同的实现。当子类重写父类的方法时,可以通过基类指针或引用来调用这些方法,并自动选择正确的实现。
- 抽象(Abstraction):
- 抽象是指通过定义类的接口来隐藏实现细节,强调对象的功能,而不是如何实现这些功能。抽象类和接口用于定义一组功能,而具体的实现由派生类提供。
例子:模拟银行账户系统
这个例子展示了如何使用封装、继承、多态和抽象来模拟一个简单的银行账户系统。
#include <iostream>
#include <string>
// 抽象类:银行账户
class BankAccount {
protected:
std::string owner; // 封装:账户所有者
double balance; // 封装:账户余额
public:
// 构造函数,初始化账户所有者和余额
BankAccount(const std::string& owner, double balance) : owner(owner), balance(balance) {}
// 抽象方法:存款
virtual void deposit(double amount) = 0;
// 抽象方法:取款
virtual void withdraw(double amount) = 0;
// 普通方法:获取账户余额
double getBalance() const {
return balance;
}
// 虚析构函数,确保子类的析构函数被调用
virtual ~BankAccount() {}
};
// 派生类:储蓄账户
class SavingsAccount : public BankAccount {
private:
double interestRate; // 封装:利率
public:
// 构造函数,初始化账户所有者、余额和利率
SavingsAccount(const std::string& owner, double balance, double interestRate)
: BankAccount(owner, balance), interestRate(interestRate) {}
// 重写存款方法:添加利息
void deposit(double amount) override {
balance += amount + (amount * interestRate);
std::cout << "Deposited " << amount << " with interest, new balance: " << balance << std::endl;
}
// 重写取款方法
void withdraw(double amount) override {
if (amount > balance) {
std::cout << "Insufficient funds" << std::endl;
} else {
balance -= amount;
std::cout << "Withdrew " << amount << ", new balance: " << balance << std::endl;
}
}
};
// 派生类:支票账户
class CheckingAccount : public BankAccount {
private:
double overdraftLimit; // 封装:透支限额
public:
// 构造函数,初始化账户所有者、余额和透支限额
CheckingAccount(const std::string& owner, double balance, double overdraftLimit)
: BankAccount(owner, balance), overdraftLimit(overdraftLimit) {}
// 重写存款方法
void deposit(double amount) override {
balance += amount;
std::cout << "Deposited " << amount << ", new balance: " << balance << std::endl;
}
// 重写取款方法:允许透支
void withdraw(double amount) override {
if (amount > balance + overdraftLimit) {
std::cout << "Overdraft limit exceeded" << std::endl;
} else {
balance -= amount;
std::cout << "Withdrew " << amount << ", new balance: " << balance << std::endl;
}
}
};
int main() {
// 使用多态:基类指针指向派生类对象
BankAccount* account1 = new SavingsAccount("Alice", 1000.0, 0.05);
BankAccount* account2 = new CheckingAccount("Bob", 500.0, 100.0);
// 调用存款和取款方法
account1->deposit(200);
account1->withdraw(150);
account2->deposit(300);
account2->withdraw(700);
// 清理内存
delete account1;
delete account2;
return 0;
}
代码解释
-
封装:
- 在
BankAccount类中,owner和balance是私有成员变量,只能通过公开的方法访问。SavingsAccount和CheckingAccount也有自己的私有成员变量(如利率和透支限额)。
- 在
-
继承:
SavingsAccount和CheckingAccount继承自BankAccount,并且可以使用基类的所有公共和受保护成员。通过继承,可以共享通用的功能,如获取余额 (getBalance)。
-
多态:
BankAccount类定义了虚函数deposit和withdraw,这些函数在子类中被重写。在main函数中,通过基类指针调用这些方法时,实际调用的是子类的实现(即多态行为)。
-
抽象:
BankAccount是一个抽象类(因为它包含纯虚函数),不能直接实例化。它为所有具体的银行账户提供了一个通用的接口(deposit和withdraw),具体的实现由派生类提供。
运行结果
运行上述代码时,输出如下:
Deposited 200 with interest, new balance: 1210
Withdrew 150, new balance: 1060
Deposited 300, new balance: 800
Withdrew 700, new balance: 100
总结
- 封装:通过隐藏对象的内部实现细节,保证对象的状态只能通过指定的接口访问。
- 继承:通过继承机制,子类可以复用父类的代码,并扩展或重写父类的功能。
- 多态:通过多态性,程序可以在运行时根据实际对象类型调用适当的方法实现,从而提高代码的灵活性和可扩展性。
- 抽象:通过抽象类和接口,定义了一组通用的功能,具体的实现由子类提供,增强了代码的模块化和可维护性。
这个例子展示了如何在C++中使用面向对象编程的四个主要特性来构建灵活且可扩展的系统。 在C++中,支持多重继承,即一个类可以继承多个父类。这与其他一些面向对象的语言(如Java)不同,在这些语言中,一个类只能继承一个父类,但可以实现多个接口。C++还通过虚继承来解决多重继承中的菱形继承问题(即多个父类有共同的基类时,派生类会有多个相同基类的副本)。
在C++中,没有“接口”这种概念,但可以通过抽象类(包含纯虚函数的类)来模拟接口的行为。一个类可以继承多个抽象类,从而实现多个接口的效果。
例子:多重继承与多接口模拟
以下是一个例子,展示了如何在C++中实现一个类继承多个父类,并实现多个接口(通过继承抽象类):
#include <iostream>
// 接口1: 可以飞行
class Flyable {
public:
virtual void fly() const = 0; // 纯虚函数,相当于接口方法
virtual ~Flyable() {}
};
// 接口2: 可以游泳
class Swimmable {
public:
virtual void swim() const = 0; // 纯虚函数,相当于接口方法
virtual ~Swimmable() {}
};
// 基类: 动物
class Animal {
public:
Animal(const std::string& name) : name(name) {}
void eat() const {
std::cout << name << " is eating." << std::endl;
}
protected:
std::string name;
};
// 派生类: 鸭子 (同时继承 Animal, Flyable, Swimmable)
class Duck : public Animal, public Flyable, public Swimmable {
public:
Duck(const std::string& name) : Animal(name) {}
// 实现 Flyable 接口
void fly() const override {
std::cout << name << " is flying." << std::endl;
}
// 实现 Swimmable 接口
void swim() const override {
std::cout << name << " is swimming." << std::endl;
}
// 鸭子特有的方法
void quack() const {
std::cout << name << " is quacking." << std::endl;
}
};
int main() {
// 创建一个 Duck 对象
Duck donald("Donald");
// 通过 Animal 基类的方法
donald.eat();
// 通过 Flyable 接口的方法
donald.fly();
// 通过 Swimmable 接口的方法
donald.swim();
// 调用鸭子特有的方法
donald.quack();
return 0;
}
代码解释
-
Flyable接口:- 这是一个纯虚基类,定义了一个纯虚函数
fly(),所有继承Flyable的类都必须实现这个函数。
- 这是一个纯虚基类,定义了一个纯虚函数
-
Swimmable接口:- 同样是一个纯虚基类,定义了一个纯虚函数
swim(),所有继承Swimmable的类都必须实现这个函数。
- 同样是一个纯虚基类,定义了一个纯虚函数
-
Animal基类:Animal类包含一个name成员变量,并定义了一个eat()方法。Duck类将继承这个基类,并使用name成员变量来打印输出。
-
Duck类:Duck类同时继承了Animal、Flyable和Swimmable,这意味着它不仅可以继承Animal的特性,还必须实现Flyable和Swimmable的接口方法。Duck类实现了fly()和swim()方法,并添加了一个quack()方法,作为鸭子特有的行为。
-
main()函数:- 创建一个
Duck对象donald,然后通过基类Animal的eat()方法,以及两个接口Flyable和Swimmable的方法来操作这个对象。
- 创建一个
运行结果
运行该代码将输出以下结果:
Donald is eating.
Donald is flying.
Donald is swimming.
Donald is quacking.
多重继承的注意事项
- 菱形继承问题:
- 当一个类继承两个基类,而这两个基类又继承自同一个祖先类时,会出现“菱形继承”问题。这意味着派生类可能会有两个祖先类的副本。通过使用
虚继承(virtual inheritance),可以避免这一问题。
- 当一个类继承两个基类,而这两个基类又继承自同一个祖先类时,会出现“菱形继承”问题。这意味着派生类可能会有两个祖先类的副本。通过使用
- 复杂性:
- 多重继承虽然灵活,但也会引入更多的复杂性,特别是在类层次结构较为复杂时。通常,尽量避免不必要的多重继承,特别是在有简单的设计替代方案时。
总结
通过这个例子,我们展示了如何在C++中使用多重继承来模拟多个父类和多个接口的效果。C++的多重继承机制非常强大,但也需要小心使用,以避免潜在的复杂性和错误。
要在C++中实现一个监听8888端口的服务器,并将接收到的数据写入文件中,同时支持断开连接后继续写入,可以使用POSIX套接字编程。这是一种较为底层的网络编程方式,适用于Linux/Unix系统。
下面是一个简单的例子,展示如何实现这个功能:
代码示例
#include <iostream>
#include <fstream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>
#define PORT 8888
#define BUFFER_SIZE 1024
#define FILENAME "received_data.txt"
int main() {
int server_fd, new_socket;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
char buffer[BUFFER_SIZE] = {0};
// 创建socket文件描述符
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 设置端口复用
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
perror("setsockopt failed");
close(server_fd);
exit(EXIT_FAILURE);
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
// 绑定端口
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
close(server_fd);
exit(EXIT_FAILURE);
}
// 监听端口
if (listen(server_fd, 3) < 0) {
perror("listen failed");
close(server_fd);
exit(EXIT_FAILURE);
}
std::ofstream outfile;
// 进入主循环,处理客户端连接
while (true) {
std::cout << "Waiting for a connection on port " << PORT << "..." << std::endl;
// 接受客户端连接
if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
perror("accept failed");
close(server_fd);
exit(EXIT_FAILURE);
}
std::cout << "Connection established. Receiving data..." << std::endl;
// 打开文件,以追加模式写入
outfile.open(FILENAME, std::ios_base::app | std::ios_base::out);
if (!outfile.is_open()) {
perror("File open failed");
close(new_socket);
continue;
}
int bytes_read;
// 接收数据并写入文件
while ((bytes_read = read(new_socket, buffer, BUFFER_SIZE)) > 0) {
outfile.write(buffer, bytes_read);
}
if (bytes_read < 0) {
perror("read failed");
} else if (bytes_read == 0) {
std::cout << "Client disconnected, waiting for new connection..." << std::endl;
}
// 关闭文件和socket
outfile.close();
close(new_socket);
}
close(server_fd);
return 0;
}
代码解析
-
创建套接字:
server_fd = socket(AF_INET, SOCK_STREAM, 0);这里使用
socket()函数创建一个套接字,AF_INET表示使用IPv4地址族,SOCK_STREAM表示使用TCP协议。 -
绑定端口:
bind(server_fd, (struct sockaddr *)&address, sizeof(address));绑定套接字到指定的端口
8888,INADDR_ANY表示绑定到所有可用的网络接口。 -
监听连接:
listen(server_fd, 3);设置套接字为监听模式,并允许最多3个待处理连接。
-
接受连接:
new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen);等待客户端连接,当一个客户端连接时,
accept()会返回一个新的套接字来处理这个连接。 -
处理数据:
int bytes_read = read(new_socket, buffer, BUFFER_SIZE); outfile.write(buffer, bytes_read);从客户端读取数据,并将其写入文件中。这里使用的是
ofstream,并以追加模式打开文件(std::ios_base::app),这样每次新连接进来时,数据都会追加到文件的末尾。 -
处理客户端断开:
- 当客户端断开连接时,
read()返回0,程序会关闭连接并继续等待新的连接。
- 当客户端断开连接时,
-
关闭连接:
- 每次处理完一个客户端的请求后,关闭这个客户端的连接和文件流,等待下一个连接。
总结
这个示例展示了如何在C++中实现一个基本的TCP服务器,它监听8888端口,并将接收到的数据写入一个文件中。如果客户端断开连接,下次新的客户端连接时,数据将继续写入文件的末尾。通过这种方式,你可以记录所有客户端发送的数据,并且不丢失数据。 当然,除了使用传统的POSIX套接字编程,还有其他几种方式可以实现类似的功能,尤其是利用C++11及以后的标准引入的新特性。下面我将介绍一些替代方案:
1. 使用boost::asio库
boost::asio是一个广泛使用的C++库,提供了跨平台的网络编程接口,并支持异步I/O操作。以下是一个使用boost::asio实现的例子:
#include <iostream>
#include <fstream>
#include <boost/asio.hpp>
#include <boost/bind/bind.hpp>
using boost::asio::ip::tcp;
class TCPServer {
public:
TCPServer(boost::asio::io_context& io_context, short port)
: acceptor_(io_context, tcp::endpoint(tcp::v4(), port)) {
start_accept();
}
private:
void start_accept() {
tcp::socket* socket = new tcp::socket(acceptor_.get_executor().context());
acceptor_.async_accept(*socket,
boost::bind(&TCPServer::handle_accept, this, socket,
boost::asio::placeholders::error));
}
void handle_accept(tcp::socket* socket, const boost::system::error_code& error) {
if (!error) {
std::cout << "New connection established." << std::endl;
std::ofstream outfile("received_data.txt", std::ios_base::app | std::ios_base::out);
if (outfile.is_open()) {
boost::asio::streambuf buffer;
boost::system::error_code ec;
while (boost::asio::read(*socket, buffer, boost::asio::transfer_at_least(1), ec)) {
outfile << &buffer;
}
if (ec == boost::asio::error::eof) {
std::cout << "Connection closed by client." << std::endl;
} else {
std::cerr << "Error during reading: " << ec.message() << std::endl;
}
outfile.close();
}
}
delete socket;
start_accept();
}
tcp::acceptor acceptor_;
};
int main() {
try {
boost::asio::io_context io_context;
TCPServer server(io_context, 8888);
io_context.run();
} catch (std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
}
return 0;
}
代码说明
-
boost::asio::io_context:io_context是boost::asio的核心,它用于管理异步操作。
-
tcp::acceptor:tcp::acceptor用于接受传入的TCP连接。
-
异步接受和处理:
async_accept方法启动异步接受连接的操作。完成后,它调用handle_accept,其中处理接收的数据并将其写入文件。
-
使用
streambuf读取数据:- 通过
boost::asio::streambuf来缓冲从套接字读取的数据,并将其写入文件。
- 通过
-
循环处理连接:
- 每当一个连接处理完毕,服务器会继续监听新的连接。
2. 使用std::thread和现代C++标准库
你可以使用C++11的标准库进行线程化编程,并结合标准I/O操作来实现监听端口和写入文件的功能:
#include <iostream>
#include <fstream>
#include <thread>
#include <vector>
#include <string>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>
#define PORT 8888
#define BUFFER_SIZE 1024
#define FILENAME "received_data.txt"
void handle_connection(int new_socket) {
std::ofstream outfile(FILENAME, std::ios_base::app | std::ios_base::out);
if (!outfile.is_open()) {
std::cerr << "Failed to open file." << std::endl;
close(new_socket);
return;
}
char buffer[BUFFER_SIZE];
ssize_t bytes_read;
while ((bytes_read = read(new_socket, buffer, BUFFER_SIZE)) > 0) {
outfile.write(buffer, bytes_read);
}
if (bytes_read == 0) {
std::cout << "Client disconnected." << std::endl;
} else {
std::cerr << "Read error." << std::endl;
}
outfile.close();
close(new_socket);
}
int main() {
int server_fd, new_socket;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
perror("setsockopt failed");
close(server_fd);
exit(EXIT_FAILURE);
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr*)&address, sizeof(address)) < 0) {
perror("bind failed");
close(server_fd);
exit(EXIT_FAILURE);
}
if (listen(server_fd, 3) < 0) {
perror("listen failed");
close(server_fd);
exit(EXIT_FAILURE);
}
std::vector<std::thread> threads;
while (true) {
std::cout << "Waiting for a connection on port " << PORT << "..." << std::endl;
if ((new_socket = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen)) < 0) {
perror("accept failed");
close(server_fd);
exit(EXIT_FAILURE);
}
std::cout << "Connection established." << std::endl;
threads.emplace_back(handle_connection, new_socket);
}
for (auto& th : threads) {
if (th.joinable()) {
th.join();
}
}
close(server_fd);
return 0;
}
代码说明
-
std::thread:- 使用
std::thread创建一个新线程来处理每个客户端连接。
- 使用
-
线程安全性:
- 由于每个线程都只处理一个客户端连接,因此不需要显式的同步措施。
-
线程池管理:
- 使用
std::vector<std::thread>来管理所有线程,确保主线程在退出时等待所有线程完成。
- 使用
3. 使用libuv库
libuv是一个多平台支持的异步I/O库,通常用于Node.js的底层。你可以使用libuv来处理网络通信,这里我们不详细展开代码,但可以作为一个方向,特别适合需要支持高并发的C++网络程序。
总结
C++中实现监听端口并处理网络通信可以有多种方式:
- POSIX套接字编程:经典的做法,直接操作系统提供的网络API。
- Boost.Asio:功能强大且跨平台,支持异步I/O。
- 标准库结合
std::thread:利用C++11及后的标准特性,简化线程管理和并发操作。 - Libuv:适合需要高并发支持的应用程序,但可能需要更多的库和编译设置。
选择哪种方式取决于你的项目需求、开发环境和对性能的要求。
要读取/dev/input/event4的数据并将其写入文件,可以使用C++中的文件I/O操作和POSIX的低级文件操作函数。/dev/input/event4通常是一个Linux系统上的输入设备(例如键盘、鼠标、触摸屏等)的事件设备文件。读取这个文件的内容可以捕获该设备的原始输入事件数据。
下面是一个基本的C++程序示例,它会读取/dev/input/event4的事件数据并将其写入到一个文件中:
C++代码示例
#include <iostream>
#include <fstream>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>
#define INPUT_DEVICE "/dev/input/event4"
#define OUTPUT_FILE "event_data.bin"
#define BUFFER_SIZE 64
int main() {
int fd = open(INPUT_DEVICE, O_RDONLY);
if (fd == -1) {
perror("Failed to open input device");
return 1;
}
std::ofstream outfile(OUTPUT_FILE, std::ios::binary | std::ios::app);
if (!outfile.is_open()) {
perror("Failed to open output file");
close(fd);
return 1;
}
struct input_event buffer[BUFFER_SIZE];
ssize_t bytes_read;
while (true) {
// 读取事件数据
bytes_read = read(fd, buffer, sizeof(buffer));
if (bytes_read < 0) {
perror("Failed to read from input device");
break;
}
// 将读取的数据写入文件
outfile.write(reinterpret_cast<char*>(buffer), bytes_read);
if (!outfile) {
perror("Failed to write to output file");
break;
}
// 输出读取的事件数量和字节数
std::cout << "Read " << bytes_read / sizeof(struct input_event) << " events (" << bytes_read << " bytes)" << std::endl;
}
outfile.close();
close(fd);
return 0;
}
代码解析
-
打开输入设备:
int fd = open(INPUT_DEVICE, O_RDONLY);- 使用
open()函数以只读模式打开/dev/input/event4设备文件。如果打开失败,程序会输出错误信息并返回。
- 使用
-
打开输出文件:
std::ofstream outfile(OUTPUT_FILE, std::ios::binary | std::ios::app);- 以二进制模式和追加模式打开文件
event_data.bin,用于保存读取到的事件数据。如果打开失败,程序会输出错误信息并返回。
- 以二进制模式和追加模式打开文件
-
读取事件数据:
bytes_read = read(fd, buffer, sizeof(buffer));- 使用
read()函数从设备文件中读取原始事件数据。数据以input_event结构存储,每个事件可能包含多个数据字段(如事件类型、代码、值等)。 input_event是定义在<linux/input.h>中的一个结构体,用于表示输入设备产生的事件。
- 使用
-
写入文件:
outfile.write(reinterpret_cast<char*>(buffer), bytes_read);- 将读取的数据写入文件中。这里使用了
reinterpret_cast来将input_event类型的指针转换为char*,以便将其作为字节流写入文件。
- 将读取的数据写入文件中。这里使用了
-
循环读取:
- 这个程序会持续读取设备文件,直到发生读取或写入错误(例如设备断开、文件系统问题等)。
-
关闭文件:
- 在程序退出之前,关闭输入设备和输出文件,以释放资源。
编译与运行
要编译这个C++程序,可以使用以下命令(假设文件名为read_event.cpp):
g++ -o read_event read_event.cpp
运行程序时需要超级用户权限(因为访问/dev/input/event4通常需要权限):
sudo ./read_event
注意事项
-
权限问题:
/dev/input/event4属于输入子系统设备,通常需要超级用户权限才能访问。如果你遇到权限错误,可以使用sudo运行程序,或者配置udev规则来授予普通用户权限。 -
文件格式: 事件数据被写入文件是原始二进制格式的
input_event结构体。如果你需要解析这些数据,可以参考<linux/input.h>头文件中的结构体定义。 -
中断程序: 程序将持续运行,直到手动中断(例如通过
Ctrl+C)。你可以通过添加条件检查或计数器来限制读取的事件数量。
总结
这个示例展示了如何在Linux系统中使用C++编写一个程序,从输入设备文件/dev/input/event4读取原始事件数据,并将这些数据写入到一个文件中。通过这种方式,你可以捕获和记录设备的原始输入数据,用于调试、数据分析或其他目的。
下面是几个POSIX兼容的例子,涵盖了创建、修改、删除文件,接受和发送网络数据,创建多线程并控制,以及poll的使用。这些例子大多数可以在Linux、macOS以及其他类Unix系统上运行。
1. 文件操作:创建、修改、删除文件
#include <iostream>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
int main() {
const char* filename = "example.txt";
// 创建文件,O_CREAT 表示创建文件,O_WRONLY 表示只写模式,S_IRUSR | S_IWUSR 设置文件权限(用户可读写)
int fd = open(filename, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR);
if (fd == -1) {
perror("Failed to create file");
return 1;
}
// 写入数据
const char* content = "Hello, POSIX!";
ssize_t bytes_written = write(fd, content, strlen(content));
if (bytes_written == -1) {
perror("Failed to write to file");
close(fd);
return 1;
}
std::cout << "Wrote " << bytes_written << " bytes to " << filename << std::endl;
// 关闭文件
close(fd);
// 修改文件名
const char* new_filename = "new_example.txt";
if (rename(filename, new_filename) == -1) {
perror("Failed to rename file");
return 1;
}
std::cout << "Renamed file to " << new_filename << std::endl;
// 删除文件
if (unlink(new_filename) == -1) {
perror("Failed to delete file");
return 1;
}
std::cout << "Deleted file " << new_filename << std::endl;
return 0;
}
2. 接收和发送网络数据
#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#define PORT 8888
#define BUFFER_SIZE 1024
void server() {
int server_fd, new_socket;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
char buffer[BUFFER_SIZE] = {0};
// 创建 socket
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
// 绑定端口
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
perror("Set socket options failed");
close(server_fd);
exit(EXIT_FAILURE);
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr*)&address, sizeof(address)) < 0) {
perror("Bind failed");
close(server_fd);
exit(EXIT_FAILURE);
}
// 监听
if (listen(server_fd, 3) < 0) {
perror("Listen failed");
close(server_fd);
exit(EXIT_FAILURE);
}
std::cout << "Waiting for a connection on port " << PORT << "..." << std::endl;
if ((new_socket = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen)) < 0) {
perror("Accept failed");
close(server_fd);
exit(EXIT_FAILURE);
}
// 接收数据
ssize_t valread = read(new_socket, buffer, BUFFER_SIZE);
std::cout << "Received: " << buffer << std::endl;
// 发送数据
const char* response = "Hello from server!";
send(new_socket, response, strlen(response), 0);
std::cout << "Response sent\n";
close(new_socket);
close(server_fd);
}
int main() {
server();
return 0;
}
3. 创建多线程并控制
#include <iostream>
#include <pthread.h>
#include <unistd.h>
void* print_message(void* thread_id) {
long tid = (long)thread_id;
std::cout << "Thread ID: " << tid << " is running." << std::endl;
sleep(2); // 模拟工作
std::cout << "Thread ID: " << tid << " finished." << std::endl;
pthread_exit(NULL);
}
int main() {
const int NUM_THREADS = 5;
pthread_t threads[NUM_THREADS];
int rc;
for (long i = 0; i < NUM_THREADS; i++) {
std::cout << "Main: creating thread " << i << std::endl;
rc = pthread_create(&threads[i], NULL, print_message, (void*)i);
if (rc) {
std::cerr << "Error: unable to create thread, " << rc << std::endl;
exit(-1);
}
}
for (int i = 0; i < NUM_THREADS; i++) {
pthread_join(threads[i], NULL); // 等待所有线程完成
}
std::cout << "Main: program exiting." << std::endl;
pthread_exit(NULL); // 结束主线程
return 0;
}
4. 使用poll来监听多个文件描述符
#include <iostream>
#include <poll.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#define TIMEOUT 5000 // 超时时间5秒
int main() {
const char* filename1 = "file1.txt";
const char* filename2 = "file2.txt";
// 打开两个文件
int fd1 = open(filename1, O_RDONLY | O_NONBLOCK);
int fd2 = open(filename2, O_RDONLY | O_NONBLOCK);
if (fd1 == -1 || fd2 == -1) {
perror("Failed to open files");
return 1;
}
struct pollfd fds[2];
// 配置文件描述符
fds[0].fd = fd1;
fds[0].events = POLLIN; // 等待读取
fds[1].fd = fd2;
fds[1].events = POLLIN;
int ret = poll(fds, 2, TIMEOUT);
if (ret == -1) {
perror("poll failed");
} else if (ret == 0) {
std::cout << "Timeout occurred! No data available." << std::endl;
} else {
if (fds[0].revents & POLLIN) {
std::cout << "Data available in " << filename1 << std::endl;
// 在这里读取数据...
}
if (fds[1].revents & POLLIN) {
std::cout << "Data available in " << filename2 << std::endl;
// 在这里读取数据...
}
}
close(fd1);
close(fd2);
return 0;
}
代码解释
-
文件操作:
open()用于创建或打开文件。write()和read()分别用于向文件写入和读取数据。rename()用于重命名文件,unlink()用于删除文件。
-
网络数据接收和发送:
- 创建一个TCP服务器,使用
socket()创建套接字,使用bind()将套接字绑定到端口上。 - 使用
accept()接受客户端连接,使用read()读取客户端发送的数据,使用send()发送数据给客户端。
- 创建一个TCP服务器,使用
-
多线程:
- 使用
pthread_create()创建线程,并使用pthread_join()等待线程完成。 - 每个线程执行
print_message()函数,该函数模拟了一些工作,并打印消息。
- 使用
-
使用
poll:poll()函数用于同时监控多个文件描述符(如文件、套接字等)。POLLIN事件表示文件描述符上有可读数据。poll()返回时,可以检查revents字段来确定哪个文件描述符准备好了I/O操作。
运行与测试
- 文件操作:编译并运行文件操作示例会在当前目录中创建、修改、重命名并删除
example.txt。 - 网络通信:编译并运行网络示例后,使用
telnet或其他TCP客户端连接到localhost的8888端口进行测试。 - 多线程:编译并运行多线程示例后,可以看到5个线程并发执行,主线程等待所有子线程完成。
- poll的使用:编译并运行
poll示例后,在程序执行期间可以通过向file1.txt或file2.txt写入数据来触发事件。
这些示例展示了POSIX编程的核心操作,适用于Linux及其他Unix-like操作系统。每个示例都可以进一步扩展,用于更加复杂的系统编程任务。