C++

C++ 知识量:19 - 82 - 316

4.1 运算符基础知识><

c++运算符- 4.1.1 -

C++中的运算符包括算术运算符、比较运算符、逻辑运算符、位运算符、赋值运算符、条件运算符、逗号运算符、sizeof运算符、类型转换运算符、赋值运算符等等。

  • 算术运算符:用于进行基本的算术运算,包括加法、减法、乘法、除法、取模等。

  • 比较运算符:用于比较两个值的大小关系,包括等于、不等于、大于、小于等。

  • 逻辑运算符:用于进行逻辑运算,包括与、或、非等。

  • 位运算符:用于对二进制位进行操作,包括按位与、按位或、按位异或等。

  • 赋值运算符:用于将右侧的值赋给左侧的变量,包括等于、加等于、减等于、乘等于等。

  • 条件运算符:用于进行条件判断,例如三目运算符。

  • 逗号运算符:用于将多个表达式连接在一起,例如逗号表达式。

  • sizeof运算符:用于获取变量或类型的大小,例如sizeof(int)。

  • 类型转换运算符:用于将一种类型转换为另一种类型,例如static_cast<int>(2.5)。

除了上述常见的运算符外,C++还支持自定义运算符,例如重载运算符。通过重载运算符,可以定义自己的运算符行为,以满足特定的需求。

运算符的优先级- 4.1.2 -

