1. 运算符概念
1.1 运算符分类
C++ 运算符的种类有三种,作用于一个运算对象的运算符是一元运算符,如取地址符和解引用符。作用于两个运算对象的运算符是二元运算符,如相等运算符和乘法运算符。除此之外,还有一个作用于三个运算符对象的三元运算符。
1.运算符和运算对象
对于含有多个运算符的复杂表达式来说,要想理解它的含义首先要理解运算符的优先级,结合律以及运算对象的求值顺序。如下所示:
5 + 10 * 20/2;
2.左值和右值
当一个对象被用作右值的时候用的是对象的值,当对象被用作左值的时候用的是对象的地址。需要注意的是左值可以位于赋值语句的右侧,右值则不能。
int x = 5; // x 是左值,因为它有地址
int y = x; // x 是右值,因为它只是一个值,并非地址
int z = x + y; // x + y 是右值,因为它只是一个临时计算出来的值
// 左值引用和右值引用也是与这些概念相关的。左值引用指向左值,右值引用指向右值。
int a = 10;
int& lref = a; // lref 是左值引用,指向左值 a
int&& rref = 5; // rref 是右值引用,指向右值 5,双重 && 表示 rref 是一个右值引用
3.重载运算符
当运算符作用于类类型的运算对象时,用户可以自定义其含义。这种自定义的过程实际上为已存在的运算符赋予了另一层含义,称之为重载运算符。我们使用重载运算符,其包括运算对象的类型和返回值的类型都是由该运算符定义的,但是运算对象的个数,运算符的优先级和结合律都是无法改变的。
#include <iostream>
class Complex {
private:
double real;
double imag;
public:
Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) {}
// 重载 + 运算符
Complex operator+(const Complex& obj) const {
Complex temp;
temp.real = real + obj.real;
temp.imag = imag + obj.imag;
return temp;
}
void display() const{
std::cout << "Real: " << real << " Imaginary: " << imag << std::endl;
}
};
int main() {
Complex c1(2.0, 3.0);
Complex c2(1.0, 4.0);
Complex result = c1 + c2; // 使用重载的 + 运算符
result.display();
return 0;
}
1.2 类型转换
在表达式求值过程中,一般二元运算符要求两个运算对象的类型相同,但很多时候即使不同也没有关系,只要它们能被转换成同一种类型即可。C++ 中的运算对象转换规则是编译器根据运算符和表达式的操作数进行类型转换的一组规则。这些规则包括:
1.隐式转换
1.整型提升:小于 int 的整型值在表达式中会被提升为 int 或更大的整型类型。
2.算术转换:运算符应用于不同类型的操作数时会执行算术转换。例如将 int 和 double 进行运算时,int 会被提升为 double。
short a = 5;
int b = a; // 整型提升,将 short 提升为 int
double y = 2.5;
double result = b + y; // 算术转换,将 int 转换为 double
2.显式转换
1.C风格的强制类型转换:在变量前用括号标注想要转换的类型即可。
2.static_cast:允许进行比较宽泛的转换,例如指针类型转换、基本类型转换。
3.const_cast:用于去除变量的 const 属性。
int a = 10;
double b = (double)a; // C 风格的强制类型转换,将 int 转换为 double
int b = 20;
double c = static_cast<double>(b); // static_cast,将 int 转换为 double
const int* ptr = new int(5); // 指向常量整数的指针
int* nonConstPtr = const_cast<int*>(ptr); // 移除常量性
*nonConstPtr = 10; // 修改值
cout << *ptr; // 原指针指向的值也发生了变化
1.3 优先级和结合律
复合表达式是指含有两个或多个运算符的表达式。求复合表达式的值需要先将运算符和运算对象合理的组合在一起,优先级与结合律决定了运算对象组合的方式。示例如下:
int a = 5, b = 10;
c = a + b * 2;
// 乘法操作符 * 拥有比加法操作符 + 更高的优先级,所以 b * 2 将首先计算。
// 乘法是左结合的,意味着 b * 2 会在与 a 相加之前先进行计算,这个表达式就变成了 c = a + 20。
// 因为加法操作符 + 有着比赋值操作符 = 更低的优先级,所以 a + 20 将会先计算。
注意: 括号无视普通的组合规则,表达式中括号括起来的部分被当成一个单元来求值,然后再与其它部分一起按照优先级组合。
2.运算符
2.1 算术运算符
运算符 | 功能 | 用法 | 运算符 | 功能 | 用法 |
---|---|---|---|---|---|
+ | 一元正号 | + expr | % | 求余 | expr % expr |
- | 一元负号 | - expr | + | 加法 | expr + expr |
* | 乘法 | expr * expr | - | 减法 | expr - expr |
/ | 除法 | expr / expr |
一元运算符的优先级最高,接下来是乘法和除法,优先级最低的是加法和减法。优先级相同时按照从左到右的顺序进行组合。
2.2 逻辑运算符
关系运算符作用于算术类型或指针类型,逻辑运算符作用于任意能转换成布尔值的类型,逻辑运算符和关系运算符的返回值都是布尔类型。
结合律 | 运算符 | 功能 | 用法 | 结合律 | 运算符 | 功能 | 用法 |
---|---|---|---|---|---|---|---|
右 | ! | 逻辑非 | !expr | 左 | >= | 大于等于 | expr >= expr |
左 | < | 小于 | expr < expr | 左 | == | 相等 | expr == expr |
左 | <= | 小于等于 | expr <= expr | 左 | ! = | 不相等 | expr != expr |
左 | > | 大于 | expr > expr | 左 | && | 逻辑与 | expr && expr |
2.3 赋值运算符
赋值运算符满足右结合律,如下所示:
ival = jval = 0; // 先计算jval=0,再把结果赋值给ival
注意: 切勿混淆相等运算符和赋值运算符。
2.4 递增和递减运算符
递增和递减运算符有两种形式,前置版本和后置版本。前置版本首先将运算对象加 1,然后将改变后的对象作为求值结果。后置版本也会将运算对象加 1,但是求值结果是运算对象改变之前那个值的副本。
int i = 0, j;
j = ++i; // j = 1, i = 1;
j = i++; // j = 1, i = 2;
1.解引用和递增运算符的优先级
后置递增运算符的优先级高于解引用运算符,如下所示:
#include <iostream>
using namespace std;
int main() {
int arr[] = { 10, 20, 30, 40, 50 };
int *ptr = arr; // 指向数组的指针,指向第一个元素
cout << "初始值: " << *ptr++ << endl; // 结果为10
return 0;
}
// ptr++ 会先将指针 ptr 的值递增(指向下一个元素),但 * 运算符作用的是递增前的 ptr 值所指向的元素,而不是递增后的值
2.5 成员访问运算符
点运算符和箭头运算符都可以用于访问成员,如下所示:
#include <iostream>
using namespace std;
// 定义一个简单的类
class MyClass {
public:
int memberVar = 10;
void memberFunction() {
cout << "这是一个成员函数" << endl;
}
};
int main() {
// 创建一个 MyClass 的对象
MyClass obj;
// 使用 . 运算符访问成员变量和成员函数
cout << "成员变量的值:" << obj.memberVar << endl;
obj.memberFunction();
// 使用 -> 运算符访问成员变量和成员函数
MyClass *ptr = &obj;
cout << "通过指针访问成员变量的值:" << ptr->memberVar << endl;
ptr->memberFunction();
return 0;
}
2.6 条件运算符
条件运算符如下所示:
#include <iostream>
#include<string>
using namespace std;
int main() {
int num = 10;
string result;
// 使用条件运算符将结果赋给字符串
result = (num > 5) ? "大于5" : "小于等于5";
// 输出结果
cout << "结果是:" << result << endl;
return 0;
}
注意: 条件运算符的优先级非常低,因此当有一条长表达式中嵌套了条件运算子表达式时,通常在子表达式两端加上括号。
2.7 位运算符
位运算符作用于整数类型的运算对象,并把运算对象看成是二进制位的集合。位运算符提供检查和设置二进制位的功能,一种名为bitset的标准库类型也可以表示任意大小的二进制位集合。
运算符 | 功能 | 用法 | 运算符 | 功能 | 用法 |
---|---|---|---|---|---|
~ | 位求反 | ~ expr | & | 位与 | expr1 & expr2 |
<< | 左移 | expr1 << expr2 | ^ | 位异或 | expr1 ^ expr2 |
>> | 右移 | expr1 >> expr2 |
1.移位运算符
左移:将一个数的二进制表示向左移动指定的位数,右侧空出的位用零填充。例如,x << y 将 x 向左移动 y 位。
右移:将一个数的二进制表示向右移动指定的位数。对于有符号整数,空出的位使用符号位填充(即保持正负号不变),而对于无符号整数,空出的位用零填充。例如,x >> y 将 x 向右移动 y 位。
#include <iostream>
using namespace std;
int main() {
int x = 10; // 二进制表示为 1010
// 左移示例
int leftShifted = x << 2; // 1010 << 2 -> 101000(二进制),相当于十进制的 40
cout << "左移结果:" << leftShifted << endl;
// 右移示例
int rightShifted = x >> 1; // 1010 >> 1 -> 101(二进制),相当于十进制的 5
cout << "右移结果:" << rightShifted << endl;
return 0;
}
2.位求反运算符
位求反运算符将运算对象逐位求反后生成一个新值,将 1 置为 0,将 0 置为 1。
3.位与、位或、位异或运算符
1.位与运算符: 如果两个运算对象的对应位置都是 1,则运算结果中该位为 1,否则为 0。
2.位或运算符: 如果两个运算对象的对应位置至少有一个为 1,则运算结果中该位为 1,否则为 0。
3.位异或运算符: 如果两个运算对象的对应位置有且只有一个为 1,则运算结果中该位为 1,否则为 0。
2.8 逗号运算符
逗号运算符含有两个运算对象,按照从左到右的顺序求值,和逻辑与、逻辑或以及条件运算符一样,逗号运算符也规定了运算对象的求值顺序。首先对左侧的表达式求值,然后将求值结果丢弃掉,逗号运算符真正的结果是右侧表达式的值。