【C++随笔02】左值和右值
【C++随笔02】左值和右值
- 一、左值和右值
- 1、字面理解——左值、右值
- 2、字面理解的问题
- 3、左值、右值
- 4、左值的特征
- 5、 右值的特征
- 6、x++和++x是左值还是右值
- 7、复合例子
- 8、通常字面量都是一个右值,除字符串字面量以外:
- 二、左值引用和右值引用
- 三、左值引用
- 1、常量左值引用
- 2、制构造函数和复制赋值运算符函数——左值引用
- 四、右值引用
- 1、移动语义和完美转发
- 2、移动语义
- 3、完美转发
- 3.1、简介、demo
- 3.2、问题:c++中的完美转发(std::forward)存在的意义?
- 简单总结
一、左值和右值
1、字面理解——左值、右值
最简单的字面理解,表达式等号左边的值为左值,等号右边的值为右值,比如
int x = 1;
int y = 3;
int z = x + y;
以上面的代码为例,
- x是左值,1是右值;
- y是左值,3是右值;
- z是左值,x+y的结果是右值。(注意,是结果)
2、字面理解的问题
在第一行代码中我们判断a是一个左值,它却在第二行变成了右值,所以这就是问题,要准确地区分左值和右值还是应该理解其内在含义。
3、左值、右值
在C++中
- 左值一般是指一个指向特定内存的具有名称的值(具名对象),它有一个相对稳定的内存地址,并且有一段较长的生命周期。
- 右值则是不指向稳定内存地址的匿名值(不具名对象)
简单来说,左值是一个有内存地址的表达式,而右值是一个没有内存地址的表达式。
基于这一特征,我们可以用取地址符&来判断左值和右值,能取到内存地址的值为左值,否则为右值。还是以上面的代码为例,因为&a和&b都是符合语法规则的,所以a和b都是左值。
4、左值的特征
左值具有以下特征:
- 左值在内存中有唯一的地址。
- 左值可以出现在赋值操作符的左边。
- 左值可以被取地址操作符(&)获取其内存地址。
- 左值可以作为函数参数或返回值。
int x = 10; // x是一个左值
int* ptr = &x; // 获取x的地址并赋给指针ptr
5、 右值的特征
右值具有以下特征:
- 右值没有内存地址。
- 右值不能作为赋值操作符的左边。
- 右值不能被取地址操作符获取其内存地址。
int y = 20; // 20是一个右值
int z = x + y; // 表达式x + y的结果是一个右值
6、x++和++x是左值还是右值
int *p = &x++; // 编译失败
int *q = &++x; // 编译成功
- x++是右值,因为在后置++操作中编译器首先会生成一份x值的临时复制,然后才对x递增,最后返回临时复制内容。
- ++x则不同,它是直接对x递增后马上返回其自身,所以++x是一个左值。
7、复合例子
int x = 1;
int get_val()
{return x;
}
void set_val(int val)
{x = val;
}
int main()
{x++;++x;int y = get_val();set_val(6);
}
- get_val函数,该函数返回了一个全局变量x,虽然很明显变量x是一个左值,但是它经过函数返回以后变成了一个右值。
原因和x++类似,在函数返回的时候编译器并不会返回x本身,而是返回x的临时复制,所以int * p = &get_val();也会编译失败。
- set_val函数,该函数接受一个参数并且将参数的值赋值到x中。在main函数中set_val(6);实参6是一个右值,但是进入函数之后形参val却变成了一个左值。
我们可以对val使用取地址符.
- 左值到右值的隐式转换
左值可以被隐式地转换为右值,例如在某些表达式中,需要右值而传入一个左值参数时,编译器会自动进行转换。
void printValue(int value)
{cout << value << endl;
}int x = 10;
printValue(x); // 编译器将x隐式地转换为右值传递给函数
8、通常字面量都是一个右值,除字符串字面量以外:
int x = 1;
set_val(6);
auto p = &“hello world”;
编译器会将字符串字面量存储到程序的数据段中,程序加载的时候也会为其开辟内存空间(rodata),所以我们可以使用取地址符&来获取字符串字面量的内存地址。
- 给大家写一个有编译错误的例子,大家调调
#include <iostream>int add1(int& x)
{return x+1;
}int main() {int a = add1(1);std::cout << a << std::endl;return 0;
}
二、左值引用和右值引用
void processValue(int& value) {// 对左值进行处理
}void processValue(int&& value) {// 对右值进行处理
}
-
左值引用和右值引用在C++11中,引入了右值引用的概念。左值引用(L-value references)用于绑定到左值,右值引用(R-value references)用于绑定到右值。
-
左值引用的声明使用单个&符号:
int x = 10;
int& lvalueRef = x; // 左值引用绑定到左值x
- 右值引用的声明使用两个&&符号:
int y = 20;
int&& rvalueRef = y + 30; // 右值引用绑定到右值表达式的结果
引用可以方便地对变量进行修改或者将其传递给函数。
右值引用在移动语义(Move Semantics)和完美转发(Perfect Forwarding)中具有重要的作用,可以提高性能和代码效率。
三、左值引用
1、常量左值引用
常量左值引用的特性显得更加有趣,它除了能引用左值,还能够引用右值,比如:
int &x1 = 7; // 编译错误
const int &x = 11; // 编译成功
在上面的代码中,第一行代码会编译报错,因为int&无法绑定一个int类型的右值,但是第二行代码却可以编译成功。请注意,虽然在结果上const int &x = 11和const int x = 11是一样的,但是从语法上来说,前者是被引用了,所以语句结束后11的生命周期被延长,而后者当语句结束后右值11应该被销毁。虽然常量左值引用可以引用右值的这个特性在赋值表达式中看不出什么实用价值,但是在函数形参列表中却有着巨大的作用。一个典型的例子就是复制构造函数和复制赋值运算符函数,
2、制构造函数和复制赋值运算符函数——左值引用
当我们使用左值引用时,通常会涉及到复制构造函数和复制赋值运算符函数。复制构造函数用于创建一个新对象,并将其初始化为已存在的对象的副本。复制赋值运算符函数用于将一个已存在的对象的值复制给另一个已存在的对象。
以下是一个简单的示例程序,演示了左值引用、复制构造函数和复制赋值运算符函数的使用:
#include <iostream>class MyObject {
private:int data;public:MyObject(int d) : data(d) {std::cout << "Constructor called with value: " << d << std::endl;}MyObject(const MyObject& other) : data(other.data) {std::cout << "Copy constructor called. Copied value: " << data << std::endl;}MyObject& operator=(const MyObject& other) {if (this != &other) {data = other.data;std::cout << "Copy assignment operator called. Copied value: " << data << std::endl;}return *this;}void printData() const {std::cout << "Data: " << data << std::endl;}
};int main() {MyObject obj1(42); // 调用构造函数MyObject obj2(obj1); // 调用复制构造函数MyObject obj3 = obj1; // 调用复制构造函数MyObject obj4(55); // 调用构造函数obj4 = obj1; // 调用复制赋值运算符函数obj1.printData(); // 输出: Data: 42obj2.printData(); // 输出: Data: 42obj3.printData(); // 输出: Data: 42obj4.printData(); // 输出: Data: 42return 0;
}
输出
Constructor called with value: 42
Copy constructor called. Copied value: 42
Copy constructor called. Copied value: 42
Constructor called with value: 55
Copy assignment operator called. Copied value: 42
Data: 42
Data: 42
Data: 42
Data: 42
在上述示例中,我们首先定义了一个名为 MyObject 的类,该类具有一个带有整数参数的构造函数、一个复制构造函数和一个复制赋值运算符函数。然后我们创建了几个 MyObject 类型的对象,并通过不同方式进行初始化和赋值。
在 main() 函数中,我们创建了 obj1,并使用拷贝构造函数将其值分别复制给 obj2 和 obj3。接下来,我们创建了 obj4,然后使用赋值运算符将 obj1 的值复制给 obj4。
最后,我们调用各个对象的成员函数 printData() 来打印它们的数据值。你可以看到,obj2、obj3 和 obj4 的数据值都与 obj1 相同,这表明复制构造函数和复制赋值运算符函数成功地将一个对象的值复制给了另一个对象。
四、右值引用
顾名思义,右值引用是一种引用右值且只能引用右值的方法。在语法方面右值引用可以对比左值引用,在左值引用声明中,需要在类型后添加&,而右值引用则是在类型后添加&&,例如:
int i = 0;
int &j = i; // 左值引用
int &&k = 11; // 右值引用
在上面的代码中,k是一个右值引用,如果试图用k引用变量i,则会引起编译错误。右值引用的特点之一是可以延长右值的生命周期。
1、移动语义和完美转发
右值引用在移动语义和完美转发中起着重要的作用。
-
移动语义:移动语义是指通过右值引用将资源(如动态分配的内存、文件句柄等)从一个对象转移到另一个对象,而不是进行深拷贝。这样可以避免不必要的内存分配和释放,提高程序性能。
-
完美转发:完美转发是指将一个函数中的参数以原样传递给另一个函数,包括参数的左值或右值属性信息。通过使用右值引用和模板,可以实现完美转发,避免了不必要的拷贝。
2、移动语义
当涉及到移动语义和完美转发时,我们需要先了解一些基本概念和问题。在C++中,对象的拷贝构造函数(Copy Constructor)和拷贝赋值运算符(Copy Assignment Operator)会对资源进行拷贝操作,这可能导致内存分配和释放的开销。在某些情况下,我们希望能够高效地转移资源的所有权而不是进行深拷贝,这就引入了移动语义和完美转发。
移动语义(Move Semantics)
移动语义是指通过右值引用将资源(如动态分配的内存、文件句柄等)从一个对象转移到另一个对象,而不是进行深拷贝。移动语义可以大大提高程序性能,因为它避免了不必要的内存分配和释放。
移动语义的实现依赖于右值引用。右值引用(R-value Reference)通过双个&&符号进行声明,并且可以绑定到右值。通过移动构造函数(Move Constructor)和移动赋值运算符(Move Assignment Operator),可以使用移动语义来实现资源的高效转移。
简单来说,移动语义允许我们从一个临时的右值对象或者一个将要被销毁的对象中“窃取”资源,然后将其传递给新对象,而无需进行资源的复制操作。
示例代码:
#include <iostream>class MyObject {
private:int data;public:MyObject(int d = 0) : data(d) {std::cout << "Constructor called with value: " << d << std::endl;}MyObject(const MyObject& other) : data(other.data) {std::cout << "Copy constructor called. Copied value: " << data << std::endl;}MyObject& operator=(const MyObject& other) {if (this != &other) {data = other.data;std::cout << "Copy assignment operator called. Copied value: " << data << std::endl;}return *this;}MyObject(MyObject&& other) noexcept : data(other.data) {std::cout << "Move constructor called. Moved value: " << data << std::endl;other.data = 0; // 清空原对象的值}MyObject& operator=(MyObject&& other) noexcept {if (this != &other) {data = other.data;std::cout << "Move assignment operator called. Moved value: " << data << std::endl;other.data = 0; // 清空原对象的值}return *this;}void printData() const {std::cout << "Data: " << data << std::endl;}
};int main() {MyObject obj1(42); // 调用构造函数MyObject obj2(obj1); // 调用复制构造函数MyObject obj3 = obj1; // 调用复制构造函数MyObject obj4(55); // 调用构造函数obj4 = obj1; // 调用复制赋值运算符函数MyObject obj5(std::move(obj1)); // 调用移动构造函数MyObject obj6 = std::move(obj2); // 调用移动赋值构造函数obj1.printData(); // 输出: Data: 42obj2.printData(); // 输出: Data: 0obj3.printData(); // 输出: Data: 42obj4.printData(); // 输出: Data: 42obj5.printData(); // 输出: Data: 42obj6.printData(); // 输出: Data: 42return 0;
}
Constructor called with value: 42
Copy constructor called. Copied value: 42
Copy constructor called. Copied value: 42
Constructor called with value: 55
Copy assignment operator called. Copied value: 42
Move constructor called. Moved value: 42
Move constructor called. Moved value: 42
Data: 0
Data: 0
Data: 42
Data: 42
Data: 42
Data: 42
- obj1和obj2为0,这证明了移动构造函数被调用,并且资源成功被转移。
3、完美转发
3.1、简介、demo
完美转发(perfect forwarding)是C++11引入的概念,用于在函数模板中将参数按原样传递给另一个函数,同时保留其值类别(lvalue或rvalue)。它可以在保持精确性的同时避免不必要的复制或移动操作,提高代码的效率。
完美转发通常与转发引用类型参数(如模板中的万能引用)一起使用,以实现泛型编程中的参数传递。在函数模板中,我们可以使用 std::forward 函数来进行完美转发。
下面是一个使用完美转发的示例:
#include <iostream>
#include <utility>// 定义一个接收传入参数的函数
void process(int& i) {std::cout << "Lvalue: " << i << std::endl;i = 100; // 修改传入的左值参数
}void process(int&& i) {std::cout << "Rvalue: " << i << std::endl;
}// 将参数转发到 process 函数
template <typename T>
void wrapper(T&& arg) {process(std::forward<T>(arg));
}int main() {int a = 42;wrapper(a); // 传递左值wrapper(123); // 传递右值std::cout << "a: " << a << std::endl;int b = 42;process(b);process(123);std::cout << "b: " << a << std::endl;return 0;
}
输出
Lvalue: 42
Rvalue: 123
a: 100
Lvalue: 42
Rvalue: 123
b: 100
在上述示例中,我们定义了两个重载的 process 函数,一个接受左值引用参数,一个接受右值引用参数。然后,我们创建了一个模板函数 wrapper,该函数接受一个通用引用类型的参数 T&& arg。
在 wrapper 函数内部,我们通过调用 std::forward 来进行完美转发,将 arg 参数原封不动地传递给 process 函数。std::forward 根据参数的值类别(lvalue还是rvalue)将参数作为对应类型的引用进行转发。
在 main 函数中,我们先传递了一个左值 x 给 wrapper 函数,然后传递了一个右值 123。程序会根据参数的值类别,选择调用合适的 process 函数,并输出相应的结果。
使用完美转发可以避免不必要的拷贝和移动操作,提高代码的效率和性能。
3.2、问题:c++中的完美转发(std::forward)存在的意义?
-
我们从上面的demo中可以看到,既然不使用forward也可以达到类似的效果,那为何还要使用完美转发(std::forward)呢?
-
说这个问题前,我们先把上面的一个demo改下
#include <iostream>class MyObject {
private:int data;public:MyObject(int d = 0) : data(d) {std::cout << "Constructor called with value: " << d << std::endl;}MyObject(const MyObject& other) : data(other.data) {std::cout << "Copy constructor called. Copied value: " << data << std::endl;}MyObject& operator=(const MyObject& other) {if (this != &other) {data = other.data;std::cout << "Copy assignment operator called. Copied value: " << data << std::endl;}return *this;}MyObject(MyObject&& other) noexcept : data(other.data) {std::cout << "Move constructor called. Moved value: " << data << std::endl;other.data = 0; // 清空原对象的值}MyObject& operator=(MyObject&& other) noexcept {if (this != &other) {data = other.data;std::cout << "Move assignment operator called. Moved value: " << data << std::endl;other.data = 0; // 清空原对象的值}return *this;}void printData() const {std::cout << "Data: " << data << std::endl;}
};template<typename T>
T* createObject(T&& t)
{return new MyObject(t);
}int main() {MyObject* newMyObject = createObject(std::move(MyObject(110)));return 0;
}
- 大家猜猜看,上面应该输出什么?应该调用移动构造函数的,对吧,实际情况输出如下,调用的却是拷贝构造函数。
Constructor called with value: 110
Copy constructor called. Copied value: 110
- 是不是很好奇,为啥调用的是拷贝构造函数,而非我们之前想象的移动构造函数呢?
我们把createObject做下简单的更改,增加std::forward,
template<typename T>
T* createObject(T&& t)
{return new MyObject(std::forward<T>(t));
}
- 结果输出如下
Constructor called with value: 110
Move constructor called. Moved value: 110
这时,才真正的如我们所想,真的调用了移动构造函数。
- 分析:经历了模版参数t这一次转发,t右值的属性被改变为了左值。
简单总结
- 左值引用用于引用左值并允许修改,
- 右值引用用于引用右值并具有移动语义和完美转发的特性。
相关文章:
【C++随笔02】左值和右值
【C随笔02】左值和右值 一、左值和右值1、字面理解——左值、右值2、字面理解的问题3、左值、右值4、左值的特征5、 右值的特征6、x和x是左值还是右值7、复合例子8、通常字面量都是一个右值,除字符串字面量以外: 二、左值引用和右值引用三、左值引用1、常…...

