无重复字符的最长字串-3
cpp
class Solution {
public:
// 解决方法:双指针
int lengthOfLongestSubstring(string s) {
// 如果字符串为空,直接返回0
if (s.length() == 0)
return 0;
// 如果字符串不为空,字符串每个字符都不同的情况下,最长字串的长度为1,所以Max初始化为1
int left = 0, right = 0, Max = 1;
//字符变量t用来存放每次两个指针范围内的后一个字符,用于与两个指针范围内的所有元素判断是否相同
char t = 0;
while (left < s.length()) {
while (right < s.length() - 1) {
//flag初始化为1用于标记,方便后续判断
int flag = 1;
//t赋值为两个指针范围内的后一个字符
t = s[right + 1];
//循环判断t是否与两个指针内有相同的字符
for (int f = left; f <= right; f++) {
//如果有相同字符flag标记为0,并break结束循环
if (s[f] == t) {
flag = 0;
break;
}
}
/*如果flag为零,说明双指针范围的后一个字符与双指针范围内有相同字符,后续的字符也没有必要判断了,break结束循环
因为此情况下无论范围再怎么扩大(right++),双指针范围内总是有这个的重复字符,不符合题意。*/
if (flag == 0)
break;
/*如果flag为1,说明双指针范围的后一个字符与双指针范围内没有相同字符,则把双指针范围的后一个字符括入到双指针范围内(right++)
并与Max比较找到最大字串*/
if (flag) {
right++;
Max = max(Max, right - left + 1);
}
}
//内层while循环结束说明,字符串从头到尾已经遍历完,或者找到了相同字符break结束循环了
//那么继续缩小左指针的范围(left),继续从左值针的位置开始查找最长字串,一直找到结尾
left++;
right = left;
}
return Max;
}
};
每日问题
什么是 C++ 中的移动语义?它的作用是什么?
C++中的移动语义
移动语义是C++11中引入的一种特性,通过引入右值引用(T&&),支持资源的高效转移,而无需昂贵的拷贝操作。移动语义的核心作用是通过移动而不是复制的方式管理资源,从而提升程序性能,特别是在处理动态分配的资源(如内存、文件句柄等)时。、
移动语义的关键概念
1.右值引用(T&&):
是一个能够绑定到右值(临时对象)的引用类型。
它允许开发者直接访问右值对象的资源,并安全地将资源转移到其他对象中。
2.移动构造函数和移动赋值运算符:
是实现移动语义的两大工具。
它们负责接收一个右值引用参数,从而将资源从一个对象移动到另一个对象。
作用
1.提高性能:
在传递或返回大型对象时,通过移动语义可以避免昂贵的拷贝操作,直接转移资源的所有权。
常用于容器类(如std:vector)在插入、删除或交换元素时。
2.避免资源浪费:
临时对象通常会在作用域结束时被销毁,移动语义使得这些资源可以被重复利用,而不是浪费掉。
3.支持现代化C++编程:
与标准库(如std:move)结合,为开发者提供了更多灵活性和效率。
移动语义的实现
移动构造函数
cpp
class MyClass {
private:
int* data;
public:
// 构造函数
MyClass(int value) : data(new int(value)) {}
// 移动构造函数
MyClass(MyClass&& other) noexcept : data(other.data) {
other.data = nullptr; // 资源转移,确保原对象不再持有资源
}
// 析构函数
~MyClass() {
delete data; // 释放资源
}
};
- 这是一个移动构造函数,接受一个右值引用 other(右值或临时对象)。
- 实现了资源转移:
- 将 other.data 的值转移给当前对象的 data。
- 将 other.data 置为 nullptr,避免原对象在销毁时重复释放资源。
- noexcept 表示此操作不会抛出异常,确保移动操作在某些场景(如标准容器的优化)中安全进行。
移动赋值运算符
cpp
class MyClass {
private:
int* data;
public:
// 构造函数
MyClass(int value) : data(new int(value)) {}
// 移动赋值运算符
MyClass& operator=(MyClass&& other) noexcept {
if (this != &other) { // 防止自我赋值
delete data; // 释放当前资源
data = other.data; // 转移资源
other.data = nullptr; // 确保原对象不再持有资源
}
return *this;
}
~MyClass() {
delete data;
}
};
配合标准库使用
1.std::move
用于将一个左值显示转换为右值,从而启用移动语义。
常见用法时将一个临时对象的资源转移到另一个对象中。
示例:
cpp
std::vector<int> v1 = {1, 2, 3};
std::vector<int> v2 = std::move(v1); // v1 的资源转移到 v2,避免拷贝
2.std::forward
通常用于完美转发函数模板参数,确保参数的值类别(左值或右值)正确传递。
使用场景
1.容器优化:
C++标准库容器(如std::vector、std::string)在插入或删除元素时,利用移动语义大幅减少不必要的拷贝。
2.返回值优化:
函数返回临时对象时,可以直接移动对象资源,避免临时对象的构造和销毁。
示例:
cpp
MyClass createObject() {
MyClass obj(42);
return obj; // 编译器自动应用移动语义
}
3.避免多余的动态分配:
动态资源管理类(如文件句柄、数据库连接)可以通过移动语义避免资源的重复分配和释放。
移动语义是 C++11 的重要特性,专注于高效的资源管理和性能优化。它通过右值引用、移动构造函数和移动赋值运算符,避免了不必要的拷贝操作,特别适合处理动态资源密集型任务。在现代 C++ 编程中,移动语义和标准库(如 std::move)的结合已经成为编写高效代码的重要工具。
右值引用是什么?如何使用右值引用实现移动语义?
右值引用是什么?
右值引用(T&&)是C++11引入的一种新类型引用,允许程序员直接操作右值(临时对象)。右值引用的主要目的是支持移动语义和完美转发。
右值与左值的区别
左值:表示一个内存中有确定存储地址的对象,可以被持久引用或操作。
例如:变量名、数组元素。
int a = 5;中的a是左值。
右值:表示临时对象或者表达式结果,通常无法直接获取地址。
例如:字面值、临时对象、函数返回的非引用置。
5、a + b的结果是右值。
右值引用可以绑定到右值上,与普通左值引用(T&)不同。
右值引用的语法
cpp
int&& r = 10; // r 是右值引用,绑定到字面值 10
int x = 20;
int&& r2 = x + 5; // r2 是右值引用,绑定到临时值 25
右值引用允许我们直接获取右值的资源,并通过移动语义避免拷贝操作。
右值引用和移动语义
移动语义的核定是将资源从一个对象"移动"到另一个对象,而不是"拷贝"它们。右值引用在实现移动语义时扮演了关键角色。
传统拷贝vs移动
1.传统拷贝:
创建一个对象的完整副本,可能会涉及动态资源的重新分配。
成本较高,特别是对动态内存的管理类(如vector)。
例如:
cpp
std::vector<int> v1 = {1, 2, 3};
std::vector<int> v2 = v1; // 复制 v1 的内容,可能导致多次内存分配
2.移动:
通过右值引用,将一个对象的资源直接转移到另一个对象。
避免了不必要的内存分配和数据复制,提高性能。
示例:
cpp
std::vector<int> v1 = {1, 2, 3};
std::vector<int> v2 = std::move(v1); // v1 的资源转移到 v2,v1 被置为"空"
如何使用右值引用实现移动语义
为了实现移动语义,需要提供移动构造函数和移动赋值运算符。
1.移动构造函数
移动构造函数接受一个右值引用作为参数,将资源从右值对象转移到当前对象。
示例:
cpp
class MyClass {
private:
int* data;
public:
// 构造函数
MyClass(int value) : data(new int(value)) {}
// 移动构造函数
MyClass(MyClass&& other) noexcept : data(other.data) {
other.data = nullptr; // 将原对象的资源置为空
}
// 析构函数
~MyClass() {
delete data; // 释放资源
}
};
2.移动赋值运算符
移动赋值运算符转移资源时需要释放当前对象已有的资源(避免内存泄漏),然后接管右值对象的资源。
示例:
cpp
class MyClass {
private:
int* data;
public:
// 构造函数
MyClass(int value) : data(new int(value)) {}
// 移动赋值运算符
MyClass& operator=(MyClass&& other) noexcept {
if (this != &other) { // 避免自我赋值
delete data; // 释放当前对象的资源
data = other.data; // 转移资源
other.data = nullptr; // 将原对象的资源置为空
}
return *this;
}
// 析构函数
~MyClass() {
delete data;
}
};
完整示例:
cpp
#include <iostream>
#include <utility> // for std::move
class MyClass {
private:
int* data;
public:
// 构造函数
MyClass(int value) : data(new int(value)) {
std::cout << "Constructing: " << *data << std::endl;
}
// 移动构造函数
MyClass(MyClass&& other) noexcept : data(other.data) {
other.data = nullptr;
std::cout << "Moving: " << *data << std::endl;
}
// 移动赋值运算符
MyClass& operator=(MyClass&& other) noexcept {
if (this != &other) {
delete data;
data = other.data;
other.data = nullptr;
std::cout << "Assigning: " << *data << std::endl;
}
return *this;
}
// 析构函数
~MyClass() {
if (data) {
std::cout << "Destructing: " << *data << std::endl;
delete data;
} else {
std::cout << "Destructing: nullptr" << std::endl;
}
}
};
int main() {
MyClass obj1(10); // 调用构造函数
MyClass obj2 = std::move(obj1); // 调用移动构造函数
MyClass obj3(20);
obj3 = std::move(obj2); // 调用移动赋值运算符
return 0;
}
输出:
cpp
Constructing: 10
Moving: 10
Constructing: 20
Assigning: 10
Destructing: nullptr
Destructing: nullptr
Destructing: 10
右值引用允许我们绑定到右值(临时对象),直接操作其资源。
移动语义通过右值引用实现,主要通过 移动构造函数 和 移动赋值运算符,实现高效的资源转移。
使用右值引用和 std::move 可以显著提升程序性能,特别是当类中包含动态资源(如内存、文件句柄)时。