yo!这里是c++11重点新增特性介绍

news2024/11/14 4:12:22

目录

前言

列表初始化

{ }初始化

initializer_list类

类型推导

auto

decltype

范围for

右值引用与移动语义

左值引用和右值引用

移动语义

1.移动构造

2.移动赋值 

3.stl容器相关更新 

右值引用和万能引用

完美转发

关键字

default

delete

final和override

可变参数模板

介绍

使用场景

lambda表达式

包装器

bind函数

线程库

后记


前言

        C++11 是 C++ 语言的一个重要更新,它加入了许多新的语言特性和标准库组件,旨在提高代码的可读性、可维护性、可移植性和安全性,同时也提高了语言的表达能力和性能。C++11 的引入,对于 C++ 程序员来说是一个里程碑式的事件,它使得 C++ 语言更加现代化和高效。因此我们要作为一个重点去学习。在把本篇文章中,主要介绍一些新增特性,比如花括号初始化、initializer_list类、auto、范围for等,其中较为重要的有右值引用、lambda表达式、线程库,内容较大的特性会专门出一片文章讲解,比如智能指针、异常相关新增特性等,下面来看看上述的详细介绍吧。

列表初始化

  • { }初始化

        C++98允许使用花括号{ }对数组或者结构体元素进行统一的列表初始值设定,C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号(=),也可不添加。列表初始化也可以适用于new表达式中,自定义类型不仅可以通过构造函数使用圆括号构造,也可以使用花括号构造,举例如下代码块。

