表达式、运算符

目录

一,表达式分词

二,运算符

1,优先级

2,结合性

3,运算顺序

4,自增和自减

三,运算符重载

1,运算符重载

2,重载限制

3,普通函数和类成员函数

4,语法规则

四,几个特殊的运算符


一,表达式分词

如何理解a---b?是a - --b还是a-- - b?

这里就涉及到编译原理中的分词原则,分词是从左往右最长匹配的贪心原则

根据贪心原则,a--b就是a-- b,是无法编译的,无法理解为a - -b,因为先分词后才能按照高级语言的语法来编译。

而a---b就是a-- - b

有了这个原则,表达式想表达的意思就没有歧义了。

比如---b这个表达式,就是-- -b,这也是无法编译的。

分词之后,求解表达式的顺序,取决于运算符的优先级和结合性。

二,运算符

1,优先级

运算符的优先级,和数学中的运算优先级意思一样,比如乘法、除法的优先级比加法、减法的优先级高。

C++ Operator Precedence

PrecedenceOperatorDescriptionAssociativity
1::Scope resolutionLeft-to-right ➔
2a++   a--Suffix/postfix increment and decrement
type()   type{}Functional cast
a()Function call
a[]Subscript
.   ->Member access
3++a   --aPrefix increment and decrementRight-to-left ←
+a   -aUnary plus and minus
!   ~Logical NOT and bitwise NOT
(type)C-style cast
*aIndirection (dereference)
&aAddress-of
sizeofSize-of[note 1]
co_awaitawait-expression (C++20)
new   new[]Dynamic memory allocation
delete   delete[]Dynamic memory deallocation
4.*   ->*Pointer-to-memberLeft-to-right ➔
5a*b   a/b   a%bMultiplication, division, and remainder
6a+b   a-bAddition and subtraction
7<<   >>Bitwise left shift and right shift
8<=>Three-way comparison operator (since C++20)
9<   <=   >   >=For relational operators < and ≤ and > and ≥ respectively
10==   !=For equality operators = and ≠ respectively
11a&bBitwise AND
12^Bitwise XOR (exclusive or)
13|Bitwise OR (inclusive or)
14&&Logical AND
15||Logical OR
16a?b:cTernary conditional[note 2]Right-to-left ←
throwthrow operator
co_yieldyield-expression (C++20)
=Direct assignment (provided by default for C++ classes)
+=   -=Compound assignment by sum and difference
*=   /=   %=Compound assignment by product, quotient, and remainder
<<=   >>=Compound assignment by bitwise left shift and right shift
&=   ^=   |=Compound assignment by bitwise AND, XOR, and OR
17,CommaLeft-to-right ➔

然而,--的优先级比-高,但是a-- -b是先计算a-b,再计算a--,这怎么理解呢?

我的理解是,运算符的优先级和结合性,其实是规定了如何添加括号,而无论如何添加括号,后置++和--依旧是在表达式计算完成之后才能计算。

顺带一提,a--+-+-b这个表达式如何理解?

答案是a-- + - + -b,加减号放在变量之前,也可以表示正负号,不难发现,这2个不需要什么规则做明显的区分,这个是没有歧义的。

和数学是一致是,3-5可以理解为3和-5这2个数的和,因为负数的定义就是如此,而且数学中也有3- -5和3- +5这种表达,是完全一致的,无论是数学还是C/C++中,这个都是没有歧义的。

2,结合性

如果两个运算符的优先级一样,比如 加法和加法,或者,加法和减法,那么就需要根据结合性确定运算顺序。

比如加减法的优先级相同,具有左结合性,a+b-c就是先算a+b,a-b+c就是先算a-b

优先级相同的运算符,要么全是左结合性,要么全是右结合性

实际上,右结合性只有其中的三个等级,即三类运算符:一元运算符、三元运算符、赋值运算符

3,运算顺序

运算符的优先级和结合性,相当于是规定了如何添加括号,而不是规定运算顺序。

比如,(a+b)*(c+d),是先计算a+b还是先计算c+d呢?这个运算顺序不影响结果,但是(a=b)+(b=a)呢?这个运算顺序就影响结果了。

同级表达式的运算顺序,是未定义的行为,具体顺序由编译器决定

不难发现,同级表达式的计算顺序会影响结果的,有3类:

(1)func1+func2,2个函数的执行顺序可能会有影响

(2)含赋值运算符的表达式,比如(a=b)+(b=a)

(3)含自增运算符++或者自减运算符--的表达式

4,自增和自减

(1)分词

分词是从左往右最长匹配的贪心原则,a---b就是a-- - b

(2)运算顺序

超脱于优先级和结合性的规律之外,自增一定是先于整个表达式,自减一定是后于整个表达式

(3)左值、右值

++a和--a是左值,a++和a--是右值

所以,++(--a)合法,(++a)-- 合法,++(a--) 不合法,(a++)-- 不合法

所以,++--a合法,++a-- 不合法,a++-- 不合法

(4)函数传参中的自增自减

比如func(a++,++a),应该是未定义的顺序。

(5)cin和cout中的自增自减

    int a=1;
    cout<<a<<a++;

输出:21

怎么理解这个式子呢?

cout是一个对象,它重载了左移运算符<<,返回的是this指针指向的本身这个cout对象

