
在C++中,表达式(expression)是由常量、变量、运算符及函数调用按照语法规则组合而成的可以求值的结构。表达式的求值过程会产生一个特定的值,并可能伴随副作用(如赋值表达式、函数调用等)。
最基本的表达式包括单一的常量(如42)或变量(如age)。更复杂的表达式则可以包含多个运算符与操作数,如price * quantity + tax。C++编译器会根据运算符优先级和结合性等规则对表达式进行解析和计算。
在C++中,我们有不同类型的运算符,就像数学中有不同的运算符号一样。
-5中的负号,或者&address中的取地址符号。它们只关心一个操作数。a + b中的加号,x == y中的等号。它们需要两个操作数。条件 ? 值1 : 值2。如果条件为真,结果就是值1;如果为假,结果就是值2。有趣的是,有些符号在不同的地方有不同的意思。比如*符号,当它出现在变量前面时,就像一把钥匙,用来打开指针指向的盒子(解引用)。当它出现在两个数之间时,就是乘法符号。编译器很聪明,会根据上下文知道你想用哪种意思。
当你把米和厘米相加时,需要先把它们转换成相同的单位。C++也是这样,当不同类型的数一起运算时,需要转换成相同的类型。
比如,当你把整数5和浮点数3.14相加时,C++会自动把整数转换成浮点数,然后进行计算。这就像把5米转换成5.0米,然后和3.14米相加。
但是,有些转换是不允许的。比如你不能把一个人的名字(字符串)直接转换成身高(数字),这没有意义。同样,你不能把指针(内存地址)转换成浮点数。
还有一个有趣的现象:小的整数类型(比如char、short)在运算时会被自动提升为int类型。这就像把厘米自动转换成米来计算,计算完再转换回去。
在数学中,+号总是表示加法。但在C++中,我们可以给+号赋予新的含义。比如,当+号用在字符串之间时,它表示连接字符串,而不是数学加法。
这就是运算符重载:给现有的运算符符号赋予新的含义。比如,
cout << "Hello"中的<<符号,在数学中表示左移,但在这里表示输出。
但是,我们不能改变运算符的基本性质。比如,+号永远需要两个操作数,我们不能让它只需要一个操作数。这是不符合数学逻辑的。
想象一下,左值就像是房子的地址,右值就像是房子里的东西。
左值表示一个位置,就像房子的门牌号。你可以把东西放到这个位置,也可以从这个位置取东西。比如变量名age就是一个左值,它表示内存中的一个位置。
右值表示一个值,就像房子里的家具。它只是一个临时的值,用完就没了。比如数字42就是一个右值,它只是一个值,不占用固定的内存位置。
当你写age = 42时,age是左值(位置),42是右值(值)。你把值42放到了位置age中。
但是,有些左值是不能被赋值的,比如const常量。这就像有些房子是博物馆,你可以参观里面的东西,但不能把东西搬进去或搬出来。
当你看到表达式2 + 3 * 4时,你知道应该先算乘法,再算加法,结果是14而不是20。这就是优先级的作用。
优先级决定了哪些运算先进行。就像数学中,乘除法比加减法优先级高。
结合性决定了相同优先级的运算从左到右还是从右到左进行。比如10 - 5 - 3,因为减法是左结合的,所以从左到右计算:(10 - 5) - 3 = 2,而不是10 - (5 - 3) = 8。
让我们看一个更复杂的例子:8 + 6 * 4 / 2 + 3。按照优先级和结合性,计算过程是:
6 * 4 = 2424 / 2 = 128 + 12 = 2020 + 3 = 23括号就像数学中的括号一样,可以改变运算顺序。比如(8 + 6) * (4 / 2) + 3,先算括号里的:
8 + 6 = 144 / 2 = 214 * 2 = 2828 + 3 = 31优先级错误会导致程序出错。比如,如果你想访问数组的第5个元素:
|int grades[] = {85, 92, 78, 96, 88}; int fifth = *(grades + 4); // 正确:先算加法,再解引用 int wrong = *grades + 4; // 错误:先解引用,再加4
第一个表达式先计算grades + 4得到第5个元素的地址,然后解引用得到值88。第二个表达式先解引用grades得到第一个元素的值85,然后加4得到89。
优先级告诉我们运算的顺序,但不告诉我们操作数什么时候被计算。比如在表达式getScore() + getBonus() * getMultiplier()中,我们知道先算乘法再算加法,但不知道这三个函数哪个先调用。
这通常不是问题,除非函数之间有副作用。比如:
|int count = 0; cout << count << " " << ++count << endl; // 未定义行为
这个表达式的结果是不确定的,因为<<运算符不保证操作数的求值顺序。可能输出0 1,也可能输出1 1,甚至可能输出其他结果。
只有少数几个运算符保证求值顺序:
&&:先算左边,如果左边为假就不算右边||:先算左边,如果左边为真就不算右边?::先算条件,再算对应的分支,:从左到右依次计算理解表达式求值就像理解数学运算一样。运算符有不同的优先级和结合性,就像数学中的运算顺序。 类型转换就像单位换算,让不同类型的数能够一起运算。左值和右值的概念帮助我们理解变量和值的区别。 最重要的是,括号可以改变运算顺序,让我们精确控制表达式的求值过程。
在C++中,算术运算符就像我们日常做加减乘除的符号。你可以用它们对数字进行各种计算。常见的有加号+、减号-、乘号*、除号/,还有取余数的%。这些运算符不仅可以用在整数上,也可以用在浮点数(带小数的数)上。
有些运算符比其他运算符“更急”,会先算。比如你写2 + 3 * 4,计算机会先算3 * 4,再加2,结果是14。这是因为乘法和除法的优先级比加法和减法高。如果优先级一样,比如8 - 3 + 2,就会从左到右算,先算8 - 3得5,再加2得7。
有些运算符只需要一个数,比如-a表示取a的相反数,这叫一元运算符。比如你有int temperature = 10; int cold = -temperature;,cold的值就是-10。
大多数算术运算符需要两个数,比如a + b、x / y,这叫二元运算符。
%运算符用来求余数。比如你有12个苹果,3个人平分,每人分到多少个?用12 / 3得4,每人4个。如果是13个苹果,还是3个人,13 / 3得4,但还剩下1个分不掉,这个1就是余数。你可以用13 % 3得到1。
注意,
%只能用在整数之间,不能用在小数上。如果你写5.5 % 2,编译器会报错。
当你用两个整数相除时,结果会自动去掉小数部分。比如17 / 5,结果是3,因为3*5=15,剩下2被丢掉了。如果你想得到小数结果,需要让其中一个数是小数(类型转换),比如17.0 / 5,结果就是3.4。
有时候,计算结果超出了变量能表示的范围,这叫“溢出”。比如你用short类型存储一个很大的数:
|short s = 32767; // short能表示的最大值 s = s + 1; // 超过了最大值 cout << s << endl; // 结果可能是-32768
这就像一个计数器,数到头了又从最小值开始。不同的电脑可能表现不一样,有的会直接变成负数,有的甚至会让程序崩溃。所以,写程序时要注意不要让变量“爆表”。
当你用负数做除法或取余时,C++规定结果要“向零截断”。比如:
|int a = -17; int b = 4; cout << a / b << endl; // 输出-4 cout << a % b << endl; // 输出-1
也就是说,-17除以4等于-4,余数是-1。无论正负,余数的符号和被除数一样。
有些算术运算符还能用在指针上,比如你有一个数组:
|int scores[] = {60, 70, 80, 90}; int* p = scores; p = p + 2; // p现在指向scores[2],也就是80
这就像在一排房间里往后走两步,p就指向了第三个房间。
算术运算符让你可以像做算术题一样操作变量。要注意优先级和结合性,理解整数除法和取余的规则,避免溢出带来的意外结果。 如果涉及指针,也可以用加减法让指针“走动”。写代码时,记得用合适的类型和范围,避免超出类型范围或未定义的结果。
在C++中,除了做加减乘除,我们还经常需要让计算机判断“对还是错”、“大还是小”。这时候就要用到关系运算符和逻辑运算符。
关系运算符就像我们日常比较两个数字、字母或者变量的大小。比如你想知道小明的分数是不是比小红高,就可以用>运算符。
举个例子:
|int xiaoming = 85; int xiaohong = 90; bool result = xiaoming > xiaohong; // result是false,因为85不大于90
你还可以用<、>=、<=、==、!=来判断两个值的关系。比如:
|int age = 18; bool isAdult = age >= 18; // isAdult是true
有时候,我们需要判断多个条件同时成立或者至少有一个成立。这时就要用到逻辑运算符。C++里最常用的有三个:
&&(与):只有左右两边都为真,结果才为真。||(或):只要有一边为真,结果就为真。!(非):把真假反过来。比如你想判断一个学生是否及格并且年龄大于等于16岁:
|int score = 75; int age = 17; bool pass = (score >= 60) && (age >= 16); // pass是true
如果你想判断一个人是不是未成年人或者成绩不及格:
|int score = 55; int age = 15; bool needHelp = (age < 18) || (score < 60); // needHelp是true
!运算符可以把结果反过来,比如:
|bool isOpen = false; bool isClosed = !isOpen; // isClosed是true
C++的逻辑运算符有个很聪明的地方,叫“短路”。比如在&&运算中,如果左边已经是false,右边就不会再计算了,因为无论右边是什么,结果都不可能为真。同理,||运算时,如果左边已经是true,右边就不会再算。
比如:
|int a = 0; if (a != 0 && (10 / a > 1)) { // 这里不会出错,因为a==0时,右边不会被计算 }
这些运算的结果都是true或false,在C++里其实就是1和0。你可以用它们直接赋值给bool类型,也可以用在if、while等条件判断里。
在C++中,赋值运算符=就像是把一个东西放进盒子里。你可以把一个值放进变量这个“盒子”里,让它以后可以随时取出来用。
比如说,你有一个变量score,你可以这样给它赋值:
|int score; score = 95; // 把95放进score这个盒子
赋值运算符不仅可以用来给变量第一次存值,还可以随时更新变量的内容。比如你玩游戏得分,每次得分都可以更新:
|score = score + 10; // 得到10分后,score变成原来的分数加10
C++还提供了“复合赋值运算符”,让你写代码更简洁。比如+=,它的意思就是“把右边的值加到左边变量上”。上面的加分可以写成:
|score += 10; // 和score = score + 10一样
同理,还有-=、*=、/=、%=等。比如:
|int apples = 8; apples -= 3; // 吃掉3个苹果,现在剩下5个
这些复合赋值运算符让你不用重复写变量名,代码更简洁。
赋值表达式的结果是左边变量的新值。比如:
|int a = 5; int b; b = a = 20; // a和b最后都是20
这里,a = 20的结果是20,然后再赋值给b,所以a和b都变成20。
需要注意的是,赋值运算符的优先级比较低。如果你在一个复杂的表达式里用赋值,要注意加括号,避免出错。
在C++中,有两个非常常用的运算符,能让你让变量的值自动加一或者减一。这就是自增(++)和自减(--)运算符。
你可以把它们想象成计数器上的按钮。每按一次++,数字就加一;每按一次--,数字就减一。
比如你在做俯卧撑,每做一个就让计数器加一:
|int pushups = 0; pushups++; // 做了一个俯卧撑,pushups变成1 pushups++; // 又做一个,pushups变成2
同理,如果你在倒计时,每过一秒就让数字减一:
|int seconds = 10; seconds--; // 还剩9秒
自增和自减有两种写法:前缀(++a、--a)和后缀(a++、a--)。它们的区别在于:
举个例子:
|int n = 5; int a = ++n; // n先加1变6,然后a等于6 int b = n++; // b等于6,然后n再加1变7
在循环里,自增自减特别常用。比如你想统计有多少个学生报名:
|int students = 0; for (int i = 0; i < 30; ++i) { students++; } // 最后students就是30
需要注意的是,只有变量(能被修改的左值)才能用自增自减。常量或者表达式不能直接用,比如
(a + b)++是错误的。
在C++中,我们经常会用到“点号(.)”和“箭头(->)”这两个运算符来访问对象的成员。你可以把它们想象成“钥匙”,用来打开对象里的“抽屉”取出你想要的东西。 关于成员我们会在后面详细介绍,你现在只需要知道它们是用来访问对象的成员的。
点号(.)用在对象本身上,比如你有一个学生对象student,你想知道他的年龄,就可以写:
|Student student; student.age = 18; cout << student.age << endl; // 输出18
如果你有一个指向学生的指针,比如:
|Student* p = &student;
这时你要用箭头(->)来访问成员:
|cout << p->age << endl; // 还是输出18
其实,p->age和(*p).age是一样的。箭头运算符就是帮你省去了先解引用再点成员的麻烦。
需要注意的是,解引用(*)的优先级比点号低,所以如果你写*p.age,编译器会以为你要访问p的成员age,然后再对结果解引用,这样会报错。正确的写法是(*p).age。
点号和箭头运算符的区别很简单:
如果你用错了,比如对指针用点号,或者对对象用箭头,编译器都会报错。
在C++中,条件运算符(?:)就像是一个简短的if-else语句,可以让你在一个表达式里做简单的判断。它的格式是:条件 ? 值1 : 值2。
你可以把它想象成一个自动售货机。你投币(条件),如果钱够了,就给你可乐(值1);如果钱不够,就给你矿泉水(值2)。
比如你想判断一个学生的成绩是否及格:
|int score = 75; string result = (score >= 60) ? "及格" : "不及格"; // result是"及格"
这里,如果分数大于等于60,结果就是"及格";否则就是"不及格"。
条件运算符有个特点:它只会计算其中一个值。如果条件为真,只计算值1;如果条件为假,只计算值2。这就像自动售货机,只会给你一种饮料,不会两种都给你。
你可以把条件运算符嵌套使用,就像俄罗斯套娃一样。比如你想把成绩分成三个等级:
|int score = 85; string grade = (score >= 90) ? "优秀" : (score >= 60) ? "及格" : "不及格"; // grade是"及格"
这里先判断是否大于等于90,如果是就"优秀";如果不是,再判断是否大于等于60,如果是就"及格",否则就"不及格"。
嵌套太多会让代码很难读,建议不要超过两三层。
条件运算符的优先级比较低,所以在复杂表达式中使用时,通常需要加括号。比如你想根据条件输出不同的内容:
|int age = 18; cout << ((age >= 18) ? "成年人" : "未成年人") << endl; // 正确 cout << (age >= 18) ? "成年人" : "未成年人" << endl; // 错误
如果不加括号,编译器可能会误解你的意思,导致输出错误的结果。
在C++中,位运算符就像是直接操作数字的二进制位。你可以把它们想象成开关面板,每个位就像一个开关,你可以单独控制每个开关的开关状态。
位运算符包括:~(按位取反)、<<(左移)、>>(右移)、&(按位与)、^(按位异或)、|(按位或)。
左移运算符<<就像把所有的开关向左移动几位,右边补0。右移运算符>>就像把所有的开关向右移动几位。
比如你有一个8位的数字:
|unsigned char light = 0b10011011; // 二进制表示 light = light << 2; // 左移2位,变成0b01101100 light = light >> 1; // 右移1位,变成0b00110110
这就像把一排灯泡向左或向右移动,移动后空出来的位置用0填充。
按位取反运算符~会把每个位都翻转:0变成1,1变成0。
|unsigned char status = 0b10101010; unsigned char inverted = ~status; // 变成0b01010101
这就像把一排开关全部翻转一遍。
按位与&:只有两个位都是1时,结果才是1。就像两个开关都要打开,灯才会亮。
按位或|:只要有一个位是1,结果就是1。就像两个开关中只要有一个打开,灯就会亮。
按位异或^:两个位不同时,结果才是1。就像两个开关状态不同时,灯才会亮。
|unsigned char a = 0b11001100; unsigned char b = 0b10101010; unsigned char result1 = a & b; // 0b10001000 unsigned char result2 = a | b; // 0b11101110 unsigned char result3 = a ^ b; // 0b01100110
位运算符在实际编程中很有用。比如你想管理一个用户的权限:
|unsigned int permissions = 0; // 初始没有任何权限 // 定义各种权限 const unsigned int READ = 1; // 0001 const unsigned int WRITE = 2; // 0010 const unsigned int EXECUTE = 4; // 0100 const unsigned int DELETE = 8; // 1000
这也是linux中文件权限的实现方式。
位运算符通常用于无符号整数,因为带符号整数的符号位处理方式在不同机器上可能不同。另外,要注意不要混淆位运算符和逻辑运算符:
&和&&、|和||、~和!是不同的。
在C++中,除了算术类型的自动转换,还有很多其他类型的自动转换和手动转换。理解这些转换,就像理解不同容器之间如何倒水一样,有的可以自动倒,有的需要你亲自操作。
大多数情况下,当你用到一个数组时,C++会自动把它转换成指向第一个元素的指针。比如:
|int numbers[5] = {1, 2, 3, 4, 5}; int* p = numbers; // numbers自动变成指向第一个元素的指针
但有些特殊情况不会自动转换,比如用sizeof、&取地址、decltype推断类型时,数组不会变成指针。
有些指针可以自动转换,比如数字0或者nullptr可以自动变成任何类型的指针:
|int* p1 = nullptr; // 合法 char* p2 = 0; // 也合法
另外,任何类型的指针都可以自动转换成void*,而void*也可以转换成const void*。
C++会自动把数字或指针转换成bool类型。只要不是0(或空指针),就会变成true,否则就是false。
|int n = 10; if (n) { cout << "n不是0" << endl; } int* p = nullptr; if (!p) { cout << "p是空指针" << endl; }
你可以把一个普通变量的指针或引用自动转换成const类型的指针或引用:
|int value = 42; const int* cp = &value; // 合法 const int& ref = value; // 合法
但反过来就不行,不能把const的指针或引用转回普通类型。
有些类可以自定义自动转换规则,比如你可以让字符串字面量自动变成string类型:
|string name = "Alice"; // 字符串字面量自动转成string
输入流对象(如cin)在条件判断时也会自动转换成bool,表示上一次输入是否成功:
|string input; while (cin >> input) { // 只要输入成功就继续 }
有时候你需要手动告诉编译器“我要把这个东西变成那种类型”,这叫做强制类型转换(cast)。
比如你想让两个整数做除法时得到小数结果,可以这样:
|int a = 7, b = 2; double result = static_cast<double>(a) / b; // 结果是3.5
C++有几种常用的强制转换方式:
static_cast:安全的类型转换,比如int转double,或者void*转回原来的指针类型。const_cast:用来去掉const属性,但要非常小心,不能随便修改原本是const的数据。reinterpret_cast:直接“重新解释”内存内容,非常危险,只有在你完全明白自己在做什么时才用。比如:
|const char* text = "hello"; char* modifiable = const_cast<char*>(text); // 小心使用 int num = 1234; void* vp = # int* np = static_cast<int*>(vp); // void*转回int*
在C++中,sizeof运算符可以帮你测量一个类型或变量在内存中占用了多少字节。你可以把它想象成一个“电子秤”,用来称一称你的变量到底有多大。
比如你想知道一个int类型变量占多少字节:
|int age = 20; cout << sizeof(age) << endl; // 输出4(假设int是4字节)
你也可以直接测量类型:
|cout << sizeof(double) << endl; // 输出8(假设double是8字节)
这在写跨平台代码时特别有用,因为不同电脑上同一个类型的大小可能不一样。
逗号运算符(,)可以让你在一个表达式里依次做多件事,最后只保留最后一个表达式的结果。你可以把它想象成做饭时的步骤:先洗菜,再切菜,最后炒菜,只有炒菜的结果才是最终的“菜”。
比如:
|int a = 1, b = 2; int result = (a += 3, b *= 2, a + b); // 先a加3,再b乘2,最后a+b作为结果 cout << result << endl; // 输出8(a=4, b=4, a+b=8)
逗号运算符常见于for循环里,比如:
|for (int i = 0, j = 10; i < j; ++i, --j) { // i从0递增,j从10递减 }
这里每次循环,i加1,j减1。
现在让我们通过一些简单的练习题来巩固本章学到的知识。每道题都涵盖了运算符和表达式的核心概念,请先尝试独立完成,然后再查看答案。
编写一个程序,计算并输出以下表达式的值:
15 / 4 和 15.0 / 43 * 4 + 2 和 3 * (4 + 2)观察它们的结果有什么不同,并解释原因。
|#include <iostream> using namespace std; int main() { cout << "15 / 4 = " << 15 / 4 << endl; // 输出:3(整数除法) cout << "15.0 / 4 = " << 15.0 / 4 << endl; // 输出:3.75(浮点数除法) cout << "3 * 4 + 2 = " << 3 * 4 + 2 << endl; // 输出:14(先乘后加)
编写一个程序,读取用户输入的一个年份,判断它是否为闰年。闰年的规则是:能被4整除但不能被100整除,或者能被400整除。
|#include <iostream> using namespace std; int main() { int year; cout << "请输入一个年份:"; cin >> year; // 判断是否为闰年 bool isLeapYear = (year % 4 == 0 && year % 100 != 0) || (year % 400 ==
分析下面代码的输出结果,并解释 x++ 和 ++x 的区别:
|int x = 5; cout << x++ << endl; // 输出什么? cout << x << endl; // 输出什么? cout << ++x << endl; // 输出什么?
|#include <iostream> using namespace std; int main() { int x = 5; cout << x++ << endl; // 输出:5(先使用x的值,再自增) cout << x << endl; // 输出:6(x已经自增为6) cout << ++x << endl; // 输出:7(先自增,再使用x的值) return 0; }
编写一个程序,读取用户输入的一个分数(0-100),使用条件运算符判断并输出对应的等级:
|#include <iostream> using namespace std; int main() { int score; cout << "请输入分数(0-100):"; cin >> score; // 使用条件运算符判断等级 char grade = (score >= 90) ? 'A' : (score >= 80) ? 'B' : (score
编写一个简单的计算器程序,读取两个数字和一个运算符(+、-、*、/),然后计算并输出结果。注意处理除零的情况。
|#include <iostream> using namespace std; int main() { double num1, num2; char op; cout << "请输入第一个数字:"; cin >> num1; cout << "请输入运算符(+、-、*、/):"; cin >> op; cout << "请输入第二个数字:"; cin >> num2; double
整数除法会截断小数部分,而浮点数除法会保留小数。运算符优先级决定了表达式的计算顺序:乘除优先于加减,括号可以改变运算顺序。
这道题综合运用了逻辑运算符 &&(与)和 ||(或),以及取余运算符 %。条件表达式的逻辑是:要么(能被4整除且不能被100整除),要么能被400整除。
x++ 是后置自增,先使用变量的值,然后再自增;++x 是前置自增,先自增,然后再使用变量的值。两者的区别在于返回的值不同,但都会让变量增加1。
条件运算符 ? : 的语法是 条件 ? 值1 : 值2,如果条件为真返回值1,否则返回值2。可以嵌套使用来处理多个条件。这种方式比 if-else 更简洁,但要注意可读性。
这个程序综合运用了输入输出、条件判断、switch语句和错误处理。使用 double 类型支持小数运算,在除法运算前检查除数是否为0,避免程序崩溃。