C++和标准库速成(八)——指针、动态数组、const、constexpr和consteval
目录
- 1. 指针和动态数组
- 1.1 栈和自由存储区
- 1.2 使用指针
- 1.3 动态分配的数组
- 1.4 空指针常量
- 2. const
- 2.1 const修饰类型
- 2.2 const与指针
- 2.3 使用const保护参数
- 2.4 const方法(建议)
- 3. constexpr
- 4. consteval
- 参考
1. 指针和动态数组
动态内存允许所创建的程序具有在编译期大小可变的数据,大多数复杂程序都会以某种方式使用动态内存。
1.1 栈和自由存储区
C++程序中的内存分为两部分——栈和自由存储区。将栈可视化的一种方式就是将其看作一幅纸牌,当前顶部的牌代表程序的当前作用域,通常是正在执行的函数。当前函数中声明的所有变量将占用顶部栈帧的内存。如果当前函数foo()调用了另一个函数bar(),一张新牌就会被放在牌堆上面,这样bar()就会拥有自己的栈帧供其运行。任何从foo()传递给bar()的参数都会从foo()栈帧复制到bar()栈帧。
栈帧很好,因为它为每个函数提供了独立的内存空间。如果在foo()栈帧中声明了一个变量,那么除非专门要求,否则调用bar()函数不会更改该变量。此外,foo()函数执行完毕时,栈帧就会消失,该函数中声明的所有变量都不会再占用内存。在栈上分配内存的变量不需要由程序员释放内存,这个过程是自动完成的。
自由存储区是与当前函数或栈帧完全独立的内存区域。如果想在函数调用结束之后仍然保存其中声明的变量,可以将变量放到自由存储区中。自由存储区的结构不如栈复杂,可以将它当作一堆位。程序可在任何时候向其中添加新的位或修改已有的位。必须确保释放在自由存储区上分配的任何内存,这个过程不会自动完成,除非使用了智能指针。
警告:这里介绍指针是因为你将会遇到它们,尤其是在遗留代码中。但是在新代码中,仅在不涉及所有权的情况下,才允许使用此类原始/裸指针。
1.2 使用指针
可以通过显式分配内存的方式将任何东西放到自由存储区中。例如,要将一个整数放在自由存储区中,需要为其分配内存,但是首先需要声明一个指针:
int* myIntegerPointer;
int类型后面的*表示,所声明的变量引用/指向某个整数内存。可将指针看作指向动态分配自由存储区中内存的一个箭头,它还没有指向任何内容,因为你还没有把它指派给任何内容,它是一个未初始化的变量。在任何时候都应避免使用未初始化的变量, 尤其是未初始化的指针,因为它们会指向内存中的某个随机位置。使用这种指针很可能使程序崩溃。这就是总是应同时声明和初始化指针的原因。如果不希望立即分配内存,可以把它们初始化为空指针nullptr。
int* myIntegerPointer { nullptr };
空指针是一个特殊的默认值,有效的指针都不含该值,在布尔表达式中使用时会被转换成false。
if ( !myIntegerPointer ) {/* myIntegerPointer is a null pointer. */
}
使用new操作符分配内存:
myIntegerPointer = new int;
在此情况下,指针指向一个整数值的地址。为访问这个值,需要对指针解引用。可将解引用看作沿着指针箭头寻找自由存储区中实际的值。为给自由存储区中新分配的整数赋值,可采用如下代码:
*myIntegerPointer = 8;
注意:这并非将myIntegerPointer的值设置为8,在此并没有改变指针,而是改变了指针所指的内存。如果真要重新设置指针的值,它将指向内存地址8,这可能是一个随机的无用内存单元,最终会导致程序崩溃。
使用完动态分配的内存后,需要使用delete操作符释放内存。为防止在释放指针所指的内存后再使用指针,建议将指针设置为nullptr。
delete myIntegerPointer;
myIntegerPointer = nullptr;
警告:在解引用之前指针必须有效。对null或未初始化的指针解引用会导致未定义的行为。程序可能崩溃,也可能继续运行,却给出奇怪的结果。
指针并非总是指向自由存储区内存,可声明一个指向栈中变量甚至指向其他指针的指针。为让指针指向某个变量,需要使用取址运算符&。
int i { 8 };
int* myIntegerPointer { &i }; // points to the variable with the value 8.
C++使用特殊语法处理指向结构体或类的指针。从技术上讲,如果指针指向某个结构体或类,可以首先用*对指针解引用,然后使用普通的.语法访问其中的字段,如下面的代码所示,在此假定存在一个名为getEmployee()的函数,它返回一个指向Employee实例的指针。
Employee* anEmployee { getEmployee() };
std::cout << (*anEmployee).salary << "\n";
此语法有一点混乱。->运算符允许同时对指针解引用并访问字段。下面的代码与前面的代码等效,但阅读起来更方便。
Employee* anEmployee { getEmployee() };
std::cout << anEmployee->salary << "\n";
逻辑短路可与指针一起使用,以免使用无效指针,如下所示。
bool isValidSalary { ( anEmployee && anEmployee->salary > 0 ) };
或者稍微详细一点:
bool isValidSalary { ( anEmployee != nullptr && anEmployee->salary > 0 ) };
仅当anEmployee有效时,才对其进行解引用以获取salary。如果它是一个空指针,则逻辑运算短路,不再解引用anEmployee指针。
1.3 动态分配的数组
自由存储区也可以用于动态分配数组。使用new[]操作符可给数组分配内存:
int arraySize { 8 };
int* myVariableSizedArray { new int[arraySize] };
这条语句分配足够的内存,用于存储arraySize个整数。下图展示了执行这条语句后栈和自由存储区的情况。可以看到指针变量仍在栈中,当动态创建的数组在自由存储区中。