几个nlp的小任务(多选问答)
@TOC 安装库 多选问答介绍 定义参数、导入加载函数 缓存数据集 随机选择一些数据展示 进行数据预处理部分(tokenizer) 调用t...
【C++学习记录】为什么需要异常处理,以及Try Catch的使用方法
1.什么是异常,什么是错误? 程序无法保证100%正确运行,万无一失。有的错误在编译时能发现,比如:关键字拼写、变量名未定义、括号不配对、语句末尾缺分号等。这是在编译阶段发现的,称为编译错误。 有的能正常…...

孪生网络(Siamese Network)
基本概念 孪生网络(Siamese Network)是一类神经网络结构,它是由两个或更多个完全相同的网络组成的。孪生网络通常被用于解决基于相似度比较的任务,例如人脸识别、语音识别、目标跟踪等问题。 孪生网络的基本思想是将输入数据同时…...

【Redis】Redis是什么、能干什么、主要功能和工作原理的详细讲解
🚀欢迎来到本文🚀 🍉个人简介:陈童学哦,目前学习C/C、算法、Python、Java等方向,一个正在慢慢前行的普通人。 🏀系列专栏:陈童学的日记 💡其他专栏:CSTL&…...
8月26日,每日信息差
1、上海发布两项支持高级别自动驾驶的5G网络标准,在上海市通管局的指导下,由上海移动和中国信息通信研究院牵头组织二十余家标准起草单位共同参与编写的《支持高级别自动驾驶的5G网络规划建设和验收要求》和《支持高级别自动驾驶的5G网络性能要求》等两项…...
云和恩墨面试(部分)
一面 软件架构设计方案应该包含哪些内容,哪些维度 二面 架构师如何保证软件产品质量线程屏障(或者说线程栅栏)是什么,为什么要使用线程屏障事务传播⾏为为NESTED时,当内部事务发生异常时,外部事务会回滚吗?newBing:…...

