赋值,表达式和打印

赋值语句

赋值语句建立对象引用值

变量名在首次赋值时会被创建

变量名在引用前必须先赋值

执行隐式赋值的一些操作:显示赋值由“=”连接,隐式赋值如:模块导入,函数和类的定义,for循环变量以及函数参数全都是隐式赋值运算。因为赋值语句在任何出现的地方的工作原理都相同,所有这些环境都是在运行时把变量名和对象的引用值绑定起来而已

 

赋值语句的形式
运算解释
spam = 'Spam'基本形式
spam, ham = 'Spam', 'Ham'元组赋值运算(位置性)
[spam, ham] = ['yum', 'YUM']列表赋值运算(位置性)
a, b, c, d = 'spam'序列赋值运算,通用性
a, *b = 'spam'扩展的序列解包
spam = ham = 'lunch'多目标赋值运算
a += 43增强赋值运算,等价于a=a+43

增强赋值形式输入较少,而且通常执行的更快。在python中,每个二元表达式运算符都有增强赋值语句(+=,*=,**=,&= ...)。

序列赋值语句实际上支持右侧任何可迭代对象:

[a, b, c] = (1, 2, 3)
(a, b, c) = "ABC"
a, b, c = range(3)

扩展序列解包:

>>> seq = [1, 2, 3, 4]
>>> a, b, c, d = seq
>>> print(a, b, c, d)
1 2 3 4
>>> a, b = seq
Traceback (most recent call last):
  File "<pyshell#8>", line 1, in <module>
    a, b = seq
ValueError: too many values to unpack (expected 2)
>>> a, *b = seq
>>> a
1
>>> b
[2, 3, 4]
>>> *a, b = seq
>>> a
[1, 2, 3]
>>> b
4
>>> a, *b, c = seq
>>> b
[2, 3]

序列解包VS分片

>>> a, *b, c = 'spam'
>>> a, b, c
('s', ['p', 'a'], 'm')
>>> S = 'spam'
>>> S[0], S[1:3], S[3]
('s', 'pa', 'm')

相似但不完全相同,一个序列解包赋值总是返回多个匹配项的一个列表,而分片把相同类型的一个序列作为分片的对象返回。

边界情况:

尽管扩展的序列解包很灵活,一些边界情况还是要注意。

带星号的名称可能只匹配单个的项,都是总是会向其赋值一个列表

如果没有剩下的内容可以匹配嗲星号的名称,它会赋值一个空的列表

>>> seq
[1, 2, 3, 4]
>>> a, b, c, *d = seq
>>> a, b, c, d
(1, 2, 3, [4])
>>> a, b, c, d, *e = seq
>>> a, b, c, d, e
(1, 2, 3, 4, [])

注意如下几种情况:

>>> a, *b, c, *d =seq    #多个星号
SyntaxError: two starred expressions in assignment
>>> a, b = seq    #值少了而没有星号
Traceback (most recent call last):
  File "<pyshell#37>", line 1, in <module>
    a, b = seq
ValueError: too many values to unpack (expected 2)
>>> *a = seq    #带星号的名称紫色没有编写到一个列表中
SyntaxError: starred assignment target must be in a list or tuple
>>> *a, = seq    #这个ok
>>> a
[1, 2, 3, 4]

 

多目标赋值语句:

a = b = c = 'spam'

多目标赋值以及共享引用:

注意,这里只有一个对象,由三个变量共享(全都指向内存同一对象)。这种行为对于不可变类型没问题,若是可变对象,那么一处修改,三处共享结果。

 

变量命名规则:

语法:(下划线或字母)+(任意数目的字母,数字或下划线)

区分大小写

禁止使用保留字

Python3中的保留字
Falseclassfinallyisreturn
Nonecontinuefor lambdatry
Truedeffromnonlocalwhile
anddelglobalnotwith
aselififor yield
assertelseimportpassbreak
exceptinraise  

此外,因为import语句中的模块变量名会变成脚本的变量,这种限制也会扩展到模块的文件名:你可以写and.py等文件,但是你无法将他们导入,因为其变量名没有.py扩展名时,就会变成代码中的变量。

 

命名惯例‘

变量名前后有下划线时,如__name__,通常对解释器有特殊意义,你应该避免使用这种样式。

以下是Python遵循的一些惯例:

以单一下划线开头的变量(_x)不会被from modle import * 语句导入

前后有下划线的变量名是系统定义的变量名

以双下划线开头,结尾没下划线的变量名,是类的本地变量

其他惯例:

类变量名通常以一个大写字母开头,模块变量名以小写字母开头。self虽非保留字,但在类中一般有特殊的角色。内置变量名,这些是预先定义的变量名,但并非保留字(所以可以重新赋值 open = 42行得通,但是这样做之后可能不能打开文件了)

变量名没有类型,它只是对象的引用值,类型是跟对象绑定在一起的。

 

表达式语句:

常见Python表达式语句
运算解释
spam(eggs, ham)函数调用
spam.ham(eggs)方法调用
spam在交互模式解释器内打印变量
print(a, b, c, sep=' ')打印
yield x**2产生表达式的语句

