C++ Primer 学习笔记 第四章 表达式

2023-11-12

表达式由运算对象组成,对表达式求值得到一个结果。字面值和变量是最简单的表达式,其结果就是字面值和变量的值。把一个运算符和一个或多个运算对象组合起来可以生成较复杂的表达式。

C++定义了一元运算符、二元运算符和三元运算符。作用于一个运算对象的运算符是一元运算符,二三元同理。

函数调用也是一种特殊的运算符,它对运算对象的数量没有限制。

一些符号既可以作为一元运算符也可以作为二元运算符,比如*。它是一元还是二元运算符由它的上下文决定。

表达式求值过程中,运算对象常常由一种类型转换为另外一种类型,如二元运算符通常要求两个运算对象类型相同,但很多时候不相同也没关系,只要它们能转换为一种类型即可。小整数类型(bool、char、short等)通常会被提升成较大的整数类型(主要是int)。

C++定义了运算符作用于内置类型和复合类型对象时所执行的操作,当作用于类类型时,用户可以自行定义其含义,称之为重载运算符。IO库的>>和<<以及string对象、vector对象和迭代器使用的运算符都是重载的运算符。使用重载运算符时,其运算对象类型和返回值类型都是由该运算符定义,但运算对象个数、运算符优先级和结合律都无法改变。

C++表达式不是右值就是左值,这是由C语言继承而来,原来:左值可以位于赋值语句的左侧,右值则不能。在C++中二者区别很大:左值表达式的求值结果是一个对象或一个函数,但以常量对象为代表的某些左值实际不能作为赋值语句左侧运算对象,此外,虽然某些表达式求值结果是对象,但它们是右值而非左值。简单归纳:当对象用作左值时,用的是对象的身份(在内存中的位置),用作右值时,用的是对象的值。一个重要原则是:在需要右值的地方可以用左值来代替,但不能把右值当做左值使用。当一个左值被当成右值使用时,实际使用的是它的内容(值)。

·赋值运算符需要一个非常量左值作为其左侧运算对象,得到的结果也仍然是一个左值。
·取地址符作用于一个左值对象,返回一个指向该运算对象的指针,这个指针是右值。
·内置的解引用运算符、下标运算符、迭代器解引用运算符、string和vector的下标运算符的求值结果是一个左值。
·内置类型和迭代器的递增递减运算符作用于左值对象,其前置版本所得的结果也是左值。

使用decltype时,如果表达式求值结果是左值,decltype作用于该表达式得到引用类型,如p是int*类型,因为解引用生成左值,所以decltype(*p)的结果是int&,又因为取地址生成右值,所以decltype(&p)结果是int**(即不带引用)。

复合表达式指含有两个或多个运算符的表达式。求复合表达式的值时,优先级和结合律决定了运算对象的组合方式,表达式中的括号无视上述规则。

算术运算符满足左结合律,如果运算符优先级相同,将按照从左到右的顺序组合运算对象。

int i = f1() * f2();    // 我们不知道f1先被调用还是f2
int i = 0;
cout << i << " " << ++i << endl;    // 未定义的,可能输出0 1也可能1 1,甚至还可能做出其他操作

.运算符优先级高于*解引用高于加减乘除:

vector<int> ivec;
ivec.push_back(2);
cout << *ivec.begin() + 1 << endl;    // 输出3

逻辑与&&、逻辑或||、条件运算符(?:)、逗号运算符(,)明确规定了运算对象求值顺序。

运算对象的求值顺序与优先级和结合律无关。

建议:在拿不准时候用括号强制让表达式组合关系符合程序设计要求;如改变了某个运算对象的值,在表达式其他地方不要再出现这个运算对象。

以下为算术运算符(满足左结合律,能作用于任何算术类型以及能转化为算术类型的类型,其运算对象和求值结果都是右值,表达式求值之前小整数类型运算对象被提升成较大的整数类型,所有运算对象最终会转化成同一类型):

运算符 功能
+ 一元正号
- 一元负号
* 乘法
/ 除法
% 求余
+ 加法
- 减法

以上表格优先级顺序由 - -分隔。

一元正号运算符、加法运算符和减法运算符能作用于指针。当一元正号作用于一个指针或算数值时,返回运算对象值的一个提升后的副本。

bool b = true;
bool b2 = -b;    // b2为true,-b用到表达式后b被提升为1,加负号为-1,-1再转化为true

