当前位置: 首页 > news >正文

c/c++开发,无可避免的操作符operator(篇一),操作符重载

一、操作符号重载

        虽然c/c++内置了大量各类操作符,开发者可以很方便将其应用数学运算、成员访问、类型转换、内存分配等执行语句中,但很多时候,也需要根据项目应用需要,通过操作符重载,能够针对类类型的操作数定义不同的操作符版本,尤其是针对于自定义类型对象进行操作时。

        1.1 语句表达式

        c/c++是运算符和它们的操作数的序列排列组合成的一项计算。C++ 定义了许多内置类型间的操作符和自动转换。使用这些操作及转换能够编写丰富的混合类型表达式。表达式分为:

        1)求值表达式,可以产生一个结果(比如 2+2 的求值产生结果 4),也可能产生副作用(比如对 std::printf("%d",4) 的求值在标准输出上打印字符 '4')。

        2)不求值表达式,运算符 typeid、sizeof、noexcept 和 decltype (C++11 起) 的操作数是不求值表达式(除非运算符为 typeid 而操作数是多态泛左值),因为这些运算符仅查询其操作数的编译期性质。

        3)弃值表达式,用来实施操作数其副作用的表达式。从这种表达式计算的值被舍弃。这样的表达式包括任何表达式语句的完整表达式,内建逗号运算符的左边的实参,以及转型到类型 void 的转型表达式的实参。弃值表达式的计算结果永远不进行数组到指针和函数到指针转换。当且仅当该表达式是 volatile 限定的泛左值,并是标识、数组下标、成员访问、成员指针、条件、逗号等表达式时,进行左值到右值转换。       

        C++ 允许我们重定义操作符,用于类类型对象时表达,通常c/c++提供了如下常见运算符:

赋值:
a=b  a+=b  a-=b  a*=b  a/=b  a%=b  a&=b  a|=b  a^=b  a<<=b  a>>=b
自增自减: 
++a --a  a++  a--
算术: 
+a -a  a+b  a-b  a*b  a/b  a%b  ~a  a&b  a|b  a^b  a<<b  a>>b
逻辑: 
!a  a&&b  a||b 
比较: 
a==b  a!=b  a<b  a>b  a<=b  a>=b  a<=>b
成员访问: 
a[b] *a &a  a->b  a.b  a->*b  a.*b
其他:
a(...)  a,b  ?: 

        其中,部分内置操作符不能进行重载:

:: .* . ?:

        其他特殊操作符:

类型转换:
static_cast dynamic_cast const_cast reinterpret_cast C风格强制转型 
内存分配:
new new[] delete delete[]
常量:
sizeof typeid 
sizeof... noexcept alignof //C++11 起

        1.2 重载操作符-operator

        重载操作符是具有特殊名称的函数:保留字 operator 后接需定义的操作符号。像任意其他函数一样,重载操作符具有返回类型和形参表。如果需要,可以像内置转换那样使用类类型转换,将一个类型的对象隐式转换到另一类型。尤其是自定义类型时,需要配套创建相关操作符号。

//test1.h
#ifndef _TEST_1_H_
#define _TEST_1_H_#include <ostream>class Obj
{
public:Obj(int val_);~Obj();Obj& operator=(const Obj& rhs); //一元操作符,赋值运算=Obj& operator=(const int& rhs); //一元操作符,隐式赋值运算=friend Obj operator+(const Obj&, const Obj&);//二元操作符+friend std::ostream& operator<<(std::ostream&, const Obj&);//io运算操作符<<
private:int val;
};#endif //_TEST_1_H_
//test1.cpp
#include "test1.h"Obj::Obj(int val_) : val(val_)
{};Obj::~Obj()
{};Obj& Obj::operator=(const Obj& rhs)
{if(this==&rhs)// 防止自赋值return *this;val = rhs.val;return *this;
};Obj& Obj::operator=(const int& rhs)
{val = rhs;return *this;
};Obj operator+(const Obj& lhs, const Obj& rhs)
{Obj obj(lhs);obj.val+=rhs.val;return obj;
};std::ostream& operator<<(std::ostream& os, const Obj& obj)
{os << obj.val;return os;
};
//main.cpp
#include "test1.h"#include <iostream>int main(int argc, char* argv[])
{Obj a(10);std::cout << "a = " << a << "\n";//io运算符调用Obj b = 11;//隐式转换std::cout << "b = " << b << "\n";Obj c = a;//赋值运算std::cout << "c = " << c << "\n";Obj d = b+c;//+运算std::cout << "d = " << d << "\n";std::cout << "a+b+c = " << a+b+c << "\n";return 0;
};
//out log
a = 10
b = 11
c = 10
d = 21
a+b+c = 31

        上述示例中,为Obj类型创建了赋值运算符、int转Obj转换赋值运算、IO移位操作符及加号运算操作符,使Obj类型的使用像内置类型一样直观,就是如同内置类型一样自如运用表达式。

        为类型创建重载操作符,必须选择是将操作符设置为类成员还是普通非成员函数。在某些情况下,开发者没有选择,操作符必须是成员;在另一些情况下,可以依据业务需要决定将操作符设置为类成员还是普通非成员函数:

  • 赋值(=)、下标([])、调用(())和成员访问箭头(->)等操作符必须定义为成员,将这些操作符定义为非成员函数将在编译时标记为错误。
  • 像赋值一样,复合赋值操作符通常应定义为类的成员,与赋值不同的是,不一定非得这样做,如果定义非成员复合赋值操作符,不会出现编译错误。
  • 改变对象状态或与给定类型紧密联系的其他一些操作符,如自增、自减和解引用,通常就定义为类成员。
  • 对称的操作符,如算术操作符、相等操作符、关系操作符和位操作符,最好定义为普通非成员函数。

        1.3 重载操作符注意事项

        重载操作符必须具有至少一个类类型或枚举类型的操作数。即重载操作符不能重新定义用于内置类型对象的操作符的含义,内置类型的操作符,其含义不能改变,例如开发者不能在自定义整型类型的加号操作符:

int operator+(int, int);                //error 非成员运算符要求类类型或枚举类型的参数
int operator+(const int, const int);    //error 非成员运算符要求类类型或枚举类型的参数
int operator+(const int&, const int&);  //error 非成员运算符要求类类型或枚举类型的参数
int& operator+(const int&, const int&); //error 非成员运算符要求类类型或枚举类型的参数
int* operator+(const int&, const int&); //error 非成员运算符要求类类型或枚举类型的参数

        不要重载具有内置含义的操作符,例如:

  • 赋值操作符、取地址操作符和逗号操作符对类类型操作数有默认含义。如果没有特定重载版本,编译器就自己定义以下这些操作符。
  • 合成赋值操作符进行逐个成员赋值:使用成员自己的赋值操作依次对每个成员进行赋值。
  • 默认情况下,取地址操作符(&)和逗号操作符(.)在类类型对象上的执行,与在内置类型对象上的执行一样。取地址操作符返回对象的内存地址,逗号操作符从左至右计算每个表达式的值,并返回最右边操作数的值。
  • 内置逻辑与(&&)和逻辑或(||)操作符使用短路求值。如果重新定义该操作符,将失去操作符的短路求值特征。

        通过为给定类类型的操作数重定义操作符,可以改变这些操作符的含义。重载逗号、取地址、逻辑与、逻辑或等等操作符通常不是好做法。这些操作符具有有用的内置含义,如果我们定义了自己的版本,就不能再使用这些内置含义。

        定义操作符为类的成员,左操作数将只能是该类类型的对象。作为类成员的重载函数,this 作为隐藏的左操作数(有一个隐含的 this 形参,限定为第一个操作数),其形参看起来比操作数数目少 1,大多是一元操作符。重载一元操作符,如果作为成员函数就没有(显式)形参,如果作为非成员函数就有一个形参。类似的二次操作符时,形参+1。通常将算术和关系操作符定义非成员函数,而将赋值操作符定义为成员:

Obj& operator+=(const Obj& rhs);//成员函数,一元操作符+=
friend Obj operator+(const Obj&, const Obj&);//普通函数,二元算术操作符+Obj& Obj::operator+=(const Obj& rhs)
{val += rhs.val;return *this;
}Obj operator+(const Obj& lhs, const Obj& rhs)
{Obj obj(lhs);obj.val+=rhs.val;return obj;
};

        通常在操作符定义为非成员函数时,操作符通常需要访问类的私有部分,通常将它们设置为所操作类的友元。

friend Obj operator+(const Obj&, const Obj&);//二元操作符+Obj operator+(const Obj& lhs, const Obj& rhs)
{Obj obj(lhs);obj.val+=rhs.val;    //直接操作私有变量return obj;
};

        如果害怕友元会破环类型数据的封装性,可以采用类似功能的成员操作符替代来达成目的,例如对于+号操作符,可以用public 成员 operator+= 实现:

Obj& operator+=(const Obj& rhs);//一元操作符+=
Obj& Obj::operator+=(const Obj& rhs)
{val += rhs.val;return *this;
};

        即使重载了相关的操作符,也不会改变这些操作符的优先级、结合性或操作数目,不管操作
数的类型和操作符的功能定义如何。

//两者的优先级、结合次序并没有不同
//总是将两实参绑定到 operator+,并且将结果用作 operator= 右操作数。
int x=0; y=1;
int k = x+y;//两者的优先级、结合次序并没有不同,Obj a=0;  b=1;
Obj c = a+b; //两者的优先级、结合次序并没有不同,

        约束限制

  • 不能重载运算符 ::(作用域解析)、.(成员访问)、.*(通过成员指针的成员访问)及 ?:(三元条件)。
  • 不能创建新运算符,例如 **、<> 或 &|。
  • 不可能更改运算符的优先级、结合方向或操作数的数量。
  • 重载的运算符->必须要么返回裸指针,要么(按引用或值)返回同样重载了运算符->的对象。
  • 运算符 && 与 || 的重载失去短路求值。

        重载操作符并不保证操作数的求值顺序,尤其是,不会保证内置逻辑 AND、逻辑 OR和逗号操作符的操作数求值。在 && 和 ||的重载版本中,两个操作数都要进行求值,而且对操作数的求值顺序不做规定。因此,重载 &&、|| 或逗号操作符不是一种好的做法。

        1.4 重载操作符使用

        重载操作符和内置操作符一样,都支持显式调用和隐式调用:

Obj a = 10; Obj b = a;//隐式
std::cout << "a+b = " << a+b << "\n";//隐式
std::cout << "a+b = " << operator+(a,b) << "\n";//显式,向使用函数一样

        调用成员操作符函数与调用任意其他函数是一样的:指定运行函数的对象,然后使用点或箭头操作符获取希望调用的函数,同时传递所需数目和类型的实参。

Obj a = 10; b = 11; //隐式
a += b;             //隐式
a.operator+=(b);    //显式,成员函数调用方式 

二、操作符重载设计设计习惯

        有时我们需要定义自己的操作符时,表达逻辑最好和内置操作类型行为保持一致,例如不要重载operator+操作符,却做了减法的工作。如果行为不能和内置类型存在差异,最好采用成员函数或普通函数来实现,给这些函数其一个知其意的名字,比重载操作符更好。

        重载的赋值运算应在赋值的内置含义基础上进行定制,而不是完全绕开,例如类成员变量是vector,那么重载操作符内部操作就直接使用vector操作,而非另起灶炉,除非成员变量没有提供支持。

        大多数操作符对类对象没有意义,为类重载操作符时,除了提供几个常规的操作符(如,赋值=、相等==)外,其余的根据开发进度过程涉及到需要在按需添加。另外操作符号通常是按归类一起提供的:

        2.1 赋值操作符重载设计

        赋值操作符应该是每个自定义类型必备的,赋值必须返回对 *this 的引用。即使开发者不显示定义,编译器也会自动创建同类赋值操作符,开发者也可以为一个类定义许多附加的赋值操作符,这些赋值操作符会因右操作符类型不同而不同。当然+=、-=操作符也是赋值操作符范畴,它也是返回自身,但是通常它们会与+、-操作符配套一起。同样,先a*=b  a/=b  a%=b  a&=b  a|=b  a^=b  a<<=b  a>>=b运算操作符号都类似=操作符一样,this作为左操作数,并返回对 *this 的引用。

//test1.h
class Obj
{
public://...otherObj& operator=(const Obj& rhs); //一元操作符,赋值运算=,不主动定义,一般编译器会默认定义这个Obj& operator=(const int& rhs); //一元操作符,隐式赋值运算=Obj& operator=(const char& rhs); //一元操作符,隐式赋值运算=Obj& operator+=(const Obj& rhs);//一元操作符+=Obj& operator-=(const Obj& rhs);//一元操作符-=
private:int val;
};
//test1.cpp
Obj& Obj::operator=(const Obj& rhs)
{if(this==&rhs)return *this;val = rhs.val;return *this;
};Obj& Obj::operator=(const int& rhs)
{val = rhs;return *this;
};Obj& Obj::operator=(const char& rhs)
{val = rhs;return *this;
};

        2.2 算术操作符重载设计

        如果一个类有算术操作符或位操作符,一般定义为非成员函数(除以自身为左操作数的),通常,也提供相应的复合赋值操作符一般是个好的做法。例如,Obj类定义了 +操作符,逻辑上,它也应该定义 +=。使其操作行为与内置操作符一样:复合赋值的行为应与 + 之后接着 = 类似。算术操作符+a -a  a+b  a-b  a*b  a/b  a%b  ~a  a&b  a|b  a^b  a<<b  a>>b等都是类似的实现原理。

//test1.h
class Obj
{
public://...otherObj& operator+=(const Obj& rhs);//一元操作符+=Obj& operator-=(const Obj& rhs);//一元操作符-=friend Obj operator+(const Obj&, const Obj&);//二元操作符+friend Obj operator-(const Obj&, const Obj&);//二元操作符-
private:int val;
};Obj& Obj::operator+=(const Obj& rhs)
{val += rhs.val;  //会改变对象状态return *this;    //返回本身
}
Obj& Obj::operator-=(const Obj& rhs)
{val -= rhs.val;  //会改变对象状态return *this;    //返回本身
}
//加法操作符并不改变操作符的状态,操作符是对 const 对象的引用;
Obj operator+(const Obj& lhs, const Obj& rhs)
{Obj obj(lhs);//它产生并返回一个新的 Obj 对象,该对象初始化为 lhs 的副本。obj.val+=rhs.val;return obj;    //不会改变对象状态,返回拷贝对象
};
Obj operator-(const Obj& lhs, const Obj& rhs)
{Obj obj(lhs);//它产生并返回一个新的 Obj 对象,该对象初始化为 lhs 的副本。obj.val-=rhs.val;return obj;    //不会改变对象状态,返回拷贝对象
};

        2.3 关系操作符重载设计

       比较及关系操作符号,一般定义为非成员函数,例如类定义 < 操作符,最好也能提供<=、>、>= 这些操作符;提供类==操作符号,最好也能提供!=操作符号。

