news 2026/7/21 23:26:41

C++入门指南:从环境搭建到面向对象编程实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++入门指南:从环境搭建到面向对象编程实战

1. 从“Hello World”到构建思维:为什么C++依然是硬核开发的基石

每次看到有人问“C++教程1”该从哪开始,我总会想起自己第一次在屏幕上打出“Hello, World!”时的那种既兴奋又茫然的感觉。兴奋的是,一个简单的程序就能让机器听从你的指令;茫然的是,这背后庞大的知识体系——指针、内存、面向对象——仿佛一座看不到顶的山。十几年过去了,C++这门语言非但没有过时,反而在游戏引擎、高频交易、嵌入式系统、基础设施软件这些对性能和控制力有极致要求的领域,地位愈发稳固。它就像编程世界里的“重工业”,入门门槛不低,但一旦掌握,你对计算机的理解将不再是隔着一层抽象的黑箱,而是能亲手拧紧每一个螺丝,设计出既高效又可靠的系统。

对于初学者,尤其是被Python、JavaScript这些“快速出活”的语言宠坏的新手来说,C++的第一课往往不是语法,而是思维的转变。它不帮你自动管理内存,没有“万物皆对象”的默认便利,你需要明确地告诉计算机每一字节内存从哪里来、到哪里去。这种“直面硬件”的体验,正是其核心价值所在:它强迫你理解程序在机器层面是如何运行的。因此,这篇“教程1”的目的,远不止教会你配置环境和写几个例子。我希望带你搭建一个坚实的、可迭代的C++学习环境,并理解支撑起第一个程序背后的那些关键概念,为你后续征服指针、面向对象、模板等更复杂的领域铺平道路。无论你是学生备战竞赛,还是开发者想深入系统底层,或是游戏爱好者想一窥引擎奥秘,从这里开始,方向就对了。

2. 环境搭建:选择你的“武器库”与“工作台”

工欲善其事,必先利其器。C++开发的第一步,永远绕不开编译器和集成开发环境的选择。这不像Python装个解释器就能跑,C++需要一个工具链将你写的“高级”代码翻译成机器能直接执行的指令。

2.1 编译器选择:GCC、Clang与MSVC的三国演义

编译器是C++的灵魂翻译官。主流选择有三个,它们各有侧重:

  1. GCC (GNU Compiler Collection):开源世界的基石,跨平台支持极好(Linux, Windows via MinGW, macOS)。它严格遵循标准,生成的代码优化能力强,是学习C++标准特性的绝佳选择。在Linux上,它几乎是默认选项。
  2. Clang/LLVM:后起之秀,以出色的编译速度、清晰易懂的错误和警告信息著称。它对C++新标准支持非常激进和快速。macOS上的Xcode默认使用Clang,在Windows上也可以通过LLVM项目或MSYS2获取。
  3. MSVC (Microsoft Visual C++):Windows平台的“地头蛇”,与Windows SDK和Visual Studio深度集成。如果你主要开发Windows原生应用,特别是带图形界面的程序,MSVC是最自然、支持最好的选择。它的调试器体验在Windows上无出其右。

注意:对于纯新手,我强烈建议在初期固定使用一个平台和一套工具链,避免跨平台兼容性问题分散你的精力。例如,在Windows上学习,可以首选Visual Studio 2022 Community版(自带MSVC),它提供了开箱即用的完整体验。如果你想体验更接近Linux的环境,则可以选择MSYS2 + MinGW-w64 GCC的组合。

2.2 集成开发环境:从“全能战舰”到“轻量编辑器”