溢出和其它算术运算异常:算术表达式产生未定义结果可能有两种情况,一是数学性质本身,如0做除数;二是计算结果超出了该类型所能表示的范围。

整数相除结果还是整数。小数部分被抛弃掉。

取余%运算符参与运算的对象必须是整数类型:

int i = 32767;
cout << i % 5 << endl;    // 正确
cout << i % 5.1 << endl;    // 错误,运算对象不全为整数

对于除法运算,两运算对象符号相同商为正,否则为负。C++语言早期版本允许结果为负值的商向上或向下取整数,C++11新标准规定只能向0取整(直接切除小数部分)。

根据取余运算,如果m和n是整数且n非0,则表达式(m / n) * n + m % n的求值结果与m相等,隐含意思是,如果m%n结果不为0,那么m%n符号与m相同。

C++早期允许m%n的符号匹配n的符号,而且m/n的商向负无穷方向取整,这在新标准中被禁用。

21 % 6;    // 结果为3
21 % 7// 结果为0
-21 % -8;    // 结果为-5
21 % -5;    // 结果为1

逻辑和关系运算符(作用于算术类型或指针类型,逻辑运算符作用于任何能转化为布尔值的类型,逻辑运算符和关系运算符的返回值都是布尔类型。值为0的运算对象(算术类型或指针)表示假,否则表示真。对于这两类运算符,运算对象和求值结果都是右值):

结合律 运算符 功能 用法
! 逻辑非 !expr
< 小于 expr < expr
<= 小于等于 expr <= expr
> 大于 expr > expr
>= 大于等于 expr >= expr
== 相等 expr == expr
!= 不相等 expr != expr
&& 逻辑与 expr && expr
|| 逻辑或 expr || expr

上表中空白格为优先级分界线。

逻辑与和逻辑或运算符都是先求左侧运算对象再求右侧运算对象,当左侧运算对象无法确定表达式结果时才会计算右侧运算对象的值,这种策略称为短路求值:
(1)对逻辑与,只有左侧表达式值为真时才对右侧对象求值。
(2)对逻辑或,只有左侧表达式值为假时才对右侧对象求值。

while(index != s.size() && !isspace(s[index])){
    index++;
}    // 只有index没到末尾时才对右侧求值,避免了取到范围之外的值

以下为逻辑或的例子:

// 输出vector<string>中内容,如遇到空串或以逗号结尾的串时换行,其余用空格隔开
	vector<string> svec;
	svec.push_back("ssss");
	svec.push_back("ddddd");
	svec.push_back("");
	svec.push_back("sds");
	svec.push_back("sds");
	svec.push_back("dsasad,");
	svec.push_back("sds");
	svec.push_back("sds");
	for (const string &s : svec) {    // s声明为了const的引用,当串特别大时避免了值的拷贝
		cout << s;    // 空串输出时控制台什么都不打印
		if (s.empty() || s[s.size() - 1] == ',') {    // 先判断s是否为空,如s为空表达式为真,就不会再对s最后一位取值判断
			cout << endl;
		} else {
			cout << " ";
		}
	}

对于比较运算符:

if (i < j < k);    // 当i与j比较结果先提升成整型,再与提升成整型的k比较
if (i < j && j < k);    // 这才是数学中的i < j < k

当我们测试一个算术对象或指针对象的真值:

if(val);    // 如果val是任意非0值,条件为真
if(!val);    // 如果val是0时,条件为真

if(val == true);    // 如果val值为1时才为真

相等性判断在与运算符和或运算符之前。

const char* cp = "ghjgj";    // 字符串字面值类型为const char *
cout << *cp << endl;    // 对cp解引用输出的是char型元素,cp相当于数组名,相当于指针指向第一个字符,故输出g
cout << cp[0] << endl;    // 同上

大于等于号的优先级高于相等和不等号。

赋值表达式左侧运算对象必须是一个可修改的左值:

int i = 0, j = 0, k = 0;
const int ci = i;

1024 = k;    // 错误,字面值是右值
i + j = k;    // 错误,算术表达式是右值
ci = k;    // 错误,ci是常量(不可修改)左值

赋值运算的结果是它的左侧运算对象,并且是一个左值。结果的类型就是左侧运算对象的类型,如赋值运算符两侧运算对象类型不同,则右侧运算对象将转换为左侧运算对象的类型:

k = 0;    // 结果是int型0
k = 3.14159;    // 结果是int型3

C++新标准:

k = {3.14};    // 错误,窄化转换

列表初始化时如果左侧运算对象是内置类型,那么初始化列表只能包含一个值,而且该值即使转化的话其所占空间也不应该大于目标类型空间。如果是类类型,赋值运算的细节由类本身决定。

无论左侧对象是什么,初始值列表都可以为空,编译器会创建一个值初始化的临时量并将其赋给左侧运算对象:

	string s = {};
	double d = {};
	cout << s.size() << endl;    // 输出0
	cout << d << endl;    // 输出0

赋值运算满足右结合律:

int i,j;
i = j = 0;    // 正确,ij都赋值为0
 
int ival, *pval;
ival = pval = 0;    // 错误,虽然0能赋值给指针和整型类型,但在多重赋值语句中,对象的类型必须和右侧类型相同或由右侧类型转换得到。此句中int*不能转化为int
pval = ival = 0;    // 错误,int不能赋值给int*

赋值运算符优先级较低:

while((i = get_value()) != 42);    // 如果不加括号,条件中会先判断get_value()的值是否是42,之后赋值给i一个布尔值

因为赋值运算符优先级低于关系运算符,所以在条件语句中,赋值部分应该加上括号。

复合运算符:+=、-=、*=、/=、%=、<<=、>>=、&=、^=、|=

以上任何复合运算符都等价于a = a op b;。区别在于普通运算符会求值两次,一次是作为右边子表达式的一部分求值,另一次是作为赋值运算的左侧对象求值。这两种方法除了对程序的性能有些许影响外可以忽略不计。

递增和递减运算符为对象加1和减1提供了一种简洁的书写形式,还可用于迭代器,由于很多迭代器本身不支持算术运算,因此递增和递减是必须的。

递增和递减运算符必须作用于左值运算对象,前置版本将对象作为左值返回,后置版本将对象作为右值返回。

建议除非必须,否则不用后置版本,前置版本把值加1后直接返回改变了的运算对象,而后置版本需要将原始值存储下来以便返回这个未修改的内容,如果我们不需要修改前的版本值,那么后置版本的操作就是一种浪费。

混用解引用和递增运算符:

auto pbeg = v.begin();
cout << *pbeg++ << endl;    // 输出pbeg指向的对象,并使pbeg指向下一个对象
cout << *++pbeg  << endl;    // 输出pbeg指向的下一个对象

由于后置递增运算符优先级高于解引用,因此第一个输出语句等价于*(pbeg++)。而前置递增运算符和解引用运算符优先级相同,且满足右结合律,因此pbeg会先指向下一个对象,然后再解引用,等价于*(++pbeg)。

运算对象可按任意顺序求值:

// 以下循环目的是将string对象的第一个单词大写,但实际上以下循环行为未定义
while (beg != s.end() && !isspace(*beg))
    *beg = toupper(*beg++);

问题在于,赋值运算符两端都用到了beg,并且右侧对象还改变了beg的值,编译器可能按以下思路处理:

*beg = toupper(*beg);    // 先处理左侧的值
*(beg + 1) = toupper(*beg);    // 先处理右侧的值

也可能按其他方式处理。

成员访问运算符有点运算符.和箭头运算符->,并且ptr->mem等价于(*ptr).mem。因为解引用优先级低于点运算符。

箭头运算符作用在指针类型的运算对象,结果是左值;点运算符作用的对象的成员是左(右)值时,结果就是左(右)值。

iter++->empty();    // iter是vector<string>::iterator类型,此句含义为先返回iter指向对象的empty()成员,再令iter指向下一个成员。

条件运算符cond ? expr1 : expr2允许我们把简单if-else语句嵌入单个表达式中:

string finalgrade = (grade < 60) ? "fail" : "pass";

条件运算符只对expr1和expr2其中一个求值。

当条件运算符的两个表达式都是左值或能转换成同一种左值类型时,运算结果就是左值,否则就是右值。

条件运算符可以嵌套:

finalgrade = (grade > 90) ? "high pass" : (grade < 60) ? "fail" : "pass";    // 90以上high pass,60-90pass,60以下fail

条件运算符满足右结合律。

条件运算符优先级非常低,当输出语句中使用时:

cout << ((grade < 60) ? "fail" : "pass") << endl;    // 正确
cout << grade < 60 ? "fail" : "pass";    // 错误,试图比较cout和60
cout << (grade < 60) ? "fail" : "pass";    // 错误,输出0或1

位运算符作用于整数类型和bitset类型的运算对象,并把运算对象看做二进制位集合。

以下为位运算符(左结合律):

运算符 功能 用法
~ 位求反 ~ expr
<< 左移 expr1 << expr2
>> 右移 expr1 >> expr2
& 位与 expr & expr
^ 位异或 expr ^ expr
| 位或 expr | expr

表中–表示优先级分界线。

一般,如果运算对象是“小整型”,则它的值会被自动提升为较大的整数类型。运算对象可以是带符号的,也可以是无符号的,如果运算对象是带符号的且它的值为负,那么位运算符如何处理运算对象的“符号位”依赖于机器,而且,此时的左移操作可能会改变符号位的值,因此是未定义行为。

移位运算符右侧运算对象一定不能为负,而且值必须严格小于结果的位数,否则会产生未定义行为。二进制位或者向左移或者向右移,移出边界之外的位就被舍弃掉了:

unsigned char bits = 0233;    // bits为八进制数
bits << 8;    // bits提成为了int类型,然后左移8位

左移运算符在右侧插入0,右移运算符时如果运算对象是无符号类型则在左侧插入0,如果是带符号类型在左侧插入符号位的副本或值为0的副本。

位求反运算符将各位0置为1,1置为0:

unsigned char bits = 0227;
~bit;    // bit被提升为int类型,之后1变0,0变1

使用位运算符:

// 该班有30名学生,用每一位代表一个学生是否通过测验
unsigned long quiz1 = 0;    // unsigned long保证最低32位,能容纳所有学生
quiz1 |= (1UL << 27);    // 代表27号学生通过,使quiz1和只有27位为1的unsigned long的值位或,使quiz1第27位置为1
quiz1 &= ~(1UL << 27);    // 代表27号学生未通过,使quiz1和只有第27位为0的unsigned long的值位与,使quiz1第27位置的值为0
bool status = quiz1 & (1UL << 27);    // 查看第27号学生是否通过测验

移位运算符(又叫IO运算符)满足左结合律。

sizeof运算符返回一条表达式或一个类型名字所占的字节数,满足右结合律,其所得的值是一个size_t类型的常量表达式,其运算对象有两种形式:

sizeof (type);
sizeof expr;    // 返回表达式结果类型的大小,但并不实际计算其运算对象的值。
Sales_data data, *p;
sizeof(Sales_data);    // 存储Sales_data类型的对象所占的空间大小
sizeof data;    // data的类型的大小,同上
sizeof p;    // 指针所占的大小
sizeof *p;    // p所指类型的空间大小,即sizeof(Sales_data)
sizeof data.revenue;    // Sales_data的revenue成员对应类型的大小
sizeof Sales_data::revenue;    // 同上,C++11新标准,在不创建类的对象时也能获取成员的大小

以上代码中,sizeof *p的*和sizeof优先级一样并且sizeof满足右结合律,表达式从右往左顺序组合,它等价于sizeof(*p),而且sizeof不会实际求运算对象的值,即使p是一个无效(未初始化)的指针也能获取其所指类型的大小。

对数组执行sizeof运算得到整个数组所占空间大小。

对string或vector对象执行sizeof运算只返回该类型固定部分的大小,不会计算对象中元素占了多少空间。

获取数组元素个数:

int num = sizeof(ia) / sizeof(*ia);
	int x[10]; 
	int* p = x;
	cout << sizeof(x) / sizeof(*x) << endl;    // 输出数组元素个数10
	cout << sizeof(p) / sizeof(*p) << endl;    // 输出指针所占空间与其所指的元素类型所占空间的比值,p实际指向数组x的第一个元素,因此输出值为1

逗号运算符含两个运算对象,按从左往右顺序求值,和逻辑与、逻辑或以及条件运算符一样,它也规定了运算对象求值的顺序。对于逗号运算符,首先对左侧的表达式求值,然后将求值结果丢弃掉,逗号运算符的真正结果是右侧表达式的值,如果右侧运算对象是左值,那么最终的求值结果也是左值:

// 把从size到1的值赋给ivec中的元素
vector<int>::size_type cnt = ivec.size();
for (vector<int>::size_type ix = 0; ix != ivec.size(); ++ix, --cnt){
    ivec[ix] = cnt;
}