//test1.h
class Obj
{
public://...otherstatic int cmp_Obj(const Obj &obj1, const Obj &obj2);
private:int val;
};inline bool operator==(const Obj& obj1, const Obj& obj2) { return Obj::cmp_Obj(obj1, obj2) == 0; }
inline bool operator!=(const Obj& obj1, const Obj& obj2) { return Obj::cmp_Obj(obj1, obj2) != 0; }
inline bool operator>=(const Obj& obj1, const Obj& obj2) { return Obj::cmp_Obj(obj1, obj2) >= 0; }
inline bool operator<=(const Obj& obj1, const Obj& obj2) { return Obj::cmp_Obj(obj1, obj2) <= 0; }
inline bool operator>(const Obj& obj1, const Obj& obj2) { return Obj::cmp_Obj(obj1, obj2) > 0; }
inline bool operator<(const Obj& obj1, const Obj& obj2) { return Obj::cmp_Obj(obj1, obj2) < 0; }
//test1.cpp
int Obj::cmp_Obj(const Obj &obj1, const Obj &obj2)
{return obj1.val - obj2.val;
}

      2.4 自增自减操作符重载设计

         C++ 语言不要求自增操作符或自减操作符一定作为类的成员,但是,因为这些操作符改变操作对象的状态,所以更倾向于将它们作为成员。对内置类型而言,自增操作符和自减操作符有前缀和后缀两种形式。那么为我们自己的类定义自增操作符和自减操作符时,同样也可提供前缀和后缀两种实现方式。

//test1.h
class Obj
{
public://...otherObj operator++();//一元操作符++,前缀式操作符Obj operator--();//一元操作符--,前缀式操作符Obj operator++(int rhs);//一元操作符++,后缀式操作符Obj operator--(int rhs);//一元操作符--,后缀式操作符
private:int val;
};
//test1.cpp
//为了与内置类型一致,前缀式操作符应返回被增量或减量对象的引用
Obj Obj::operator++()
{++val;return *this;
}
Obj Obj::operator--()
{--val;return *this;
}
//为了与内置操作符一致,后缀式操作符应返回旧值(即,尚未自增或自减的值),
//并且,应作为值返回,而不是返回引用。
Obj Obj::operator++(int rhs)
{Obj ret(*this);++*this;return ret;
}
Obj Obj::operator--(int rhs)
{Obj ret(*this);--*this;return ret;
}

        操作符的后缀式比前缀式复杂一点,先记住对象在加 1/减 1 之前的当前状态。这些操作符定义了一个局部 Obj对象,将它初始化为 *this 的副本,即 ret 是这个对象当前状态的副本。
保存了当前状态的副本后,操作符调用自己的前缀式操作符分别进行加 1 或减1:++*this
调用这个对象的Obj前缀自增操作符,返回之后,对象本身加了 1,但返回的是尚未自增的原值。

        自增(++)和自减(--)操作符经常由诸如迭代器这样的类实现,这样的类提供类似于指针的行为来访问序列中的元素。例如,可以定义一个类,该类指向一个数组并为该数组中的元素提供访问检查。

        2.5 输入输出操作符重载设计

        支持 I/O 操作的类所提供的 I/O 操作接口,一般应该与标准库 iostream为内置类型定义的接口相同,因此,许多类都需要重载输入和输出操作符。输出操作符 << 的重载是较常实现的一个操作符号,为了与 IO 标准库一致,操作符应接受 ostream& 作为第一个形参,对类类型 const 对象的引用作为第二个形参,并返回对ostream 形参的引用。

//test1.h
class Obj
{
public://...otherfriend std::ostream& operator<<(std::ostream&, const Obj&);//io运算操作符<<
private:int val;
};
//test1.cpp
std::ostream& operator<<(std::ostream& os, const Obj& obj)
{os << obj.val;return os;
};

        输出操作符 << 的重载,其左操作数为 ostream 类型,因此只能定义非成员函数。需要建议的是,输出操作符应输出对象的内容,进行最小限度的格式化,例如,换行符等就尽量不要给出,那是操作符调用者该操心的事。

        输入操作符 >> 的重载,这个相对如<<操作符,一般使用到较少,但是作为配对使用原则,类似设计者一般也会同版本给出:

//test1.h
class Obj
{
public://...otherfriend std::istream& operator>>(std::istream& in, Obj&);//io运算操作符>>
private:int val;
};
//test1.cpp
std::istream& operator>>(std::istream& in, Obj& obj)
{//最好安全检查in >> obj.val;  //取出数据写入valreturn in;
}

        2.6 逻辑操作符重载尽量避免

        前面也讲述过,重载操作符并不保证操作数的求值顺序,尤其是,不会保证内置逻辑 AND、逻辑 OR和逗号操作符的操作数求值。因此,重载 &&、|| 或逗号操作符不是一种好的做法。

        2.7 成员访问操作符设计

        为了支持指针型类,例如迭代器,C++ 语言允许重载解引用操作符(*)和箭头操作符(->)。箭头操作符必须定义为类成员函数。解引用操作不要求定义为成员。

        解引用操作符和箭头操作符常用在实现智能指针的类中,例如在本专栏的C/C++开发,无可避免的内存管理(篇四)-智能指针备选_py_free-物联智能的博客-CSDN博客。的“2.1 仿auto_ptr 的类模板”知识点就做过类似的成员访问操作符设计实现:

#ifndef _AUTO_PTR_H_
#define _AUTO_PTR_H_#include <iostream>template<typename T>
class AutoPtr
{
public:explicit AutoPtr(T* p=0) : m_ptr(p) {std::cout << "AutoPtr create!\n";};~AutoPtr() { std::cout << "AutoPtr delete!\n";delete m_ptr; };T& operator*() const { return *m_ptr; };T* operator->() const { return m_ptr;};private:T* m_ptr; // dumb pointer
};void func1(void)
{AutoPtr<int> a(new int(100));std::cout << "*a = " << *a << "\n";std::cout << "a->() = " << a->() << "\n";
}#include "autoptr.cpp"
#endif // _AUTO_PTR_H_

        解引用操作符是个一元操作符。在这个类中,解引用操作符定义为成员,因此没有显式形参,该操作符返回对 AutoPtr所指向的 T的引用。

        箭头操作符与众不同。它可能表现得像二元操作符一样:接受一个对象和一个成员名。对对象解引用以获取成员。不管外表如何,箭头操作符不接受显式形参。

a->(); 本质上是 (a->)();

        这里没有第二个形参,因为 -> 的右操作数不是表达式,相反,是对应着类成员的一个标识符。没有明显可行的途径将一个标识符作为形参传递给函数,相反,由编译器处理获取成员的工作。

        下标操作符,即operator[],可以从容器中检索单个元素的容器类一般会定义下标操作符,下标操作符必须定义为类成员函数。本专栏博文c/c++开发,无可避免的模板编程实践(篇九)-c++11的新顺序容器_py_free-物联智能的博客-CSDN博客