IDE能极大提升编码效率。根据你的学习阶段和偏好,有两种主流路径:

  • 一站式重型IDE:Visual Studio

    • 优点:安装包虽然大,但几乎包含了C++开发所需的一切:强大的MSVC编译器、顶尖的调试器、直观的项目管理、丰富的代码分析和重构工具。你不需要关心底层配置,点击“新建项目”就能开始写代码,非常适合初学者快速上手,避免在环境配置上卡住。
    • 如何做:前往Visual Studio官网,下载Community(社区)版,它是完全免费的。安装时,在“工作负载”中务必勾选“使用C++的桌面开发”。安装完成后,新建一个“控制台应用”项目,你就拥有了一个可以直接编译运行的C++项目模板。
  • 编辑器+工具链:VS Code

    • 优点:轻量、灵活、跨平台。VS Code本身只是一个编辑器,但通过强大的扩展生态系统,你可以将它配置成媲美IDE的开发环境。这种方式能让你更清晰地理解编译、链接、调试这些底层过程,对深入学习有益。
    • 核心配置:在VS Code中搭建C++环境,需要几个关键步骤:
      1. 安装编译器:如前所述,通过MSYS2安装MinGW-w64 GCC,或直接安装Clang。
      2. 安装VS Code C++扩展:微软官方提供的“C/C++”扩展,提供代码智能感知、调试等功能。
      3. 配置任务(Tasks):在项目目录下创建.vscode/tasks.json文件,告诉VS Code如何调用编译器(如g++)来编译你的代码。一个简单的配置是定义“build”任务,执行g++ -g main.cpp -o main.exe这样的命令。
      4. 配置调试(Launch):创建.vscode/launch.json文件,配置调试器(如GDB)的启动参数,使你能够设置断点、单步执行、查看变量。
    • 实操心得:很多新手卡在“* 正在执行任务: c/c++: gcc.exe 生成活动文件...”或“找不到c/c++编辑器设置”这类错误上,根本原因往往是环境变量PATH没有正确设置,导致VS Code找不到g++.exegdb.exe。务必确保你的编译器bin目录(例如C:\msys64\mingw64\bin)已添加到系统的环境变量PATH中,并重启VS Code。

2.3 第一个项目的创建与结构

无论你用哪种方式,创建的第一个项目都应该是一个简单的控制台程序。项目里通常至少包含两个文件:

  • main.cpp:你的源代码文件,程序入口点。
  • 对于VS Code等编辑器,可能还需要tasks.json,launch.json,c_cpp_properties.json等配置文件。
  • 对于Visual Studio,则会自动生成.vcxproj项目文件和.sln解决方案文件。

一个健康的初学者项目目录应该清晰明了,避免所有文件堆在根目录。可以这样组织:

my_first_cpp_project/ ├── src/ │ └── main.cpp ├── include/ (将来存放头文件) ├── build/ (存放编译生成的文件,如.exe, .o) └── .vscode/ (VS Code的配置文件)

main.cpp中,我们写下经典的起点:

#include <iostream> int main() { std::cout << "Hello, World!" << std::endl; return 0; }