如果两种类型可以相互转换,那么它们就是关联的。

隐式转换:

int ival = 3.541 + 3;    // ival被赋值为6

以上代码中C++不会直接将两个不同类型的值相加,而是先根据类型转换规则设法将运算对象的类型统一后再求值,上述类型转换是自动进行的,无需程序员介入,有时甚至不需要程序员了解,因此它们被称为隐式转换。

算术类型之间的隐式转换被设计得尽可能避免损失精度,很多时候,如果表达式中既有整数类型也有浮点数类型时,整型会转换为浮点型,上面例子中,3转换为double类型,然后执行浮点数加法,所得结果类型是double。接下来进行初始化,初始值被转换为被初始化对象的类型,即double转化为int。

何时发生隐式转换:
(1)大多数表达式中,比int类型小的整型值先提升为较大的整数类型。
(2)条件中,非布尔值转化为布尔类型。
(3)初始化过程中,初始值转化为变量类型;赋值语句中,右侧运算对象转换成左侧运算对象类型。
(4)如果算术运算或关系运算的运算对象有多种类型,需要转换成同一种类型。(如带符号数和无符号比较大小时)
(5)函数调用时。

算术转换含义是把一种算术类型转换为另外一种算术类型。算术转换的规则定义了一套类型转换的层次,其中运算符的运算对象将转换成最宽的类型(如比较大的运算对象类型是long double,那么另外一个运算对象类型也会转化为long double)。还有当表达式中既有浮点数又有整数类型时,整数值将转换成相应的浮点类型。

整型提升负责把小整数类型转换成较大的整数类型。对于bool、char、signed char、unsigned char、short、和unsigned short等类型,只要它们所有可能的值都能存在int中,它们就被提升为int,否则提升成unsigned int类型。较大的char类型(wchar_t、char16_t、char32_t)提升成int、unsigned int、long、unsigned long、long long、和unsigned long long 中最小的一种类型,前提是转换后的类型要能容纳原类型所有可能的值。

某个运算符运算对象类型不一致,首先执行整型提升,如果提升后两者类型相同,无需进行下一步转换,否则如果提升后的结果都是带符号或不带符号的时,则两者转化为较大的类型。如果一个运算对象是带符号的,一个是无符号的,并且无符号类型大于等于带符号类型,那么带符号运算对象转化成无符号的。最后一种结果是带符号类型大于无符号类型,此时转换结果依赖于机器,此时如果无符号类型的所有值都能存在该带符号类型中,则无符号类型的运算对象转换为带符号类型,否则,带符号类型转化为无符号类型(例如long和unsigned int,long带符号类型大于unsigned int无符号类型,当int和long的大小相同时,都转化为unsigned int,如果long类型占用空间比int大,则转化为long类型,其实以上都是选择最小的能盛下所有可能数的类型),以下为类型转换具体规则:

  1. 如果int的字节长度小于long的字节长度

    类型等级由高到低依次为:long double、double、float、 unsigned long long、long long、unsigned long、long、unsigned int、int

  2. 如果int的字节长度等于long的字节长度

    类型等级由高到低依次为:long double、double、float、unsigned long long、long long、unsigned long、unsigned int、long、int

在大多数用到数组的表达式中,数组自动转化成指针,在数组用作decltype关键字参数、取地址符、sizeof以及typeid等运算符对象时,不会转化为指针,如果引用一个数组int (&arrRef)[10] = array;,上述转换也不会发生。

指针的转换:常量整数值0或字面值nullptr能转换成任意指针类型;指向任意非常量的指针都能转化成void*;指向任意对象的指针都能转化成const void*。

条件判断时,如果指针或算术类型的值为0,转换为false,否则转换成true。

允许将指向非常量类型的指针转换成指向相应常量类型的指针,对于引用也是这样。

类类型定义的转换:

string s = "a value";    // 字符串字面值转化成string类型
while (cin >> s);    // istream类型转换成布尔类型

显式转换:将对象显式地转换成另外一种类型:

int i = 5, j = 2;
double slope = i / j;    // slope值仍然是2,要用强制类型转换把i和/或j显式转换为浮点型才能得到2.5

命名的强制类型转换:

cast-name<type>(expression);

其中,type是转换的目标类型而expression是要转换的值。如果type是引用类型,则结果是左值。cast-name是static_cast、dynamic_cast、const_cast和reinterpret_cast中的一种。dynamic_cast支持运行时类型识别。

