【C++】C++11<包装器没写>
文章目录
- 一、初始化列表的统一
- 1.列表初始化
- 2.initializer_list
- 二、声明
- 1.auto
- 2.decltype
- 3.nullptr
- 三、范围for
- 四、智能指针
- 五、STL中的变化
- 1.新容器
- array
- forward_list
- 2.接口
- 六、右值引用
- 1.左值引用和右值引用
- 2.右值引用的使用场景和意义
- 3.左值引用和右值引用的价值和场景
- 4.完美转发
- 七、可变参数模板
- 1.可变参数模板
- 求参数个数
- 取参数值:
- 2.emplace系列
- 八、lambda表达式
- 引子
- 语法
- 九、包装器
- 1.functional
- 2.bind
- 十、新增成员函数
一、初始化列表的统一
1.列表初始化
在C++98中,标准允许使用花括号{}对数组或者结构体元素进行统一的列表初始值设定。比如:
struct Point
{int _x;int _y;
};
int main()
{int array1[] = { 1, 2, 3, 4, 5 };int array2[5] = { 0 };Point p = { 1, 2 };return 0;
}
C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定义的类型,一切皆可使用{}初始化,使用{}初始化时,可添加等号(=),也可不添加。
而且使用这样的{}会去调用构造函数
同时这样的话就可以应用于new中
int* ptr1 = new int[3]{ 1, 2, 3 };Point* ptr2 = new Point[2]{ p0,p1 };Point* ptr3 = new Point[2]{ {1,1},{0,0} };
struct Point
{//explicit Point(int x, int y)// :_x(x)// , _y(y)//{// cout << "Ponint(int x, int y)" << endl;//}Point(int x, int y):_x(x), _y(y){cout << "Ponint(int x, int y)" << endl;}int _x;int _y;
};int main()
{int x = 1;int y = { 2 };int z{ 3 };int a1[] = { 1,2,3 };int a2[]{ 1,2,3 };//都是在调用构造函数Point p0(1, 2);//C++11Point p1 = { 1, 2 };Point p2{ 1,2 };const Point& q = { 1,2 };int* ptr1 = new int[3]{ 1, 2, 3 };Point* ptr2 = new Point[2]{ p0,p1 };Point* ptr3 = new Point[2]{ {1,1},{0,0} };return 0;
}
建议:日常定义,不要去掉=,显得奇怪,降低代码可读性。
其实上面这些用{}的本质是一个多参数的隐式类型转换,因为之前string中的单参数的隐式类型转换
string s = "xxxxx";
explicit关键字,可以防止隐式类型转换;若加在构造函数前面,那么就无法使用{}这样进行初始化了
再比如,我们可以使用引用进行验证,如果没有explicit关键字,这个引用还可以编译通过
这里必须要加const,因为隐式类型转换要产生一个临时对象,这个临时对象具有常性。
2.initializer_list
下面两段代码是同一个语法吗?
struct Point
{Point(int x, int y):_x(x),_y(y){cout << "Ponint(int x, int y)" << endl;}int _x;int _y;
};int main()
{vector<int> v = { 1,2,3,4,5,6,7 };Point p = { 1,2 };return 0;
}
其实不是的,对于vector,它后面的花括号参数是可以改变的,而对于Point,它后面的花括号参数是不可以改变的。
所以说,这两个其实是利用不同的规则进行初始化的。
第二个我们好理解,就是多参数的隐式类型转换。
那么第一个是咋回事呢?其实C++11做了这样一件事。它新增了一个类型,叫做initializer_list
那么initializer_list是如何实现的呢?
template<class T>
class initializer_list
{
private:const T* _start;const T* _finish;
}
然后我们赋值时候所给的数组其实是存储在常量区的,当我们赋值的时候,这两个指针其实一个指向常量区的开始,一个指向常量区的结尾
所以当我们打印这个类型的大小的时候,我们会发现,在32位下是8字节,64位是16字节
里面的类型会自己推导
回到上面的话题:vector为什么可以直接接收initializer_list的类型呢?
其实本质上是vector写了一个构造函数,即支持使用initializer_list初始化的构造函数。
所以现在也解释了为什么vector看上去使用{}初始化可以有任意个类型,其实是两次构造函数得到的。
vector(initializer_list<value_type> il)
{reserve(il.size());for(auto& e : il){push_back(e);}
}
如下是在我们原本的vector容器中进行改造的:
不仅仅是vector中可以这样使用,在map中也有initializer_list初始化:
这样在map中这样用其实比较有点意思,首先map的插入需要的是pair类型,所以实际上里层的两个花括号是多参数的隐式类型转换,将两个字符串转化为pair类型,然后外层的花括号就是initializer_list了。
二、声明
1.auto
实现自动类型推断;当一个类型过于长,就可使用auto提高效率,但是对于不熟悉代码的人,会影响可读性;
详解:在C++98中auto是一个存储类型的说明符,表明变量是局部自动存储类型,但是局部域中定义局部的变量默认就是自动存储类型,所以auto就没什么价值了。C++11中废弃auto原来的用法,将其用于实现自动类型推断。这样要求必须进行显示初始化,让编译器将定义对象的类型设置为初始化值的类型。
2.decltype
decltype可先认为是typeid的升级版(不仅会提取出类型名称,还可以使用提取出的类型);但这里还和aotu不一样,auto是根据值得类型去推端出来的,而decltype是根据传的参数已经定好的。
而且decltype还可以推导表达式的类型:
在函数类模板中:
3.nullptr
由于C++中NULL被定义成字面量0,这样就可能回带来一些问题,因为0既能指针常量,又能表示整形常量。所以出于清晰和安全的角度考虑,C++11中新增了nullptr,用于表示空指针。
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
看输出的第二行,就发现不是预期的,而nullptr很好的解决了该问题。
三、范围for
本质就是迭代器,有begin和end两个成员函数,在STL已经详解。
四、智能指针
详解
五、STL中的变化
1.新容器
标记的为新容器,最有用的是unordered_map和unordered_set。
array
array对标的其实就是普通的数组,它两在用法上几乎没有任何区别,甚至是字节大小都一样,唯一不同的就是他们两个的类型不同。这俩个都是静态的数组:
虽然这两个用起来没有任何区别,但是array对于[]运算符重载要比普通的更严格一些
1
.C语言数组越界检查,越界读基本检查不出来,越界写是抽查,因为其本质是指针的解引用
2
.array越界后会强制报错,主要原因就是它的[]运算符本质是operator[]函数的调用,内部会有检查的
不过总体说array还是比较鸡肋的,因为我们更喜欢使用vector,而且它还可以初始化,
vector<int> v(10,0);
补充:array开的空间过大,导致栈溢出。
forward_list
接口:
它只支持头插和头删除,因为尾插尾删效率太低。
如果非要用可以使用insert和erase。但是这两个是往该节点后面插入或删除的。
本质就是一个单链表加了一些功能
2.接口
容器内部变化:
1.都支持initializer_list构造,支持列表初始化
2.增加了cbegin、cend系列接口(鸡肋)
3.移动构造和移动赋值
4.右值引用参数的插入
六、右值引用
1.左值引用和右值引用
传统的C++语法中就有引用的语法,而C++11中新增了的右值引用语法特性,所以之前的引用就叫做左值引用。无论左值引用还是右值引用,都是给对象取别名。
什么是左值?什么是左值引用?
左值
是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址
,一般可以对它赋值,左值可以出现赋值符号的左边,也可以出现在赋值符号的右边,而右值不能出现在赋值符号左边,只能出现在赋值符号的右边。所以左边的一定是左值,右边的不一定是右值;
左值一般可以对它进行赋值,有几个例外就是定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址。左值引用就是给左值的引用,给左值取别名
。
还有字符串常量、字符串上每一个元素都是左值
什么是右值,什么是右值引用?
右值
也是一个表示数据的表达式,如:字面常量(字符)、表达式返回值,函数返回值(这个不能是左值引用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址
。右值引用就是对右值的引用,给右值取别名。
0
0
总之,记住一句话:
左值一定可以取地址,右值无法取出地址
左值引用就是给左值取别名
右值引用就是给右值取别名
语法:右值引用就是使用两个&&即可
int main()
{double x = 1.1, y = 2.3;//右值不能取地址10;x + y;//以下是右值引用int&& rr1 = 10;double&& rr2 = x + y;return 0;
}
左值引用给右值取别名,只需要加上一个const就可以了,但是绝不可以直接使用左值引用去引用右值,这会直接报错:
int main()
{const int& r = 0;return 0;
}
右值引用给左值取别名,不可以直接使用,但是可以给左值加上move就可以了,但是move可能会对这个左值造成一些其他影响:
int main()
{//左值引用给右值取别名const int& r = 0;//右值引用给左值取别名int a = 0;int&& r1 = move(a);return 0;
}
总结:
- 左值和右值的区别就是能否取地址,左值可以取地址,右值不可以取地址
- 左值引用可以给左值取别名,不可以引用右值;右值引用可以给右值取别名,但是不可以引用左值
- const左值引用可以给右值取别名,右值引用可以给move以后的左值取别名
2.右值引用的使用场景和意义
先分析左值引用价值:
引用的价值:减少拷贝、增加效率
左值引用解决的问题:
1.做参数:减少拷贝、做输出型参数;
2.做返回值:减少拷贝、引用返回(可修改返回对象如operator[])
然后左值引用有一种场景还没有解决:那就是返回局部对象不可以使用左值引用。即下面的场景,我们只能去使用传值返回,不能传左值引用返回,因为无论是左值引用返回还是const左值引用返回其实本质上都是引用那块空间,而这块空间出了作用域就销毁了。销毁了以后我们还拿到这个别名的话,那么问题就大了,因为就相当于野指针了。我们没有权限去访问。
所以这个地方在之前只能使用传值返回
而传值返回的话,如果编译器不加任何优化,那么s返回的时候要产生一个临时对象,这是一次拷贝构造,然后用这个临时对象在拷贝构造给ans,这又是一次拷贝构造,代价实在太大了。如果这个str字节有十几万的话,代价很大的。所以编译器将这里给优化为了一次拷贝构造。
这里为了方便标记,我们使用自己在学习STL写的string类
namespace my
{class string{public:typedef char* iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}string(const char* str = ""){_size = strlen(str);_capacity = _size;_str = new char[_capacity + 1];strcpy(_str, str);cout << "默认构造 string(const char* str = "")" << endl;}//现代写法 //s2(s1)void swap(string& tmp){::swap(_str, tmp._str);::swap(_size, tmp._size);::swap(_capacity, tmp._capacity);}string(const string& s):_str(nullptr),_size(0),_capacity(0){string tmp(s._str);swap(tmp);cout << "深拷贝 string(const string& s)" << endl;}string& operator=(const string& s){if (&s != this){string tmp(s);swap(tmp);}cout << "深拷贝 string& operator=(const string& s))" << endl;return *this;}~string(){delete[] _str;_str = nullptr;_capacity = _size = 0;}const char* c_str() const {return _str;}size_t size()const {return _size;}size_t capacity()const{return _capacity;}char& operator[](size_t pos)const{assert(pos < _size);return _str[pos];}void reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}void push_back(const char ch){insert(_size, ch);}void append(const char* str){insert(_size, str);}void append(const string& s){append(s._str);}string& operator+=(char ch){push_back(ch);return *this;}string& operator+=(const char* str){append(str);return *this;}string& insert(size_t pos, char ch){assert(pos <= _size);//满需扩容if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}size_t end = _size + 1;while (end > pos){_str[end] = _str[end - 1];--end;}_str[pos] = ch;++_size;return *this;}string& insert(size_t pos, const char* str){assert(pos <= _size);size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}//挪动数据size_t end = _size + len;while (end >= pos + len){_str[end] = _str[end - len];--end;}strncpy(_str + pos, str, len);_size += len;return *this;}void erase(size_t pos, size_t len = npos){assert(pos <= _size);if (len == npos || pos + len >= _size){_str[pos] = '\0';_size = pos;}else{strcpy(_str + pos, _str + len + pos);_size -= len;}}bool operator>(const string& s)const{return strcmp(_str, s._str) > 0;}bool operator==(const string& s)const{return strcmp(_str, s._str) == 0;}bool operator<(const string& s)const{/*return strcmp(_str, s._str) < 0;*/return !(*this > s);}bool operator>=(const string& s)const{return *this >(s) || operator==(s);}bool operator<=(const string& s)const{return *this <(s) || operator==(s);}bool operator!=(const string& s)const{return !(*this==s);}void clear(){_str[0] = '\0';_size = 0;}size_t find(char ch, size_t pos = 0)const {assert(pos < _size);for (size_t i = pos; i < _size; i++){if (ch == _str[i]){return i;}}return npos;}size_t find(const char* sub, size_t pos = 0)const{assert(pos < _size);assert(sub);//kmp算法/bm算法const char* ptr = strstr(_str + pos, sub);if (ptr == nullptr){return npos;}else{return ptr - _str;}}//"hello world"string substr(size_t pos, size_t len = npos)const{assert(pos <= _size);size_t reallen = len;if (npos == len || pos + len > _size){reallen = _size - pos;}string tmp;for (size_t i = 0; i < reallen; i++){tmp += _str[pos + i];}return tmp;}void resize(size_t n, char ch = ' '){if (n <= _size){_str[n] = '\0';_size = n;}else{reserve(n);while (_size != n){_str[_size] = ch;_size++;}_str[n] = '\0';}}private:char* _str;size_t _capacity;size_t _size;public:const static size_t npos = -1;};ostream& operator<<(ostream& out,const string& s){for (size_t i = 0; i < s.size(); ++i){out << s[i];}return out;}istream& operator>>(istream& in, string& s){s.clear();char ch;ch = in.get();const size_t N = 32;char buff[N];size_t i = 0;while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == N - 1){buff[i] = '\0';s += buff;i = 0;}ch = in.get();}buff[i] = '\0';s += buff;return in;}
}
来看第一个框,在函数s中str被返回,会被拷贝给临时变量,临时变量拷贝给ans又是一次拷贝构造,但这里为何只有一次呢,实则是编译器做了优化;我们打开vs2019的终端采用命令行模式加入-fno-elide-constructors
参数去掉优化,重新编译运行,发现果然如此。
好,现在我们先暂放这里,接着往下看:
下面的代码是否构成函数重载?
很明显构成函数重载,参数不同的类型;
那这个呢?
其实也会的。
但是这里const左值引用是可以引用右值,而下面的也能引用右值。那么当我们写出下面的这段代码的时候,会发生什么呢?
他们会走向最匹配的函数
而且如果没有下面的,编译器也是可以跑的
所以在这里,如果有右值引用的版本,就会走右值引用版本,会走最匹配的
然后现在我们再去回过头来看前面的代码,我们知道,前面的代码在编译器不优化的情况下,代价有点太大了
在这里一共涉及到了三次深拷贝,第一次是str创建的时候,第二次是str返回的时候会产生一个临时对象,第三次是将这个临时对象给s时候,还要发生一次深拷贝。
然而其中有两块空间可以说是浪费掉了,如下图打×的部分都是被浪费掉了
那么有没有什么办法可以进行优化呢?
其实关于右值:
我们可以把它分为内置类型的右值和自定义类型的右值
而内置类型的右值我们一般也称为纯右值
,自定义类型的右值一般也称为将亡值
而函数返回值,表达式返回值也是一个右值。并且对于我们上面s字符串的操作,比如说s1+s2,to_string,其实本质都是函数调用,(s1+s2是一个运算符重载,其实本质也是函数调用),而这些函数调用返回的都是将亡值。
换句话说:
s = 左值 //只能老老实实进行深拷贝
s = 右值将亡值 //可以进行移动拷贝
这里的移动拷贝其实就是交换资源,如下所示,就是移动拷贝
来分析一下,由于func函数返回的是一个临时对象,这个临时对象就是一个右值,既然是右值,那么我们就使用右值引用,正好交换资源。即可,相比使用const左值引用要减少了一次深拷贝。
使用const左值引用的话,const对象无法被修改,所以只能去使用一次拷贝构造去创建一个可以交换资源的对象,然后再进行交换。而对于右值引用,就不存在无法被修改的问题了。所以可以直接去交换资源
而且由于编译器会自动走最匹配的,所以对于右值会走向第二个函数,只有当第二个函数不存在的时候,才会走向第一个函数。
如下图所示,第一次深拷贝是func函数中要返回一个临时对象所造成的,第二次拷贝是移动拷贝所必须的,但是这里的移动拷贝里面仅仅只是交换资源,几乎没有消耗。
所以使用了移动拷贝的话,那么就只剩下两次消耗了,一次是func函数中要开一个str字符串,一次是要返回一个临时对象,两次消耗,但是只有一次深拷贝。在赋值这里就没有任何消耗了。而原来的就是三次消耗,即需要两次深拷贝。
原来:
现在:
那么上面的场景是我们不让编译器优化的场景,那么如果让编译器优化呢?
我们可以在利用右值引用写出这样的拷贝构造函数:
string(string&& s):_str(nullptr){cout << "string(string&& s) -- 移动拷贝" << endl;swap(s);}
反正右值都是将亡值,没什么用的值,那么不妨直接用来作为资源即可,即直接交换,直接就可以几乎没有任何开销了,没有深拷贝了
这样的,原来的const左值引用,虽然可以引用右值,但是由于const,导致我们无法直接利用这个将亡值的资源,我们只能眼睁睁看着这个将亡值自己消亡,却无法直接拿走他的资源。所以只能自己去利用它创造一个对象,用这个新的对象去交换资源,这样就多了一次深拷贝了。
可见直接0开销了,而前面编译器优化后还有1次开销呢,可见充分利用了右值的将亡特性。
不过在这里还有一些疑惑的点就是,s要返回的时候,str是一个左值,那么他就必须得用来拷贝构造来构造一个临时对象,这个临时对象确实可以零开销的进行拷贝构造了,但是这里应该还有一次拷贝构造啊?为什么打印结果里没有呢?
其实本身编译器就会做出一些优化:即连续的构造、拷贝构造会进行合二为一,甚至是合三为一。而编译器在这里做出的优化其实就是直接用str去拷贝构造ret。即不需要借助中间的临时变量了。那么func返回的其实就是str本身。而str虽然是一个左值,但是他本身符合将亡值的特征。因为出了作用域,它即将销毁,所以编译器此时做出了第二个优化:把str本身识别为右值。相当于给move了一下,右值引用去引用左值。
总的来说编译器直接进行了两次优化:
1.连续的构造、拷贝构造合二为一(不需要临时变量了,只有传值返回才可以)
2.编译器把str识别为了右值(因为str虽然是左值,但是符合将亡值特征,相当于进行了一次特殊处理)
而且还需要注意的是,这里千万不可以写传引用返回
首先就是传引用返回的话,那么这块空间已经被销毁了,就出现了野指针的问题了。其次只有传值返回的时候,编译器才会进行优化,如果不传值返回,编译器就不会进行上面的优化了。
而且传值返回所造成的第一个优化,即不需要临时变量的本质其实就是把拷贝放在了str还没有销毁的时候,即在函数内部。而传引用返回就一定不可以了
在上面如果个str加上一共move,相当于我们也将他认为是右值了,这样其实也是可以的
一旦我们加上了拷贝构造的右值引用,那么对于编译器无法第一种优化的场景也可以使用第二次种优化。
在这里因为我们并不是连续的拷贝和拷贝构造。而是一次拷贝构造和一次赋值运算符重载。
拷贝构造是由于func要返回一个临时对象,但是这个我们可以将str识别为将亡值,就可以使用移动拷贝了。将str的资源转移到临时对象中去
然后这个临时对象又进一步的使用赋值运算符重载,这里又是一次移动拷贝,因为刚好这个临时对象是一个右值。又一次的转移资源。
最终整个过程没有任何的消耗,仅仅只是两次转移资源,代价极低
3.左值引用和右值引用的价值和场景
对于右值引用的移动拷贝,实际上我们一般只将其用于自定义类型中,尤其是深拷贝的场景,比如vector<vecor>这种拷贝代价极大的场景,而对于内置类型,对其使用右值引用的移动拷贝其实意义并不是很大,或者说没有任何意义。并不能带来一丝的优化。甚至对于浅拷贝的自定义类型也没有任何价值。只有深拷贝的自定义类型才有价值。
左值引用的核心价值就是减少拷贝,提高效率
而右值引用的核心就是价值就是进一步减少拷贝,弥补左值引用没有解决的场景。如:传值返回。
那么右值引用的场景有哪些呢?
场景一:自定义类型中深拷贝的类,且必须传值返回的场景
我们可以同时对比满足和不满足的场景
此时str的地址后四位是0fe8
当出了移动拷贝结束后,此时ans的地址后四位是0fe8;
所以最终结果为交换了资源,而下面这个是不会交换资源的
像下面第一种种move也是不会转移走ret的资源的,但是第二种move这样使用会转移走资源;
我们可以理解为,当一个值被move修饰后作为参数传入其他函数,它的内容就会被掏空,然后自己拿到的是函数里别人给他的值;
场景二:容器的插入接口,如果插入对象是右值,可以利用移动构造转移资源给数据结构中的对象,也可以减少拷贝
如下图所示:
不难发现第二种和第三种才是最高效的,但第二种操作之后,str1的值就没了,被掏空了;一般我们采用第三种,相当于创造了个临时对象(或匿名对象),具有常性的。是一个将亡值,就会导致它会去调用右值引用。
如果没有右值引用的话,即没有移动构造的话,那么三个都将是拷贝+拷贝构造。(我们这里只是没有将拷贝打印出来而已)
即便是在STL的其他容器中,基本都是有右值引用的。用来提高效率
4.完美转发
万能引用
(引用折叠)
下面代码是一个模板,在这个模板中,有一个看上去像右值引用的存在,但是实际上,它并不是右值引用,而是万能引用。
void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }
template<typename T>
void PerfectForward(T&& t)
{Fun(t);
}
万能引用:它既可以接收左值,又可以接收右值
当实参为左值的时候,它就是左值引用,我们也称为引用折叠
当实参为右值的时候,它就是右值引用
所以对于下面的代码,我们就可以知道,这些实际调用的都是右值,左值,右值,const左值,const右值。他们调用的实际上不是同一个函数
void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }
template<typename T>
void PerfectForward(T&& t)
{Fun(t);
}int main()
{PerfectForward(10); // 右值int a;PerfectForward(a); // 左值PerfectForward(std::move(a)); // 右值const int b = 8;PerfectForward(b); // const 左值PerfectForward(std::move(b)); // const 右值return 0;
}
但是当我们运行的时候
运行结果为如下所示:
我们会发现结果其实不符合我们的预期
这是什么情况?难道全折叠了?理论上应该不可能的吧。
我们先用下面这段代码来观察一下
这里为何是左值引用?
我们可以用下面这段代码来发现一些问题。我们发现虽然
b0左值引用了a,b1右值引用了a,但是他们两个本身却是左值,因为他们可以取出地址。
我们知道,右值有两个属性:第一个是不可以取地址,第二个是不可以修改。
而这里b1不仅可以取地址,还可以进行修改。
而右值引用似乎却可以进行修改?其实它也必须得修改,如果它不支持修改,那么它就出问题;
来看自己实现的右值引用:
我们会发现,我们在当函数s返回的这个临时变量对象进行调用移动拷贝的时候,参数s是右值引用了str,但是s居然可以被修改,而且这个s还可以传递给一个左值引用去修改。
所以说s是一个左值;
基于此我们再做一个简单的问题复现:
变量 int&& j = 10; 声明了一个右值引用 j,即 j 是一个右值引用类型的变量。尽管 j 是一个右值引用类型的变量,但 j 本身是一个左值,因为它是一个命名变量,可以被赋值和取址。这是C++语言中的一个特性:尽管 j 是右值引用类型,但 j 本身是左值。
该解释初听可能有点绕,小伙伴们多多体会。
此时最初的疑惑可解:
那我们如何让它按最初的想法实现它本应有的功能呢?
C++在这里搞出来了一个完美转发
当t是左值的时候,保持左值属性
当t是右值的时候,保持右值属性
关键词:forward
想到完美转发,我们可以突然意识到前面有一个问题似乎编译器底层应该用的就是完美转发了,即下面的val本来是左值,但是我们需要它的右值属性,所以可以使用完美转发
然后我们可以去尝试修改一下我们之前所是实现的链表,如下是之前的链表:
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string.h>
#include<assert.h>
using namespace std;
namespace my
{class string{public:typedef char* iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}string(const char* str = ""){_size = strlen(str);_capacity = _size;_str = new char[_capacity + 1];strcpy(_str, str);
// cout << "默认构造 string(const char* str = "")" << endl;}//现代写法 //s2(s1)void swap(string& tmp){::swap(_str, tmp._str);::swap(_size, tmp._size);::swap(_capacity, tmp._capacity);}string(const string& s):_str(nullptr),_size(0),_capacity(0){string tmp(s._str);swap(tmp);cout << "深拷贝构造 string(const string& s)" << endl;}string(string&& s):_str(nullptr), _size(0), _capacity(0){swap(s);cout << "移动拷贝构造 string(string&& s)" << endl;}string& operator=(const string& s){if (&s != this){string tmp(s);swap(tmp);}cout << "深拷贝赋值拷贝 string& operator=(const string& s))" << endl;return *this;}string& operator=(string&& s){swap(s);cout << "移动赋值拷贝 string& operator=(string&& s)" << endl;return *this;}~string(){delete[] _str;_str = nullptr;_capacity = _size = 0;}const char* c_str() const {return _str;}size_t size()const {return _size;}size_t capacity()const{return _capacity;}char& operator[](size_t pos)const{assert(pos < _size);return _str[pos];}void reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}void push_back(const char ch){insert(_size, ch);}void append(const char* str){insert(_size, str);}void append(const string& s){append(s._str);}string& operator+=(char ch){push_back(ch);return *this;}string& operator+=(const char* str){append(str);return *this;}string& insert(size_t pos, char ch){assert(pos <= _size);//满需扩容if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}size_t end = _size + 1;while (end > pos){_str[end] = _str[end - 1];--end;}_str[pos] = ch;++_size;return *this;}string& insert(size_t pos, const char* str){assert(pos <= _size);size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}//挪动数据size_t end = _size + len;while (end >= pos + len){_str[end] = _str[end - len];--end;}strncpy(_str + pos, str, len);_size += len;return *this;}void erase(size_t pos, size_t len = npos){assert(pos <= _size);if (len == npos || pos + len >= _size){_str[pos] = '\0';_size = pos;}else{strcpy(_str + pos, _str + len + pos);_size -= len;}}bool operator>(const string& s)const{return strcmp(_str, s._str) > 0;}bool operator==(const string& s)const{return strcmp(_str, s._str) == 0;}bool operator<(const string& s)const{/*return strcmp(_str, s._str) < 0;*/return !(*this > s);}bool operator>=(const string& s)const{return *this >(s) || operator==(s);}bool operator<=(const string& s)const{return *this <(s) || operator==(s);}bool operator!=(const string& s)const{return !(*this==s);}void clear(){_str[0] = '\0';_size = 0;}size_t find(char ch, size_t pos = 0)const {assert(pos < _size);for (size_t i = pos; i < _size; i++){if (ch == _str[i]){return i;}}return npos;}size_t find(const char* sub, size_t pos = 0)const{assert(pos < _size);assert(sub);//kmp算法/bm算法const char* ptr = strstr(_str + pos, sub);if (ptr == nullptr){return npos;}else{return ptr - _str;}}//"hello world"string substr(size_t pos, size_t len = npos)const{assert(pos <= _size);size_t reallen = len;if (npos == len || pos + len > _size){reallen = _size - pos;}string tmp;for (size_t i = 0; i < reallen; i++){tmp += _str[pos + i];}return tmp;}void resize(size_t n, char ch = ' '){if (n <= _size){_str[n] = '\0';_size = n;}else{reserve(n);while (_size != n){_str[_size] = ch;_size++;}_str[n] = '\0';}}private:char* _str;size_t _capacity;size_t _size;public:const static size_t npos = -1;};ostream& operator<<(ostream& out,const string& s){for (size_t i = 0; i < s.size(); ++i){out << s[i];}return out;}istream& operator>>(istream& in, string& s){s.clear();char ch;ch = in.get();const size_t N = 32;char buff[N];size_t i = 0;while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == N - 1){buff[i] = '\0';s += buff;i = 0;}ch = in.get();}buff[i] = '\0';s += buff;return in;}
}
而对于STL库里里面的代码来说就是正常的移动拷贝了
主要原因就是因为,list的push_back接口只有const左值引用版本,为了解决这个问题,我们只能去使用一个右值引用版本的来处理
所以我们现在来进行修改list
改造后:
除此之外,我们还可以这样做,这样做的话,也就是说是,只需要使用一个万能引用就可以了。不过这个函数我们必须加上模板,不然的话对于const类型是无法进行构造的。
七、可变参数模板
1.可变参数模板
里的三个点就代表了,可以写任意个参数
这里面其实就相当于有一个数组把这个实参存起来,然后printf会依次访问数组里面的元素。
以上就是函数的可变参数
而模板参数和函数参数是很类似的,模板参数传递的是类型,函数参数传递的是对象。函数的可变参数是传多个对象,而模板的可变参数就是可以传多个类型
Args是一个模板参数包,args是一个函数形参参数包
声明一个参数包Args…args,这个参数包中可以包含0到任意个模板参数。
求参数个数
template<class T,class ...Args>
void showList(T val, Args... args)
{cout << sizeof...(args) << endl;
}
int main()
{showList(1.1);showList(1.1, 2);showList(1.1, 85u, 90.3f);showList(22, 55);return 0;
}
template<class ...Args>
void showList(Args... args)
{cout << sizeof...(args) << endl;
}
int main()
{showList(1.1);showList(1.1, 2);showList(1.1, 85u, 90.3f);showList(22, 55);return 0;
}
看到这里正常返回参数个数;
取参数值:
template<class T>
void showList(T val)
{cout << val << endl;
}
template<class T,class ...Args>
void showList(T val, Args... args)
{cout << typeid(val).name() << val << endl;showList(args...);
}
int main()
{showList(1.1);showList(1.1, 2);showList(1.1, 85u, 90.3f);showList(22, 55, 2, 85, 69);return 0;
}
可清晰地看出它每次将n个参数拆分为1 和n-1;当n=0时,当单个参数传入,不在形成参数包;
这样看着很奇怪,要想查看哪些参数是一组的还得自己去处理,怎样给他区分一下呢?
那就给他加一个参数包进去
但显示他根本就没有调用新增的函数,分析一下,原来它会默认的将参数包放入2号中去拆分,当剩一个参数的时候就说明是该包完了,去换行即可,之前新增的删掉。
template<class T>
void showList(T val)
{cout << val << endl;
}
template<class T,class ...Args>
void showList(T val, Args... args)
{cout << typeid(val).name() << ":" << val << " ";showList(args...);
}int main()
{showList(1.1);showList(1.1, 2);showList(1.1, 85u, 90.3f);showList(22, 55, 2, 85, 69);return 0;
}
注意:参数包不可当做数组去访问
它这里其实就用了一个编译时的递归。
一开始会将第一个参数传给T,然后剩下的参数包都传给下一层函数。最上面就是结束条件。
在库里面就有一个类似的接口
不过它的参数只有一个参数包,那么应该如何传递呢?我们也搞一个:
template<class T>
void showList(T val)
{cout << val << endl;
}
template<class T,class ...Args>
void showList(T val, Args... args)
{cout << typeid(val).name() << ":" << val << " ";showList(args...);
}
template<class ...Args>
void showList(Args... args)
{cout << "lllllllllllllllllllllllll" << endl;showList(args...);
}//`注意:参数包不可通过遍历数组去访问
int main()
{showList(1.1);showList(1.1, 2);showList(1.1, 85u, 90.3f);showList(22, 55, 2, 85, 69);return 0;
}
我们发现它就不调用参数包形式的参数,查文档发现多个参数它会首先去适配1 与n-1的参数值写法,那我们给他再变一下,写成子函数形式:
template<class T>
void _showList(T val)
{cout << val << endl;
}
template<class T,class ...Args>
void _showList(T val, Args... args)
{cout << typeid(val).name() << ":" << val << " ";_showList(args...);
}
template<class ...Args>
void showList(Args... args)
{cout << "lllllllllllllllllllllllll" << endl;_showList(args...);
}
int main()
{showList(1.1);showList(1.1, 2);showList(1.1, 85u, 90.3f);showList(22, 55, 2, 85, 69);return 0;
}
成功!
代码简化:
void _showList()
{cout << endl;
}
template<class T,class ...Args>
void _showList(T val, Args... args)
{cout << val << " ";_showList(args...);
}
template<class ...Args>
void showList(Args... args)
{_showList(args...);
}int main()
{showList(1.1);showList(1.1, 2);showList(1.1, 85u, 90.3f);showList(22, 55, 2, 85, 69,"asjxkasjck");return 0;
}
这也符合参数包的概念,参数个数可以为0~n;其实给本质就是递归调用。
这里的核心逻辑就是,在Showlist中,会将参数包传给PrintArg这个函数,这个函数只会解析第一个参数,后面的逗号是一个逗号表达式,用于初始化数组,后面的三个点就是有几个参数就会相当于调用了几次PrintArg这个函数
void Showlist()
{cout << endl;
}
template<class T>
void PrintArg(T t)
{cout << t << " ";
}
template<class ...Args>
void Showlist(Args... args)
{int a[] = { (PrintArg(args),0)... };cout << endl;
}int main()
{Showlist(1);Showlist(1, 1.1);Showlist(1, 1.1, 1.2);Showlist(1, 1.1, 1.3, 1.2, string("xxxxx"));return 0;
}
2.emplace系列
在C++11以后,很多库里面都加上了emplace系列接口
正常尾插:
int main()
{std::list< std::pair<int, char> > mylist;mylist.push_back(make_pair(40, 'd'));mylist.push_back({ 50, 'e' });for (auto e : mylist)cout << e.first << ":" << e.second << endl;return 0;
}
现在有了emplace以后,我们就可以下面的写法了。
int main()
{std::list< std::pair<int, char> > mylist;// emplace_back支持可变参数,拿到构建pair对象的参数后自己去创建对象// 那么在这里我们可以看到除了用法上,和push_back没什么太大的区别mylist.emplace_back(10, 'a');mylist.emplace_back(20, 'b');mylist.emplace_back(make_pair(30, 'c'));mylist.push_back(make_pair(40, 'd'));mylist.push_back({ 50, 'e' });for (auto e : mylist)cout << e.first << ":" << e.second << endl;return 0;
}
虽然emplace效率稍高一些,但是其实还好,因为并没有太大差距,因为移动拷贝的效率很低
要是真要说的,反倒是内置类型和浅拷贝的效率可以提高一些
,因为深拷贝的量级基本在一个量级
。而浅拷贝是就显得差距比较大了
其中最为核心的原因就是emplace可以传参数包,这就导致了它可以传对象,可以传对象过去。而push_back只能传对象
八、lambda表达式
引子
当我们想要对一个类中的数据进行排序的时候,我们想要使用sort的话,显然我们是无法直接进行排序的,当然我们可以使用运算符重载来支持直接排序,但是这里会出现一个问题,那就是一个类有很多的成员,我们如果想要对这个成员排序完成以后,还想要对其他成员进行排序,这个时候我们就只能使用仿函数了,来进行各种各样的排序,如下代码所示:
struct Goods
{string _name; // 名字double _price; // 价格int _evaluate; // 评价Goods(const char* str, double price, int evaluate):_name(str), _price(price), _evaluate(evaluate){}
};struct ComparePriceLess
{bool operator()(const Goods& gl, const Goods& gr){return gl._price < gr._price;}
};
struct ComparePriceGreater
{bool operator()(const Goods& gl, const Goods& gr){return gl._price > gr._price;}
};
struct CompareEvaluateGreater
{bool operator()(const Goods& gl, const Goods& gr){return gl._evaluate > gr._evaluate;}
};
int main()
{vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,3 }, { "菠萝", 1.5, 4 } };sort(v.begin(), v.end(), ComparePriceLess()); //价格升序sort(v.begin(), v.end(), ComparePriceGreater()); //价格降序sort(v.begin(), v.end(), CompareEvaluateGreater()); //评价降序
}
但是上面代码还有一些问题,那就是假如一个命名不规范等问题出现的时候,会非常麻烦
有没有更好的办法呢?当然有,那就是lambda表达式
如下所示,就是一个lambda表达式的简单例子:
总结:
函数指针 ------能不用就不用
仿函数---------类重载operator(),对象可以像函数一样使用
lambda表达式------匿名函数对象,函数内部可以直接定义使用
语法
lambda表达式书写格式:[capture-list] (parameters) mutable -> return-type { statement }
各部分说明
-
[capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量提供lambda函数使用。
-
(parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略
-
mutable:默认情况下,
lambda函数总是一个const函数
,mutable可以取消其常性
。使用该修饰符时,参数列表不可省略(即使参数为空)
。 -
->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可
省略
。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。 -
{statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量
int main()
{int a = 0;int b = 2;auto add1 = [](int x, int y) ->int {return x + y; };//没有明确的名称auto add2 = [](int x, int y) {return x + y; };cout << add1(a, b) << endl;cout << add2(a, b) << endl;return 0;
}
再改之前的代码
int main()
{vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,3 }, { "菠萝", 1.5, 4 } };sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2)->bool {return g1._price < g2._price; }); //价格升序sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2)->bool {return g1._price > g2._price; }); //价格降序sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2)->bool {return g1._evaluate > g2._evaluate; }); //评价降序return 0;
}
接下类再看捕获的用法:
[var]:表示值传递方式捕捉变量var,捕捉后为const类型
[=]:表示值传递方式捕获所有父作用域中的变量(包括this),捕捉后为const类型
[&var]:表示引用传递捕捉变量var
[&]:表示引用传递捕捉所有父作用域中的变量(包括this)
[this]:表示值传递方式捕捉当前的this指针
注意事项:
a. 父作用域指包含lambda函数的语句块
b. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。
比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量 [&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量
c. 捕捉列表不允许变量重复传递,否则就会导致编译错误。
比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复
d. 在块作用域以外的lambda函数捕捉列表必须为空。
e. 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都 会导致编译报错。
f. lambda表达式之间不能相互赋值,即使看起来类型相同
捕捉列表出来的是不可以修改的;如果真的想修改捕捉到的变量,可以加上mutable
如果想修改外面的,可以使用引用捕捉
而且引用捕捉是可以捕捉const变量的。只不过捕捉以后无法修改,但是可以进行访问,他们的地址都是一样的
示例:
不可赋值:
我们会发现其实这两个对象的类型其实是不一样的。所以当然无法赋值。
其实lambda表达式的底层就是仿函数,这里的add1,add2,add3都是一些仿函数对象,只不过他们的类型是编译器自己生成的。我们看不到而已。
测试代码:
class Rate
{
public:Rate(double rate) : _rate(rate){}double operator()(double money, int year){return money * _rate * year;}
private:double _rate;
};
int main()
{// 函数对象double rate = 0.49;Rate r1(rate);r1(10000, 2);// lamberauto r2 = [=](double monty, int year)->double {return monty * rate * year; };r2(10000, 2);return 0;
}
唯一的标识符为uuid;
所以lambda表达式底层其实就是仿函数,就像范围for的底层是迭代器一样
九、包装器
1.functional
2.bind
十、新增成员函数
默认成员函数
c98:6个
C++11:8个
若自己全没写析构、赋值构造、拷贝构造函数的话,会默认生成移动构造;
=default;强制生成默认类型函数;
=delete;强制是对象不能被拷贝;
override:检查虚函数是否被重写,没重写会报错
final:修饰虚函数不能重写且类不能被继承;
面试:用delete关注字实现一个类只能在堆上创建;
在不使用delete关键字时:
1.私有析构函数
2.私有构造
使用delete:
制动析构函数,
~Heap() = delete;
Heap* ptr = new Heap;
operator delete(this);
相关文章:
【C++】C++11<包装器没写>
文章目录 一、初始化列表的统一1.列表初始化2.initializer_list 二、声明1.auto2.decltype3.nullptr 三、范围for四、智能指针五、STL中的变化1.新容器arrayforward_list 2.接口 六、右值引用1.左值引用和右值引用2.右值引用的使用场景和意义3.左值引用和右值引用的价值和场景4…...
《如何避免虚无》速读笔记
文章目录 书籍信息概览躺派(出世)卷派(入世)虚无篇:直面虚无自我篇:认识自我孤独篇:应对孤独幸福篇:追寻幸福超越篇:超越自我 书籍信息 书名:《如何避免虚无…...
【微机及接口技术】- 第四章 内部存储器及其接口(中)
文章目录 第三节 半导体存储器与CPU的连接一、存储芯片与CPU连接中应关注的问题二、存储器扩展1. 位扩展:2. 字扩展3. 字位扩展 三、实现片选控制的方法1. 全译码法2. 部分译码法3. 线选法 第三节 半导体存储器与CPU的连接 一、存储芯片与CPU连接中应关注的问题 C…...
Mysql 数据库下载安装
安装准备 步骤1:输入WindowsMysql下载地址:https://dev.mysql.com/downloads/,选择MySQL Installer for Windows。 步骤2:下载MySQL安装文件 mysql-install-community-8.0.22.0.msi 步骤3:登录MySQL, 如…...
蓝桥杯刷题笔记
奇怪的捐赠 #include <cstdio> #include <iostream> #include <cmath> using namespace std; int main(){// 初始化变量num为1000000,代表总金额为100万元int num 1000000;// 初始化变量cnt为0,用于记录最终划分的份数int cnt 0;//…...
数仓开发团队日常1
第一章:数据的召唤 2005年7月18日,星期一,上午8:30 城市商业银行总行大楼 盛夏的阳光透过高耸的银行大楼玻璃幕墙,在大理石地面上投下斑驳的光影。李明远站在城市商业银行总行大厦前,抬头望着这座在城市金融区并不算高的建筑,却感到一种莫名的压迫感。他整了整领带,深…...
Pgvector的安装
Pgvector的安装 向量化数据的存储,可以为 PostgreSQL 安装 vector 扩展来存储向量化数据 注意:在安装vector扩展之前,请先安装Postgres数据库 vector 扩展的步骤 1、下载vs_BuildTools 下载地址: https://visualstudio.microso…...
学习笔记—C++—入门基础()
目录 C介绍 参考文档 C第一个程序 命名空间namespace namespace的价值 namespace的定义 namespace使用 指定命名空间访问 using将命名空间中某个成员展开 展开命名空间中全部成员 输入和输出 缺省参数 函数重载 引用 引用的概念 应用 const引用 指针和引用的关…...
Pytorch实现之利用深度残差GAN做运动图像的去模糊
简介 简介:采用类似U-Net的解码编码的结构,结合10层的残差连接结构作为生成器,改进PatchGAN得到更大的感受野来作为鉴别器。生成器的损失为内容损失,鉴别器的损失为WGAN-GP损失。大家可以尝试这个模型来解决运动图像的去模糊化。 论文题目:基于深度残差生成对抗网络的运…...
[Windows] XHS-Downloader V2.4 | 小红书无水印下载工具 支持多平台批量采集
[Windows] XHS-Downloader 链接:https://pan.xunlei.com/s/VON4ygFN1JcyzLJJIOqIpqodA1?pwdsinu# XHS-Downloader 是一款开源免费的小红书内容下载工具,支持无水印视频 / 图文提取、多链接批量处理及账号作品采集。其核心优势包括: 全平台…...
从零构建大语言模型全栈开发指南:附录与资源-2.数据集大全-公开语料库、多模态数据集与领域专用数据源
👉 点击关注不迷路 👉 点击关注不迷路 👉 点击关注不迷路 文章大纲 附录与资源-2. 数据集大全:公开语料库、多模态数据集与领域专用数据源一、公开语料库:通用语言模型的基石1.1 主流文本语料库1.2 预处理工具与策略二、多模态数据集:跨模态理解的桥梁2.1 视觉-语言数…...
SDL多线程编程
文章目录 1. SDL 线程基础2. 线程同步3. 线程池4. 注意事项5. 示例:在多个线程中进行图形渲染和输入处理总结在 SDL(Simple DirectMedia Layer)中,多线程编程通常用于提高应用程序的响应性和性能,尤其是在需要同时处理多个任务的场景中,例如渲染、输入处理和音频等。SDL …...
LINUX 4 tar -zcvf -jcvf -Jcvf -tf -uf
cp -r mv: 1.移动文件到目录 2.文件改名 3.目录改名 s 上面是打包 下面是打包并压缩...
STL剖析
1. vector 是一个封装了动态大小数组的顺序容器;数组内容器严格按照线性顺序排序,支持随机访问,因此提供随机访问指针,例如vector::iterator ivite; 并且为了降低空间配置得速度成本,vector实际分配大小要比需求大一点…...
【数据集】Romanov数据集
1. 数据集背景 名称:Romanov 单细胞转录组数据集 来源:Romanov et al., Cell Reports, 2017 原始论文标题: "Molecular interrogation of hypothalamic organization reveals distinct dopamine neuronal subtypes" GEO Accession…...
Baklib企业CMS的核心要素是什么?
企业CMS工作流协同创新 现代企业内容管理的核心挑战在于多角色协作效率与流程可视化的平衡。以Baklib为代表的协同型CMS,通过动态权限分级架构与实时版本追踪技术,构建了从内容草拟、多级审批到版本发布的完整闭环。系统支持多人同时编辑功能࿰…...
JavaWeb 课堂笔记 —— 02 JavaScript
本系列为笔者学习JavaWeb的课堂笔记,视频资源为B站黑马程序员出品的《黑马程序员JavaWeb开发教程,实现javaweb企业开发全流程(涵盖SpringMyBatisSpringMVCSpringBoot等)》,章节分布参考视频教程,为同样学习…...
Kafka 回溯消费
Kafka 回溯消费 是一个非常实用的能力,尤其当你: 消费端挂掉/处理异常消息数据出错/业务需要重跑要对某一段历史数据“重新拉取并消费”日志审计/数据补偿/BI分析 下面我来详细讲讲 Kafka 如何实现“回溯消费”,并配上使用方式、注意事项 &…...
LeetCode算法题(Go语言实现)_32
题目 在一个大小为 n 且 n 为 偶数 的链表中,对于 0 < i < (n / 2) - 1 的 i ,第 i 个节点(下标从 0 开始)的孪生节点为第 (n-1-i) 个节点 。 比方说,n 4 那么节点 0 是节点 3 的孪生节点,节点 1 是…...
CSS Text(文本)学习笔记
一、文本格式化 CSS 提供了多种文本格式化属性,用于控制文本的外观和布局。这些属性可以改变文本的颜色、对齐方式、修饰、大小写转换、缩进等。 1. 文本颜色 CSS 的 color 属性用于设置文本的颜色。颜色可以通过以下方式指定: 十六进制值:…...
MySQL篇(五)MySQL主从同步原理深度剖析
MySQL篇(五)MySQL主从同步原理深度剖析 MySQL篇(五)MySQL主从同步原理深度剖析一、引言二、MySQL主从同步基础概念主库(Master)从库(Slave)二进制日志(Binary Log&#x…...
AGI大模型(10):prompt逆向-巧借prompt
1 提示词逆向 明确逆向提示词⼯程概念 我们可以给ChatGPT提供⼀个简洁的提示词,让它能够更准确地理解我们所讨论的“逆向提示词⼯程”是什么意思,并通过这个思考过程,帮它将相关知识集中起来,进⽽构建⼀个专业的知识领域 提示词:请你举⼀个简单的例⼦,解释⼀下逆向pro…...
【问题记录】C语言一个程序bug定位记录?(定义指针数组忘记[])
背景 写了个小的程序,一直段错误。特此记录 代码 主要代码 int main_mytest(int argc, char *argv) {char *argv_my {"echo","/proc/cpuinfo",};main_mytest(sizeof(argv_my)/sizeof(char*), argv_my); }int main_mytest(int argc, char *a…...
Systemd构建自动化备份服务与外部存储管理
实训背景 你是一家数据公司的系统管理员,需设计一套自动化备份系统,满足以下需求: 定期备份:每周日凌晨1点将 /data 目录压缩备份到 /backups。外部存储挂载:插入USB设备时自动挂载到 /mnt/usb,并触发增量…...
基于Python的微博数据采集
摘要 本系统通过逆向工程微博移动端API接口,实现了对热门板块微博内容及用户评论的自动化采集。系统采用Requests+多线程架构,支持递归分页采集和动态请求头模拟,每小时可处理3000+条数据记录。关键技术特征包括:1)基于max_id的评论分页递归算法 2)HTML标签清洗正则表达…...
Linux | I.MX6ULL开发板固件烧录所需文件详述(9)
01 搞清楚手里的开发板是 EMMC 还是 NAND FLASH 。默认我的商业级是EMMC ,开关:10011010 终结者i.MX6ULL 开卡板分为工业级和商业级两种不同的开发板。 商业级的核心板,它的存储是 EMMC 的,EMMC 的存储是类似于正方形的芯片,旁边是 NAND FLASH的一个封装,因为我们这里…...
单片机实现多线程的方法汇总
在单片机上实现“多线程”的方法有几种,下面按照从简单到复杂、从轻量到系统性来列出常见的方案: 🧵 一、伪多线程(最轻量) 方法:主循环 状态机 / 定时器轮询 主循环中轮流调用各个任务的处理函数&#x…...
探秘叁仟智盒设备:智慧城市的智能枢纽
在智慧城市建设的宏伟蓝图中,各类先进技术与设备层出不穷,叁仟智盒设备作为其中的关键一环,正悄然发挥着巨大作用,为城市的智能化转型注入强大动力。 一、叁仟智盒设备概述 叁仟智盒设备是杭州叁仟智慧城市科技有限公司旗下的重…...
(一)前端程序员转安卓开发分析和规划建议
最近因为公司前端业务萎缩,考虑内部转安卓开发岗,结合自己的经验分享几点建议。前端程序员转安卓开发是一个值得深入分析和规划的职业转型选择。以下是对这一转型的详细分析以及具体的规划建议,帮助大家更好地理解和准备这一转变。 一、技能和…...
配置管理:夯实软件开发与运维根基
配置管理是对系统配置信息进行管理的活动,以下从定义、目的、主要活动、实施流程等方面为你详细介绍: 一、定义 配置管理是通过技术或行政手段对软件产品及其开发过程和生命周期进行控制、规范的一系列措施。配置管理的目标是记录软件产品的演化过程&a…...
PyTorch构建自定义模型
PyTorch 提供了灵活的方式来构建自定义神经网络模型。下面我将详细介绍从基础到高级的自定义模型构建方法,包含实际代码示例和最佳实践。 一、基础模型构建 1. 继承 nn.Module 基类 所有自定义模型都应该继承 torch.nn.Module 类,并实现两个基本方法&…...
JVM虚拟机篇(一)深入理解JVM:组成部分、运行流程及程序计数器详解
JVM虚拟机篇(一)深入理解JVM:组成部分、运行流程及程序计数器详解 JVM虚拟机篇(一)深入理解JVM:组成部分、运行流程及程序计数器详解一、引言二、JVM的组成部分2.1 类加载子系统2.2 运行时数据区2.3 执行引…...
从零构建大语言模型全栈开发指南:第三部分:训练与优化技术-3.1.2Tokenization策略:BPE算法与词表设计
👉 点击关注不迷路 👉 点击关注不迷路 👉 点击关注不迷路 文章大纲 3.1.2 Tokenization策略:BPE算法与词表设计1. BPE(Byte-Pair Encoding)算法原理与实现1.1 BPE核心思想1.2 BPE算法步骤2. 词表设计关键要素2.1 词表规模与模型性能2.2 特殊标记设计3. BPE变体与改进算…...
学透Spring Boot — 013. Spring Web-Flux 函数式风格的控制器
这是我的学透Spring Boot的第13篇文章,更多文章请移步我的专栏 学透 Spring Boot_postnull咖啡的博客-CSDN博客 目录 传统风格的Spring MVC 函数式编程风格的Spring MVC 引入WebFlux依赖 定义Handler类 定义Router类 WebFlux不生效 灵魂拷问 Spring Web MVC…...
L33.【LeetCode题解】快乐数(双指针思想)
目录 1.题目 2.分析 3.代码 4.提交结果 5.题外话 证明:一定是循环的 前置知识:鸽巢原理 不严格证明 1.题目 https://leetcode.cn/problems/happy-number/ 编写一个算法来判断一个数 n 是不是快乐数。 「快乐数」 定义为: 对于一个正整数,每一次将…...
gltf unity-Unity中Gltf模型的使用与优化技巧
在现代游戏开发和3D应用领域,高质量模型是提升用户体验的关键因素之一。GLTF(GL Transmission Format)作为一款开放标准的3D模型交换格式,已经被越来越多的开发者所认可。Unity引擎,作为全球领先的3D游戏开发平台&…...
Oracle数据库指南
目录 一、前言 二、Oracle数据库基础入门篇 1. Oracle体系结构概述 2. 安装与配置 3. SQL语言入门 三、PL/SQL编程与高级特性 1. PL/SQL基础语法 2. 触发器与任务调度 3. 高级特性 四、日常维护与监控 1. 备份与恢复策略 2. 日志管理与故障排查 3. 自动化运维 五…...
Qt -信号与槽
博客主页:【夜泉_ly】 本文专栏:【暂无】 欢迎点赞👍收藏⭐关注❤️ 目录 前言引入connect调用链模板类型的connectQObject::connectImplQObjectPrivate::connectImpl qobject_p_p.hconnect作用总结ai对信号与槽的模拟实现 前言 面向对象&am…...
macos 魔搭 模型下载 Wan-AI ComfyUI
环境安装 ➜ ~ sw_vers ProductName: macOS ProductVersion: 15.3.2 ➜ ~ pip --version pip 24.3.1 from /opt/homebrew/lib/python3.11/site-packages/pip (python 3.11)安装ModelScope SDK pip install modelscope➜ ~ modelscope download --help Traceback (most r…...
Xshell Plus 6下载与安装
文章目录 Xshell Plus 6 简介(一)网络连接与协议支持(二)会话管理(三)安全特性(四)文件传输功能(因集成Xftp 6 )(五)个性化与便捷功能…...
Kubernetes 集群搭建(一):从环境准备到 Calico 网络插件部署
(一)虚拟环境准备 名称ip备注m1192.168.101.131mastern1192.168.101.132workern2192.168.101.133worker (二)集群统一配置 2.1 关闭防火墙和selinux systemctl stop firewalld systemctl disable firewalld sed -i s/enforcin…...
【国产突围!致远电子ZXDoc如何打破Vector垄断,成为新能源车研发“神器”?】
摘要:在汽车“新四化”浪潮下,国产汽车总线工具链软件正迎来高光时刻!广州致远电子推出的ZXDoc以全栈自主化技术硬核国产芯片生态,斩获2024金辑奖“最佳技术实践应用奖”,成为新能源车企研发工程师的“效率倍增器”。本…...
3-Visual Studio 2022打包NET开发项目为安装包
引言 本文将上一期博文>>>门店管理系统开发<<<开发的项目打包为Windows安装包 一,安装扩展 安装此扩展:installer Projects 二,创建安装程序项目 创建项目 右键解决方案-添加-新建项目 选择setup Project项目 填写项目名…...
Cookie、Session、Token、JWT的区别和使用场景
Cookie、Session和Token的区别 存储位置数据容量安全性生命周期性能Cookie客户端(通常是浏览器)4KB、Cookie数量也有限制不安全、XSS(跨站脚本攻击)、CSRF(跨站请求伪造)可以设置过期时间,过期后…...
P1883 【模板】三分 | 函数
题目描述 给定 n 个二次函数 f1(x),f2(x),…,fn(x)(均形如 ax2bxc),设 F(x)max{f1(x),f2(x),...,fn(x)},求 F(x) 在区间 [0,1000] 上的最小值。 输入格式 输入第一行为正整数 T,表示有 T 组数据。 每组…...
Ruoyi-vue plus 5.2.2 flowble设计流程点击开始流程图错误
网关设置条件或者是事件删除后出现,点击网关节点无法找到下面的事件节点。 配置页面事件错误,点背景配置进去了事件,发现再次加载,或者删除的时候VUE页面无法加载。 解决方式:查看XML文件,这个节点是否存在…...
MySQL学习笔记(三)——图形化界面工具DataGrip
目录 1. 图形化界面工具 2.下载 3. 安装 3.1 安装步骤 3.2 激活说明 4. 使用 4.1 汉化教程 4.2 使用 1. 图形化界面工具 上述,我们已经讲解了通过 DDL 语句,如何操作数据库、操作表、操作表中的字段,而通过 DDL 语句执行在命令进行操…...
keil软件仿真
设置 选择软件仿真。 修改参数。 获取参数 找到自己的芯片信号。这里用的是F103ZET6 复制下来,并对其进行修改。 接下来进入仿真即可...
每日一题(小白)模拟娱乐篇14
直接理解题意,一分钟扩散一次,那么2020分钟也就是需要循环2020次,然后加入扩散后的条件,每一个次扩散使方格子的总量1(只要有一个点扩散就无需看其他的点),若干次循环过后总数之和即所有黑色格子…...
(二)使用Android Studio开发基于Java+xml的安卓app之环境搭建
以下是使用Android Studio搭建基于Java和XML的Android应用开发环境的详细步骤: 一、系统要求 操作系统:Windows 7/8/10/11(64位)内存:建议8GB及以上磁盘空间:至少5GB空闲(建议预留10GB以上&…...