Python工作中常犯的错误,表达式语句通常用于执行可于原处修改列表的列表方法:

>>> ll = [1,2]
>>> ll.append(3)
>>> ll
[1, 2, 3]
>>> ll = ll.append(4)    #append return None
>>> print(ll)
None

 

打印

写入文件的文件对象方法(如file.write(str))。打印操作类似,但更加专注--文件写入方法是把字符串写入任意的文件,print默认把对象打印到stdout流,添加了一下自动的格式化,不需要把对象转换为字符串。

print([object,...][, sep=' '][, end='\n'][, file=sys.stdout])

sep, end, file部分如果给出的话,必须作为关键字参数给定。

print(a, b, c, sep='...', end='--', file=open('data.txt', 'w')

这里打印的过程中,把文本打印到一个输出文件或者其他的可兼容对象(其他是流定向的一种形式)

 

Learning Python, Fourth Edition, by Mark Lutz.

<think>嗯,用户这次想深入了解C语言中赋值表达式的返回值特性。从引用材料来看,用户提供了两个关键知识点:一是赋值表达式返回左值引用,二是容易忽视表达式返回值可能导致的条件判断陷阱。用户可能正在学习C语言的基础语法,特别是表达式求值相关的细节。这类问题通常出现在理解运算符优先级或调试诡异行为时——比如用户代码里那个经典的(a*=2)(a+=3)乱序打印问题。ta应该是个细心的学习者,不然不会注意到这种语言底层特性。材料中的示例程序特别值得分析:那个printf参数表的执行顺序与赋值返回值密切相关。在C语言中,函数参数求值顺序是未定义的,但每个赋值表达式确实会返回左值引用。这意味着当(a*=2)(a+=3)在参数表里乱序执行时,修改的是同一个a的内存地址,但返回值机制让它们各自“假装”返回了新的值。安全方面要注意提醒用户赋值与等于混淆的经典错误。有些静态代码检查工具(如Lint)会专门标记if(x=5)这种写法,正是因为这个特性太容易导致bug。需要特别强调返回值是左值引用这点。它解释了为什么能写出a=b=c这样的链式赋值——因为(b=c)返回的就是被赋值的b,这个结果又作为下一个赋值运算符的左操作数。这跟数学里的等号传递性完全不是同一个概念。用户给出的逻辑表达式材料其实展示了另一个角度:C语言把非零都视为真。这可以赋值返回值联动,解释为什么if(a=10)能成立——因为10作为返回值被解释为真值。这点在Python里就会报错,因为Python不允许赋值在if内使用。</think> 好的,我们来详细解释一下C语言中**赋值表达式**的**返回值特性**。这是一个非常重要但容易被初学者忽略的特性,对理解代码行为(特别是涉及赋值与比较混合的表达式)非常关键。 ## 核心特性解释 1. **赋值表达式本身有值:** 在C语言中,赋值语句(如 `a = 5;`)不仅仅是执行“把右边的值存储到左边变量”这个动作。 2. **返回值的类型与内容:** * 一个赋值表达式的**值**,就是这个赋值操作完成后**被赋值变量(左值)的值**。 * 这个表达式的**类型**,与被赋值变量的类型一致。 3. **返回值是“左值引用”:** (引用[1]提到的核心概念) 更深层次一点说,赋值表达式的返回值**不仅仅是一个值**,它实际上是一个指向那个被赋值变量的**引用**(或者说一个可以代表那个变量位置的表达式,即“左值”)。这使得赋值表达式可以继续用于其他操作(比如做进一步的赋值操作符的左操作数)。 ### 这个特性带来的行为解释 1. **链式赋值 (Chained Assignment):** ```c a = b = c = 10; ``` * `c = 10` 这个表达式首先执行:将10赋值给`c`。 * `c = 10` 这个表达式的**值**是赋值后的c的值,也就是10(而且这个值本质上是`c`的引用)。 * 然后`b = (c = 10)` 的执行等价于 `b = 10`(使用了上一个赋值表达式的值)。 * 同理,`a = (b = 10)` 等价于 `a = 10`。 * 最终 `a`、`b`、`c` 的值都是10。 2. **在条件语句中使用赋值:** (参考[2]提到的陷阱) ```c int num; if (num = 0) { // 不会执行这里的代码 } if (num = 10) { // 会执行这里的代码 } ``` * `if (num = 0)`: 执行 `num = 0`。这个赋值表达式的**值**就是赋值后的`num`的值,也就是 **0**。在C语言中,条件语句把 **0** 解释为 **假(false)**,所以条件不成立。 * `if (num = 10)`: 执行 `num = 10`。这个赋值表达式的**值**就是赋值后的`num`的值,也就是 **10**。在C语言中,**任何非零值**都被解释为 **真(true)**,所以条件成立。 3. **在函数参数中使用赋值:** (理解引用[1]中的示例) ```c #include <stdio.h> int main() { int a = 1; printf("%d, %d, a = %d\n", (a *= 2), (a += 3), a); return 0; } ``` * **关键点:** `printf`函数的参数**计算顺序在C语言标准中未定义**(由编译器决定),但`a`在函数调用前后是同一个变量。 * **赋值表达式返回值的体现:** * `a *= 2` 相当于 `a = a * 2`。执行后,`a`的值变为 **2**。这个表达式的**值**就是新的`a`的值 **2**。 * `a += 3` 相当于 `a = a + 3`。执行后,`a`的值变为 **5**。这个表达式的**值**就是新的`a`的值 **5**。 * 第三个参数 `a` 就是当前 `a` 的值(可能受前面计算顺序影响)。 * **输出不确定性的根源:** 因为参数计算顺序未定义,编译器可以先算 `(a += 3)` 再算 `(a *= 2)`,或者先算 `(a *= 2)` 再算 `(a += 3)`,或者交叉进行。这会导致不同编译器下: * `(a *= 2)` 的值可能是 **2** 或 **8** (取决于它是否在 `a += 3` 之后计算)。 * `(a += 3)` 的值可能是 **5** 或 **4** (取决于它是否在 `a *= 2` 之后计算)。 * 最后打印的 `a` 值都是 **5**。 * **常见的输出可能 (如某些编译器):** `8, 5, a = 5` 或者 `2, 5, a = 5` 或 `5, 10, a = 5` (绝对不可能!看下面解释)。 * **为什么不可能出现 `5, 10, a = 5`?** 因为计算 `(a += 3)` *总是* 会将 `a` 增加 3(无论其他参数怎么算),计算 `(a *= 2)` *总是* 会将 `a` 乘以 2(无论其他参数怎么算)。最后 `a` 变成 5 (`1 -> *2 -> 2 -> +3 -> 5` 或 `1 -> +3 -> 4 -> *2 -> 8`?矛盾!)。实际上,**两次赋值操作都实际发生了**,并且都改变了同一个 `a`,所以无论顺序如何,执行完这两个赋值操作后,`a` 的最终值一定是表达式链计算的结果(如果是 `a *= 2` 然后 `a += 3` 结果是5;如果是 `a += 3` 然后 `a *= 2` 结果是8)。所以输出可能: * `2, 5, a = 5` (顺序:`a *= 2` -> `a += 3` -> `printf`) * `8, 4, a = 8` (顺序:`a += 3` -> `a *= 2` -> `printf`,注意此时第二个参数`a += 3`的值是4,因为它完成后`a`是4) (此输出结果较少见但合法) * `8, 5, a = 5` (顺序复杂,比如先计算`a += 3`但未写回?但C语言语义通常认为是立即写回的,这种输出不太符合标准语义,但标准未定义顺序,理论上编译器可能产生奇怪行为)。 ### 重要结论与警告 1. **`=` 是运算符:** 赋值操作符 `=` 是一个**运算符**(优先级仅高于逗号运算符)。因此它会产生一个值。 2. **与比较运算符 `==` 的区别:** 这是最常见的错误来源之一。赋值 `=` 比较 `==` 是**完全不同的运算符**。 * `a = b`: 把 `b` 的值赋给 `a`,表达式的**值**是 `a`(赋值后的值)。 * `a == b`: 比较 `a` `b` 是否相等,表达式的**值**是 `1`(真)或 `0`(假)。 3. **易错点与陷阱:** * **条件判断中的赋值错误:** 极容易因为手误或思考失误写成 `if (x = 5)` 而不是 `if (x == 5)`。前者的意思是:将5赋给`x`,然后检查`x`的值(此时是5,非零,条件为真),这不是开发者想要做的比较操作!**大多数编译器会对此发出警告(Warning)**,强烈建议不要忽略这些警告。 * **参数求值顺序:** 如引用[1]中的`printf`例子所示,在函数参数中使用有副作用的表达式(如赋值、`++`, `--`)其结果依赖于未定义的求值顺序,这样的代码是**高度不可移植难以预测的**,应**极力避免**。 4. **应用场景:** * **简洁的链式赋值:** `a = b = c = value;` * **在某些循环条件中进行赋值与检查:** `while ((ch = getchar()) != EOF) { ... }`。这里利用了`getchar()`的返回值被赋值给`ch`,然后立即检查`ch`是否不等于`EOF`的特性。这在C语言中是很常见且被接受的惯用法(前提是清晰理解其行为)。 ## 总结 C语言中赋值表达式(`=`, `+=`, `-=`, `*=`, `/=` 等复合赋值)的核心返回值特性是:**赋值表达式的值是赋值操作完成之后被赋值变量(左值)的值(本质上是左值的引用)。** 理解这个特性是理解链式赋值、条件语句中常见的赋值误用陷阱、函数调用中有副作用参数行为的基础。在条件判断中区分 `=` `==` 是避免严重逻辑错误的关键。编写可移植、清晰的代码时,应当避免在有副作用表达式的顺序未定义的地方(如函数参数)使用它们。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值