编译并运行它,看到终端输出“Hello, World!”,你的C++长征就正式迈出了第一步。这个简单的过程背后,其实隐藏着C++程序的几个核心生命周期:预处理(处理#include)、编译(将.cpp变成机器码.obj)、链接(将多个.obj和库文件合并成可执行文件)、运行

3. 核心语法基石:理解“内存视角”下的变量与函数

C++的语法是它表达能力的载体。在第一阶段,你需要建立的最重要观念是:程序是数据和操作的集合,而数据存在于内存中

3.1 基本数据类型与变量:内存的“格子”

在C++中,声明一个变量,就是在内存中申请一个特定大小的“格子”,并给它起个名字。基本数据类型决定了这个格子的大小和解释方式。

  • int:整数,通常占4字节。int age = 25;
  • double:双精度浮点数,通常占8字节。double price = 19.99;
  • char:字符,占1字节。char grade = 'A';
  • bool:布尔值,truefalse

关键技巧:使用sizeof(类型或变量)运算符可以查看该类型在你的编译平台上实际占用的字节数。例如std::cout << sizeof(int) << std::endl;。这能帮助你建立直观的内存大小概念。

变量的生命周期与作用域是初学者容易混淆的点。在函数内部声明的变量(局部变量),生命周期仅限于该函数执行期间,函数结束即被销毁。在函数外部声明的变量(全局变量),在整个程序运行期间都存在。应尽量避免滥用全局变量,因为它会使得程序状态难以追踪和理解。

3.2 指针与引用:内存地址的“导航仪”

这是C++的第一个“分水岭”。理解了指针,你就理解了C++操控内存的精髓。

  • 指针:一个存储内存地址的变量。int* ptr = &age;这里,&是取地址符,ptr保存了变量age所在的内存地址。*ptr则是解引用,表示“访问ptr所指向地址的值”,即age的值。
  • 引用:一个变量的别名。int& ref = age;此后,ref就是age的另一个名字,对ref的操作直接作用于age。引用必须在定义时初始化,且不能改变其绑定的对象。

为什么需要它们?

  1. 避免大对象拷贝:当需要向函数传递一个大的结构体或类对象时,传递指针或引用(通常是const引用)比传递整个对象的副本要高效得多。
  2. 函数修改实参:C++默认是值传递,函数内修改形参不影响实参。通过传递指针或引用,函数可以修改调用者传来的变量。
  3. 动态内存管理:这是指针的核心舞台。使用new运算符在堆(Heap)上申请内存,返回的是该内存的地址(指针)。这块内存的生命周期由你手动控制,需要用delete释放。
// 动态内存示例 int* dynamicArray = new int[10]; // 在堆上申请10个int的空间 for(int i = 0; i < 10; ++i) { dynamicArray[i] = i * i; } // ... 使用这个数组 delete[] dynamicArray; // 必须手动释放,否则内存泄漏

致命陷阱:忘记delete会导致内存泄漏;对已经delete的指针再次delete会导致未定义行为(程序崩溃是常见结果)。现代C++推荐使用智能指针(如std::unique_ptr,std::shared_ptr)来自动化这个过程,但在学习初期,理解手动管理的原理至关重要。

3.3 函数:封装操作的单元

函数是组织代码的基本模块。C++函数涉及几个关键概念:

  • 函数签名:包括返回类型、函数名、参数列表。它决定了函数的调用方式。
  • 参数传递方式:值传递、指针传递、引用传递。这是理解函数行为的关键。
  • 函数重载:允许同一作用域内有多个同名函数,只要它们的参数列表不同(类型、数量、顺序)。编译器根据调用时提供的实参来决定调用哪个版本。
  • 递归函数:函数调用自身。递归是解决某些问题(如树遍历、分治算法)的优雅方式,但必须定义清晰的递归终止条件,否则会导致栈溢出。
// 值传递 vs 引用传递 void swapByValue(int a, int b) { int temp = a; a = b; b = temp; } // 无效,只交换了副本 void swapByReference(int& a, int& b) { int temp = a; a = b; b = temp; } // 有效,交换了原变量 // 函数重载 void print(int i) { std::cout << "Integer: " << i << std::endl; } void print(double d) { std::cout << "Double: " << d << std::endl; }

4. 面向对象入门:用类和对象构建抽象世界

C++的核心优势之一是其对面向对象编程(OOP)的强大支持。OOP让我们能用更接近现实世界的方式来建模和解决问题。

4.1 类与对象:蓝图与实物

是自定义的数据类型,是对象的蓝图。它封装了数据(成员变量)操作这些数据的方法(成员函数)对象是类的实例,是根据蓝图创建出来的具体“实物”。

class Rectangle { private: // 访问修饰符:私有成员,只能在类内部访问 double width; double height; public: // 公有成员,可以在类外部访问 // 构造函数:在创建对象时自动调用,用于初始化 Rectangle(double w, double h) : width(w), height(h) { std::cout << "A rectangle is created." << std::endl; } // 成员函数 double area() const { // const 成员函数,承诺不修改对象状态 return width * height; } void setWidth(double w) { if(w > 0) width = w; // 可以通过函数添加数据验证逻辑 } double getWidth() const { return width; } // 析构函数:在对象销毁时自动调用,用于清理资源 ~Rectangle() { std::cout << "Rectangle destroyed." << std::endl; } }; int main() { Rectangle rect(5.0, 3.0); // 创建对象,调用构造函数 std::cout << "Area: " << rect.area() << std::endl; // 调用成员函数 // rect.width = 10; // 错误!width是private成员 rect.setWidth(10.0); // 正确,通过公有接口修改 return 0; } // rect离开作用域,自动调用析构函数

4.2 三大特性:封装、继承、多态

  1. 封装:将数据和对数据的操作捆绑在一起,并对外隐藏内部实现细节(通过private/protected)。这提高了代码的安全性和可维护性。如上例,我们不允许直接修改width,而是通过setWidth函数,这让我们可以在函数内加入有效性检查。

  2. 继承:允许我们基于已有的类创建新类,新类(派生类)继承原有类(基类)的属性和方法,并可以添加新的或覆盖已有的。这实现了代码的复用和层次化抽象。

    class Shape { // 基类 public: virtual double area() const = 0; // 纯虚函数,使Shape成为抽象类 virtual ~Shape() {} // 虚析构函数,确保正确释放派生类资源 }; class Circle : public Shape { // 派生类,公有继承 private: double radius; public: Circle(double r) : radius(r) {} double area() const override { // 重写(覆盖)基类的虚函数 return 3.14159 * radius * radius; } };
  3. 多态:“多种形态”。通过基类的指针或引用来操作派生类对象,并根据对象的实际类型来调用相应的方法。这需要借助虚函数来实现。

    void printArea(const Shape& shape) { std::cout << "Area: " << shape.area() << std::endl; // 多态调用 } int main() { Circle circle(5.0); printArea(circle); // 传入Circle对象,但形参是Shape引用,调用的是Circle::area() }

    核心要点:多态是面向对象设计模式的基石。它允许我们编写出更通用、更灵活的代码。基类的析构函数通常应声明为virtual,以确保通过基类指针删除派生类对象时,能正确调用派生类的析构函数,避免资源泄漏。

5. 标准库初探:站在巨人的肩膀上

C++标准库(STL)是一个强大的、泛型的组件集合,它提供了容器、算法、迭代器等工具,能极大提升开发效率。初学者应尽早熟悉其中最常用的部分。

5.1 容器:数据的“百宝箱”

容器用于存储和管理数据集合。最常用的有:

  • std::vector:动态数组。在尾部插入/删除效率高,支持随机访问。是默认首选的序列容器。
    #include <vector> std::vector<int> scores = {90, 85, 88}; scores.push_back(95); // 添加元素 for(int score : scores) { // 范围for循环 std::cout << score << " "; }
  • std::map/std::unordered_map:关联容器,存储键值对。
    • std::map基于红黑树,键值有序。
    • std::unordered_map基于哈希表,查找效率通常更高(平均O(1)),但键值无序。
    #include <map> #include <string> std::map<std::string, int> studentAge; studentAge["Alice"] = 20; studentAge["Bob"] = 21; std::cout << studentAge["Alice"] << std::endl; // 输出 20

5.2 算法与迭代器:通用操作的利器

STL算法(在<algorithm>头文件中)提供了一系列通用操作,如排序、查找、计数等。它们通过迭代器与容器协作。迭代器可以理解为指向容器中元素的“智能指针”。

#include <algorithm> #include <vector> std::vector<int> nums = {5, 2, 8, 1, 9}; std::sort(nums.begin(), nums.end()); // 使用迭代器指定范围进行排序 // 现在 nums 变为 {1, 2, 5, 8, 9} auto it = std::find(nums.begin(), nums.end(), 5); // 查找值为5的元素 if (it != nums.end()) { std::cout << "Found: " << *it << std::endl; // 解引用迭代器获取值 }

使用STL算法能让你写出更简洁、更不易出错、且通常更高效的代码,因为它们是由专家实现并高度优化的。

6. 实战演练:从零构建一个简易通讯录管理程序

让我们把前面学到的知识串联起来,实现一个命令行下的简易通讯录程序。这个程序将用到类、vector、文件输入输出等。

6.1 需求分析与类设计

我们需要一个Contact类来表示单个联系人,一个AddressBook类来管理所有联系人。

  • Contact类属性:姓名(std::string)、电话号码(std::string)。
  • AddressBook类功能:添加联系人、显示所有联系人、根据姓名查找、将通讯录保存到文件、从文件加载。

6.2 核心代码实现

// contact.h #ifndef CONTACT_H #define CONTACT_H #include <string> class Contact { private: std::string name; std::string phone; public: Contact() = default; Contact(const std::string& n, const std::string& p) : name(n), phone(p) {} // Getter 和 Setter std::string getName() const { return name; } void setName(const std::string& n) { name = n; } std::string getPhone() const { return phone; } void setPhone(const std::string& p) { phone = p; } // 显示联系人信息 void display() const; }; #endif
// address_book.h #ifndef ADDRESS_BOOK_H #define ADDRESS_BOOK_H #include "contact.h" #include <vector> #include <string> class AddressBook { private: std::vector<Contact> contacts; public: void addContact(const Contact& contact); void displayAll() const; Contact* findContactByName(const std::string& name); // 返回指针,便于修改 bool saveToFile(const std::string& filename) const; bool loadFromFile(const std::string& filename); }; #endif
// address_book.cpp #include "address_book.h" #include <iostream> #include <fstream> // 文件流 #include <algorithm> void AddressBook::addContact(const Contact& contact) { contacts.push_back(contact); std::cout << "Contact added successfully." << std::endl; } void AddressBook::displayAll() const { if (contacts.empty()) { std::cout << "Address book is empty." << std::endl; return; } for (const auto& contact : contacts) { contact.display(); } } Contact* AddressBook::findContactByName(const std::string& name) { // 使用算法 find_if 和 lambda 表达式进行查找 auto it = std::find_if(contacts.begin(), contacts.end(), [&name](const Contact& c) { return c.getName() == name; }); if (it != contacts.end()) { return &(*it); // 返回找到的联系人的地址 } return nullptr; // 未找到 } bool AddressBook::saveToFile(const std::string& filename) const { std::ofstream outFile(filename); if (!outFile.is_open()) { std::cerr << "Error: Cannot open file for writing." << std::endl; return false; } for (const auto& contact : contacts) { outFile << contact.getName() << "," << contact.getPhone() << "\n"; // 简单CSV格式 } outFile.close(); return true; } bool AddressBook::loadFromFile(const std::string& filename) { std::ifstream inFile(filename); if (!inFile.is_open()) { std::cerr << "Error: Cannot open file for reading." << std::endl; return false; } contacts.clear(); // 清空现有数据 std::string line; while (std::getline(inFile, line)) { size_t commaPos = line.find(','); if (commaPos != std::string::npos) { std::string name = line.substr(0, commaPos); std::string phone = line.substr(commaPos + 1); contacts.emplace_back(name, phone); // 使用 emplace_back 直接构造 } } inFile.close(); return true; }
// main.cpp - 简单的命令行交互 #include "address_book.h" #include <iostream> #include <limits> void clearInputBuffer() { std::cin.clear(); std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); } int main() { AddressBook myBook; int choice = 0; std::string filename = "contacts.txt"; // 尝试从文件加载现有通讯录 myBook.loadFromFile(filename); do { std::cout << "\n=== Address Book Manager ===" << std::endl; std::cout << "1. Add Contact" << std::endl; std::cout << "2. View All Contacts" << std::endl; std::cout << "3. Search Contact" << std::endl; std::cout << "4. Save and Exit" << std::endl; std::cout << "Enter your choice: "; std::cin >> choice; clearInputBuffer(); // 清除输入缓冲区残留的换行符 switch (choice) { case 1: { std::string name, phone; std::cout << "Enter name: "; std::getline(std::cin, name); std::cout << "Enter phone: "; std::getline(std::cin, phone); myBook.addContact(Contact(name, phone)); break; } case 2: myBook.displayAll(); break; case 3: { std::string name; std::cout << "Enter name to search: "; std::getline(std::cin, name); Contact* found = myBook.findContactByName(name); if (found) { std::cout << "Found: "; found->display(); } else { std::cout << "Contact not found." << std::endl; } break; } case 4: if (myBook.saveToFile(filename)) { std::cout << "Address book saved to " << filename << ". Goodbye!" << std::endl; } break; default: std::cout << "Invalid choice. Please try again." << std::endl; } } while (choice != 4); return 0; }

这个实战项目虽然简单,但涵盖了从类设计、STL容器使用、文件I/O到基本用户交互的多个核心知识点。你可以在此基础上扩展功能,比如删除联系人、修改信息、按电话号码查找等。

7. 避坑指南与高效学习路径

学习C++的路上布满“坑”,提前了解能让你少走很多弯路。

7.1 编译与链接常见错误

  1. “undefined reference to ...” 链接错误:这是最常见的问题之一。意味着编译器找到了函数声明(通常在头文件中),但链接器在所有的.obj文件和库中找不到该函数的定义。

    • 排查:检查你是否实现了该函数(在.cpp文件中写了函数体),或者是否将包含了该函数实现的源文件(.cpp)加入了编译(在IDE的项目中,或在命令行编译时列出了该文件)。
    • 库文件缺失:如果你使用了第三方库(如OpenCV),需要确保在链接器设置中正确指定了库文件(.lib,.a)的路径和名称。
  2. “error: microsoft visual c++ 14.0 or greater is required”:这通常发生在Windows上尝试用pip安装某些Python包时,这些包包含需要编译的C++扩展。解决方案是安装Microsoft Visual C++ Build Tools或完整的Visual Studio(包含MSVC编译器)。

  3. 头文件包含错误#include <iostream>(尖括号)用于包含标准库或系统头文件。#include "myheader.h"(双引号)用于包含你自己项目中的头文件。混用可能导致找不到文件。

7.2 内存管理陷阱

  1. 内存泄漏new了忘记delete,或者new[]了却用delete而非delete[]释放。养成习惯:在写new的时候,立刻把对应的delete也写上。更好的习惯是:优先使用STL容器(如vector)和智能指针,让它们帮你管理内存。
  2. 悬空指针:指针指向的内存已被释放,但指针本身未被置空。后续解引用会导致崩溃。释放内存后,立即将指针设为nullptr
  3. 野指针:未初始化的指针。声明指针时,如果没有立即赋值,应初始化为nullptr

7.3 高效学习路径建议

  1. 夯实基础:不要急于求成。把变量、指针、引用、函数、类、继承这些基础概念彻底理解。多写代码,多调试。理解每一个错误信息。
  2. 理解而非记忆:C++语法细节繁多(比如const的各种位置含义),死记硬背效果很差。理解其设计意图和适用场景(如const成员函数表示该函数不会修改对象状态),才能灵活运用。
  3. 阅读优秀代码:在GitHub上找一些高质量的开源C++项目(规模适中的),阅读其源码,学习其代码组织、命名规范、设计模式。
  4. 实践驱动:学完一个知识点,立刻用它做一个小练习或改进之前的项目。比如学完vector,就把通讯录里用数组存储的部分改成vector
  5. 善用调试器:学会使用调试器(Visual Studio的调试器、GDB等)设置断点、单步执行、查看变量和调用栈。这是定位复杂逻辑错误的终极武器。
  6. 拥抱现代C++:在掌握C++11/14的核心特性(如自动类型推导auto、范围for循环、智能指针、Lambda表达式、移动语义)后,积极使用它们。它们能让你的代码更安全、更简洁、更高效。不要停留在C++98的风格里。

C++的学习曲线确实陡峭,但每翻过一座山,你看到的风景和获得的能力提升,是其他语言难以比拟的。它教会你的不仅是语法,更是一种严谨、高效、贴近机器的思维方式。从配置好环境、理解第一个指针、写出第一个类开始,一步步积累,你终将能够驾驭这门强大的语言,去构建那些真正考验性能与智慧的软件系统。记住,编程是一门实践的手艺,多写,多思考,多踩坑,然后爬出来,这就是成长的全部过程。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/20 17:39:46

鸿蒙原生开发手记:徒步迹 - 文件选择与上传组件

鸿蒙原生开发手记&#xff1a;徒步迹 - 文件选择与上传组件 实现文件选择和上传功能组件 前言 文件选择和上传是移动应用中常见的功能&#xff0c;如上传头像、路线图片、轨迹文件等。HarmonyOS 提供了 DocumentViewPicker 和 FilePicker 等系统文件选择器&#xff0c;结合网络…

作者头像 李华
网站建设 2026/7/21 23:25:38

ShardingSphere-JDBC分库分表实战与原理详解

1. ShardingSphere-JDBC核心概念解析ShardingSphere-JDBC作为Apache顶级开源项目ShardingSphere的核心组件&#xff0c;本质上是一个增强版的JDBC驱动。我在实际项目中使用它解决过多次分库分表难题&#xff0c;其设计理念是在JDBC层提供透明化的数据分片服务。与常规中间件不同…

作者头像 李华
网站建设 2026/7/20 17:36:18

gpt-tokenizer实战:构建AI聊天应用的令牌计数与成本估算

gpt-tokenizer实战&#xff1a;构建AI聊天应用的令牌计数与成本估算 【免费下载链接】gpt-tokenizer The fastest JavaScript BPE Tokenizer Encoder Decoder for OpenAIs GPT models (gpt-5, gpt-o*, gpt-4o, etc.). Port of OpenAIs tiktoken with additional features. 项…

作者头像 李华
网站建设 2026/7/20 17:31:46

小程序毕设选题推荐:百货零售供应链数字化智能管理系统 移动端百货供应链进销存服务平台 中小型商超物资供应链管理小程序【附源码、mysql、文档、调试+代码讲解+全bao等】

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/7/20 17:29:55

JavaSE 基础语法 -逻辑控制

在我们平常的生活中&#xff0c;我们每天都在进行各种判断和重复操作&#xff0c;例如&#xff1a; 如果今天下雨&#xff0c;就带伞。如果考试成绩大于或者等于60分&#xff0c;就及格。每天坚持跑步五公里&#xff0c;直到完成目标。 程序也是一样&#xff0c;如果程序只能从…

作者头像 李华