volatile 关键字详解
目录 volatile volatile 关键用在什么场景下: volatile 关键字防止编译器优化: volatile 是一个在许多编程语言中(包括C和C)用作关键字的标识符。它用于告诉编译器不要对带有该关键字修饰的变量进行优化,以确保变量在…...

Ceph入门到精通-大流量10GB/s LVS+OSPF 高性能架构
LVS 和 LVSkeepalived 这两种架构在平时听得多了,最近才接触到另外一个架构LVSOSPF。这个架构实际上是LVSKeepalived 的升级版本,我们所知道LVSKeepalived 架构是这样子的: 随着业务的扩展,我们可以对web服务器做水平扩展…...
Unity光照相关
1. 光源类型 Unity支持多种类型的光源,包括: 1. 点光源(Point Light):从一个点向四周发射光线,适用于需要突出物体的光源。 2. 平行光(Directional Light):从无限远处…...
Qt基本类型
QT基本数据类型定义在#include <QtGlobal> 中,QT基本数据类型有: 类型名称注释备注qint8signed char有符号8位数据qint16signed short16位数据类型qint32signed short32位有符号数据类型qint64long long int 或(__int64)64位有符号数据类型&#x…...

前端基础(Element、vxe-table组件库的使用)
前言:在前端项目中,实际上,会用到组件库里的很多组件,本博客主要介绍Element、vxe-table这两个组件如何使用。 目录 Element 引入element 使用组件的步骤 使用对话框的示例代码 效果展示 vxe-table 引入vxe-table 成果展…...