static_cast:任何具有明确定义的类型转换,只要不包含底层const,都可以使用static_cast:

double slope = static_cast<double>(j) / i;

当我们需要把较大的算术类型赋值给较小类型时,一般编译器会给出警告,使用static_cast可以消除警告。

我们也可以使用static_cast找回存在于void*指针中的值:

void *p = &d;
double *dp = static_cast<double*>(p);    // 我们必须确保转换后的类型与指针所指类型相同,否则会产生未定义后果

const_cast:只能改变运算对象的底层const:

const char *pc;
char *p = const_cast<char*>(pc);    // 正确,但通过p写值是未定义行为

如果对象本身不是一个常量,通过这种方法获得写权限是合法行为,但如果对象是一个常量,那么写操作会产生未定义后果。

static_cast和const_cast都能添加对象的const属性,但只有const_cast能移除对象的const属性。

只有const_cast能改变表达式的常量属性,使用其他形式的命名强制类型转换改变表达式的常量属性都将引发编译器错误,同样地,也不能用const_cast改变表达式的类型:

const char* cp;
char* q = static_cast<char*>(cp);    // 错误,static_cast不能转换掉const性质
static_cast<string>(cp);    // 正确,字符串字面值转换成string类型
const_cast<string>(cp);    // 错误,const_cast只能改变常量属性

reinterpret_cast:通常为运算对象的位模式提供较低层次上的重新解释,可以在任意指针(或引用)类型之间转换;指针到足够大的整数类型的转换;从无视大小的整数类型(包括枚举类型)到指针类型的转换:

int *ip;
char *pc = reinterpret_cast<char*>(ip);

我们必须牢记pc所指的真实对象是一个int而非字符,如把pc当成普通的字符指针使用就可能在运行时发生错误。

const int a = 10;
int* p = (int*)(&a);    // 等价于const_cast<int*>(&a),强制类型转换    
*p = 20;    // 含义为通过p改变a的值,但由于a是const变量,具体行为未定义,取决于编译器,我用的编译器可以更改a
cout << "a = " << a << ", *p = " << *p << endl;    // 输出a = 10, *p = 20
const int *c = &a;    // 令c也指向a
cout << *c << endl;    // 输出20

以上代码中第一句输出时,a的值还是10,这是因为由于a是常量表达式,所以在编译时就用10直接替换掉a,而*p=20是在运行时才知道的,因此实际上a的值已经被修改,但a输出还是10。这时再用一个指针取a中的值就可以取到20了。

reinterpret_cast本质上依赖于机器。

建议避免强制类型转换。

旧式的强制类型转换:

type (expr);    // 函数形式的强制类型转换
(type) expr;    // C语言风格的强制类型转换
const string* ps;
void* pv;
pv = (void*)ps;
pv = static_cast<void*>(const_cast<string*>(ps));    // 与上句等价

int i;
char* pc;
i = int(*pc);
i = static_cast<int>(*pc);    // 与上句等价

double d;
pv = &d;
pv = static_cast<void*>(&d);    // 与上句等价

pc = (char*)pv;
pc = static_cast<char*>(pv);

