C++基础语法3

size_t

是C++中专门用来表示大小、长度、索引、数量等非负整数值的类型

1.无符号整数溢出

// 里程表:只能显示 0-999999

size_t 里程 = 5; // 显示 000005

size_t 目标 = 10; // 显示 000010

// 你想计算:5 - 10

// 但里程表没有负数!

// 它会从 000005 往前倒...

// 999999 ← 倒9下

// 999998 ← 倒8下

// 999997 ← 倒7下

// 999996 ← 倒6下

// 999995 ← 倒5下 最终停在这里!

2. 平台适配性(自动匹配)

复制代码
// 在32位系统上:
size_t 大小;  // 实际上是 unsigned int (4字节)
int 数值;     // 实际上是 int (4字节)

// 在64位系统上:
size_t 大小;  // 自动变成 unsigned long long (8字节)
int 数值;     // 仍然是 int (4字节) ← 注意!没变!

// 这就是关键:size_t 自动匹配指针大小
// 而 int 保持固定

3.必须使用 size_t 的场景

标准库函数参数和返回值

复制代码
// 内存操作函数
void* memset(void* ptr, int value, size_t num);
void* memcpy(void* dest, const void* src, size_t num);
void* memmove(void* dest, const void* src, size_t num);

// 字符串操作
size_t strlen(const char* str);
size_t strlcpy(char* dest, const char* src, size_t size);

// 文件操作
size_t fread(void* ptr, size_t size, size_t count, FILE* stream);
size_t fwrite(const void* ptr, size_t size, size_t count, FILE* stream);

则在调用函数时必须使用size_t

复制代码
#include <cstring>
#include <cstdio>

int main() {
    char buffer[100];
    const char* source = "Hello";
    
    // ✅ 必须用 size_t
    size_t 长度 = strlen(source);           // 返回 size_t
    size_t 拷贝长度 = sizeof(buffer);
    strlcpy(buffer, source, 拷贝长度);      // 参数是 size_t
    
    return 0;
}

接受某些调用的返回值:

cpp 复制代码
#include <vector>
#include <string>
#include <array>
#include <list>

int main() {
    std::vector<int> vec = {1, 2, 3};
    std::string str = "Hello";
    
    // ✅ 必须用 size_t
    size_t vec大小 = vec.size();     // 返回 size_t
    size_t str长度 = str.length();    // 返回 size_t
    size_t vec容量 = vec.capacity();  // 返回 size_t
    
    // 遍历容器
    for (size_t i = 0; i < vec.size(); ++i) {  // ✅
        // vec.size() 返回 size_t
    }
    
    return 0;
}

构造函数和析构函数

构造函数

作用

构造函数是创建对象时自动调用的特殊函数,用于初始化对象。

语法说明

复制代码
DataStorageServer(const std::string& db_path, 
                 const std::string& storage_path);
  1. 函数名DataStorageServer→ 必须与类名相同

  2. 参数

    • const std::string& db_path→ 数据库文件路径

    • const std::string& storage_path→ 存储文件路径

  3. const 引用const std::string&表示

    • 参数是只读的(不能修改)

    • 通过引用传递(避免拷贝开销)

      "我保证不修改你,所以请把你的'本体'直接给我用,别费劲拷贝一份新的了。"

    • 可以是临时字符串

调用时机

复制代码
// 创建DataStorageServer对象时自动调用
DataStorageServer server("./data.db", "./storage");
//             ↑
// 构造函数被调用,传入两个路径参数

构造函数的任务

在这个项目中,构造函数需要:

  1. db_path初始化数据库连接

  2. storage_path设置存储目录

  3. 创建子模块(DatabaseManager, TaskQueue等)

  4. 初始化内部状态

  5. 启动必要的后台线程


析构函数

作用

析构函数是对象销毁时自动调用的特殊函数,用于清理资源。

语法说明

复制代码
~DataStorageServer() = default;
  1. 函数名~DataStorageServer→ 类名前加波浪号

  2. = default:使用编译器生成的默认析构函数

  3. 无参数:析构函数不能有参数

  4. 无返回值:不能指定返回类型

调用时机

复制代码
{
    DataStorageServer server("./data.db", "./storage");
    // 使用server...
    
} // ← 右括号处,server对象超出作用域
  // 析构函数自动调用,清理资源

用 = default适用于:

= default表示"使用编译器自动生成的析构函数",适用于:

  1. 没有动态分配的资源

  2. 所有成员都有合适的析构函数

  3. 不需要特殊清理逻辑

更好的写法

实际上,DataStorageServer不应该用默认析构函数,因为:

需要的清理工作
复制代码
~DataStorageServer() {
    // 1. 停止所有后台线程
    stopMonitoring();
    
    // 2. 停止任务队列
    task_queue.stop();
    
    // 3. 等待任务完成
    task_queue.waitForCompletion();
    
    // 4. 关闭数据库连接
    db_manager.close();
    
    // 5. 清理缓存
    cache.clear();
    
    // 6. 写入关闭日志
    logger.log("DataStorageServer 正常关闭");
}
默认析构函数的问题
复制代码
~DataStorageServer() = default;  // 危险!

// 如果成员是unique_ptr,会自动删除
// 但如果成员是原始指针,会内存泄漏
// 如果线程还在运行,程序会崩溃
// 如果数据库连接未关闭,可能损坏数据

正确实现示例

头文件声明

复制代码
// DataStorageServer.h
class DataStorageServer {
public:
    // 构造函数
    DataStorageServer(const std::string& db_path, 
                     const std::string& storage_path);
    
    // 析构函数(必须自定义)
    ~DataStorageServer();
    
    // 禁用拷贝(避免多个对象管理同一资源)
    DataStorageServer(const DataStorageServer&) = delete;
    DataStorageServer& operator=(const DataStorageServer&) = delete;
    
