当前位置: 首页 > news >正文

C++学习:六个月从基础到就业——C++基础语法回顾:指针与引用基础

C++学习:六个月从基础到就业——C++基础语法回顾:指针与引用基础

本文是我C++学习之旅系列的第六篇技术文章,主要回顾C++中的指针与引用基础,包括内存模型、指针操作、引用特性以及它们的区别与应用场景。查看完整系列目录了解更多内容。

引言

指针和引用是C++中最强大也最具挑战性的特性之一,它们提供了对内存的直接访问和操作能力,使C++在系统编程、高性能计算等领域具有显著优势。同时,不正确地使用指针也是导致内存泄漏、段错误和其他难以调试的问题的主要原因。本文将详细介绍C++中指针与引用的基础概念、语法和使用方法,帮助你构建对这些重要特性的清晰理解。

内存模型基础

在深入指针和引用之前,我们需要了解C++程序的内存模型:

内存布局

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

  1. 代码段(Text Segment):存储程序的机器代码。
  2. 数据段(Data Segment):存储全局变量和静态变量。
  3. 堆(Heap):用于动态内存分配,程序员负责管理。
  4. 栈(Stack):用于存储局部变量、函数参数和返回地址。

在这里插入图片描述

变量与内存地址

每个变量在内存中都占据一定空间,并拥有一个唯一的内存地址:

int number = 42;
std::cout << "number的值: " << number << std::endl;
std::cout << "number的地址: " << &number << std::endl;

上面代码中,&number返回变量number的内存地址,这是指针概念的基础。

指针基础

什么是指针

指针是一种存储内存地址的变量。通过指针,我们可以间接访问存储在该地址的数据。

指针声明与初始化

声明一个指针变量的语法如下:

type* pointer_name;  // 或 type *pointer_name;

初始化指针:

int number = 42;
int* p = &number;  // p指向number的地址

空指针初始化:

int* p = nullptr;  // C++11推荐的空指针初始化方式
int* p = NULL;     // 传统C风格,等同于int* p = 0;
int* p = 0;        // 直接使用0,不推荐

解引用操作符

解引用操作符(*)用于访问指针指向的内存位置的值:

int number = 42;
int* p = &number;
std::cout << *p << std::endl;  // 输出42,即p指向的值

*p = 100;  // 通过指针修改值
std::cout << number << std::endl;  // 输出100

指针的类型

指针变量的类型决定了解引用时如何解释内存中的数据:

int number = 0x12345678;  // 十六进制表示
int* p_int = &number;
char* p_char = (char*)&number;  // 需要强制类型转换

std::cout << *p_int << std::endl;  // 输出整数305419896(0x12345678)
std::cout << (int)*p_char << std::endl;  // 输出78或120(取决于系统的字节序)

指针算术

指针支持简单的算术操作:

int arr[5] = {10, 20, 30, 40, 50};
int* p = arr;  // p指向数组首元素

std::cout << *p << std::endl;       // 输出10
std::cout << *(p + 1) << std::endl; // 输出20
std::cout << *(p + 2) << std::endl; // 输出30

指针递增或递减会根据指针类型调整偏移量:

char* p_char = new char[5];
int* p_int = new int[5];

std::cout << "p_char: " << (void*)p_char << std::endl;
std::cout << "p_char + 1: " << (void*)(p_char + 1) << std::endl;  // 地址加1字节

std::cout << "p_int: " << p_int << std::endl;
std::cout << "p_int + 1: " << (p_int + 1) << std::endl;  // 地址加4字节(在大多数系统上)

delete[] p_char;
delete[] p_int;

const与指针

const可以和指针结合使用,形成几种不同的组合:

// 指向常量的指针(不能通过指针修改所指对象)
const int* p1 = &number;
// *p1 = 50;  // 错误:不能通过p1修改number

// 常量指针(指针本身不能改变)
int* const p2 = &number;
*p2 = 50;      // 正确:可以修改number
// p2 = &another;  // 错误:p2不能指向其他变量

// 指向常量的常量指针(两者都不能改变)
const int* const p3 = &number;
// *p3 = 50;      // 错误
// p3 = &another;  // 错误

记忆技巧:读从右到左。例如,const int* p读作"p是一个指向const int的指针"。

void指针

void指针可以存储任何类型对象的地址,但在使用前需要转换为具体类型:

int number = 42;
void* p = &number;  // 存储int的地址

// 使用前需要转换
int* p_int = static_cast<int*>(p);
std::cout << *p_int << std::endl;  // 输出42