C++学习记录——이십팔 C++11(4)
文章目录 包装器1、functional2、绑定 这一篇比较简短,只是因为后要写异常和智能指针,所以就把它单独放在了一篇博客,后面新开几篇博客来写异常和智能指针 包装器 1、functional 包装器是一个类模板,对可调用对象类型进行再封装…...

UE学习记录03----UE5.2 使用拖拽生成模型
0.创建蓝图控件,自己想要展示的样子 1.侦测鼠标拖动 2.创建拖动操作 3.拖动结束时生成模型 3.1创建actor , 创建变量EntityMesh设为可编辑 生成Actor,创建变量EntityMesh设为可编辑 屏幕鼠标位置转化为3D场景位置 4.将texture设置为变量并设为可编辑&am…...
Spring Cache框架(缓存)
1、介绍: Spring Cache 是一个框架,实现了基于注解的缓存功能,只需要简单加个注解,就能实现缓存功能。它提供了一层抽象,底层可以切换不同的cache实现。具体就是通过CacheManager 接口来实现不同的缓存技术。 针对不同…...

Linux学习之Ubuntu 20使用systemd管理OpenResty服务
sudo cat /etc/issue可以看到操作系统的版本是Ubuntu 20.04.4 LTS,sudo lsb_release -r可以看到版本是20.04,sudo uname -r可以看到内核版本是5.5.19,sudo make -v可以看到版本是GNU Make 4.2.1。 需要先参考我的博客《Linux学习之Ubuntu 2…...
[数据集][目标检测]疲劳驾驶数据集VOC格式4类别-4362张
数据集格式:Pascal VOC格式(不包含分割的txt文件,仅仅包含jpg图片和对应的xml) 图片数量(jpg文件个数):4362 标注数量(xml文件个数):4362 标注类别数:4 标注类别名称:["closed_eye","closed_mouth"…...

matlab使用教程(25)—常微分方程(ODE)选项
1.ODE 选项摘要 解算 ODE 经常要求微调参数、调整误差容限或向求解器传递附加信息。本主题说明如何指定选项以及每个选项与哪些微分方程求解器兼容。 1.1 选项语法 使用 odeset 函数创建 options 结构体,然后将其作为第四个输入参数传递给求解器。例如࿰…...
MybatisPlus简单到入门
一、MybatisPlus简介 1、入门案例(重点): 1.SpringBoot整合MP1).创建新模块选择,Spring项初始化。2).选择当前模块使用的技术,只保留MySQL Driver就行,不要选择mybatis避免与后面导入mybatisPlus的依赖发…...
9. 优化器
9.1 优化器 ① 损失函数调用backward方法,就可以调用损失函数的反向传播方法,就可以求出我们需要调节的梯度,我们就可以利用我们的优化器就可以根据梯度对参数进行调整,达到整体误差降低的目的。 ② 梯度要清零,如果梯…...
React Native 开发环境搭建(全平台详解)
React Native 开发环境搭建(全平台详解) 在开始使用 React Native 开发移动应用之前,正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南,涵盖 macOS 和 Windows 平台的配置步骤,如何在 Android 和 iOS…...
《Playwright:微软的自动化测试工具详解》
Playwright 简介:声明内容来自网络,将内容拼接整理出来的文档 Playwright 是微软开发的自动化测试工具,支持 Chrome、Firefox、Safari 等主流浏览器,提供多语言 API(Python、JavaScript、Java、.NET)。它的特点包括&a…...
基础测试工具使用经验
背景 vtune,perf, nsight system等基础测试工具,都是用过的,但是没有记录,都逐渐忘了。所以写这篇博客总结记录一下,只要以后发现新的用法,就记得来编辑补充一下 perf 比较基础的用法: 先改这…...
Java多线程实现之Callable接口深度解析
Java多线程实现之Callable接口深度解析 一、Callable接口概述1.1 接口定义1.2 与Runnable接口的对比1.3 Future接口与FutureTask类 二、Callable接口的基本使用方法2.1 传统方式实现Callable接口2.2 使用Lambda表达式简化Callable实现2.3 使用FutureTask类执行Callable任务 三、…...
Spring AI 入门:Java 开发者的生成式 AI 实践之路
一、Spring AI 简介 在人工智能技术快速迭代的今天,Spring AI 作为 Spring 生态系统的新生力量,正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务(如 OpenAI、Anthropic)的无缝对接&…...
聊一聊接口测试的意义有哪些?
目录 一、隔离性 & 早期测试 二、保障系统集成质量 三、验证业务逻辑的核心层 四、提升测试效率与覆盖度 五、系统稳定性的守护者 六、驱动团队协作与契约管理 七、性能与扩展性的前置评估 八、持续交付的核心支撑 接口测试的意义可以从四个维度展开,首…...

