【C++学习记录】—— 【12】【表达式】

零.前言

感觉前面那么详细的记录,导致浪费了很多时间,现在可能一篇文章记录一章的重点,所以可能会损失一些简单的信息。

一.基础

1.基础概念

表达式是由一个或多个运算对象组成,对表达式求值,会得到一个结果,字面值和变量是最简单的表达式,用运算符把一个或多个运算式组合起来,就可以生成比较复杂的表达式。
C++里有一元运算符二元运算符三元运算符等多种运算符,如其名,比如&*的作用对象是一个,所以是一元运算符,而==*这些符号作用对象是两个,所以是二元运算符。函数调用也是一种特殊的运算符。

1.1 组合运算符和运算对象

对复杂的表达式,我们要理解运算符的优先级结合律以及运算对象的求值顺序

1.2 运算对象转换

一般来说,只要能被转换成同一种类型,就能参与运算。且小整数类型(如short、char)会被提升为较大的整数类型,比如int。

1.3 重载运算符

重载运算符,可以让用户自定义一些运算符的含义,让它能够作用于飞内置的类型。使用重载运算符的时候,运算对象的类型以及返回值类型都是重载运算符定义的(程序员自己写代码定义),运算符的优先级和结合律是无法=被改变的。

1.4 左值和右值

左右值得概念可能有些抽象,目前可以这样理解:
左值是用的对象的身份(在内存中的位置)
右值是用的对象的值(也就是它的内容)

2.优先级和结合律

复合表达式指含有两个以上运算符的表达式。优先级和结合律决定了表达式中的每个运算符对应的运算对象来自表达式的哪一部分。
一般来说,表达式的组合方式能够决定表达式的最终值。
且括号可以无视优先级和结合律

3.求值顺序

如果没有指定顺序的运算符来说,如果表达式指向并修改了同一个对象,则将会引发错误并产生未定义的行为。有四种运算符明确规定了运算对象的求值顺序:&&||?:,
比如‘&&’运算符,它会先求左侧对象的值,如果左侧对象为真了,就不会计算右侧对象的值。
所以我们在面对复合表达式的时候:

  1. 在拿不准的时候,用括号来强制把表达式转换成你的需求
  2. 如果改变了某个对象的值,就不要再其他地方使用这个运算对象

二.算数运算符

对于算数运算符+ - * / %这些运算符号,都满足左结合律,他们的运算对象和求值结果都是右值。

且他们在运算的时候,可能会得到提升

    bool b = true;
    bool b2 = -b;

    cout << b2;
    // 输出是1

为啥?因为true是1,在进行-运算的时候,b2变成了-1,但是-1不是0,再转换成bool的时候,就变成了真,也就是1。

算数运算的异常,我们常见的还有溢出,比如:

    short a = 32767;
    cout << ++a;

此时输出的a是-32768

对于取余,现在C++不管是正负,一律去除小数点后的部分

三.逻辑和关系运算符

这一节没啥太多说的,但记住一点,如果一个非bool值,最好不要与truefalse比较

四.赋值运算符

赋值满足右结合律,且赋值的优先程度较低,最好在条件语句中的赋值,打上括号
比如:

int get_value(void)
{
    int a;
    cin >> a;
    return a;
}

int main()
{
    int i = 0;
    while((i = get_value()) != 42)
    {
        cout << i << endl;
    }
}

五.递增和递减运算符

有两种版本的递增和递减:
++aa++
这两者的区别是:++a是对自身+1,并返回自身
a++是先返回一个a的值,然后再对a+1。
所以我们再for里面,最好使用++a,而非a++。使用a++就造成了一种浪费

六.成员访问运算符

简单来说,记住:
ptr->element 等价于(*ptr).element

七.条件运算符

表达式为:
cond ? expr1 : expr2;
如果cond成立,那么执行 expr1,否则执行expr2. 该语句可以嵌套:

    string a;
    int c;

    c = 10;
    a = (c < 60) ? "NO" : (c > 90) ? "Excellent" : "Yes";
    cout << a << endl; 

    c = 70;
    a = (c < 60) ? "NO" : (c > 90) ? "Excellent" : "Yes";
    cout << a << endl; 

    c = 95;
    a = (c < 60) ? "NO" : (c > 90) ? "Excellent" : "Yes";
    cout << a << endl; 
    /*
	NO
	Yes
	Excellent
	*/