运算符优先级表:
在这里插入图片描述
在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++ Primer 学习笔记 第四章 表达式 的相关文章

  • c++智能指针

    C 智能指针详解 本文系转载 原文出处 诚然原博主总结的非常好 我只是加一些自己觉得需要补充的地方 并且在最后给出目前c 11在智能指针这方面的弥补 一 简介 由于 C 语言没有自动内存回收机制 程序员每次 new 出来的内存都要手动 de
  • 分布式系统实现幂等性的方式

    幂等性 接口的幂等性就是同样的数据 在实现方法的多次调用 得到的结果一致 对于查询接口 天然的保持幂等性 但是对于cud来说 如何保持幂等性 看法 从以下方式来看 要保证幂等性 必须要有一个标识 至始至终的保持不变的标识 只有这样 后来的操
  • 开源点云数据集整理汇总

    目录 一 ModelNet40 1 网址 2 模型 二 ShapeNet 1 网址 2 模型 三 S3DIS Dataset 1 网址 2 模型 四 ScanNet 1 网址 2 模型 五 RGB D Object Dataset 1 网址
  • 麻雀算法极限学习机SSA-ELM回归预测及其MATLAB代码实现

    作者简介 热爱科研的Matlab仿真开发者 修心和技术同步精进 matlab项目合作可私信 个人主页 Matlab科研工作室 个人信条 格物致知 更多Matlab仿真内容点击 智能优化算法 神经网络预测 雷达通信 无线传感器 信号处理 图像
  • 哈希 学习笔记

    Tips Hash 哈希 散列 Tips 哈希经常与哈希函数指一个意思 本文中哈希与哈希函数不做特殊区分 默认就是一个意思 什么是哈希 在记录的关键字与记录的存储地址之间建立的一种对应关系叫哈希函数 哈希函数就是一种映射 是从关键字到存储地
  • AI笔记(1)

    回归与分类 回归 在数学表示 数值是一个连续性的 要预测的一个值 回归分析是一种预测性的建模技术 它研究的是因变量和自变量之家的关系 这种技术通常用于预测分析 通过使用直线或曲线来拟合数据点 目标是使曲线到数据点的距离差异最小 分类 分类模
  • python重新加载某方法

    20221014 引言 我记得 在很久之前我就弄过这部分内容 当时也是在jupyter notebook中进行实验 然后在这个过程中 需要重新加载某个方法 因为在实验过程中 修改了这个py文件中的函数 整体的思路就是这样 这次也是遇到了这个
  • nginx+ftp实现图片的上传与访问

    1 Nginx的安装 在前面的博客讲到 具体见下面的网址 Nginx的安装 http blog csdn net zbw18297786698 article details 52556293 2 Linux安装ftp组件 2 1 安装vs
  • 随e行创建L2TP 809 错误【一键脚本】

    请下载下面的文件 809 repair zip 右键点击 809 repaire bat 文件 以管理员身份运行 选择 是 根据提示 输入对应字符 yes 以完成对注册表的修改 等待脚本完成对注册表和服务的更改 按任意键退出窗口 809错误
  • LeetCode 热题 HOT 100:链表专题

    LeetCode 热题 HOT 100 https leetcode cn problem list 2cktkvj 文章目录 2 两数相加 19 删除链表的倒数第 N 个结点 21 合并两个有序链表 23 合并 K 个升序链表 141 环
  • windows服务器修改SSL证书友好名称

    系统环境 操作系统 windows 2016 web服务器 IIS 10 修改原因 阿里云上创建的免费证书 友好名称均为alias 在一些集成环境中操作绑定对应站点的SSL证书的时候 可能会因为证书友好名称相同 而找不到对应的SSL证书 操
  • UNIAPP保存base64图片

    制作APP分享二维码 后端生成base64图片 需要前端保存到相册 上代码 saveImgFile base64 base64为base64图片值 const bitmap new plus nativeObj Bitmap test bi