C++使用 new 来创建动态数组
问题: 不能使用变量定义数组大小 原因: 这是因为数组在内存中是连续存储的,编译器需要在编译阶段就确定数组的大小,以便正确地分配内存空间。如果允许使用变量来定义数组的大小,那么编译器就无法在编译时确定数组的大…...

Linux nano命令的基本使用
参考资料 GNU nanoを使いこなすnano基础 目录 一. 简介二. 文件打开2.1 普通方式打开文件2.2 只读方式打开文件 三. 文件查看3.1 打开文件时,显示行号3.2 翻页查看 四. 文件编辑4.1 Ctrl K 复制 和 Ctrl U 粘贴4.2 Alt/Esc U 撤回 五. 文件保存与退出5.1 Ctrl …...

spring Security对RBAC及其ABAC的支持使用
RBAC (基于角色的访问控制) RBAC (Role-Based Access Control) 是 Spring Security 中最常用的权限模型,它将权限分配给角色,再将角色分配给用户。 RBAC 核心实现 1. 数据库设计 users roles permissions ------- ------…...
Python爬虫实战:研究Restkit库相关技术
1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的有价值数据。如何高效地采集这些数据并将其应用于实际业务中,成为了许多企业和开发者关注的焦点。网络爬虫技术作为一种自动化的数据采集工具,可以帮助我们从网页中提取所需的信息。而 RESTful API …...