就讲述如何定义一个仿std::array容器类时,就做了典型的operator[]操作符实现:

#include <cstring>
#include <cassert>template<typename T,int SIZE>
struct my_array
{my_array() {memset(Buf,0,SIZE);};~my_array(){};T& operator[]( const int pos ){assert(pos >= 0 && pos < SIZE);//边界判断return Buf[pos];}T& at(const int pos){assert(pos >= 0 && pos < SIZE);return Buf[pos];}//其他实现T Buf[SIZE];
};void myarray_test(void)
{//调用my_array<int,3> a;          //int类型数据,长度大小3a[0] = 9;a.at(1) = 10;std::cout << "a.at(0)=" << a.at(0) << "\n";std::cout << "a[1]=" << a[1] << "\n";
};

        定义下标操作符比较复杂的地方在于,它在用作赋值的左右操作符数时都应该能表现正常。下标操作符出现在左边,必须生成左值,可以指定引用作为返回类型而得到左值。只要下标操作符返回引用,就可用作赋值的任意一方。可以对 const 和非 const 对象使用下标也是个好主意。应用于 const 对象时,返回值应为 const 引用,因此不能用作赋值的目标。类定义下标操作符时,一般需要定义两个版本:一个为非 const 成员并返回引用,另一个为 const 成员并返回 const 引用,如果只选择定义一个,建议选择后者。

三、特殊操作符重载设计习惯

        3.1 调用操作符及函数对象

        调用操作符,即operator(),该操作符可以有零或多个形参和返回值。当用户定义的类重载了函数调用运算符 operator() 时,它就成为函数对象 (FunctionObject) 类型。这种类型的对象能用于函数调用式的表达式。

//test1.h
class Obj
{
public://...otherint operator()();int operator()(int val_);
private:int val;
};
//test1.cpp
int Obj::operator()()
{return val;
}
int Obj::operator()(int val_)
{return val_<0?-val_:val_;
}
//main.cpp
//Obj obj_op(10);std::cout << "obj_op() = " << obj_op() << "\n";std::cout << "obj_op(-100) = " << obj_op(-100) << "\n";
//out log
obj_op() = 10
obj_op(-100) = 100

        尽管 Obj 是一个对象而不是函数,我们仍然可以“调用”该对象,效果是运行由 Obj 对象定义的重载调用操作符。一个类可以定义函数调用操作符的多个版本,由形参的数目或类型、返回约束加以区别。

        定义了调用操作符的类,其对象常称为函数对象,即它们是行为类似函数的对象,在标准库中经常用作通用算法的实参关于函数对象,在本专栏的博文:C/C++开发,无可避免的多线程(篇四).线程与函数的奇妙碰撞_py_free-物联智能的博客-CSDN博客有更多的描述,大家可移步参考,这里就不展开论述。

        3.2 布尔运算符

        布尔运算符主要是基于类类型 bool安全问题引入的,在 C++11 引入显式转换函数之前,设计一个能用于布尔语境的类(比如,if(obj) { ... })会出现问题:给定一个用户定义转换函数,如T::operator bool() const;,则隐式转换序列允许再多一步标准转换序列,也就是 bool 结果会转换成 int,允许诸如 obj << 1; 或 int i = obj; 这样的代码。

        一个早期的解决方案可参见 std::basic_ios,它定义 operator! 和 operator void*(C++11 前),使得如 if(std::cin) {...} 的代码能编译,因为 void* 能转换为 bool,但int n = std::cout; 不能,因为 void* 不可转换至 int。这仍然允许无意义代码能编译,如 delete std::cout;。许多 C++11 前的第三方库设计带有更为复杂的解决方案,称作安全 Bool 手法。
        显式 bool 转换也能用于解决安全 bool 问题

explicit operator bool() const { ... } // (C++11 起)

        用户定义类在有意用于布尔语境时,可以定义bool重载运算符 operator bool,而 operator! (布尔取反运算符)的受期待行为是返回 operator bool 的取反,由于内建运算符 ! 进行按语境到 bool 的转换,用户定义类可以只提供 operator bool 而无需重载 operator!。

//test1.h
class Obj
{
public://...otherexplicit operator bool() const;
private:int val;
};
//test1.cpp
Obj::operator bool() const
{return 0!=val;
}
//main.cpp
//Obj obj_b(0);if(!obj_b)std::cout << "obj_b() = " << obj_b() << "\n";obj_b = 1;if(obj_b)std::cout << "obj_b() = " << obj_b() << "\n";
//out log
obj_b() = 0
obj_b() = 1

        3.3 内存分配操作符-operator new/delete