八.位运算符

C++里有对二进制的支持,有个类叫bitset,他们也支持二进制
因为位运算会让每个位进行运算,所以推荐使用无符号的类型,以免发生奇怪的现象。
其他的就很常规,如果不懂,可以参考其他地方的内容。

九.sizeof运算符

使用sizeof会得到一个size_t的常量表达式,且有一下规则:

  1. char或类型为char的表达式执行siezof 结果为1
  2. 对引用类型执行,会得到引用类型所占的空间大小
  3. 对指针执行,会得到指针所占的空间大小,而非其指向对象的大小
  4. 对解引用指针执行,会得到其指向对象的大小,且不需要该指针是否有效
  5. 对数组执行,会得到其所占空间的大小,所以如果需要得到一个数组的长度,可以sizeof(arry) / sizeof(arry[1])
  6. string或者vector执行,会得到其固定部分的大小,而非所有元素所占的空间

十.逗号运算符

对于逗号运算符来说,他会先对左侧的表达式求值,然后把左侧的值丢掉,对右侧表达式求值,并返回右侧的值。

    int a = 10, b = 0;
    int c;

    c = ++a , ++b;
    cout << a << ","<< b << ","<< c << ",";
    //11,1,11,

十一.类型转换

1.隐式转换

在有些情况下,程序会自动将某些值进行转换,这个过程是自动且无需外部介入的。

  1. 在大多数表达式里,比int类型小的整型,会优先提升为较大的整数
  2. 在条件中,非布尔类型会换成布尔类型
  3. 初始化中,初始化转换成变量的类型;赋值语句中,右侧对象会转换成左侧运算对象的类型
  4. 如果算数运算或者关系运算的对象有多种类型,则会转换成同一种类型。
  5. 函数调用时,也会发生类型转换
    当然,除了这些,还有些特殊的隐式转换,比如数组会转换成首元素的指针等等

2.算数转换

小整型会提升成大整型,叫做整型提升
如果一个是无符号的,一个有符号,且无符号大于带符号类型的,那么带符号的会转换成无符号的
反之,带符号的大于无符号的,则转换结果根据机器而定。

3.显示转换

早期的C语言的强制转换
type (expr)
(type) expr
C++则提供了更多的方法:
cast-name<type>(expr)
其中cast有四种static_cast dynamic_cast const_cast reinterpret_cast

static_cast:只要不包含底层const,就可以用这个来转换,举两个例子:

{
    int a = 5, b = 2;
    double c = static_cast<double> (a) / b;

    cout << c;
    //2.5
    double a = 3.1415;
    void *p = &a;

    double *dp = static_cast<double*> (p);

    cout << *dp;
    // 3.1415

const_cast
可以改变运算对象的底层const,作用可以理解为“去掉const性质”。
就可以使用该转换,让对象从可读编程可读写。
但如果对象是一个常量,那么就又会产生未定义的后果
reinterpret_cast
这玩意儿就是从很底层的进行类型的强制转换。很危险。
dynamic_cast
后面会提到。

所以,为了安全,尽量少使用显示类型转换而多使用隐式转换
比如我就喜欢:
double a = 1.0 * c / d;(c d是int型)

十二.运算符优先级表

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,下面是关于栈应用(表达式求值)的学习笔记。 ## 1. 表达式求值的基本概念 ### 1.1 中缀表达式 中缀表达式是我们平时写的表达式,如:2 + 3 * 4 - 6 / 2。 ### 1.2 后缀表达式 后缀表达式也叫逆波兰表达式,它是一种不包含括号的表达式。在后缀表达式中,运算符在操作数的后面,因此也叫后缀表示法。例如,上面的中缀表达式的后缀表达式为:2 3 4 * + 6 2 / -。 ### 1.3 前缀表达式 前缀表达式也叫波兰式,它与后缀表达式类似,只是运算符在操作数的前面。例如,上面的中缀表达式的前缀表达式为:- + * 3 4 2 / 6 2。 ### 1.4 运算符优先级 在中缀表达式中,运算符有不同的优先级。通常,乘法和除法的优先级高于加法和减法。如果有括号,则括号内的表达式优先计算。 ### 1.5 中缀表达式转后缀表达式 将中缀表达式转换成后缀表达式的过程,也叫中缀表达式的后缀表达式化。具体的转换规则如下: - 遍历中缀表达式的每个元素。 - 如果当前元素是操作数,则将其加入后缀表达式中。 - 如果当前元素是运算符,则判断其与栈顶运算符的优先级,如果栈顶运算符优先级高于或等于当前运算符,则弹出栈顶运算符加入后缀表达式中,并继续比较下一个栈顶运算符,直到当前运算符的优先级高于栈顶运算符或栈为空时,将当前运算符入栈。 - 如果当前元素是左括号“(”,则直接入栈。 - 如果当前元素是右括号“)”,则依次弹出栈顶运算符加入后缀表达式中,直到遇到左括号为止,此时将左括号弹出,但不加入后缀表达式中。 ### 1.6 后缀表达式求值 将后缀表达式求值的过程,也叫后缀表达式的求值。具体的求值规则如下: - 遍历后缀表达式的每个元素。 - 如果当前元素是操作数,则将其入栈。 - 如果当前元素是运算符,则弹出栈顶的两个操作数,进行运算,并将运算结果入栈。 - 遍历完后缀表达式后,栈中只剩下一个元素,即为表达式的值。 ## 2. 表达式求值的实现 ### 2.1 中缀表达式转后缀表达式的实现 中缀表达式转后缀表达式可以使用栈来实现。具体的代码实现如下: ```cpp #include <iostream> #include <stack> #include <string> using namespace std; // 判断一个字符是否为操作符 bool isOperator(char c) { return c == '+' || c == '-' || c == '*' || c == '/'; } // 判断两个操作符的优先级 int getPriority(char op1, char op2) { if ((op1 == '*' || op1 == '/') && (op2 == '+' || op2 == '-')) { return -1; } else if ((op1 == '+' || op1 == '-') && (op2 == '*' || op2 == '/')) { return 1; } else { return 0; } } // 将中缀表达式转换成后缀表达式 string infixToPostfix(string infix) { stack<char> opStack; // 运算符栈 string postfix; // 后缀表达式 for (char c : infix) { if (isdigit(c)) { // 如果是数字,直接加入后缀表达式 postfix += c; } else if (isOperator(c)) { // 如果是操作符 while (!opStack.empty() && opStack.top() != '(' && getPriority(opStack.top(), c) >= 0) { postfix += opStack.top(); // 弹出栈顶操作符加入后缀表达式 opStack.pop(); } opStack.push(c); } else if (c == '(') { // 如果是左括号,直接入栈 opStack.push(c); } else if (c == ')') { // 如果是右括号 while (!opStack.empty() && opStack.top() != '(') { postfix += opStack.top(); // 弹出栈顶操作符加入后缀表达式 opStack.pop(); } opStack.pop(); // 弹出左括号 } } while (!opStack.empty()) { // 将剩余的操作符加入后缀表达式 postfix += opStack.top(); opStack.pop(); } return postfix; } int main() { string infix = "2+3*4-6/2"; string postfix = infixToPostfix(infix); cout << postfix << endl; // 输出后缀表达式:234*+62/- return 0; } ``` ### 2.2 后缀表达式求值的实现 后缀表达式求值也可以使用栈来实现。具体的代码实现如下: ```cpp #include <iostream> #include <stack> #include <string> using namespace std; // 判断一个字符是否为操作符 bool isOperator(char c) { return c == '+' || c == '-' || c == '*' || c == '/'; } // 计算两个操作数的运算结果 int calculate(int a, int b, char op) { if (op == '+') { return a + b; } else if (op == '-') { return a - b; } else if (op == '*') { return a * b; } else { return a / b; } } // 计算后缀表达式的值 int evaluate(string postfix) { stack<int> numStack; // 操作数栈 for (char c : postfix) { if (isdigit(c)) { // 如果是数字,将其转换成整数并入栈 int num = c - '0'; numStack.push(num); } else if (isOperator(c)) { // 如果是操作符 int b = numStack.top(); numStack.pop(); int a = numStack.top(); numStack.pop(); int result = calculate(a, b, c); numStack.push(result); } } return numStack.top(); } int main() { string postfix = "234*+62/-"; int result = evaluate(postfix); cout << result << endl; // 输出计算结果:8 return 0; } ``` ## 3. 总结 栈在表达式求值中的应用是很常见的,掌握了这个知识点,对于编写计算器等应用程序会有很大的帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

康娜喵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值