随机推荐

  • Kotlin快速入门

    kotlin kotlin 完全兼容 java 可以和java互相调用 2017年谷歌正式将其作为android开发的第一语言 kotlin编译后产生与java编译后相同的class字节码文件 基础语法 第一个代码 hello world
  • Method的invoke()方法的使用

    public Object invoke Object obj Object args throws IllegalAccessException IllegalArgumentException InvocationTargetExcep
  • idea插件开发入门

    前言 最近想研究一款自动在idea中定位缺陷及发送JIRA的快捷工具 方便提升报自动化脚本的bug的效率 因为idea插件学习是必不可少了 沉淀小结如下 idea插件开发入门 插件用途 工程创建 配置文件 Action实现 开发语法 常用对
  • js三种获取数组的最后一个元素的方法

    一 pop 方法 pop 方法 删除数组最后一个元素 并返回该元素 所以利用这个方法可以取到数组的最后一个 同理shift 可以取到数组的第一个元素 shift 删除数组第一个元素 并返回该元素 let arr 1 2 3 let ele
  • css background image size,html - css background image content size - Stack Overflow

    body margin 0 height 100vh top box text align center height 20 border 1px solid blue background red two area display inl
  • https http 重定向 302 303

    笔者在项目中曾经遇到过这样的情况 就是整个web应用使用的https协议 但是其中某一个表单提交单服务器端 服务器返回了一个重定向的响应 但是此时浏览器直接被拦截 没有做重定向处理 F12打开一看 是因为在https的请求中 混杂着http
  • 解决docker下载安装速度慢的问题

    1 进入etc docker 目录 root iZwz98nzsodcbigjqrrmxmZ cd etc docker 2 在该目录下新建daemon json文件 registry mirrors https docker mirror
  • git 查看远程分支在不同的终端看到的分支不同的解决方法

    多人协作开发的时候 有时候有很多分支 并且在有些分支在a电脑删除之后 在b电脑查看当前分支 居然还有那个被删除掉了的分支 git fetch prune origin 直接执行这行代码 之后再查看一下远程分支 git branch a a
  • SpringBoot在K8s下实现优雅停机

    在K8s中 当我们实现滚动升级之前 务必要实现应用级别的优雅停机 否则滚动升级时 还是会影响到业务 本文介绍SpringBoot应用实现优雅停机 此次教程基于SpringBoot 2 5 0 1 加入必要依赖
  • 简单理解常量、常量池、运行时常量池和字符串常量池

    1 常量 常量在java中就值的是一般的字面量 比如字符串 整数 浮点数等等数据 简单理解java中什么叫常量 2 常量池 也叫静态常量池或者class文件常量池 说常量池一定要指明是编译器产生的 它的组成为字面量和符号引用 3 运行时常量
  • 决策树模型——鸢尾花分类

    构建一个决策树分类模型 实现对鸢尾花的分类 1 lris数据集介绍 鸢尾花数据集是机器学习领域中非常经典的一个分类数据集 数据集全名为 Iris Data Set 总共包含150行数据 每一行由4个特征值及一个目标值 类别变量 组成 其中4
  • 先进API生产力工具eqable HTTP,一站式开发调试工具推荐

    简介 Reqable是什么 Regable Fiddler Charles Postman Reqable是HTTP一站式开发 调试国产化解决方案 拥有更便捷的体验 更先进的协议 更高效的性能和更精致的界面 Reqable是一款跨平台的专业
  • 【日常积累】HTTP和HTTPS的区别

    背景 在运维面试中 经常会遇到面试官提问http和https的区别 今天咱们先来简单了解一下 超文本传输协议HTTP被用于在Web浏览器和网站服务器之间传递信息 HTTP协议以明文方式发送内容 不提供任何方式的数据加密 如果攻击者截取了We
  • 【Python】类与对象进阶

    类与对象有三大特性 封装 多态和继承 其中类和对象之间也涉及到很多的细节和方法 例如方法的分类 对象的权限等等 所有类和对象的进阶还有很多的知识点需要学习 目录 权限 私有属性和私有方法 继承 单继承 多继承 多态 实例 类与对象的内存理解
  • 利用Python消费RocketMQ消息队列数据

    语言 python3 6 环境 centos 7 1 安装 rocketmq python 地址见 https pypi org project rocketmq pip install rocketmq 2 安装rocketmq clie
  • 【定量分析、量化金融与统计学】R语言:多元线性回归实例

    今天来做一个R语言的多元线性回归的实例 题目是这样的 练习 度假村排名 旅游胜地 专门介绍高级度假和住宿的杂志 Spas 在 读者选择 评选的世界20家独立海滨精品酒店中榜上有名 所显示的数据是这些酒店根据Resorts温泉年度读者选择调查
  • Springboot整合RestTemplate发送http请求

    据技术选型总结常见的三种方式发送http请求 本文介绍Springboot整合RestTemplate发送http请求方式 其他两种如下链接 java原生发送http请求 程序三两行的博客 CSDN博客 HttpClient和OkHttp发
  • java语言简介

    什么是Java语言 一种面向对象的语言 编写程序的开始就是编写类的开始 class 用于定义类 一种平台无关的语言 必须程序运行的解释环境 真正的运行步骤为 javac编译 java解释执行 一种健壮的语言 吸收了C C 语言的优点 但是去
  • Idea安装配置Maven【简述】

    Maven 是一个管理和构建Java项目的工具 它主要的生命周期 编译 测试 打包 发布 Maven项目可以在不同IDE使用 比如 Idea 和 eclipse 他们自身的项目是不能互通的 然而使用Maven构建的项目可以在这两个不同平台使
  • C++ Primer 学习笔记 第四章 表达式

    表达式由运算对象组成 对表达式求值得到一个结果 字面值和变量是最简单的表达式 其结果就是字面值和变量的值 把一个运算符和一个或多个运算对象组合起来可以生成较复杂的表达式 C 定义了一元运算符 二元运算符和三元运算符 作用于一个运算对象的运算