因为重载是不改变优先级和结合性的,所以上述代码相当于

    int a=1;
    (cout<<a)<<(a++);

这应该也是未定义行为,但是编译器拓展支持了。

我猜测,这个可能是先把a++作为参数传进去了,再把a作为参数传进去了。

三,运算符重载

1,运算符重载

运算符重载的方法是在把 “operator加上操作符” 类似于函数来重载。

重载之后还是可以像普通函数一样调用,也可以简写,直接用操作符就行。

示例:

#include<iostream>
using namespace std;

class A
{
public:
	int operator+(int b)
	{
		return a + b + 1;
	}
	A(int a)
	{
		this->a = a;
	}
private:
	int a;
};

int main()
{
	cout << A(2).operator+(3);
	cout << endl << A(4) + 5;
	return 0;
}

输出结果:

6
10

2,重载限制

(1)c++允许重载大部分运算符,但Scope resolution、Member access、Pointer-to-member后面接的是变量名而不是变量值,所以无法重载,还有三元运算符及一些关键字运算符也不能重载,其他的一元运算符和所有二元运算符都可以重载。

(2)不允许创建新的运算符。

(3)不能改变运算符操作的操作对象数,比如二元运算符重载之后还是二元运算符。

(4)不能改变运算符的优先级和结合性。

(5)不能改变语法规则。

(6)非成员运算符要求类类型或者枚举类型的参数,即运算符的操作对象中至少有一个是用户自定义类型。

(7)由以上规则推导出的规则,比如重载运算符的函数不能有默认参数,否则就会违反(3)

3,普通函数和类成员函数

运算符重载也区分普通函数和类成员函数。

大部分运算符可以重载为两种形式,只有几个运算符是只能重载成成员函数,包括Function call()

以加法为例,基本类型的加法是已经定义的,用户自定义类的加法可以自定义重载。

#include<iostream>
using namespace std;

class A
{
public:
	A(int a)
	{
		this->a = a;
	}
private:
	int a;
};
class B
{
public:
	int operator+(A a)
	{
		return b;
	}
	B(int b)
	{
		this->b = b;
	}
private:
	int b;
};

int main()
{
	//cout << A(2) + B(3);
	cout << B(3) + A(2);
	return 0;
}

因为结合性指明了 + 只能是左边对象的成员函数,不能是右边对象的成员函数。


int operator+ (A a, B b)
{
	return 5;
}

int main()
{
	cout << A(2) + B(3);
	return 0;
}

加了普通函数,就可以了。

普通函数和成员函数之间的重载:

int operator+ (B b, A a)
{
	return 10;
}

int main()
{
	//cout << B(3) + A(2);
	return 0;
}

2个函数都可以调用,优先级一样,编译失败。

PS:

因为是类似于函数重载,所以返回类型是不限制的。

4,语法规则

运算符重载不能改变语法规则,包括操作符和操作对象的位置关系、运算符函数的参数的数量和顺序。

(4.1)一元运算符

类成员函数没有参数,普通函数有一个参数。

类成员函数:

class A
{
public:
    int operator- ()
    {
        return -a;
    }
private:
    int a=5;
};

int main()
{
    A a;
    cout << -a;
    return 0;
}

普通函数:

class A
{
    int a=5;
};

int operator- (A a)
{
    return 0;
}

int main()
{
    A a;
    cout << -a;
    return 0;
}

自增自减运算符是个例外,只有自增自减运算符存在2种语法,前缀式和后缀式的语法不同。

前缀自增:

class A
{
public:
	void operator++ ()
	{
		cout << 123;
	}
};

int main()
{
	A a;
	++a;
	return 0;
}

后缀自增:

class A
{
public:
	void operator++ (int)
	{
		cout << 123;
	}
};

int main()
{
	A a;
	a++;
	return 0;
}

这里的int参数纯粹是用来表示这是后缀++

(4.2)二元运算符重载

只有二元运算符的重载涉及参数位置的问题。

二元运算符的2个操作对象,都是在运算符的一左一右,对于非成员函数,按照左右顺序对应,对于类成员函数,当前对象对应左边,函数参数对应右边。

对于左结合性的,上面有+的例子。

对于右结合性的,以+=为例:

class A
{

};
class B
{
public:
	void operator+= (A a)
	{
		cout << 123;
	}
};


int main()
{
	A a;
	B b;
	// a += b;
	b += a;
	return 0;
}

所以不管结合性,二元运算符都是调用左边对象的成员函数,或调用普通函数:

class A
{

};
class B
{
};

void operator+= (A a, B b)
{
	cout << 123;
}
int main()
{
	A a;
	B b;
	a += b;
	// b += a;
	return 0;
}

四,几个特殊的运算符

  • ::是优先级最高的,也是唯一比括号()还高的。
  • ,是优先级最低的。
  • ()也可以重载,即仿函数。
  • ++和--的特殊性上面有提到。
  • <<和>>也有点特殊,有位运算和流操作符两种语义。

注意,++和--作为前缀和后缀是两种语法,也是两种语义,<<和>>表示位运算和流操作符虽然是两种语义,但是是同一个语法,换句话说,前缀和后缀是2个不同的运算符,而表示位运算和流操作符的是同一个运算符,只是重载的功能不同。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值