class Date
{
public:
	Date(int year, int month, int day)
		:_year(year)
		, _month(month)
		, _day(day)
	{
		cout << "Date(int year, int month, int day)" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
struct A
{
	int _a;
	int _aa;
};
int main()
{
    int a{ 0 };
	int arr1[] = { 1,2,3 };
	int arr2[] { 1,2,3 };
	int arr3[3] = { 0 };
	int arr4[3] { 0 };
	A a1 = { 1,2 };
	A a2 { 1,2 };
	int* ptr = new int[3]{ 1,1,1 };
    Date d1(1, 2, 3);
	Date d2{ 1,2,3 };
    
	return 0;
}
  • initializer_list类

        initializer_list类似于数组和向量,可以存储一组数据,并且支持迭代器,可以用于函数参数、构造函数和赋值运算符的参数中。通过使用initializer_list,可以轻松地传递一组数据给一个函数或者对象,而不必显式地指定这组数据的长度或者元素类型。STL中的不少容器就增加 std::initializer_list作为参数的构造函数,比如

eg:

类型推导

  • auto

        关键字auto在C++中是用于自动类型推导关键字。当使用auto声明变量时,编译器会根据变量的初始化表达式自动推导出变量的类型。使用auto可以简化代码,特别是当变量类型较长或较复杂时。另外,auto还可以结合迭代器模板等使用,更加灵活和简洁。当auto与&结合说明这是个引用变量,当auto与*结合说明是个指针变量,举例如下:

eg:

  • decltype

        decltype是一个关键字,用于获取表达式的类型,而不是用于实例化一个对象,可以用于函数返回值类型推断、模板参数类型推断等,举例:

eg:

范围for

        C++中的范围for是一种遍历容器、数组、字符串等可迭代对象的简便方法,实际底层就是迭代器遍历。范围for循环通过在循环中声明一个变量,在每次迭代中自动将其设为下一个元素的值来遍历可迭代对象中的元素。

 eg:

int arr[] = {1, 2, 3, 4, 5};
for (int x : arr) {
    cout << x << " ";
}
// 输出: 1 2 3 4 5

右值引用与移动语义

  • 左值引用和右值引用

        首先,无论是左值引用还是右值引用,都是给对象取别名,要弄明白左值引用和右值引用,先了解一下左值与右值是什么意思。对于左值,可以获取它的地址+对它赋值,注意左值可以出现在赋值符号的左边,也可以出现在右边,左值引用就是对左值的引用,给左值取别名;在此之前所学的引用都是左值引用,左值引用使用一个&符号来声明,比如:

	int a = 10;    //左值
	int* b = new int(1);    //左值
	const int c = 1;    //左值

	int& refa = a;    //左值引用
	int*& refb = b;    //左值引用
	const int& refc = c;    //左值引用

        对于右值,不能取地址+不能出现在赋值符号的左边,是一个表示数据的表达式,比如字面常量、表达式返回值等,右值引用就是对右值的引用,给右值取别名,比如: 

	int x = 0, y = 0;
	1;   //右值
	x + y;   //右值
	x + 1;   //右值

	int&& rr1 = 1;   //右值引用
	int&& rr2 = x + y;   //右值引用
	int&& rr3 = x + 1;   //右值引用
	//int&& ref4 = x;   //报错

左值引用与右值引用的比较:

        ①左值引用只能引用左值,不能引用右值,但是const左值引用既可引用左值,也可引用右值;

        ②右值引用只能右值,不能引用左值,但是右值引用可以move以后的左值;

其中move函数的作用就在于将左值强制转换为右值,比如:

    int a = 10;

	//int& d = 10;  //左值引用引用不了右值
	const int& d = 10;   //const左值引用可以引用右值

	//int&& e = a;   //右值引用引用不了左值
	int&& e = move(a);   //右值引用可以引用move之后的左值
  • 移动语义

1.移动构造

        那左值引用用的好好的,为什么要提出右值引用呢?我们想一下左值引用的短板,有这样一个情况,当函数返回值是一个局部变量,出了作用域就会被销毁,就不能使用(左值)引用返回,只能使用传值返回,但是传值返回至少会有一次拷贝构造(即使在编译器优化以后),因此为了减少拷贝,下面考虑其他方法——引入移动构造、移动赋值

        在此之前,先介绍一下右值的分类,包括纯右值(内置类型右值)将亡值(自定义类型右值),对于纯右值,就算是拷贝多次也无所谓,但是对于有申请资源的将亡值,拷贝一次都是极大地降低了效率,所以考虑将将亡值的资源转给需要的新对象,也就是用将亡值即将不要的资源去构造给需要的对象,这可以大大的减少拷贝,提高效率。

        通过下面一个具体的例子描述一下这个过程,在模拟实现string类时,有这样一个int转string的函数,如下图,左边是string的拷贝构造函数,右边是To_string函数传值返回的过程,正常编译器优化情况下,会将str的资源拷贝一份给main函数中的str,但是我们发现这个To_string函数中的str就是一个将亡值,退出函数str就会被释放,而main函数中的str正好是一个需要此资源的新对象,正如上面所说,将To_string函数中的str对象资源移动给main函数中的str,这样就是一次移动构造,如第二图。

        对于移动构造函数,我们可以看到,参数是一个右值引用,函数体就是进行资源的交换或者说移动,为什么To_string函数返回之后会调用移动构造函数去构造str呢?因为这里编译器会将To_string函数的局部变量返回值识别成一个右值(将亡值),就会自动去找最匹配的构造函数去构造对象。当没有移动构造函数时,这里就会去调用拷贝构造函数,因为const左值引用也可以接收一个右值,当两个都存在时,存在构造对象的地方会去调用最匹配的构造函数。

2.移动赋值 

        不仅有移动构造,还有移动赋值,原理一样,这里也简单说一下,结合在一起较为容易理解。如下图,对于移动赋值函数,参数依旧是右值引用,函数体内也是在不是两个相同的对象赋值的情况以外,交换或移动将亡值的资源给目标对象,实现资源的转移以提高效率。

3.stl容器相关更新 

         移动构造和移动赋值不仅解决了传值返回的多次拷贝问题,而且这种资源移动的思想也应用到了stl的容器上,为相关接口增加了右值引用版本,以减少对象的拷贝,如:

eg:

        同时,在原本6个默认成员函数的基础上又增加了两个默认成员函数——移动构造函数和移动赋值运算符重载。注意:

①如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任 意一个。那么编译器会自动生成一个默认移动构造;

②如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中 的任意一个,那么编译器会自动生成一个默认移动赋值,

        实际上,实现一个类有申请资源时,则得实现拷贝构造、析构、拷贝赋值以进行深拷贝,同时想减少拷贝,就得实现移动构造和移动赋值,但由于有了上面那三个,编译器就不会自动生成,所以还是得自己实现这两个,因此存在属性申请资源时,自己实现拷贝构造、析构、拷贝赋值、移动构造、移动赋值。 

  • 右值引用和万能引用

        万能引用主要有两种,一种是在函数模板中使用的一种引用类型,它的语法形式为“T&&”,其中T是一个模板参数。还有一种是“auto&&”,万能引用可以接受任意类型的实参,并且保留了实参的左右值属性。值得注意的是,必须存在类型推导才是万能引用,否则是右值引用。举例如下图,特别要注意最后一个例子,其中push_back函数得参数虽然是T&&,但是在模板实例化时T的理性就已确定,不存在类型推导,而且在前面也提到过,这个是容器新增得右值引用版本接口,不是万能引用。

eg:

  • 完美转发

        完美转发提供了一种机制来保留函数参数的完整类型信息,并将其转发给另一个函数。传统上,在C++中,当一个函数接收一个参数并将其转发给另一个函数时,它会失去原始参数的类型信息(比如说右值引用版本的接口接收一个右值引用,但是在函数体内这个变量被当作左值去使用,那当我们需要去使用它的右值特性去调用其他相关函数时就没有办法了),此时C++完美转发保留了它的左值或右值的属性。语法如下:

template<typename T>
void func(T&& arg)
{
    other_func(std::forward<T>(arg));   //完美转发
}
 

        下面通过一个例子来展现一下完美转发的使用场景,如下代码是List类的模拟实现,仅包括尾插和插入函数,在mian函数中,尾插一个“1111”的常量字符串,毫无疑问,会匹配右值引用版本的push_back函数,其中需要复用insert函数,而且需要复用右值引用版本的insert函数,但是在push_back函数的函数体内,x已经被当作成了左值,已经失去了“1111”的右值特性,此时使用万能转发保持其属性,继续会匹配右值引用版本的insert函数,在这个函数体内,也需要去调用右值引用版本的Node节点的构造函数,也就是移动构造函数,也必须通过万能引用去操作,在Node的移动构造函数中,我们也需要将右值版本的字符串放进_data中,也是通过万能转发的方法。

        从上面的例子当中可以看出,万能转发在实际开发中也是较为需要的,较为重要的。

代码:

template <class T>
struct ListNode
{
	//构造函数用来创节点
	ListNode(const T& x = T())   //左值版本
		:_data(x)
		, _prev(nullptr)
		, _next(nullptr)
	{

	}
	ListNode(T&& x)   //右值版本
		:_data(forward<T>(x))
		, _prev(nullptr)
		, _next(nullptr)
	{

	}

	T _data;
	ListNode<T>* _prev;
	ListNode<T>* _next;
};

template <class T>
class List
{
	typedef ListNode<T> Lnode;
public:
    //...

    iterator insert(iterator pos, const T& x)   //左值版本
	{
		Lnode* newNode = new Lnode(x);
		pos._node->_prev->_next = newNode;
		newNode->_prev = pos._node->_prev;
		newNode->_next = pos._node;
		pos._node->_prev = newNode;

		return iterator(newNode);  //返回插入位置的迭代器
	}

    iterator insert(iterator pos, T&& x)   //右值版本
	{
		Lnode* newNode = new Lnode(forward<T>(x));
		pos._node->_prev->_next = newNode;
		newNode->_prev = pos._node->_prev;
		newNode->_next = pos._node;
		pos._node->_prev = newNode;

		return iterator(newNode);  //返回插入位置的迭代器
	}

    void push_back(const T& x)   //左值版本
	{
		insert(end(), x);
	}

    void push_back(T&& x)   //右值版本
	{
		insert(end(), forward<T>(x));
	}
private:
	Lnode* _head;
};

 int main()
{
    List<string> lt;
    lt.push_back("1111");

    return 0;
}

关键字

  • default

        关键字default用于强制生成默认函数,可以更好的控制默认函数。比如,当只有拷贝构造函数时,运行会报错没有默认构造函数,此时使用default强制自动生成即可,如下图

eg:

 

  • delete

        关键字delete用于禁止生成默认函数,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数

eg:

  • final和override

        find和override关键字在之前的章节继承和多态中讲过,对于final,即可以修饰类不能被继承,也可以修饰虚函数不能被重写;对于override,放在子类中,检查子类虚函数是否重写了父类的虚函数,具体可见http://t.csdnimg.cn/5CvsAicon-default.png?t=N7T8http://t.csdnimg.cn/5CvsA

可变参数模板

  • 介绍

        可变参数模板可以让我们编写接受可变数量参数类型的函数和类模板。下面是一个基本可变参数的函数模板,args前面有省略号,称为参数包,其中包含若干个模板参数,我们无法直接获取其中的每个参数,只能展开参数包的方式获取,在C++中,有两种方式展开可变参数模板的参数包:递归函数方式展开和逗号表达式方式展开

template <typename... Args>
void printArgs(Args... args)
{}

递归函数方式展开:

         递归展开是指在函数或类模板中递归调用自己,并将参数包展开为独立的参数列表。这可以通过使用递归模板函数或类模板来实现。如下代码,包括递归终止函数和普通展开函数,main函数中的ShowList调用过程为:

①1传进t,其余初步传进args参数包,继续递归调用展开函数;

②'a'传进t,其余传进args参数包,此时参数包只剩一个参数"111"了;

③调用最匹配的函数,即递归终止函数,传进t,之后递归结束,每个参数也最终获取到了。

template <class T>
void ShowList(const T& t)   //递归终止函数
{
	cout << t << endl;
}

template <class T, class ...Args>
void ShowList(const T& t, Args... args)   //展开函数
{
	cout << t << endl;   //t就是参数包里的一个参数,这里进行使用即可
	ShowList(args...);
}

int main()
{
    ShowList(1, 'a', "111");
}

逗号表达式方式展开: 

        利用初始化列表来初始化一个变长数组,{(printarg(args), 0)...}将会展开成((printarg(arg1),0), (printarg(arg2),0), (printarg(arg3),0), etc... ),最终会创建一个元素值都为0的数组arr,由于是逗号表达式,在创建数组的过程中会先执行逗号表达式前面的部分printarg(args) 获取到当前的参数,也就是说在构造int数组的过程中就将参数包展开了,因此获取到参数包中的所有参数,注意这个数组的目的纯粹是为了在数组构造的过程展开参数包,使用参数的地方是在PrintArg函数中。

template <class T>
void PrintArg(T t)
{
     cout << t << " ";
}

//展开函数
template <class ...Args>
void ShowList(Args... args)
{
     int arr[] = { (PrintArg(args), 0)... };
     cout << endl;
}

int main()
{
     ShowList(1, 'A', "111");
     return 0;
}
  • 使用场景

        如果上面的参数包、展开方式你并没有看懂,那就作为了解即可,但是使用场景必须能看得懂,可变参数模板应用在stl容器的emplace相关接口上,比如

        可以看到emplace接口参数,既支持模板的可变参数,又是万能引用,也就是同时可以接受左值,也可以接受右值,下面看看如何使用这个接口:其中对于一个元素是pair类的vector,可以直接将一个pair的元素使用emplace_back插入,但是push_back的话就必须去调用make_pair函数。

int main()
{
    vector<pair<string, int>> v;
	v.emplace_back("1", 1);
	//v.push_back("1", 1);   //报错
	v.push_back(make_pair("1", 1));
	v.push_back({ "1", 1 });
    return 0;
}

lambda表达式

        lambda表达式是一种匿名函数,可以在需要函数对象的任何地方使用。lambda表达式的基本语法如下:

[capture-list] (parameters) mutable -> return-type { function-body }

        其中,

捕获列表(capture-list):用于捕获外部变量,该列表总是出现在lambda函数的开始位置,编译器根据[ ]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda 函数使用,每个变量可以指定为按值捕获或按引用捕获,

  • [var]:表示值传递方式捕捉变量var,正常情况下可读不可写,加上mutable变成了一份拷贝,就可读可写了
  • [=]:表示值传递方式捕获所有所在栈帧的变量(包括this)
  • [&var]:表示引用传递捕捉变量var
  • [&]:表示引用传递捕捉所有所在栈帧的变量(包括this)
  • 由多个捕捉项组成,并以逗号分割

eg:

        [=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量;

        [&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量,

参数(parameters):用于传递参数,与普通函数的参数列表一致,如果不需要参数传递,则可以 连同()一起省略;

mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。注意使用该修饰符时,参数列表不可省略(即使参数为空);

返回类型(return-type):用于指定返回值类型,没有返回值时此部分可省略。返回值类型明确情况下也可省略,由编译器对返回类型进行推导;

函数体(function-body):用于实现函数的具体逻辑,可以使用捕获列表的变量也可以使用参数列表的变量,

        如下图,fun2就是一个lambda表达式,值传递方式捕获了上文的所有变量,其中b是引用传递,传了一个参数c,返回值是int,这里不写也没事,因为编译器会自动推导,函数体内运算以后返回b,之后调用此lambda表达式,需要传一个参数,即可得到函数体内的计算结果。

注意:

        ①捕捉列表不允许变量重复传递,否则就会导致编译错误,比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复,就会报错;

        ②lambda表达式之间不能相互赋值,但可以拷贝构造一个lambda表达式,也可以赋值给相同类型的函数指针,比如

void (*PF)();
int main()
{
     auto f1 = []{cout << "hello world" << endl; };
     auto f2 = []{cout << "hello world" << endl; };

     //f1 = f2;   //报错

    auto f3(f1);

    PF = f2;

    return 0;
}

包装器

        包装器,也叫适配器,是一种用于以统一的方式调用不同类型函数的抽象概念,本质是一个类模板。在引入lambda表达式之后,有没有这样一个问题,有的接口用函数实现,有的用函数对象实现,还有的用lambda表达式实现,万一有场景需要把这些不同实现方式的接口聚合在一起,该用什么来接收这些接口呢?对!就是使用包装器去接收,看看它的原型:

template <class T> function;
template <class Ret, class... Args>
class function<Ret(Args...)>;

其中,Ret: 被调用函数的返回类型,Args…:被调用函数的形参,使用方式我举个例子,实现计算器的加减乘除功能,注意实现以及调用的细节。

int Add(int a, int b)
{
	return a + b;
}

class Sub
{
public:
	int operator()(int a, int b)
	{
		return a-b;
	}
};

class func
{
public:
	int Div(int a, int b)
	{
		return a / b;
	}
};

int main()
{
	function<int(int, int)> ADD = Add;   //函数名
	function<int(int, int)> SUB = Sub();   //函数对象
	function<int(func, int, int)> DIV = &func::Div;   //非静态成员函数
	function<int(int, int)> MUL = [](int a, int b) {return a * b; };   //lambda表达式

	cout << ADD(1, 2) << endl;
	cout << SUB(1, 2) << endl;
	cout << DIV(func(), 1, 2) << endl;
	cout << MUL(1, 2) << endl;

    return 0;
}

运行: 

bind函数

        bind函数是一个非常强大的函数对象适配器,它可以把一个函数和一些参数绑定起来,形成一个新的函数对象,该函数对象可以像原函数一样调用,但是它已经部分确定了原函数的参数,同时还可以实现参数顺序调整,原型如下:

template <typename F, typename... Args>
auto bind(F&& f, Args&&... args);

先看把普通函数和成员函数的一些参数绑定的例子:

int Add(int a, int b)
{
	return a + b;
}

class Func
{
public:
	int Mul(int a, int b)
	{
		return a * b;
	}
};

int main()
{
	//有了两数相加函数,实现任意数加7的功能
	auto xPlus7 = bind(Add, placeholders::_1, 7);
	cout << xPlus7(1) << endl;

	//有了两数相乘的成员函数,实现任意数加倍的功能
	auto increaseDouble = bind(&Func::Mul, Func(), placeholders::_1, 2);
	cout << increaseDouble(8) << endl;
	return 0;
}

运行:

再看调整参数的例子:

double Div(int a, int b)
{
	return (double)a / b;
}

int main()
{
	auto Divide1 = bind(Div, placeholders::_1, placeholders::_2);
	auto Divide2 = bind(Div, placeholders::_2, placeholders::_1);

	cout << Divide1(8, 2) << endl;
	cout << Divide2(8, 2) << endl;

    return 0;
}

运行:

线程库

        学了之后再补充...

后记

        从以上可以看出,c++11新增的知识点还是特别多的,本文章只是讲述了较为重要的一部分,面试时被提问频率高的一部分,还有一部分没有提到,比如新增容器(如array),空指针nullptr,有一些大家可能已经熟练于心了,对于文中讲过的知识点,其中包括范围for、右值引用、lambda表达式都是重点中的重点,希望大家能够真正的看懂并理解,拜拜!


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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数组基础知识

数组基础&#xff08;不定时更新&#xff09; 数组基础 数组基础 &#xff08;1&#xff09;数组是存放在连续内存空间上的相同类型数据的集合。数组可以方便的通过下标索引的方式获取到下标下对应的数据。数组下标都是从0开始的。数组内存空间的地址是连续的。 &#xff08;…

python-选择排序

选择排序是一种简单直观的排序算法&#xff0c;它的基本思想是每一轮选择未排序部分的最小元素&#xff0c;然后将其放到已排序部分的末尾。这个过程持续进行&#xff0c;直到整个数组排序完成。(重点&#xff1a;通过位置找元素) 以下是选择排序的详细步骤和 Python 实现&…

element ui 上传组件实现手动上传

首先需要给上传组件增加http-request属性&#xff0c;这个方法中可以获取到文件&#xff0c;并按照自己的方式进行上传。 <el-uploadreffileUploadaction#:http-requesthttpRequest:on-preview"handlePreview":on-remove"handleRemove":limit"1&q…

SpringBoot3核心原理

SpringBoot3核心原理 事件和监听器 生命周期监听 场景&#xff1a;监听应用的生命周期 可以通过下面步骤自定义SpringApplicationRunListener来监听事件。 ①、编写SpringApplicationRunListener实现类 ②、在META-INF/spring.factories中配置org.springframework.boot.Sprin…

接口测试:轻松掌握基础知识,快速提升测试技能!

1.client端和server端 开始接口测试之前&#xff0c;首先搞清楚client端与server端是什么&#xff0c;区别。 web前端&#xff0c;顾名思义&#xff0c;指用户可以直观操作和看到的界面&#xff0c;包括web页面的结构&#xff0c;web的外观视觉表现及web层面的交互实现。 web后…

Python---函数的参数类型

位置参数 理论上&#xff0c;在函数定义时&#xff0c;我们可以为其定义多个参数。但是在函数调用时&#xff0c;我们也应该传递多个参数&#xff0c;正常情况&#xff0c;其要一一对应。 相关链接&#xff1a;Python---函数的作用&#xff0c;定义&#xff0c;使用步骤&…

jQuery 第十一章(表单验证插件推荐)

文章目录 前言jValidateZebra FormjQuery.validValValidityValidForm BuilderForm ValidatorProgressionformvalidationjQuery Validation PluginjQuery Validation EnginejQuery ValidateValidarium后言 前言 hello world欢迎来到前端的新世界 &#x1f61c;当前文章系列专栏&…

单链表的反转?太细了哥们!细到离谱!

单链表的反转&#xff08;面试常出&#xff09;&#xff1a; ​ 单链表的反转&#xff0c;可以通过很多种方法实现。包括迭代法&#xff0c;递归法&#xff0c; 迭代法&#xff1a; 定义三个指针&#xff1a;prev、current和next&#xff0c;它们分别表示前一个节点、当前节点…

Excel动态选择某一行/列的最后一个数据

选择列的最后一个数据&#xff1a; 以A列为例&#xff0c;使用&#xff1a; LOOKUP(1,0/(A:A<>""),A:A)选择行的最后一个数据&#xff1a; 以第3行为例&#xff0c;使用&#xff1a; LOOKUP(1,0/(3:3<>""),3:3)示例程序 列最后一个数据&a…

IO口速度影响了什么?

我们在初学单片机的时候都知道单片机GPIO的作用是巨大的&#xff0c;在配置GPIO的时候&#xff0c;结构体初始化里有一个选项是配置输入输出速度的&#xff0c;对于这个速度输出是必须要配置的&#xff0c;输入没有明令说明需不需要配置。 这个速度对于学习过32单片机的都应该知…

python——第十三天

uuid 是通用唯一识别码&#xff08;Universally Unique identifier&#xff09;的缩写 UUID是一个128比特的数值 uuid模块&#xff1a; 获取一个128位&#xff08;比特&#xff09;的永不重复的数字&#xff0c;当然我们使用的时候会转换为32个的字符串 impor uuud uui…

js粒子效果(二)

效果: 代码: <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Particle Animation</title><…

C#,《小白学程序》第七课:列表(List)其一,编制《高铁车次信息表》

1 文本格式 /// <summary> /// 车站信息类 class /// </summary> public class Station { /// <summary> /// 编号 /// </summary> public int Id { get; set; } 0; /// <summary> /// 车站名 /// </summary>…

【算法专题】滑动窗口—无重复字符的最长子串

力扣题目链接&#xff1a;无重复字符的最长子串 一、题目解析 二、算法原理 解法一&#xff1a;暴力解法&#xff08;时间复杂度最坏&#xff1a;O(N)&#xff09; 从每一个位置开始往后枚举&#xff0c;在往后寻找无重复最长子串时&#xff0c;可以利用哈希表来统计字符出现…

2023年【汽车驾驶员(中级)】最新解析及汽车驾驶员(中级)试题及解析

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2023年汽车驾驶员&#xff08;中级&#xff09;最新解析为正在备考汽车驾驶员&#xff08;中级&#xff09;操作证的学员准备的理论考试专题&#xff0c;每个月更新的汽车驾驶员&#xff08;中级&#xff09;试题及解…

vue3 for循环创建的多个e-form 添加校验

v-for 创建 ref <el-form :model"item" :rules"state.rules" :ref"el > getRiskSpreadRef(el, index)" ></el-form>// 定义ref list const riskSpreadRefList ref<HTMLElement[]>([]);// ref存到数组 const getRiskSpread…

Error running Tomcat8: Address localhost:1099 is already in use 错误解决

摘要: 有时候运行web项目的时候会遇到 Error running Tomcat8: Address localhost:1099 is already in use 的错误&#xff0c;导致web项目无法运行。这篇 blog 介绍了解决办法。 有时候运行web项目的时候会遇到 Error running Tomcat8: Address localhost:1099 is already in …

C语言贪吃蛇(有详细注释)

这个贪吃蛇是在比特特训营里学到的&#xff0c;同时我还写了用EasyX图形库实现的图形化贪吃蛇&#xff0c;含有每个函数的实现以及游戏中各种细节的讲解&#xff0c;感兴趣的可以去看一看。 贪吃蛇小游戏 实现效果 以下就是源码&#xff0c;感兴趣的小伙伴可以cv自己玩一玩改…

vatee万腾的科技征途:Vatee独特探索的数字化力量

在数字化时代的浪潮中&#xff0c;Vatee万腾以其独特的科技征途成为引领者。公司在数字化领域的探索之路不仅是技术的创新&#xff0c;更是一种对未知的勇敢涉足&#xff0c;是对新时代的深刻洞察和积极实践。 Vatee万腾通过独特的探索&#xff0c;展示了在数字化征途上的创新力…

分享11款原型图软件,让你的创意无限发挥!

即时设计 即时设计是一个集设计、原型、开发于一身的一站式在线设计工具&#xff0c;也是一个可以云端编辑、团队写作的在线原型网站。 即时设计 - 可实时协作的专业 UI 设计工具即时设计是一款支持在线协作的专业级 UI 设计工具&#xff0c;支持 Sketch、Figma、XD 格式导入&…