    // 允许移动(C++11特性)
    DataStorageServer(DataStorageServer&&) = default;
    DataStorageServer& operator=(DataStorageServer&&) = default;
    
private:
    std::unique_ptr<DatabaseManager> db_manager;
    std::unique_ptr<TaskQueue> task_queue;
    std::unique_ptr<LRUCache<std::string, std::string>> cache;
    // ...
};

源文件实现

复制代码
// DataStorageServer.cpp
DataStorageServer::DataStorageServer(const std::string& db_path, 
                                   const std::string& storage_path) {
    // 1. 初始化日志
    logger = std::make_unique<Logger>("DataStorageServer");
    
    // 2. 创建数据库管理器
    db_manager = std::make_unique<DatabaseManager>(db_path);
    
    // 3. 创建任务队列
    task_queue = std::make_unique<TaskQueue>(*db_manager, 4);
    
    // 4. 创建缓存
    cache = std::make_unique<LRUCache<std::string, std::string>>(10000);
    
    // 5. 创建存储引擎
    storage_engine = std::make_unique<StorageEngine>(*db_manager, storage_path);
    
    // 6. 注册任务处理器
    registerTaskHandlers();
    
    // 7. 启动监控线程
    startMonitoring();
    
    logger->log("DataStorageServer 初始化完成");
}

DataStorageServer::~DataStorageServer() {
    logger->log("开始关闭DataStorageServer...");
    
    // 1. 停止接收新请求
    running = false;
    
    // 2. 停止监控线程
    stopMonitoring();
    
    // 3. 停止任务队列(等待当前任务完成)
    if (task_queue) {
        task_queue->stop();
        task_queue->waitForCompletion();
    }
    
    // 4. 清理缓存
    if (cache) {
        cache->clear();
    }
    
    // 5. 关闭数据库连接
    if (db_manager) {
        db_manager->close();
    }
    
    logger->log("DataStorageServer 已关闭");
}

重要设计原则

1. RAII原则

RAII ​ 是 R esource A cquisition I s Initialization(资源获取即初始化)的缩写。

RAII 是 C++ 的核心编程范式,用于自动资源管理,核心思想是:

对象的生命周期与资源的生命周期严格绑定。

资源获取即初始化:构造函数获取资源,析构函数释放资源。

复制代码
// 正确的RAII
{
    DataStorageServer server(...);  // 构造时获取所有资源
    // 使用server...
} // 析构时自动释放所有资源

**2. 异常安全--**构造函数应该处理异常

构造函数应该处理异常,避免部分构造的对象。

复制代码
DataStorageServer::DataStorageServer(...) {
    try {
        // 按顺序初始化
        init1();  // 如果失败,已初始化的会清理
        init2();
        init3();
    } catch (...) {
        cleanupPartiallyInitialized();  // 清理已分配的资源
        throw;  // 重新抛出异常
    }
}

3. 移动语义支持

如果对象管理大量资源,应该支持移动。

复制代码
// 移动构造函数
DataStorageServer::DataStorageServer(DataStorageServer&& other) noexcept
    : db_manager(std::move(other.db_manager))
    , task_queue(std::move(other.task_queue))
    , cache(std::move(other.cache)) {
    // 转移所有权,而不是复制
}

左值和右值

这是一个C++中极其核心且重要 的概念。简单来说,它们定义了表达式的两种不同"属性",决定了这个表达式能用在什么地方。


1. 最直观的定义(经典但不完全准确)

  • 左值 (lvalue)有名字、有明确内存地址 的表达式。你可以把它想象成一个"容器"或"变量",可以放在赋值操作符的左边

  • 右值 (rvalue)临时的、没有持久内存地址、生命周期即将结束 的表达式。通常只能放在赋值操作符的右边,作为"值"的来源。

经典例子:

复制代码
int a = 10; // a 是左值,10 是右值
int b = a;  // b 是左值,a 在这里是右值(虽然a是变量,但作为"值"的来源)
a = b + 5;  // a 是左值,(b + 5) 这个表达式的结果是一个右值

更关键的特征:

特征 左值 (lvalue) 右值 (rvalue)
**身份 (Identity)**​ 。可以取地址 (&a)。 通常没有 。不能对 (b+5)取地址 (&(b+5)错误)。
**可移动性 (Movable)**​ 可以拷贝,也可以被移动(如果允许)。 通常只可移动,因为即将销毁,其资源可以被"转移"。
生命周期 持续到其作用域结束。 短暂的、临时的,通常是表达式求值后即销毁。
常见形式 变量名、函数名、返回左值引用的函数调用、解引用指针 (*ptr)。 字面量(10, "hello")、临时对象、返回非引用类型的函数调用、算术表达式结果。
相关推荐
今儿敲了吗2 小时前
python基础学习笔记第四章
c++·笔记·python·学习
6+h2 小时前
【java IO】缓冲流详解
java·开发语言
爱丽_2 小时前
方法区 / 元空间:JDK 1.7 到 JDK 1.8 到底变了什么?
java·开发语言
xjdkxnhcoskxbco2 小时前
Java 多线程“八锁”问题深度解析
java·开发语言·多线程
無限進步D2 小时前
差分算法 cpp
c++·算法·蓝桥杯·竞赛
人还是要有梦想的2 小时前
QT的起源
开发语言·qt
柏箱2 小时前
文件上传漏洞入门:(upload-labs Pass-1 & Pass-2)
开发语言·前端·javascript
人道领域2 小时前
Day | 07 【苍穹外卖:菜品套餐的缓存】
java·开发语言·redis·缓存击穿·springcache
biter down2 小时前
C++ 精准控制对象的创建位置(堆 / 栈)
开发语言·c++