        在自己的类模板中,通过定义自己的名为 operator new 和 operator delete 的成员,可以屏蔽标准库operator new 和 operator delete 函数的调用,类就可以管理用于自身类型的内存。编译器看到类类型的 new 或 delete 表达式的时候,它查看该类是否有operator new 或 operator delete 成员,如果类定义(或继承)了自己的成员new 和 delete 函数,则使用那些函数为对象分配和释放内存;否则,调用这些函数的标准库版本。关于这方面的详细说明请参考本专栏的博文:C/C++开发,无可避免的内存管理(篇三)-规划好内存_py_free-物联智能的博客-CSDN博客的“二、new和delete表达式的秘密”去深入了解。

//BaseObj.h
#ifndef _BASE_OBJ_H_
#define _BASE_OBJ_H_#include <memory>template <typename T> 
class BaseObj 
{
public:void *operator new(std::size_t);void operator delete(void *, std::size_t);virtual ~BaseObj() { }
protected:T *next;
private:static void add_to_freelist(T*);static std::allocator<T> alloc_mem;static T *freeStore;static const std::size_t chunk;
};#include "BaseObj.cpp"
#endif //_BASE_OBJ_H_
//BaseObj.cpp
#include "BaseObj.h"#include <iostream>
#ifdef __linux__
#include <stdio.h>
#include <exception>
#endif
//
template <typename T> 
std::allocator<T> BaseObj<T>::alloc_mem;template <typename T> 
T *BaseObj<T>::freeStore = 0;template <typename T> 
const std::size_t BaseObj<T>::chunk = 24;//operator new 使用这个函数将新分配的对象放到自由列表,删除对象
template <typename T>
void *BaseObj<T>::operator new(size_t sz)
{std::cout << "BaseObj operator new func is call!\n";// new should only be asked to build a T, not an object// derived from T; check that right size is requestedif (sz != sizeof(T)){#ifdef WIN32throw std::runtime_error("BaseObj: wrong size object in operator new");#elsestd::cout << "BaseObj: wrong size object in operator new\n";#endif}if (!freeStore) {// the list is empty: grab a new chunk of memory// allocate allocates chunk number of objects of type TT * array = alloc_mem.allocate(chunk);// now set the next pointers in each object in the allocated memoryfor (size_t i = 0; i != chunk; ++i){add_to_freelist(&array[i]);}}T *p = freeStore;freeStore = freeStore->BaseObj<T>::next;return p; // constructor of T will construct the T part of the object
}
//operator delete 也使用该函数将对象放回自由列表
template <typename T>
void BaseObj<T>::operator delete(void *p, size_t)
{std::cout << "BaseObj operator delete func is call!\n";if (p != 0){// put the "deleted" object back at head of freelistadd_to_freelist(static_cast<T*>(p));}
}template <typename T>
void BaseObj<T>::add_to_freelist(T *p)
{p->BaseObj<T>::next = freeStore;freeStore = p;
}

        operator new /delete 操作符重载主要难点在与内存分配及回收上,是通过内置类型allocator内存分配器来实现的,allocator 类的 construct 及destroy 成员函数可以支持到内存分配及释放。

        3.4 转换操作

       在布尔操作符时,大家可注意到是用一个 explicit 的布尔操作符函数定义一个显式转换,若无explicit 标识,则认为是隐式转换,例如前面见到的operator =。当提供了实参类型的对象而需要一个类类型的对象时,编译器将使用该转换。这种构造函数定义了到类类型的转换。除了定义到类类型的转换之外,我们还可以定义从类类型的转换。即,我们可以定义转换操作符,给定类类型的对象,该操作符将产生其他类型的对象。像其他转换一样,编译器将自动应用这个转换。

        转换操作符是一种特殊的类成员函数,通用形式是operator type(),type 表示内置类型名、类类型名或由类型别名定义的名字。它定义将类类型值转变为其他类型值的转换。转换操作符在类定义体内声明,在保留字 operator 之后跟着转换的目标类型。

//test1.h
class Obj
{
public://...otherObj(int val_);operator int() const;
private:int val;
};
//test1.cpp
Obj::Obj(int val_) : val(val_)
{};
//各种以内置类型或自定义类型的函数名都可以
Obj::operator int() const
{return val;
}
//main.cppObj obj_i = 10;int i_ = obj_b;

        对任何可作为函数返回类型的类型(除了 void 之外)都可以定义转换函数。一般而言,
不允许转换为数组或函数类型,转换为指针类型(数据和函数指针)以及引用类型是可以的。
转换函数必须是成员函数,不能指定返回类型,并且形参表必须为空。转换函数一般不应该改变被转换的对象。因此,转换操作符通常应定义为 const 成员。

        虽然转换函数不能指定返回类型,但是每个转换函数必须显式返回一个指定类型的值。例如,operator int 返回一个 int 值;如果定义 operator Obj,它将返回一个 Obj对象,诸如此类通过类型转换操作符,可以很方便支持混合类型表达式,同时转换减少所需操作符的数目,便于代码书写及理解。

        转换函数只能应用一个类类型转换,如果存在多个,会产生歧义,则编译报错,自定义的和内置算术类型的转换起了冲突:

//接前面例子代码
if(i_==obj_i)   //需要注释inline bool operator==(const Obj& obj1, const Obj& obj2) { return Obj::cmp_Obj(obj1, obj2) == 0; }std::cout << "obj_b() = " << obj_b() << "\n";

        与使用重载操作符一样,转换操作符的适当使用可以大大简化类设计的工作并使得类的使用更简单。但是,有两个潜在的缺陷:定义太多转换操作符可能导致二义性代码,一些转换可能利大于弊。避免二义性最好的方法是,保证最多只有一种途径将一个类型转换为另一类型。最好的办法是限制转换操作符的数目,尤其是,到一种内置类型应该只有一个转换。例如如果类 Foo 具有接受类 Bar 的对象的构造函数,不要再为类 Bar 定义到类型 Foo 的转换操作。

        当然c++11标准库还提供了标准转换,从一个类型到另一类型的隐式转换。

//隐式转换
static_cast 转换一个类型为另一相关类型
dynamic_cast 在继承层级中转换
const_cast 添加或移除 cv 限定符
reinterpret_cast 转换类型到无关类型
//显示转型转换
使用 C 风格写法和函数式写法
(type)val

四、本博文演示源码

        通过定义内置操作符的重载版本,我们可以为自己的类型(即,类类型或枚举类型)的对象定义同样丰富的表达式集合。重载操作符必须具有至少一个类类型或枚举类型的操作符。应用于内置类型时,重载操作符与对应操作符具有同样数目的操作数、同样的结合性和优先级。
        大多数重载操作符可以定义为类成员或普通非成员函数,赋值操作符、下标操作符、调用操作符和箭头操作符必须为类成员。操作符定义为成员时,它是普通成员函数。具体而言,成员操作符有一个隐式 this 指针,该指针一定是第一个操作数,即,一元操作符唯一的操作数,二元操作符的左操作数。

        重载了 operator()的类的对象,称为“函数对象,常用语定义与算法产生很好的结合使用的谓词函数。类可以定义转换,可以很好帮助优化代码及设计,但是,必须注意避免避免定义一个类型与另一类型之间的多个转换,产生二义性。

        本博文撰写的代码如下,部分在其他博文中可以详细了解,文中以及给出跳转,这里就不给出那些代码。编译指令g++ main.cpp test1.cpp -o test.exe -std=c++11。

        test1.h

#ifndef _TEST_1_H_
#define _TEST_1_H_#include <istream>
#include <ostream>class Obj
{
public:Obj(int val_);~Obj();operator int() const;int operator()();int operator()(int val_);explicit operator bool() const;Obj& operator=(const Obj& rhs); //一元操作符,赋值运算=Obj& operator=(const int& rhs); //一元操作符,隐式赋值运算=Obj& operator=(const char& rhs); //一元操作符,隐式赋值运算=Obj& operator+=(const Obj& rhs);//一元操作符+=Obj& operator-=(const Obj& rhs);//一元操作符-=//Obj operator++();//一元操作符++,前缀式操作符Obj operator--();//一元操作符--,前缀式操作符Obj operator++(int rhs);//一元操作符++,后缀式操作符Obj operator--(int rhs);//一元操作符--,后缀式操作符//static int cmp_Obj(const Obj &obj1, const Obj &obj2);friend Obj operator+(const Obj&, const Obj&);//二元操作符+friend Obj operator-(const Obj&, const Obj&);//二元操作符-friend std::ostream& operator<<(std::ostream&, const Obj&);//io运算操作符<<friend std::istream& operator>>(std::istream& in, Obj&);//io运算操作符>>
private:int val;
};// inline bool operator==(const Obj& obj1, const Obj& obj2) { return Obj::cmp_Obj(obj1, obj2) == 0; }
inline bool operator!=(const Obj& obj1, const Obj& obj2) { return Obj::cmp_Obj(obj1, obj2) != 0; }
inline bool operator>=(const Obj& obj1, const Obj& obj2) { return Obj::cmp_Obj(obj1, obj2) >= 0; }
inline bool operator<=(const Obj& obj1, const Obj& obj2) { return Obj::cmp_Obj(obj1, obj2) <= 0; }
inline bool operator>(const Obj& obj1, const Obj& obj2) { return Obj::cmp_Obj(obj1, obj2) > 0; }
inline bool operator<(const Obj& obj1, const Obj& obj2) { return Obj::cmp_Obj(obj1, obj2) < 0; }#endif //_TEST_1_H_

        test1.cpp

#include "test1.h"Obj::Obj(int val_) : val(val_)
{};Obj::~Obj()
{};Obj::operator int() const
{return val;
}int Obj::operator()()
{return val;
}
int Obj::operator()(int val_)
{return val_<0?-val_:val_;
}Obj::operator bool() const
{return 0!=val;
}Obj& Obj::operator=(const Obj& rhs)
{if(this==&rhs)return *this;val = rhs.val;return *this;
};Obj& Obj::operator=(const int& rhs)
{val = rhs;return *this;
};Obj& Obj::operator=(const char& rhs)
{val = rhs;return *this;
};Obj& Obj::operator+=(const Obj& rhs)
{val += rhs.val;return *this;
}Obj& Obj::operator-=(const Obj& rhs)
{val -= rhs.val;return *this;
}Obj Obj::operator++()
{++val;return *this;
}
Obj Obj::operator--()
{--val;return *this;
}
Obj Obj::operator++(int rhs)
{Obj ret(*this);++*this;return ret;
}
Obj Obj::operator--(int rhs)
{Obj ret(*this);--*this;return ret;
}int Obj::cmp_Obj(const Obj &obj1, const Obj &obj2)
{return obj1.val - obj2.val;
}Obj operator+(const Obj& lhs, const Obj& rhs)
{Obj obj(lhs);obj.val+=rhs.val;return obj;
};Obj operator-(const Obj& lhs, const Obj& rhs)
{Obj obj(lhs);obj.val-=rhs.val;return obj;
};std::ostream& operator<<(std::ostream& os, const Obj& obj)
{os << obj.val;return os;
};std::istream& operator>>(std::istream& in, Obj& obj)
{//最好安全检查in >> obj.val;  //取出数据写入valreturn in;
}

        main.cpp

#include "test1.h"#include <iostream>int main(int argc, char* argv[])
{Obj a(10);std::cout << "a = " << a << "\n";Obj b = 11;std::cout << "b = " << b << "\n";Obj c = a;std::cout << "c = " << c << "\n";Obj d = b+c;std::cout << "d = " << d << "\n";std::cout << "a+b+c = " << a+b+c << "\n";//Obj obj_op(10);std::cout << "obj_op() = " << obj_op() << "\n";std::cout << "obj_op(-100) = " << obj_op(-100) << "\n";//Obj obj_b(0);if(!obj_b)std::cout << "obj_b() = " << obj_b() << "\n";obj_b = 1;if(obj_b)std::cout << "obj_b() = " << obj_b() << "\n";//Obj obj_i = 10;int i_ = obj_b;if(i_==obj_i)   //需要注释inline bool operator==(const Obj& obj1, const Obj& obj2) { return Obj::cmp_Obj(obj1, obj2) == 0; }std::cout << "obj_b() = " << obj_b() << "\n";return 0;
};

相关文章:

c/c++开发,无可避免的操作符operator(篇一),操作符重载

一、操作符号重载 虽然c/c内置了大量各类操作符&#xff0c;开发者可以很方便将其应用数学运算、成员访问、类型转换、内存分配等执行语句中&#xff0c;但很多时候&#xff0c;也需要根据项目应用需要&#xff0c;通过操作符重载&#xff0c;能够针对类类型的操作数定义不同的…...

【7.MySQL行格式存储】

1.MySQL数据存放文件 我们每创建一个 database&#xff08;数据库&#xff09; 都会在 /var/lib/mysql/ 目录里面创建一个以 database 为名的目录,创建一个student表 [rootxiaodainiao ~]#ls /var/lib/mysql/my_test db.opt student.frm student.ibddb.opt&#xff1a;用…...

【Linux】线程实例 | 简单线程池

今天来写一个简单版本的线程池 1.啥是线程池 池塘&#xff0c;顾名思义&#xff0c;线程池就是一个有很多线程的容器。 我们只需要把任务交到这个线程的池子里面&#xff0c;其就能帮我们多线程执行任务&#xff0c;计算出结果。 与阻塞队列不同的是&#xff0c;线程池中内有…...

ATAC-seq 数据分析实战

文章目录一、 ATAC-seq原理和基础知识1. ATAC-seq原理2. Tn5转座子1. 转座概念2. 参与分子1. 转座子&#xff08;1&#xff09; 简化的转座子结构&#xff08;2&#xff09; Tn5转座子的结构2. 转座酶3. 转座过程二、数据比对和过滤一、 ATAC-seq原理和基础知识 1. ATAC-seq原…...

设计模式-第13章(状态模式)

状态模式状态模式状态模式的好处和用处工作状态状态模式 状态模式&#xff08;State&#xff09;&#xff0c;当一个对象的内在状态改变时允许改变其行为&#xff0c;这个对象看起来像是改变了其类。 状态模式主要解决的是当控制一个对象状态转换的条件表达式过于复杂时的情况…...

ReentrantLock源码分析(一)加锁流程分析

一、ReetrantLock的使用示例 static ReentrantLock lock new ReentrantLock(); public static void main(String[] args) throws InterruptedException { new Thread(ClassLayOutTest::reentrantLockDemo, "threadA").start(); Thread.sleep(1000);…...

【C++】list的模拟实现

文章目录1.list 底层2. list的模拟实现1. list_node 类设计2. list类如何调用类型3 .push_back(正常实现)4. 迭代器的实现第一个模板参数Tconst迭代器第二个模板参数Ref第三个模板参数Ptr对list封装的理解5. insert6.push_back与 push_front(复用)7. erase8. pop_back与pop_fro…...

Python连接es笔记三之es更新操作

这一篇笔记介绍如何使用 Python 对数据进行更新操作。 对于 es 的更新的操作&#xff0c;不用到 Search() 方法&#xff0c;而是直接使用 es 的连接加上相应的函数来操作&#xff0c;本篇笔记目录如下&#xff1a; 获取连接update()update_by_query()批量更新UpdateByQuery()…...

哪个牌子的蓝牙耳机音质好?音质比较好的蓝牙耳机排名

蓝牙耳机经过多年发展&#xff0c;无论是在外观设计还是性能配置上都有很大的进步&#xff0c;越来越多的蓝牙耳机开始注重音质表现&#xff0c;逐渐有HIFI音质、无损音质出现在大众视野。那么哪个牌子的蓝牙耳机音质好&#xff1f;接下来&#xff0c;我来给大家分享几款音质比…...

Qt实用技巧:Qt中浮点数的相等比较方式(包括单精度和双精度)

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/129464152 红胖子(红模仿)的博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软…...

【数据结构初阶】双向循环链表

目录一.链表的分类二.与单链表相比三.实现增删查改1.双向循环链表结构的创建2.创建新节点3.初始化链表4.头插和尾插5.判断链表是否为空6.头删和尾删7.打印函数8.查找函数9.删除pos位置节点10.在pos前位置插入数据11.优化升级一.链表的分类 链表可有根据单向双向、有无哨兵位、…...

0104BeanDefinition合并和BeanClass加载-Bean生命周期详解-spring

文章目录1 前言2 BeanDefinition合并2.1 BeanDefinition合并在做什么&#xff1f;2.2 BeanDefinition怎么合并2.3 示例演示3 Bean Class 加载后记1 前言 下面要介绍的阶段&#xff0c;都是在调用getBean()从容器中获取bean对象的过程中发生的操作&#xff0c;我们需要更多的去…...

Java集合进阶(三)

文章目录一、Map1. 概述2. 基本功能3. 遍历4. 遍历学生对象5. 集合嵌套6. 统计字符出现次数二、Collections1. 常用方法2. 学生对象排序三、模拟斗地主一、Map 1. 概述 Interface Map<K, V>&#xff1a;K 是键的类型&#xff0c;V 是值的类型。 将键映射到值的对象&…...

【网络】什么是RPC?RPC与HTTP有什么关系?

文章目录RPC是什么RPC和HTTP的关系和区别[附]关于REST论文中提到的"HTTP不是RPC"重点参考 凤凰架构-远程过程调用 既然有HTTP为什么还要有RPC&#xff1f; RPC是什么 RPC(Remote Procedure Call)&#xff1a;即远程过程调用&#xff0c;目的是为了让计算机能够跟调用…...

[手撕数据结构]栈的深入学习-java实现

CSDN的各位uu们你们好,今天千泽带来了栈的深入学习,我们会简单的用代码实现一下栈, 接下来让我们一起进入栈的神奇小世界吧!0.速览文章一、栈的定义1. 栈的概念2. 栈的图解二、栈的模拟实现三.栈的经典使用场景-逆波兰表达式总结一、栈的定义 1. 栈的概念 栈&#xff1a;一种…...

2.线性表的顺序表示

数据结构很重要&#xff01; 数据结构很重要&#xff01;&#xff01;&#xff01; 数据结构很重要&#xff01;&#xff01;&#xff01;&#xff01; 思考 1.线性表的顺序表示内容有哪些&#xff1f;&#xff08;What&#xff09; 2.为什么要学线性表的顺序表示? ? (Why)…...

eps文件删除了能恢复吗?恢复误删eps文件的三种方法

eps文件格式专为矢量图像和图形而设计。虽然没有被广泛使用&#xff0c;但它仍然受到各种插画家和平面设计师的钟爱。eps文件十分适合创建徽标和商标设计&#xff0c;主要应用见于广告牌、海报和横幅。可是在使用设备过程中&#xff0c;难免会遇到数据丢失问题&#xff0c;如果…...

【C++】运算符重载练习——Date 类

文章目录&#x1f449;日期类介绍&#x1f448;&#x1f449;日期类实现&#x1f448;&#x1f4d5; 成员变量&#x1f4d5; 构造函数&#x1f4d5; 对应月份天数&#x1f4d5; 赋值重载&#x1f4d5; 比较运算符重载&#x1f4d5; 计算 运算符重载&#x1f449;源代码&#x1…...

Redis学习(13)之Lua脚本【环境准备】

文章目录一 Lua入门环境准备1.1 Lua简介1.2 Linux 系统安装Lua1.2.1 Lua 下载1.2.2 Lua 安装1.3 Hello World1.3.1 命令行模式1.3.2 脚本文件模式1.3.3 两种脚本运行方式1.4 Win安装Lua1.4.1 LuaForWindows的安装1.4.2 SciTE修改字体大小1.4.3 SciTE中文乱码1.4.4 SciTE快捷键工…...

关于BLE的一些知识总结

数据包长度对于BLE4.0/4.1来说&#xff0c;一个数据包的有效载荷最大为20字节对于BLE4.2以上&#xff0c;数据包的有效载荷扩大为251字节传输速率在不考虑跳频间隔的情况下&#xff0c;最大传输速率为&#xff1a;1&#xff09;BLE4.0/4.1的理论吞吐率为39kb/s&#xff1b;2&am…...

Spring框架源码分析一

如何看源码&#xff08;方法论&#xff09;不要忽略源码中的注释使用翻译工具先梳理脉络&#xff0c;然后梳理细节即总分总&#xff0c;先总体过一遍&#xff0c;再看细节&#xff0c;再做一个总结大胆猜测&#xff08;8分靠猜&#xff09;&#xff0c;小心验证&#xff0c;再调…...

CSS常用内容总结(扫盲)

文章目录前言相关概念【了解】脚本语言什么是脚本语言脚本语言有什么特点常见的脚本语言什么是动态语言&#xff0c;什么是静态语言动态语言和静态语言两者之间有何区别CSSCSS是什么CSS的特点一、CSS代码怎么写基本语法规则引入方式内部样式内联样式表外部样式代码风格二、CSS的…...

Java启蒙之语言基础

目录 一.Java标识符和关键字 1.1Java标识符 1.2Java关键字 二.数据类型和变量的概述和关系 2.1Java变量 2.2Java的数据类型 2.2.1数据类型的分类的概述 2.2.2数据类型的转换 3.Java运算符 总结 &#x1f63d;个人主页&#xff1a;tq02的博客_CSDN博客-领域博主 &#…...

数据库系统--T-SQL数据查询功能-多表查询(超详细/设计/实验/作业/练习)

目录课程名&#xff1a;数据库系统内容/作用&#xff1a;设计/实验/作业/练习学习&#xff1a;T-SQL数据查询功能-多表查询一、前言二、环境与设备三、内容四、内容练习题目&#xff1a;对应题目答案&#xff1a;五、总结课程名&#xff1a;数据库系统 内容/作用&#xff1a;设…...

Spring Boot 3.0系列【14】核心特性篇之Configuration相关注解汇总介绍

有道无术,术尚可求,有术无道,止于术。 本系列Spring Boot版本3.0.3 源码地址:https://gitee.com/pearl-organization/study-spring-boot3 文章目录 前言@Configuration@ConfigurationProperties@EnableConfigurationProperties@ConfigurationPropertiesScan@Configuratio…...

[ubuntu][jetson]给jetson增加swap空间类似于给windows加虚拟内存

具体操作如下&#xff1a; #打开性能模式 sudo nvpmodel -m 0 && sudo jetson_clocks #增加swap空间&#xff0c;防止爆内存 swapoff -a sudo fallocate -l 15G /swapfile sudo chmod 600 /var/swapfile sudo mkswap /swapfile sudo swapon /swapfile…...

小黑子—Java从入门到入土过程:第二章

Java零基础入门2.0Java系列第二章1. 注释和关键字2. 字面量3. 变量3.1 基本用法3.2 使用方式3.3 注意事项4. 变量练习5. 计算机中的数据存储5.1 计算机的存储规则5.2 进制5.3 进制间转换二进制转十八进制转十十六进制转十十进制转其他进制6. 数据类型7. 定义变量的练习8. 标识符…...

ElasticSearch搜索详细讲解与操作

全文检索基础 全文检索流程 流程&#xff1a; #mermaid-svg-7Eg2qFEl06PIEAxZ {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-7Eg2qFEl06PIEAxZ .error-icon{fill:#552222;}#mermaid-svg-7Eg2qFEl06PIEAxZ .error…...

web实现太极八卦图、旋转动画、定位、角度、坐标、html、css、JavaScript、animation

文章目录前言1、html部分2、css部分3、JavaScript部分4、微信小程序演示前言 哈哈 1、html部分 <div class"great_ultimate_eight_diagrams_box"><div class"eight_diagrams_box"><div class"eight_diagrams"><div class&…...

【LeetCode】33. 搜索旋转排序数组、1290. 二进制链表转整数

作者&#xff1a;小卢 专栏&#xff1a;《Leetcode》 喜欢的话&#xff1a;世间因为少年的挺身而出&#xff0c;而更加瑰丽。 ——《人民日报》 目录 33. 搜索旋转排序数组 1290. 二进制链表转整数 33. 搜索旋转排序数组 33. 搜索旋转排序…...

IBM Semeru Windows 下的安装 JDK 17

要搞清楚下载那个版本&#xff0c;请参考文章&#xff1a;来聊聊 OpenJDK 和 JVM 虚拟机下载地址semeru 有认证版和非认证版&#xff0c;主要是因为和 OpenJ9 的关系和操作系统的关系而使用不同的许可证罢了&#xff0c;本质代码是一样的。在 Windows 下没有认证版&#xff0c;…...

Lambda表达式和steram流

目录 引言&#xff1a; 语法: Lambda 表达式实例&#xff1a; demo演示&#xff1a; Stream流&#xff1a; 引言&#xff1a; Lambda 表达式&#xff0c;也可称为闭包&#xff0c;它是推动 Java 8 发布的最重要新特性。 Lambda 允许把函数作为一个方法的参数&#xff08;函…...

面试必会-MySQL篇

1. Mysql查询语句的书写顺序Select [distinct ] <字段名称>from 表1 [ <join类型> join 表2 on <join条件> ]where <where条件>group by <字段>having <having条件>order by <排序字段>limit <起始偏移量,行数>2. Mysql查询语…...

Hadoop入门常见面试题与集群时间同步操作

目录 一&#xff0c;常用端口号 Hadoop3.x &#xff1a; Hadoop2.x&#xff1a; 二&#xff0c;常用配置文件&#xff1a; Hadoop3.x: Hadoop2.x: 集群时间同步&#xff1a; 时间服务器配置&#xff08;必须root用户&#xff09;&#xff1a; &#xff08;1&#xff09…...

JS 数组去重的方法

// 数组去重 const arr ["1", "1", "2", "3", "5", "3", "1", "5", "4"] console.log(this.deduplicate(arr)) // [1, 2, 3, 5, 4] // 数组对象去重 const arr [ { id: 1, nam…...

PMP项目管理项目沟通管理

目录1 项目沟通管理2 规划沟通管理3 管理沟通4 监督沟通1 项目沟通管理 项目沟通管理包括通过开发工件&#xff0c;以及执行用于有效交换信息的各种活动&#xff0c;来确保项目及其相关方的信息需求得以满足的各个过程。项目沟通管理由两个部分组成&#xff1a;第一部分是制定…...

2.JVM常识之 运行时数据区

1.JVM核心组成 2.JVM 运行时数据区&#xff08;jdk8&#xff09; 程序计数器&#xff1a;线程私有&#xff0c;当前线程所执行字节码的行号指示器 jvm栈&#xff1a;线程私有&#xff0c;Java 虚拟机栈为 JVM 执行 Java 方法服务 本地方法栈&#xff1a;线程私有&#xff0c;本…...

你的游戏帐号是如何被盗的

据报道&#xff0c;2022上半年&#xff0c;中国游戏市场用户规模达到了5.54亿人&#xff0c;游戏市场销售收入1163.1亿元&#xff0c;相较去年均为同比增长的情况。如此庞大的市场规模&#xff0c;黑色产业链是绕不开的话题。 但相较于游戏中大家常见的玩家与玩家、玩家与官方…...

C++11异步编程

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录前言1、std::future和std::shared_future1.1 std:future1.2 std::shared_future2、std::async3、std::promise4、std::packaged_task前言 C11提供了异步操作相关的类…...

20230310----重返学习-DOM元素的操作-时间对象-定时器

day-024-twenty-four-20230310-DOM元素的操作-时间对象-定时器 复习 获取元素 id document.getElementById() 类名 document.getElementsByClassName() 标签名 document.getElementsByTagName() name属性 document.getElementsByName() 选择器 document.querySelector()docum…...

江苏专转本转本人后悔排行榜

江苏专转本转本人后悔排行榜 一、复习的太迟&#xff1a; 后悔指数:五颗星。 复习越到最后&#xff0c;时间一天天变少&#xff0c;要复习的内容还有很多&#xff0c;很多人都后悔没有早早开始&#xff0c;总想着多给我两月一定会考上的。 担心时间不够用&#xff0c;那就努力利…...

【算法时间复杂度】学习记录

最近开算法课&#xff0c;开几篇文章记录一下算法的学习过程。 关于算法的重要性 学习计算机当程序员的话&#xff0c;在编程过程中是绕不开算法这个大矿山的&#xff0c;需要我们慢慢挖掘宝藏。 算法&#xff08;Algorithm&#xff09;是指用来操作数据、解决程序问题的一组…...

汽车车机芯片Linux系统内核编译问题总结

谈到车机,很多人会想到华为问界上装的大屏车机,号称车机的天花板,基于鸿蒙OS的,而今天谈到的车机芯片用的是linux内核Kernel,对于它的编译,很多人一时会觉得头大,的确如果工具不是很齐全,就会遇到这样那样的问题,但是过程都会有错误提示,按照错误提示基本可以解决,而…...

Android13 音量曲线调整

Android13 音量曲线调整 Android13 上配置文件的路径&#xff1a; /vendor/sprd/modules/audio/engineconfigurable_apm/工程目录/system/etc/audio_engine_config/audio_policy_engine_stream_volumes.xml /vendor/sprd/modules/audio/engineconfigurable_apm/工程目录/sys…...

OpenHarmony通过MQTT连接 “改版后的华为IoT平台”

一、前言 本篇文章我们使用的是BearPi-HM_Nano开发板:小熊派的主板+E53_IA1扩展板 源码用的是D6_iot_cloud_oc,点击下载BearPi-HM_Nano全量源码 那么为什么要写这篇呢? 前段时间看到OpenHarmony群里,经常有小伙伴问接入华为IoT平台的问题,他们无法正常连接到华为IoT平台等…...

SQS (Simple Queue Service)简介

mazon Simple Queue Service (SQS)是一种完全托管的消息队列服务&#xff0c;可以让你分离和扩展微服务、分布式系统和无服务应用程序。 在讲解SQS之前&#xff0c;首先让我们了解一下什么是消息队列。 消息队列 还是举一个电商的例子&#xff0c;一个用户在电商网站下单后付…...

高速PCB设计指南系列(三)

第一篇 高密度(HD)电路的设计 本文介绍&#xff0c;许多人把芯片规模的&#xff22;&#xff27;&#xff21;封装看作是由便携式电子产品所需的空间限制的一个可行的解决方案&#xff0c;它同时满足这些产品更高功能与性能的要求。为便携式产品的高密度电路设计应该为装配工艺…...

【C++】C++11——左右值|右值引用|移动语义|完美转发

文章目录一、左值与右值1.概念2.引用3.注意二、右值引用的意义1.左值引用意义2.右值引用和移动语义3.容器新增三、万能引用四、完美转发一、左值与右值 1.概念 左值是什么&#xff1f;右值是什么&#xff1f; 左值是一个表示数据的表达式&#xff08;如变量名或解引用的指针&…...

[ROC-RK3399-PC Pro] 手把手教你移植主线Buildroot(基于2023.02-rc3版本)

&#x1f347; 博主主页&#xff1a;Systemcall小酒屋&#x1f347; 博主简介&#xff1a;Neutionwei&#xff0c;C站嵌入式领域新星创作者之一&#xff0c;一枚热爱开源技术、喜欢分享技术心得的极客&#xff0c;注重简约风格&#xff0c;热衷于用简单的案例讲述复杂的技术&am…...

重温线性代数

前言 对于普通的数学工作者而言&#xff0c;掌握矩阵、线性空间的基本性质和用法比领会抽象的概念更实用。数学专业的同学需要全面深入学习近世代数的理论和演绎法则&#xff0c;例如模的概念和运算。 总之&#xff0c;我个人认为&#xff0c;不论是微积分、还是线性代数&…...