历史上
在最早的通用编程语言中,如FORTRAN,这种区别是非常清楚的。在FORTRAN中,一个语句是一个执行单位,是你做的一件事。它不被称为 "行 "的唯一原因是有时它跨越了很多行。一个表达式本身不能做任何事情,你必须把它分配给一个变量。在FORTRAN中表达式是一个错误,因为它不做任何事情。你必须对这个表达式做一些事情。
FORTRAN并没有我们今天所知的语法,这个语法是和Backus-Naur Form(BNF)一起发明的,是Algol-60定义的一部分。当时,语义上的区别("有一个值 "与 "做某事")被载入了语法中:一种是表达式,另一种是语句,解析器可以将它们区分开来。
后来语言的设计者模糊了这种区别:他们允许表达式做事情,也允许语句有一个值。C语言的设计者意识到,如果允许你评估一个表达式并丢弃其结果,就不会造成任何伤害。在C语言中,每一个语法表达式都可以通过在结尾处加一个分号而成为一个语句。
表达式和语句的这种模糊性出现在所有的C语言衍生语言中(C、C++、C#和Java),它们仍然有一些语句(如while),但几乎允许任何表达式作为语句使用。
这两个 "语法类别" 会导致重复劳动。例如,C语言有两种形式的条件,if语句和?:表达式。
有时,人们希望有这样的重复:例如,在标准的C语言中,只有语句可以声明一个新的局部变量,但这种能力非常有用,GNU C编译器提供了一个GNU扩展(语句表达式),使表达式也可以声明一个局部变量。
而其他语言的设计者不喜欢这种重复,他们很早就看到,如果表达式可以有副作用,也可以有值,那么语句和表达式之间的语法区别就不是那么有用了,所以他们把它去掉了。Haskell、Icon、Lisp和ML都是没有语句的语言,它们只有表达式。即使是类似结构的循环和条件形式也被认为是表达式,而且它们有值。
一些区别描述
- 表达式会进行计算产生一个结果(结果的三个方面:值、类型和值类别),而语句不会。
- 表达式可以在语句中使用,但不能反过来使用。不过,一个例外是lambda表达式,它表示一个函数,因此可以包含函数可以包含的任何内容,除非该语言只允许有限的lambdas。
- 两种类型都可以组合,但只要类型匹配,大多数表达式都可以任意组合。每种类型的语句都有自己的组合其他语句的方式。表达式可以使用操作符“水平地”组合成更大的表达式,而语句只能通过一个接一个地编写或使用块结构来“垂直地”组合。
- 命令式语言(Fortran, C, Java,…)强调语句来构造程序,而表达式则是一种事后思考。函数式语言强调表达式。纯函数式语言具有比语句更强大的表达式。
- 一个例子
#include <iostream>
int main() {
int x;
x = 2, 3;
std::cout << x << std::endl;
return 0;
}
结果:2
#include <iostream>
int f(){ return 2, 3; }
int main() {
int x;
x = f();
std::cout << x << std::endl;
return 0;
}
结果:3
分析如下:
x=2,3是一个表达式,表达式中逗号操作符“,”的优先级低于赋值运算符“=”。所以这个表达式语句相当于(x=2),3;
return 2, 3;是返回语句,return不属于表达式,只有2,3为表达式。逗号操作符是从左到右计算,所以2,3表达式结算结果是3。
Can programming be liberated from the von Neumann style?: a functional style and its algebra of programs(编程能从冯·诺依曼风格中解放出来吗?:一种函数式风格及其程序代数)
FP,是John Backus创立的支持函数级编程范式的编程语言。它允许消去命名变量。这种语言是在Backus的1977年图灵奖获奖演讲论文《编程可以从冯诺依曼风格中解放出来吗?程序的函数式风格及其代数》中提出的。这篇论文点燃了对函数式语言研究的兴趣,最终导致了现代函数式语言,但不是Backus曾希望的函数级范式。
在他的这篇图灵奖论文中,Backus描述了FP风格与基于lambda演算的语言有着如何不同:
FP系统基于了对叫做泛函形式的一组固定的组合形式的利用。它们加上简单的定义,就是从现存函数建造新函数的唯一方式;它们不使用变量或替代规则,并且它们成为程序相关的代数的运算操作。FP系统的所有函数都是一种类型的:它们映射对象到对象之上并总是接受一个单一实际参数。
FP自身在学术界之外从未被大量使用。在1980年代,Backus创建了后继语言FL,它也保持为研究项目。
冯诺依曼风格是一种语句与表达式的编程风格,而函数式编程风格是建立在使用组合形式创建程序的基础上。
C++表达式
表达式(expression)是运算符和它们的操作数的序列,它指定一项计算。表达式的求值可以产生一个结果(比如 2+2 的求值产生结果 4),也可能产生副作用(比如i = 5; i++;的副作用i变成6)。
常见运算符 | ||||||
赋值 | 自增 | 算术 | 逻辑 | 比较 | 成员访问 | 其他 |
a = b | ++a | +a | !a | a == b | a[b] | a(...) |
特殊运算符 | ||||||
static_cast 转换一个类型为另一相关类型 |
初等表达式:任何运算符的操作数都可以是其他的表达式或初等表达式。
初等表达式包括以下各项:
- 字面量(例如 2 或 "Hello, world")
- 标识表达式,包括经过适当声明的无限定的标识符(例如 n 或 cout)或者经过适当声明的有限定的标识符(例如 std::string::npos)
- lambda 表达式 (C++11)
- 折叠表达式 (C++17)
- requires 表达式 (C++20)
- 括号中的任何表达式也被归类为初等表达式
常量表达式:定义能在编译时求值的表达式。这种表达式能用做非类型模板实参、数组大小,并用于其他要求常量表达式的语境(上下文)。
不求值表达式:运算符 typeid、sizeof、noexcept 、 decltype (C++11 起)和requires 表达式 的操作数是不求值表达式,因为这些运算符仅查询其操作数的编译期性质。因此,std::size_t n = sizeof(std::cout << 42); 不进行控制台输出。不求值的运算数被当做完整表达式 (C++14 起),所以要符合表达式的一些要求。
弃值表达式:弃值表达式是仅用来实施其副作用的表达式。从这种表达式计算的值被舍弃。这样的表达式包括任何表达式语句的完整表达式,内建逗号运算符的左边的实参,以及转型到类型 void 的转型表达式的实参。运算符delete是弃值表达式,因其返回值是void类型。
求值顺序:求值任何表达式的任何部分,包括求值函数参数的顺序都是未说明的(除了一些例外)。编译器能以任何顺序求值任何操作数和其他子表达式,并且可以在再次求值同一表达式时选择另一顺序。
#include <cstdio>
int a() { return std::puts("a"); }
int b() { return std::puts("b"); }
int c() { return std::puts("c"); }
void z(int, int, int) {}
int main() {
z(a(), b(), c()); // 允许全部 6 种输出排列
return a() + b() + c(); // 允许全部 6 种输出排列
}
可能的输出:
b
c
a
c
a
b
优先级和结合性:优先级 指定包含多个运算符的表达式中的运算顺序。 结合性指定是否在包含多个具有相同优先级的运算符的表达式中,将操作数分组在其左侧或右侧的一个(a=b=c)。
结合性规定对于一元运算符是冗余的,只为完备而给出:一元前缀运算符始终从右到左结合(delete ++*p 为 delete(++(*p)))而一元后缀运算符始终从左到右结合(a[1][2]++ 为 ((a[1])[2])++)。
优先级和结合性是编译时概念,与求值顺序无关,后者是运行时概念。
优先级 | 运算符 | 描述 | 结合性 |
1 | :: | 作用域解析 | 从左到右 |
2 | a++ a-- | 后缀自增与自减 | |
type() type{} | 函数风格转型 | ||
a() | 函数调用 | ||
a[] | 下标 | ||
. -> | 成员访问 | ||
3 | ++a --a | 前缀自增与自减 | 从右到左 |
+a -a | 一元加与减 | ||
! ~ | 逻辑非和逐位非 | ||
(type) | C 风格转型 | ||
*a | 间接(解引用) | ||
&a | 取址 | ||
sizeof | 取大小[注 1] | ||
co_await | await 表达式 (C++20) | ||
new new[] | 动态内存分配 | ||
delete delete[] | 动态内存分配 | ||
4 | .* ->* | 成员指针 | 从左到右 |
5 | a*b a/b a%b | 乘法、除法与余数 | |
6 | a+b a-b | 加法与减法 | |
7 | << >> | 逐位左移与右移 | |
8 | <=> | 三路比较运算符(C++20 起) | |
9 | < <= | 分别为 < 与 ≤ 的关系运算符 | |
> >= | 分别为 > 与 ≥ 的关系运算符 | ||
10 | == != | 分别为 = 与 ≠ 的相等性运算符 | |
11 | a&b | 逐位与 | |
12 | ^ | 逐位异或(互斥或) | |
13 | | | 逐位或(可兼或) | |
14 | && | 逻辑与 | |
15 | || | 逻辑或 | |
16 | a?b:c | 三元条件[注 2] | 从右到左 |
throw | throw 运算符 | ||
co_yield | yield 表达式 (C++20) | ||
= | 直接赋值(C++ 类默认提供) | ||
+= -= | 以和及差复合赋值 | ||
*= /= %= | 以积、商及余数复合赋值 | ||
<<= >>= | 以逐位左移及右移复合赋值 | ||
&= ^= |= | 以逐位与、异或及或复合赋值 | ||
17 | , | 逗号 | 从左到右 |
C++语句
语句(statement)是依序执行的 C++ 程序片段。任何函数体都是语句的序列。
类型:
- 表达式语句(expression statement);
- 复合语句(compound statement);
- 选择语句(selection statement);
- 循环语句(iteration statement);
- 跳转语句(jump statement);
- 声明语句(declaration statement);
- try 块;
- atomic 与 synchronized 块(TM TS)。