// C风格转换(不推荐)
int* p_int2 = (int*)p;

多重指针

指针也可以指向另一个指针,形成多级间接引用:

int number = 42;
int* p = &number;       // 指向int的指针
int** pp = &p;          // 指向指针的指针

std::cout << **pp << std::endl;  // 输出42
**pp = 100;                      // 修改number的值
std::cout << number << std::endl; // 输出100

指针与数组

数组名与指针

数组名在大多数情况下会退化为指向首元素的指针:

int arr[5] = {10, 20, 30, 40, 50};
int* p = arr;  // arr退化为&arr[0]

std::cout << *p << std::endl;      // 输出10
std::cout << *(arr + 1) << std::endl;  // 输出20,等同于arr[1]

主要区别:

  • 数组名是常量,不能修改指向(不能执行arr++
  • sizeof(arr)返回整个数组的大小,而sizeof§返回指针的大小

数组的指针算术

通过指针可以遍历数组:

int arr[5] = {10, 20, 30, 40, 50};
int* p = arr;

for (int i = 0; i < 5; i++) {
    std::cout << *(p + i) << " ";  // 等同于p[i]或arr[i]
}
std::cout << std::endl;

// 或者直接递增指针
p = arr;  // 重置指针位置
for (int i = 0; i < 5; i++) {
    std::cout << *p << " ";
    p++;
}
std::cout << std::endl;

指针数组

指针数组是存储指针的数组:

int a = 10, b = 20, c = 30;
int* arr[3];  // 存储int指针的数组

arr[0] = &a;
arr[1] = &b;
arr[2] = &c;

for (int i = 0; i < 3; i++) {
    std::cout << *arr[i] << " ";  // 输出10 20 30
}
std::cout << std::endl;

数组指针

数组指针是指向数组的指针:

int arr[3] = {10, 20, 30};
int (*p)[3] = &arr;  // p是指向包含3个int的数组的指针

std::cout << (*p)[0] << " " << (*p)[1] << " " << (*p)[2] << std::endl;  // 输出10 20 30

指针与字符串

字符串字面量与指针

字符串字面量可以用字符指针表示:

const char* str = "Hello";  // str指向字符串常量

// 注意:不能修改字符串字面量
// str[0] = 'h';  // 错误:未定义行为

// 如果需要修改,应该使用数组
char str_array[] = "Hello";
str_array[0] = 'h';  // 合法

字符指针数组

常用于程序参数、命令行参数等:

const char* fruits[] = {"apple", "banana", "cherry"};

for (int i = 0; i < 3; i++) {
    std::cout << fruits[i] << std::endl;
}

动态内存管理

new和delete操作符

C++使用new和delete操作符进行动态内存分配:

// 分配单个对象
int* p = new int;
*p = 42;
std::cout << *p << std::endl;
delete p;  // 释放内存

// 分配并初始化
int* q = new int(100);
std::cout << *q << std::endl;
delete q;

// 分配数组
int* arr = new int[5]{10, 20, 30, 40, 50};
for (int i = 0; i < 5; i++) {
    std::cout << arr[i] << " ";
}
std::cout << std::endl;
delete[] arr;  // 注意使用delete[]释放数组

内存泄漏

未正确释放动态分配的内存会导致内存泄漏:

void memory_leak() {
    int* p = new int(42);
    // 忘记调用delete p; -- 内存泄漏
}

垂悬指针

指向已释放内存的指针称为垂悬指针(dangling pointer):

int* p = new int(42);
delete p;  // p现在是垂悬指针
// *p = 100;  // 未定义行为,可能导致崩溃

// 好习惯:释放后将指针设为nullptr
p = nullptr;
if (p) {  // 检查指针是否有效
    *p = 100;
}

智能指针简介

现代C++提供了智能指针来自动管理内存,避免内存泄漏:

#include <memory>

// unique_ptr:独占所有权
std::unique_ptr<int> up = std::make_unique<int>(42);  // C++14
std::cout << *up << std::endl;
// 离开作用域时自动释放

// shared_ptr:共享所有权
std::shared_ptr<int> sp1 = std::make_shared<int>(100);
std::shared_ptr<int> sp2 = sp1;  // 两个指针共享同一对象
std::cout << *sp1 << " " << *sp2 << std::endl;
// 当最后一个shared_ptr销毁时释放对象

引用基础

什么是引用

引用是一个变量的别名,必须初始化,并且初始化后不能改变引用的对象。

引用声明与初始化

int number = 42;
int& ref = number;  // ref是number的引用

std::cout << ref << std::endl;  // 输出42
ref = 100;  // 修改number的值
std::cout << number << std::endl;  // 输出100

引用必须在声明时初始化:

int& ref;  // 错误:引用必须初始化

引用与指针的区别

  1. 引用必须初始化,指针可以不初始化
  2. 引用初始化后不能改变指向,指针可以随时改变
  3. 没有"空引用",但有空指针
  4. 引用总是指向某个对象,访问安全性高于指针
  5. 语法上,引用使用更简单(不需要解引用操作符)
int a = 10, b = 20;
int& ref = a;  // ref引用a
int* ptr = &a; // ptr指向a

// 修改引用所指的变量
ref = 100;  // a变为100
std::cout << a << std::endl;

// 修改指针所指的变量
*ptr = 200; // a变为200
std::cout << a << std::endl;

// 改变指针指向
ptr = &b;   // 现在ptr指向b
*ptr = 300; // b变为300
std::cout << a << " " << b << std::endl;

// 引用不能改变指向
// ref = b;  // 这并非改变引用指向,而是将b的值赋给a

const引用

const引用可以引用常量或临时对象:

const int& ref1 = 42;  // 引用字面量(临时对象)
int x = 10;
const int& ref2 = x;   // 引用变量,但不能通过ref2修改x
// ref2 = 20;  // 错误:不能通过const引用修改值

const引用常用于函数参数,避免复制大对象:

void printString(const std::string& s) {
    std::cout << s << std::endl;
    // s = "modified";  // 错误:不能修改
}

引用作为函数参数

引用作为函数参数可以避免复制,同时允许函数修改实参:

// 通过引用修改参数
void increment(int& num) {
    num++;
}

// 使用const引用避免复制大对象
void printVector(const std::vector<int>& vec) {
    for (int val : vec) {
        std::cout << val << " ";
    }
    std::cout << std::endl;
}

int main() {
    int x = 10;
    increment(x);
    std::cout << x << std::endl;  // 输出11
    
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    printVector(numbers);
    
    return 0;
}

引用作为函数返回值

函数可以返回引用,但需要注意不要返回局部变量的引用:

// 安全:返回全局或静态变量的引用
int& getGlobalRef() {
    static int global_var = 42;
    return global_var;
}

// 危险:返回局部变量的引用
int& getDangling() {
    int local_var = 10;
    return local_var;  // 危险:函数返回后local_var不再存在
}

int main() {
    int& ref = getGlobalRef();
    ref = 100;
    std::cout << getGlobalRef() << std::endl;  // 输出100
    
    // int& dangerous = getDangling();  // 垂悬引用,未定义行为
    
    return 0;
}

右值引用(C++11)

C++11引入了右值引用,主要用于移动语义和完美转发:

// 右值引用使用&&声明
int&& rref = 42;  // 绑定到字面量(右值)

// 左值引用不能绑定到右值
// int& lref = 42;  // 错误,除非使用const引用

// 但右值引用不能绑定到左值
int x = 10;
// int&& rref2 = x;  // 错误

// 除非使用std::move将左值转换为右值引用
int&& rref3 = std::move(x);  // 正确

右值引用的主要用途是实现移动语义,避免不必要的复制:

#include <vector>
#include <string>
#include <utility>  // 为std::move

void moveExample() {
    std::string str = "Hello";
    std::vector<std::string> vec;
    
    // 移动而非复制,将str的内容转移到vector中
    vec.push_back(std::move(str));
    
    // 此时str可能为空(取决于具体实现)
    std::cout << "str after move: " << str << std::endl;
    std::cout << "vec[0]: " << vec[0] << std::endl;
}

函数指针

函数指针是指向函数的指针,可用于实现回调和策略模式:

// 定义函数
int add(int a, int b) {
    return a + b;
}

int subtract(int a, int b) {
    return a - b;
}

int main() {
    // 声明函数指针
    int (*operation)(int, int);
    
    // 指向add函数
    operation = add;
    std::cout << operation(5, 3) << std::endl;  // 输出8
    
    // 指向subtract函数
    operation = subtract;
    std::cout << operation(5, 3) << std::endl;  // 输出2
    
    return 0;
}

函数指针可以作为函数参数,实现回调:

// 使用函数指针作为参数
void processNumbers(int* arr, int size, int (*processor)(int)) {
    for (int i = 0; i < size; i++) {
        arr[i] = processor(arr[i]);
    }
}

// 回调函数
int square(int x) {
    return x * x;
}

int doubleValue(int x) {
    return x * 2;
}

int main() {
    int numbers[5] = {1, 2, 3, 4, 5};
    
    // 将所有元素平方
    processNumbers(numbers, 5, square);
    for (int i = 0; i < 5; i++) {
        std::cout << numbers[i] << " ";  // 输出1 4 9 16 25
    }
    std::cout << std::endl;
    
    // 将所有元素乘以2
    processNumbers(numbers, 5, doubleValue);
    for (int i = 0; i < 5; i++) {
        std::cout << numbers[i] << " ";  // 输出2 8 18 32 50
    }
    std::cout << std::endl;
    
    return 0;
}

指针与引用的应用场景

何时使用指针

  1. 需要表示"没有对象"的概念(空指针)
  2. 需要能够改变引用的对象
  3. 进行动态内存分配
  4. 处理复杂的数据结构(如链表、树)
  5. 需要指针算术运算

何时使用引用

  1. 函数参数需要修改调用者提供的对象
  2. 函数参数是大型对象,想避免复制但不会修改它(const引用)
  3. 在运算符重载中
  4. 需要确保变量始终引用有效对象

指针和引用的选择建议

  1. 优先使用引用,因为它更安全、语法更简单
  2. 如果需要"无对象"的概念或需要改变指向,使用指针
  3. 对于类成员变量,通常使用指针表示可选关系,使用引用表示必需关系
  4. 现代C++中,优先考虑使用智能指针而非原始指针

常见陷阱与错误

未初始化指针

int* p;  // 未初始化,包含垃圾值
*p = 10;  // 危险:可能导致段错误

解决方法:总是初始化指针,通常为nullptr。

内存泄漏

void func() {
    int* p = new int[1000];
    // 忘记 delete[] p;
}  // 退出函数时内存泄漏

解决方法:使用智能指针或确保每个new都有对应的delete。

垂悬指针/引用

int* createNumber() {
    int num = 42;
    return &num;  // 错误:返回局部变量的地址
}

int& createRef() {
    int num = 42;
    return num;  // 错误:返回局部变量的引用
}

解决方法:不要返回局部变量的指针或引用。

越界访问

int arr[5] = {1, 2, 3, 4, 5};
int* p = arr;
std::cout << p[10] << std::endl;  // 错误:越界访问

解决方法:确保指针访问在有效范围内,现代C++中优先使用std::vector和std::array。

对空指针解引用

int* p = nullptr;
*p = 10;  // 错误:空指针解引用,导致段错误

解决方法:在解引用前检查指针是否为空。

最佳实践

  1. 始终初始化指针

    int* p = nullptr;  // 明确表示指针不指向任何对象
    
  2. 使用智能指针

    std::unique_ptr<int> p = std::make_unique<int>(42);
    std::shared_ptr<int> sp = std::make_shared<int>(42);
    
  3. 引用参数使用const

    void func(const std::string& s);  // 避免复制,且不修改s
    
  4. 指针参数验证

    void process(int* p) {
        if (!p) return;  // 检查空指针
        // 处理p指向的数据
    }
    
  5. 使用nullptr而非NULL或0

    int* p = nullptr;  // C++11推荐方式
    
  6. 避免裸指针管理资源

    // 不好的方式
    File* f = new File("data.txt");
    // 使用f...
    delete f;
    
    // 好的方式
    std::unique_ptr<File> f = std::make_unique<File>("data.txt");
    // 使用f...
    // 自动释放资源
    
  7. 避免复杂的指针类型

    // 难以理解
    int* (*fp)(int**, char* const[]);
    
    // 使用using或typedef简化
    using FuncPtr = int* (*)(int**, char* const[]);
    FuncPtr fp;
    

实际应用案例

案例1:简单链表实现

#include <iostream>

// 链表节点结构
struct Node {
    int data;
    Node* next;
    
    Node(int val) : data(val), next(nullptr) {}
};

// 简单链表类
class LinkedList {
private:
    Node* head;
    
public:
    LinkedList() : head(nullptr) {}
    
    // 析构函数释放所有节点内存
    ~LinkedList() {
        Node* current = head;
        while (current) {
            Node* next = current->next;
            delete current;
            current = next;
        }
    }
    
    // 在链表头添加节点
    void prepend(int val) {
        Node* newNode = new Node(val);
        newNode->next = head;
        head = newNode;
    }
    
    // 在链表尾添加节点
    void append(int val) {
        Node* newNode = new Node(val);
        
        if (!head) {
            head = newNode;
            return;
        }
        
        Node* current = head;
        while (current->next) {
            current = current->next;
        }
        current->next = newNode;
    }
    
    // 删除指定值的节点
    void remove(int val) {
        if (!head) return;
        
        // 如果头节点匹配
        if (head->data == val) {
            Node* temp = head;
            head = head->next;
            delete temp;
            return;
        }
        
        // 检查其他节点
        Node* current = head;
        while (current->next && current->next->data != val) {
            current = current->next;
        }
        
        if (current->next) {
            Node* temp = current->next;
            current->next = temp->next;
            delete temp;
        }
    }
    
    // 打印链表
    void print() const {
        Node* current = head;
        while (current) {
            std::cout << current->data << " -> ";
            current = current->next;
        }
        std::cout << "nullptr" << std::endl;
    }
};

int main() {
    LinkedList list;
    
    list.append(1);
    list.append(2);
    list.append(3);
    list.prepend(0);
    
    std::cout << "原始链表: ";
    list.print();
    
    list.remove(2);
    std::cout << "删除2后: ";
    list.print();
    
    return 0;
}

案例2:函数回调系统

#include <iostream>
#include <vector>
#include <functional>  // 用于std::function

// 使用std::function实现更灵活的回调系统
class CallbackSystem {
private:
    std::vector<std::function<void(int)>> callbacks;
    
public:
    // 注册回调函数
    void registerCallback(const std::function<void(int)>& callback) {
        callbacks.push_back(callback);
    }
    
    // 触发所有回调
    void triggerCallbacks(int value) {
        for (const auto& callback : callbacks) {
            callback(value);
        }
    }
};

// 普通函数回调
void printValue(int value) {
    std::cout << "回调值: " << value << std::endl;
}

// 带状态的函数对象
class ValueModifier {
private:
    int modifier;
    
public:
    ValueModifier(int mod) : modifier(mod) {}
    
    void operator()(int value) const {
        std::cout << "修改后的值: " << value * modifier << std::endl;
    }
};

int main() {
    CallbackSystem system;
    
    // 注册普通函数
    system.registerCallback(printValue);
    
    // 注册函数对象
    ValueModifier doubler(2);
    system.registerCallback(doubler);
    
    // 注册lambda表达式
    system.registerCallback([](int value) {
        std::cout << "Lambda: " << value << "的平方是" << value * value << std::endl;
    });
    
    // 触发回调
    system.triggerCallbacks(5);
    
    return 0;
}

案例3:智能指针管理资源

#include <iostream>
#include <memory>
#include <vector>
#include <string>

// 模拟资源类
class Resource {
private:
    std::string name;
    
public:
    Resource(const std::string& n) : name(n) {
        std::cout << "Resource " << name << " created." << std::endl;
    }
    
    ~Resource() {
        std::cout << "Resource " << name << " destroyed." << std::endl;
    }
    
    void use() const {
        std::cout << "Using resource " << name << std::endl;
    }
};

// 展示unique_ptr用法
void uniquePtrDemo() {
    std::cout << "\n=== unique_ptr演示 ===" << std::endl;
    
    // 创建unique_ptr
    std::unique_ptr<Resource> res1 = std::make_unique<Resource>("First");
    
    // 使用资源
    res1->use();
    
    // 无法复制unique_ptr
    // std::unique_ptr<Resource> res2 = res1;  // 编译错误
    
    // 但可以移动所有权
    std::unique_ptr<Resource> res2 = std::move(res1);
    
    // 原指针现在为空
    if (!res1) {
        std::cout << "res1现在为空" << std::endl;
    }
    
    // res2拥有资源
    res2->use();
    
    // 在函数结束时,res2自动销毁,调用Resource析构函数
}

// 展示shared_ptr用法
void sharedPtrDemo() {
    std::cout << "\n=== shared_ptr演示 ===" << std::endl;
    
    // 创建shared_ptr
    std::shared_ptr<Resource> res1 = std::make_shared<Resource>("Shared");
    
    {
        // 创建共享资源的第二个指针
        std::shared_ptr<Resource> res2 = res1;
        std::cout << "引用计数: " << res1.use_count() << std::endl;  // 输出2
        
        // 两个指针都可以使用资源
        res1->use();
        res2->use();
        
        // 作用域结束,res2销毁,但资源依然存在
    }
    
    std::cout << "引用计数: " << res1.use_count() << std::endl;  // 输出1
    res1->use();
    
    // 在函数结束时,res1销毁,引用计数变为0,资源被释放
}

// 展示weak_ptr用法
void weakPtrDemo() {
    std::cout << "\n=== weak_ptr演示 ===" << std::endl;
    
    // weak_ptr用于解决shared_ptr循环引用问题
    std::shared_ptr<Resource> res = std::make_shared<Resource>("Weak Demo");
    
    // 创建weak_ptr,不增加引用计数
    std::weak_ptr<Resource> weak = res;
    
    std::cout << "引用计数: " << res.use_count() << std::endl;  // 输出1
    
    // 使用weak_ptr前需要检查资源是否存在
    if (auto shared = weak.lock()) {
        shared->use();
    } else {
        std::cout << "资源已不存在" << std::endl;
    }
    
    // 释放原始shared_ptr
    res.reset();
    
    // 现在weak_ptr已经失效
    if (auto shared = weak.lock()) {
        shared->use();
    } else {
        std::cout << "资源已不存在" << std::endl;
    }
}

int main() {
    uniquePtrDemo();
    sharedPtrDemo();
    weakPtrDemo();
    
    return 0;
}

总结

指针和引用是C++中最强大且最具挑战性的特性。指针提供了对内存的直接访问和操作能力,但也带来了内存泄漏、段错误等风险。引用则提供了更安全、更简洁的间接访问方式,但缺乏部分指针的灵活性。

关键点回顾:

  1. 指针存储内存地址,通过解引用操作符(*)访问数据
  2. 引用是对象的别名,必须初始化且不能改变所引用的对象
  3. 空指针(nullptr)表示指针不指向任何对象
  4. 指针算术允许在内存中移动指针
  5. new/delete用于动态内存分配和释放
  6. 智能指针(unique_ptr、shared_ptr、weak_ptr)提供自动内存管理
  7. const与指针结合形成多种组合,控制修改权限
  8. 函数指针允许存储和调用函数,用于回调和策略模式
  9. 右值引用(&&)支持移动语义,提高性能

在实际编程中,应该优先使用更安全的抽象(如智能指针、容器类)而非原始指针,遵循RAII原则管理资源,避免常见陷阱如内存泄漏和垂悬指针。理解并正确使用指针和引用,是掌握C++语言的重要一步。

在下一篇文章中,我们将探讨C++中的结构体与枚举,这些是组织和表示数据的重要工具。

参考资料

  1. Bjarne Stroustrup. The C++ Programming Language (4th Edition)
  2. Scott Meyers. Effective Modern C++
  3. cppreference.com - 指针
  4. cppreference.com - 引用
  5. C++ Core Guidelines - 指针和引用

这是我C++学习之旅系列的第六篇技术文章。查看完整系列目录了解更多内容。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.dtcms.com/a/90303.html

相关文章:

  • 5款视觉OCR开源模型
  • WELL健康建筑认证是什么?
  • 2025年渗透测试面试题总结-某 长亭(题目+回答)
  • [M模拟] lc2711. 对角线上不同值的数量差(对角线遍历+前后缀分解)
  • Python条件处理,新手入门到精通
  • 【系统架构设计师】软件质量管理
  • 常见电子元器件介绍
  • Ollama Embedding模型运行与使用
  • Bluetooth Beacons的介绍和技术实现
  • 基于动态 FOF(基金中的基金)策略的基金交易推荐系统的设计与实现思路
  • 【QT】 布局器
  • LDAP安装和基本使用
  • Android Launcher实战:完美复刻iOS风格Hotseat布局优化
  • Clio:具备锁定、用户认证和审计追踪功能的实时日志记录工具
  • Redis原理: List BRPOP分析
  • Android开发代码中设置Margin
  • Docker安装 Nacos 微服务
  • 【WebGIS教程1】WebGIS学习初步知识了解 · 概述
  • Allpaires正交表工具使用
  • Codeforces Round 1003 (Div. 4)
  • 心房颤动新机制:ATM/p53通路早期抑制
  • 最新DeepSeek-V3-0324:AI模型性能提升与新特性解析
  • Xshell远程登录腾讯云高性能应用服务
  • 2.基于多线程的TCP服务器实现
  • ASO A/B 测试:解锁数据驱动的应用商店优化
  • Python爬虫异常处理:自动跳过无效URL
  • 笔记整理三
  • ngx_http_index_t
  • mmdetection安装
  • 微软提出 Logic-RL:基于规则的强化学习释放大语言模型推理能力