现在已经分配了内存,可将myVarialbeSizedArray当作基于栈的普通数组使用。
myVariableSizedArray[3] = 2;
使用完这个数组后,应该将其从自由存储区中删除,这样其他变量就可以使用这块内存。在C++中,可使用delete[]操作符完成这一任务。
delete[] myVariableSizedArray;
myVariableSizedArray = nullptr;
delete后的方括号表明所删除的是一个数组!
注意:避免使用C中的malloc()和free(),而使用new和delete,或者使用new[]和delete[]。
警告:在C++中,每次调用new时,都必须相应地调用delete;每次调用new[]时,都必须相应地调用delete[],以避免内存泄漏。如果未调用delete或delete[],或调用不匹配,会导致内存泄漏。
1.4 空指针常量
在C++11之前,常量NULL用于表示空指针。NULL只是简单地定义为常量0,这会导致一些问题。分析下面的例子:
void func(int i) {std::cout << "func(int)" << "\n";
}int main() {func(NULL);
}
这段代码定义了一个func()函数,它有一个整型参数。main()函数通过参数NULL调用func(),NULL被当作一个空指针常量。但是,NULL不是指针,而等价于整数0,所以实际调用的是func(int)。这可能不是预期的行为,因此,有些编译器会给出警告。
可引入真正的空指针常量nullptr来解决这个问题。下面的代码使用了真正的空指针,并且导致了编译错误,因为我们没有重载参数为指针的func()版本。
func(nullptr);
2. const
在C++中有很多方法使用const关键字。所有用法都是相关的,但存在微妙的差别。基本上,const是constant的缩写,它表示某些内容保持不变。编译器通过将任何试图将其更改的行为标记为错误,用来保证此要求。此外,启用优化后,编译器可以利用此知识生成更好的代码。
2.1 const修饰类型
如果已经认为关键字const与常量有一定关系,就正确地揭示了它的一种用法。在C语言中,程序员经常使用预处理器的#define机制声明一个符号名称,其值在程序执行时不会变化,如版本号。在C++中,鼓励程序员使用const取代#define定义常量。使用const定义常量就像定义变量一样,只是编译器保证代码不会改变这个值。实例如下:
const int versionNumberMajor { 2 };
const int versionNumberMinor { 1 };
const std::string productName { "Super Hyper Net Modulator" };
const double PI { 3.141592653589793238462 };
可以将任何变量标记为const,包括全局变量和类中的数据成员。
2.2 const与指针
当变量通过指针包含一层或多层间接时,应用const将变得棘手。考虑以下代码:
int* ip { nullptr };
ip = new int[10];
ip[4] = 5;
假设你决定对ip使用const。暂时不要考虑这样做的用处,考虑它意味着什么。你要是阻止ip变量本身被更改,还是要阻止其指向的值被更改?也就是说,你要阻止第二行还是第三行?
为了防止指向的值被修改,可以用下面这种方式将const添加到ip的声明中。
const int* ip { nullptr };
ip = new int[10];
ip[4] = 5; // does not compile!
现在,你无法修改ip指向的值。一种替代的但在语义上等效的书写方式如下:
int const* ip { nullptr };
ip = new int[10];
ip[4] = 5; // does not compile!
将const放在int之前还是之后在功能上没有区别。
如果想将ip本身标记为const,而不是它指向的值,需要这样写:
int* const ip { nullptr };
ip = new int[10]; // does not compile!
ip[4] = 5; // error: dereferencing a null pointer.
现在,ip本身无法更改,编译器要求你在声明它时对其进行初始化,可以使用如先前代码中的nullptr或如下所示的新分配的内存。
int* const ip { new int[10] };
ip[4] = 5;
也可以像下面这样,将指针本身和指针所指的值都标记为const。
int const* const ip { nullptr };
这是另一种等效的写法:
const int* const ip { nullptr };
尽管此语法可能看起来令人困惑,但实际上存在一个简单的规则:const关键字作用于其直接左侧的内容。再次考虑这一行:
int const* const ip { nullptr };
从左到右,第一个const直接位于单词int的右侧,因此它适用于ip指向的int,指定你不能更改IP指向的值。第二个const直接位于*的右侧,因此它适用于指向int的指针,该指针是ip变量,指定你不能更改ip指针本身。
该规则令人困惑的原因是一个例外。也就是,第一个const可以放在变量之前,如下所示。
const int* const ip { nullptr };
这种例外语法比其他语法更常遇到。
可以将这个规则扩展到任意级别的间接级别,正如以下示例:
const int* const* const* const ip { nullptr };
该声明中存在3个*表明这是一个三级指针,从右到左,第一个const直接位于*的右边,表明第三级指针ip是常量,不能修改它指向的地址;第二个const直接位于*的右边,表明第二级指针*ip是常量,不能修改它指向的地址;第三个const直接位于*的右边,表明第一级指针**ip是常量,不能修改它指向的地址;第四个const表明**ip解引用后的值为常量,不能修改。
2.3 使用const保护参数
在C++中,可将非const变量转换为const变量。为什么想这样做呢?这提供了一定程度的保护,防止其他代码修改变量。如果你调用同事编写的一个函数,并且想确保这个函数不会传递改变给它的实参,可以告诉同事让函数采用const参数。如果这个函数试图改变参数的值,就不会让编译通过。
在下面的代码中,调用mysteryFunction()时string自动转换为const string。如果编写mysteryFunction()的人员试图修改所传递字符串的值,代码将无法编译。有绕过这个限制的方法,但是需要有意识地这么做,C++只是阻止无意义地修改const变量。
void mysteryFunction(const std::string* someString) {*someString = "Test"; // will not compile.
}int main() {std::string myString { "The string" };mysteryFunction(&myString);
}
还可以在原始类型参数上使用const,以防止在函数体中意外修改它们。例如,以下函数具有const整型参数。在函数体中,无法修改整数param。如果尝试对其修改,则编译器将生成错误。
void func(const int param) {/* not allowed to change param... */
}
2.4 const方法(建议)
const关键字的第二个用途是将类方法标记为const,以防止它们修改类的数据成员。可以修改前面介绍的AirlineTicket类,以将所有只读方法标记为const。如果任何const方法尝试修改AirlineTicket数据成员之一,则编译器将提示错误。
export class AirlineTicket {
public:double calculatePriceInDollars() const;std::string getPassengerName() const;void setPassengerName(std::string name);int getNumberOfMiles() const;void setNumberOfMiles(int miles);bool hasEliteSuperRewardsStatus() const;void setHasEliteSuperRewardsStatus(bool status);private:std::string m_passengerName { "Unknown Passenger" };int m_numberOfMiles { 0 };bool m_hasEliteSuperRewardsStatus { false };
};// all methods omitted...
注意:为了遵循const-correctness原则,建议将不改变对象的任何数据成员的成员函数声明为const。与非const成员函数也被称为赋值函数相对,这些成员函数也称为检查器。
3. constexpr
C++中一直有常量表达式的概念,即在编译器求值的表达式。在某些情况下,必须使用常量表达式。例如,定义数组时,数组的大小需要为常量表达式。由于此限制,以下代码在C++中无效。
const int getArraySize() {return 32;
}int main() {std::array<int, getArraySize()> myArray {}; // invalid in C++.
}
使用constexpr关键字,getArraySize()函数可以被重定义,允许在常量表达式中调用它。
constexpr int getArraySize() {return 32;
}int main() {std::array<int, getArraySize()> myArray {}; // ok.
}
你甚至可以这样做:
int myArray[getArraySize() + 1]; // ok.
将函数声明为constexpr对函数的功能施加了很多限制,因为编译器必须能够在编译期对函数求值。例如,允许constexpr函数调用其他constexpr函数,但不允许调用任何非constexpr函数。这样的函数不允许有任何副作用,也不能引发任何异常。
通过定义constexpr构造函数,可以创建用户自定义类型的常量表达式变量。与constexpr函数一样,constexpr类也有很多限制。下面的Rect类定义了constexpr构造函数,他还定义了执行一些计算的constexpr getArea()方法。
class Rect {
public:constexpr Rect(std::size_t width, std::size_t height) :m_width { width }, m_height { height } {}constexpr std::size_t getArea() const {return m_width * m_height;}
private:std::size_t m_width {}, m_height {};
}
使用这个类声明constexpr对象是非常容易的。
constexpr Rect r { 8, 2 };
std::array<int, r.getArea()> myArray {}; // ok.
4. consteval
上一节讨论的constexpr关键字指定函数在编译期执行,但不能保证一定在编译期执行。采用以下constexpr函数:
constexpr double inchToMm(double inch) {return inch * 25.4;
}
如果按以下方式调用,则会在需要时在编译期对函数求值。
constexpr double const_inch { 6.0 };
constexpr double mml { inchToMm(const_inch) }; // at compile time.
然而,如果按以下方式调用,函数将不会在编译期被求值,而是在运行时。
double dynamic_inch { 8.0 };
double mm2 { inchToMm(dynamic_inch) }; // at run time.
如果确实希望保证始终在编译期对函数进行求值,则需要使用C++20的consteval关键字将函数转换为所谓的立即函数。可以按照如下方式更改inchToMm()函数:
consteval double inchToMm(double inch) {return inch * 25.4;
}
参考
[比] 马克·格雷戈勒著 程序喵大人 惠惠 墨梵 译 C++20高级编程(第五版)
相关文章:
C++和标准库速成(八)——指针、动态数组、const、constexpr和consteval
目录 1. 指针和动态数组1.1 栈和自由存储区1.2 使用指针1.3 动态分配的数组1.4 空指针常量 2. const2.1 const修饰类型2.2 const与指针2.3 使用const保护参数2.4 const方法(建议) 3. constexpr4. consteval参考 1. 指针和动态数组 动态内存允许所创建的程序具有在编…...
超声重建,3D重建 超声三维重建,三维可视化平台 UR 3D Reconstruction
1. 超声波3D重建技术的实现方法与算法 技术概述 3D超声重建是一种基于2D超声图像生成3D体积数据的技术,广泛应用于医学影像领域。通过重建和可视化三维结构,3D超声能够显著提高诊断精度和效率,同时减少医生的脑力负担。本技术文档将详细阐述…...
[HelloCTF]PHPinclude-labs超详细WP-Level 6Level 7Level 8Level 9-php://协议
由于Level 6-9 关的原理都是通用的, 这里就拿第6关举例, 其他的关卡同理 源码分析 定位到代码 isset($_GET[wrappers]) ? include("php://".$_GET[wrappers]) : ; 与前几关发生变化的就是 php:// 解题分析 这一关要求我们使用 php协议 php:// 协议 php://filte…...
【Linux】Bash是什么?怎么使用?
李升伟 整理 什么是 Bash? Bash(Bourne Again Shell)是一种 命令行解释器(Shell),广泛用于 Unix 和 Linux 操作系统。它是 Bourne Shell(sh) 的增强版,提供了更多的功能…...
cmake结合qt开发界面程序实例
在使用 CMake 构建 Qt 界面应用程序时,你需要设置 CMakeLists.txt 文件来指定项目配置、源文件、库依赖等。以下是一个简单的示例,展示了如何创建一个包含 Qt 界面(使用 QWidget)的 Qt 项目,并使用 CMake 进行构建。 …...
vue3二次封装tooltip实现el-table中的show-overflow-tooltip效果
开发过程中遇到需要根据后端返回的数据长度来判断是否需要使用el-tooltip的情况,想到el-table里面就有这种交互效果,如果不论文字是否超出容器长度都展示tooltip的话,交互效果难免会差很多,所以二次封装了这个组件: 给…...
如何创建并保存HTML文件?零基础入门教程
原文:如何创建并保存HTML文件?零基础入门教程 | w3cschool笔记 本文将以Windows系统为例,教你用最简单的记事本创建并保存第一个HTML网页。 📝 第一步:准备工具 文本编辑器:使用系统自带的记事本ÿ…...
React19源码系列之FiberRoot节点和Fiber节点
在上一篇文章,看了createRoot函数的大致流程。 createContainer函数创建并返回了FiberRoot 。FiberRoot是由createFiberRoot函数创建, createFiberRoot函数还将 FiberRoot和 根Fiber 通过current属性建立起了联系。将FiberRoot作为参数传给 ReactDOMRoo…...
每天看一篇漏洞报告
前言: 内容来源于乌云漏洞 今日思考xss漏洞, 今天看到一篇文章,里面详细说了xss的绕过技巧,虽然时间久了,没有去尝试,待会有时间去测试一下 以下是整理后的文章,原文在下面 文章链接&#…...
采用贝塞尔函数,进行恒定束宽波束形成算法
matlab采用贝塞尔函数,进行恒定束宽波束形成算法 beselle.m , 1452 20191225160928.png , 43700 20191225160935.png , 45238 20191225161010.png , 76862...
TCP协议的多线程应用、多线程下的网络编程
DAY13.2 Java核心基础 多线程下的网络编程 基于单点连接的方式,一个服务端对应一个客户端,实际运行环境中是一个服务端需要对应多个客户端 创建ServerSocketNable类,多线程接收socket对象 public class ServerSocketNable implements Run…...
华为中小型企业项目案例
实验目的(1) 熟悉华为交换机和路由器的应用场景 (2) 掌握华为交换机和路由器的配置方法 实验拓扑实验拓扑如图所示。 华为中小型企业项目案例拓扑图 实验配置市场部和技术部的配置创建VLANLSW1的配置 [LSW1]vlan batch 10 20 [LSW1]q…...
LabVIEW VI Scripting随机数波形图自动生成
通过LabVIEW VI Scripting 技术,实现从零开始编程化创建并运行一个随机数波形监测VI。核心功能包括自动化生成VI框架、添加控件与函数、配置数据流逻辑及界面布局优化,适用于批量生成测试工具、教学模板开发或复杂系统的模块化构建。通过脚本化操作&…...
MATLAB 控制系统设计与仿真 - 26
状态空间控制系统概述 状态空间描述 现代控制理论是建立在状态空间基础上的控制系统分析和设计理论,它用状态变量来刻画系统的内部特征,用‘一节微分方程组’来描述系统的动态特性。系统的状态空间模型描述了系统输入/输出与内部状态之间的关系&#x…...
Python----计算机视觉处理(Opencv:图像镜像旋转)
一、图像镜像旋转 图像的旋转是围绕一个特定点进行的,而图像的镜像旋转则是围绕坐标轴进行的。图像镜像旋转,也可 以叫做图像翻转,分为水平翻转、垂直翻转、水平垂直翻转三种。 通俗的理解为,当以图片的中垂线为x轴和y轴时&#x…...
C++从入门到入土(八)——多态的原理
目录 前言 多态的原理 动态绑定与静态绑定 虚函数表 小结 前言 在前面的文章中,我们介绍了C三大特性之一的多态,我们主要介绍了多态的构成条件,但是对于多态的原理我们探讨的是不够深入的,下面这这一篇文章,我们将…...
国产编辑器EverEdit - 语法着色文件的语法
1 语法着色定义(官方文档) 1.1 概述 EverEdit有着优异的语法着色引擎,可以高亮现存的绝大多数的编程语言。在EverEdit的语法着色中有Region和Item两个概念,Region表示着不同的区块。而Item则代表着这些区块中不同的部分。一般情况下,Region…...
vue3当中使用Pinia的store的组件化开发模式
一、安装与初始化 安装Pinia npm install pinia # 或 yarn add pinia目的:引入Pinia核心库,为状态管理提供基础支持。 挂载Pinia实例 在main.js中初始化并注入Vue应用: import { createApp } from vue import { createPinia } from pinia i…...
PyCharm安装redis,python安装redis,PyCharm使用失败问题
报错信息 Usage: D:\wb2\wbrj_pys\venv\Scripts\python.exe -m pip install [options] [package-index-options] … D:\wb2\wbrj_pys\venv\Scripts\python.exe -m pip install [options] -r [package-index-options] … D:\wb2\wbrj_pys\venv\Scripts\python.exe -m pip instal…...
保姆级离线TiDB V8+解释
以前学习的时候还是3版本,如今已经是8版本了 https://cn.pingcap.com/product-community/?_gl1ujh2l9_gcl_auMTI3MTI3NTM3NC4xNzM5MjU3ODE2_gaMTYwNzE2NTI4OC4xNzMzOTA1MjUz_ga_3JVXJ41175MTc0MTk1NTc1OC4xMS4xLjE3NDE5NTU3NjIuNTYuMC41NDk4MTMxNTM._ga_CPG2VW1Y4…...
PyTorch 深度学习实战(17):Asynchronous Advantage Actor-Critic (A3C) 算法与并行训练
在上一篇文章中,我们深入探讨了 Soft Actor-Critic (SAC) 算法及其在平衡探索与利用方面的优势。本文将介绍强化学习领域的重要里程碑——Asynchronous Advantage Actor-Critic (A3C) 算法,并展示如何利用 PyTorch 实现并行化训练来加速学习过程。 一、A…...
【服务器知识】Nginx路由匹配规则说明
Nginx路由匹配规则说明 **一、Nginx路由匹配核心机制****二、匹配规则语法详解**1. **精确匹配 ()**2. **前缀匹配 (^~ 或 /)**3. **正则匹配 (~ 或 ~*)**4. **通配符匹配 (*)** **三、路由匹配优先级顺序****四、高级路由技巧**1. **条件判断 (if语句)**2. **路径重写 (rewrit…...
Docker换源加速(更换镜像源)详细教程(2025.3最新可用镜像,全网最详细)
文章目录 前言可用镜像源汇总换源方法1-临时换源换源方法2-永久换源(推荐)常见问题及对应解决方案1.换源后,可以成功pull,但是search会出错 补充1.如何测试镜像源是否可用2.Docker内的Linux换源教程 换源速通版(可以直…...
OpenAI--Agent SDK简介
项目概述 OpenAI Agents SDK 是一个轻量级但功能强大的框架,用于构建多智能体工作流。它主要利用大语言模型(LLM),通过配置智能体、交接、护栏和跟踪等功能,实现复杂的工作流管理。以下是对其各个部分运行过程和代码流…...
pytorch小记(十三):pytorch中`nn.ModuleList` 详解
pytorch小记(十三):pytorch中nn.ModuleList 详解 PyTorch 中的 nn.ModuleList 详解1. 什么是 nn.ModuleList?2. 为什么不直接使用普通的 Python 列表?3. nn.ModuleList 的基本用法示例:构建一个包含两层全连…...
SpringData Redis:RedisTemplate配置与数据操作
文章目录 引言一、Redis概述与环境准备二、RedisTemplate基础配置三、连接属性配置四、操作String类型数据五、操作Hash类型数据六、操作List类型数据七、操作Set类型数据八、操作ZSet类型数据九、事务与管道操作总结 引言 Redis作为高性能的NoSQL数据库,在分布式系…...
Qt按钮控件常用的API
1.创建按钮 QPushButton *btnnew QPushButton; 以顶层方式弹出窗口控件 代码: #include "widget.h" #include "ui_widget.h" #include"QPushButton"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui-&…...
如何检查CMS建站系统的插件是否安全?
检查好CMS建站系统的插件安全是确保网站安全的重要环节,对于常见的安全检查,大家可以利用以下几种有效的方法和工具,来帮你评估插件的安全性。 1. 检查插件来源和开发者信誉 选择可信来源:仅从官方插件库或可信的第三方开发者处…...
修改HuggingFace模型默认缓存路径
huggingface模型的默认缓存路径是~/.cache/huggingface/hub/ 通常修改为自己的路径会更为方便。 方式一:cache_dir 参数 可以通过from_pretrained函数中的 cache_dir 参数来指定,缺点,每次都需要手动指定,比较麻烦。 如&#x…...
ORA-12541: TNS:no listener
问题描述:使用 PL/SQL Developer 连接数据库时出现ORA-12541错误。 1.查询监听状态 [oraclelocalhost admin]$ lsnrctl statusLSNRCTL for Linux: Version 19.0.0.0.0 - Production on 15-MAR-2025 10:47:57Copyright (c) 1991, 2021, Oracle. All rights reserv…...