C++中运算符的优先级如下:

  1. 括号(( ))具有最高优先级。

  2. 成员访问运算符(.``)和箭头运算符(->`)具有相同的优先级,比下一级运算符优先级高。

  3. 算术运算符(+ - * / %)具有相同的优先级,比下一级运算符优先级高。

  4. 移位运算符(<< >>)具有相同的优先级,比下一级运算符优先级高。

  5. 关系运算符(< <= > >=)具有相同的优先级,比下一级运算符优先级高。

  6. 等于运算符(==)具有相同的优先级,比下一级运算符优先级高。

  7. 不等于运算符(!=)具有相同的优先级,比下一级运算符优先级高。

  8. 位与运算符(&)具有相同的优先级,比下一级运算符优先级高。

  9. 位异或运算符(^)具有相同的优先级,比下一级运算符优先级高。

  10. 位或运算符(|)具有相同的优先级,比下一级运算符优先级高。

  11. 逻辑与运算符(&&)具有相同的优先级,比下一级运算符优先级高。

  12. 逻辑或运算符(||)具有相同的优先级,比下一级运算符优先级高。

  13. 条件运算符(? :)具有相同的优先级,比下一级运算符优先级高。

  14. 赋值运算符(= += -= *= /= %= >>= <<= &= ^= |=)具有相同的优先级,比下一级运算符优先级高。

  15. 逗号运算符(,)具有最低的优先级。

结合律- 4.1.3 -

在C++中,大多数运算符都是符合结合律的。结合律意味着当一个操作数有多个运算符时,运算符的优先级和结合方向不会影响运算结果。

例如,考虑以下表达式:

3 + 4 * 5

根据C++的运算符优先级和结合律,这个表达式的计算结果是23。尽管乘法运算符的优先级高于加法运算符,但结合律保证了先执行乘法运算,然后再执行加法运算。

但是,有些运算符是不符合结合律的。例如,逻辑运算符&&和||。当一个表达式中有多个逻辑运算符时,它们的结合方向会直接影响运算结果。

例如,考虑以下表达式:

true && false || true

由于&&运算符的优先级高于||运算符,因此首先会执行&&运算。然后,由于&&运算符的左侧为false,因此整个&&表达式的结果为false。接下来,由于||运算符的左侧为false,因此整个||表达式的结果为true。因此,整个表达式的计算结果是true。

需要注意的是,C++中的逻辑运算符&&和||都是短路运算符。这意味着在计算逻辑表达式时,如果已经可以确定整个逻辑表达式的结果,那么后续的运算将不会被执行。这可以帮助优化程序的性能。

求值顺序- 4.1.4 -

在C++中,表达式的求值顺序主要遵循以下规则:

  1. 括号内的运算总是比括号外的运算先执行。

  2. 同一优先级的运算符按照从左到右的顺序执行。

  3. 不同优先级的运算符按照优先级从高到低的顺序执行。

  4. 同一优先级的运算符,如果结合方向是从左到右,则按照从左到右的顺序执行;如果结合方向是从右到左,则按照从右到左的顺序执行。

例如,考虑以下表达式:

a + b * c + d % e / f % g

在这个表达式中,有四个乘法运算符、一个加法运算符、一个模运算符和一个除法运算符。根据上述规则,这个表达式的求值顺序如下:

  1. 首先,括号内的运算先执行。在这个表达式中,没有括号,所以不需要考虑这一步。

  2. 然后,执行乘法运算。根据结合方向从左到右的原则,先执行b * c,得到结果bc。然后执行a + bc,得到结果abc。

  3. 接下来,执行模运算。根据结合方向从左到右的原则,先执行d % e,得到结果de。然后执行abc + de,得到结果abcde。

  4. 最后,执行除法运算。根据结合方向从左到右的原则,先执行f / g,得到结果fg。然后执行abcde / fg,得到最终结果abcde/fg。

需要注意的是,C++中的算术运算符(如+、-、*、/和%)都是短路运算符。这意味着在计算表达式时,如果运算符的左侧表达式的结果已经可以确定整个表达式的结果,那么右侧表达式将不会被执行。这可以帮助优化程序的性能。

运算对象转换- 4.1.5 -

在C++中,有些运算符可以用于不同类型的对象。在这些情况下,C++会尝试进行一种叫做"类型提升"的转换,将运算对象转换为适当的类型以便进行运算。这种转换可能包括将较低精度的类型(如char或short)转换为较高精度的类型(如int或long),或者将非整数类型转换为整数类型。

然而,这种自动类型提升可能会导致一些意想不到的结果,特别是在涉及浮点数和整数转换时。例如,将浮点数转换为整数通常采用"向下取整"的方式,也就是说会丢弃小数部分。

如果想对两个不同类型的对象执行运算,并且希望结果具有特定的精度,需要明确地进行类型转换。C++提供了几种类型转换运算符,包括static_cast、dynamic_cast、const_cast和reinterpret_cast。这些运算符的使用取决于具体需求。

例如,如果想将一个浮点数转换为整数,可以使用静态转换(static_cast):

float f = 3.14;    
int i = static_cast<int>(f);  // i现在是3,小数部分被丢弃

如果想将一个整数转换为浮点数,也可以使用静态转换:

int i = 3;    
float f = static_cast<float>(i);  // f现在是3.0

重载运算符- 4.1.6 -

C++允许程序员重载(或自定义)运算符,以改变它们的行为。重载的运算符可以用于类定义中,以实现类的特定行为。以下是如何在C++中重载运算符的基本步骤:

  1. 声明运算符函数:运算符重载函数必须被声明为成员函数或者友元函数。它不能是全局函数,因为全局函数可能会与类中的其他成员函数产生冲突。

  2. 定义运算符重载函数:运算符重载函数的定义应该符合其原始形式,但是可以带有额外的参数。例如,如果想重载加号运算符,函数可能看起来像这样:返回_type operator+(const Type& other)。

  3. 返回类型:运算符重载函数的返回类型应该符合预期。例如,加法运算符的返回类型可能是想相加的两个对象的类型。

  4. 参数:运算符重载函数可以有一个或多个参数。例如,加法运算符可能接受两个参数。

  5. 调用顺序:运算符重载函数的调用顺序应该符合预期。例如,加法运算符应该先调用一个对象的成员函数,然后再调用另一个对象的成员函数。

下面是一个简单的例子,展示了如何在C++中重载加号运算符:

class Complex {  
public:  
    Complex(double real = 0.0, double imag = 0.0): m_real(real), m_imag(imag) {}  
    // 重载加法运算符  
    Complex operator+(const Complex& b) const {  
        return Complex(m_real + b.m_real, m_imag + b.m_imag);  
    }  
private:  
    double m_real;  
    double m_imag;  
};

在这个例子中,创建了一个名为Complex的类,它代表了一个复数。重载了加号运算符,因此可以将两个Complex对象相加。

左值和右值- 4.1.7 -

在C++中,表达式可以被分为左值和右值。这种分类基于表达式是否可以独立存在。

1. 左值(Lvalue):左值是可以被赋值的表达式的统称。简单来说,左值是指向可寻址的对象的表达式。例如,变量、数组元素或结构体成员等。这些表达式可以出现在赋值语句的左边,表示一个可以存储值的实体。

例如:

int a = 10; // 'a'是一个左值,因为它是一个可寻址的变量,可以被赋值。

2. 右值(Rvalue):右值是不能独立存在的表达式的统称。这些表达式通常出现在赋值语句的右边,表示一个临时的、不能存储值的实体。例如,常量、字面量或临时对象等。

例如:

10 + a; // '10'是一个右值,因为它是一个常量,不能被赋值。

在C++中,右值引用是C++11引入的一个重要特性,允许开发者利用右值进行优化,例如移动语义和完美转发等。通过右值引用,可以将临时对象(右值)的资源“移动”到另一个对象(左值)中,从而提高代码效率。