C 语言基础 2 - 运算与输入输出
一、 引言
C 语言是一种功能强大、灵活多变的编译型语言,广泛应用于系统编程、嵌入式开发等领域。运算与选择结构是 C 语言编程的基础,它们构成了程序逻辑的核心。
C 语言是一种编译型语言,从创建 C 程序到运行出最终结果,包括:编辑、编译、链接和运行四个过程。
1. 程序设计语言与自然语言的对比
- 自然语言:
- 种类:世界上有5651种语言,汉语、英语是主流。
- 特点:
- 汉语:象形文字,二维空间表达(上下左右),笔画组成一个字,二义性较强,需要标点符号分割句子。
- 英语:拼音文字,一维空间表达(从左到右),句与句之间用标点符号分割,单词含义人为定义。
- 程序设计语言:
- 目的:表达机械化可执行的算法。
- 范围:程序设计语言可以描述的范围,远远小于自然语言的描述范畴。
- 特点:上下文无关,机器仅关注当前语句的语义并按此执行。需要严格的词法、语法要求。
2. 程序设计语言的基本要素
- 基本语句:
- 赋值语句:将表达式的计算结果放到某个变量里。
- 判断(选择)语句:根据表达式的真假决定执行后续的哪个语句。
- 循环语句:根据表达式的真假决定是否循环执行某些语句。
3. 程序设计过程
- 转换:把自然语言描述的算法转变为程序语言的语义、语法和词法,让机器能够无歧义地解释和执行。
- 程序员需知:
- 理解自然语言与程序语言的差别。
- 熟悉所用编程语言的词法、语法和语义。
- 使用程序语言的词法、语法和语义表达自然语言的思想。
- 辅助工具:流程图,用于图形化表达算法,更好的完成自然语言到程序语言的转换。
4. 保留字与标识符
- 保留字:
- 定义:编程时不能用于其他用途的字,如C语言中的
int
、float
等。 - 作用:防止误用,导致程序出错。
- C语言中的保留字(也称为关键字)是语言本身定义的一些具有特殊意义的标识符,不能用作变量名、函数名或任何其他标识符。以下是C语言标准(C99和C11)中的保留字列表:
- 定义:编程时不能用于其他用途的字,如C语言中的
保留字 | 用途 |
---|---|
auto | 存储类说明符,用于声明自动变量(通常省略,因为默认就是auto) |
break | 跳转语句,用于跳出循环或switch语句 |
case | switch语句中的分支标签 |
char | 数据类型,用于声明字符类型的变量或数组 |
const | 类型修饰符,用于声明常量(只读变量) |
continue | 跳转语句,用于跳过循环中的剩余部分,直接进入下一次迭代 |
default | switch语句中的默认分支标签 |
do | 循环控制语句,用于构成do-while循环 |
double | 数据类型,用于声明双精度浮点类型的变量或数组 |
else | 条件语句的一部分,用于在if语句条件不满足时执行 |
enum | 枚举类型声明 |
extern | 存储类说明符,用于声明在其他文件中定义的变量或函数 |
float | 数据类型,用于声明单精度浮点类型的变量或数组 |
for | 循环控制语句,用于构成for循环 |
goto | 跳转语句,用于无条件跳转到程序中标记的位置 |
if | 条件语句,用于根据条件执行不同的代码块 |
inline | 函数说明符,用于建议编译器将函数内联扩展(在某些实现中可能忽略) |
int | 数据类型,用于声明整型变量或数组 |
long | 数据类型,用于声明长整型变量或数组(也可以与int组合为long int) |
register | 存储类说明符,用于建议编译器将变量存储在寄存器中(在某些实现中可能忽略) |
restrict | 类型修饰符,用于限制指针(C99引入) |
return | 函数返回语句,用于从函数返回一个值 |
short | 数据类型,用于声明短整型变量或数组(也可以与int组合为short int) |
signed | 类型修饰符,用于声明有符号类型的整数(默认情况) |
sizeof | 运算符,用于获取类型或变量的大小(以字节为单位) |
static | 存储类说明符,用于声明静态变量或函数 |
struct | 结构体类型声明 |
switch | 条件语句,用于根据表达式的值选择多个代码块中的一个来执行 |
typedef | 类型定义,用于为数据类型定义新的名称(别名) |
union | 联合类型声明 |
unsigned | 类型修饰符,用于声明无符号类型的整数 |
void | 数据类型,用于声明不返回值的函数或表示空类型(例如void*) |
volatile | 类型修饰符,用于声明可能会被意外修改的变量 |
while | 循环控制语句,用于构成while循环 |
_Bool | 数据类型,用于声明布尔类型的变量(C99引入,等价于bool,但为C++保留) |
_Complex | 数据类型,用于声明复数的变量(C99引入) |
_Imaginary | 数据类型,用于声明虚数的变量(C99引入) |
_Generic | 类型选择运算符(C11引入) |
_Noreturn | 函数说明符,用于声明不返回的函数(C11引入) |
_Static_assert | 静态断言(C11引入) |
_Thread_local | 存储类说明符,用于声明线程局部存储的变量(C11引入) |
注意:
_Bool
、_Complex
、_Imaginary
、_Generic
、_Noreturn
、_Static_assert
和_Thread_local
这些保留字在C90标准中并不存在,是C99或C11标准中引入的。- 在实际编程中,通常避免使用带有下划线前缀的标识符,因为C标准库和编译器可能会使用这些名称。
这个列表应该涵盖了C语言标准中的所有保留字。然而,不同的编译器可能会引入额外的保留字或关键字,所以在使用特定编译器时,最好查阅其文档以获取完整的信息。
- 标识符:
- 定义:程序员在编程时自定义的符号,如变量名、常数名、函数名等。
- 命名规则:
- 一般用英文字母开头,后面可以跟字母、数字和下划线。
- 长度一般在30个字符以下。
- 要有含义,便于记忆和阅读,如
int sum;
比int a;
更易于理解。 - C语言中,大写字母和小写字母是有差别的,如
int SumofStudent;
不同于int sumofstudent;
。 - 可以用下划线“_”开始,但通常用作系统库函数的名称,因此变量和函数名尽可能避免使用。
- 不能与保留字冲突,如不能使用
int
作为变量名。 - 不要与常用的库函数中的函数名称冲突,如不要使用
printf
作为变量名。
- 示例:
- 合法的标识符:
month
- 非法的标识符:
5xy
(以数字开头),int
(保留字),your name
(包含空格)
- 合法的标识符:
5. 数据类型、常量和变量
- 数据类型:定义变量时,要指定其数据类型,如
int
、double
等。机器会根据数据类型为变量分配相应的地址和空间。 - 常量:程序中其值不能改变的量,编译系统不会为常量分配存储空间。常量通常用于给变量赋值,如
#define PI 3.14
定义了一个符号常量PI
。 - 示例代码:
#include "stdio.h"
#define PI 3.14
void main() {
double r, circum, area; // 定义3个双精度变量
r = 1.0;
circum = 2 * PI * r; // 计算圆的周长
area = PI * r * r; // 计算圆的面积
printf("circum=%f, area=%f\n", circum, area);
}
-
说明:
#define PI 3.14
是符号常量的定义,属于编译预处理命令。void main()
是主函数的定义,程序从这里开始执行。double r, circum, area;
定义了三个双精度变量。- 接下来的几行计算了圆的周长和面积,并输出结果。
二、 赋值与运算
1. 语句与表达式
C 程序由若干个函数构成,现阶段涉及的程序只包含 main()函数;
函数由语句序列构成,语句完成具体的操作。
语句按其作用和形式分为:
- 声明语句 — 非执行语句
- 表达式语句 — 表达式;
- 控制语句 — 控制程序执行流程
- 复合语句 — { }
- 空语句 — ;
示例代码:
#include <stdio.h>
int main() {
double a, b, x;
// 输入a和b的值
printf("请输入一元一次方程的系数a和常数项b(用空格分隔): ");
scanf("%lf %lf", &a, &b);
// 检查a是否为零,避免除以零的错误
if (a == 0) {
printf("输入错误,a不能为0,否则为非法一元一次方程。\n");
}
} else {
// 计算x的值
x = -b / a;
// 输出结果
printf("方程的解是: x = %.2lf\n", x);
}
return 0;
}
#include <stdio.h>
#include <math.h>
int main() {
double a, b, c, x1, x2, Delta;
// 输入部分
printf("请输入一元二次方程ax^2+bx+c=0的三个系数:\n");
printf("请输入系数 a = \t");
scanf("%lf", &a);
printf("请输入系数 b = \t");
scanf("%lf", &b);
printf("请输入系数 c = \t");
scanf("%lf", &c);
Delta = b * b - 4 * a * c;
printf("a=%.2f,\nb=%.2f,\nc=%.2f\n", a, b, c);
if (fabs(a) > 1e-9) // 即a!=0,使用一个小数来避免浮点数比较的问题
{
if (Delta < 0)
{
printf("方程有没有实根!\n");
}
else if (Delta == 0)
{
x1 = x2 = -b / (2 * a);
printf("方程有两个相等的根:\nx1 = x2 = %.2f\n", x1);
}
else
{
x1 = (-b + sqrt(Delta)) / (2 * a);
x2 = (-b - sqrt(Delta)) / (2 * a);
printf("方程的两个实根分别为\nx1 = %.2f,\nx2 = %.2f\n", x1, x2);
}
}
else if (fabs(b) > 1e-9) // 即b!= 0 ,使用小数来避免浮点数比较的问题
{
x1 = -c / b;
printf("方程是一元一次方程,x1 = -c/b = %.2f\n", x1);
}
else if (fabs(c) > 1e-9) // 即c!= 0 ,使用小数来避免浮点数比较的问题
{
printf("方程无解,因为0 * x^2 + 0 * x+%.2f=0不成立。\n", c);
}
else
{
printf("x可以是任意值,因为方程是0=0。\n");
}
return 0;
}
2. 运算符与表达式
算术运算符:
- 如:
+
、-
、*
、/
、%
,用于执行基本的数学运算。注意,整数除法结果向下取整,如5/2
结果为2
。
类型 | 运算符 | 运算符号的含义 | 实例 |
---|---|---|---|
算术运算 | +、-、*、/ | 用于整数、浮点类型, | a*x-b/2-3 |
% | 求余数 | A=5; B=A%2; | |
++、-- | 变量自身 +1,或变量自身-1 | i++; j--; |
数学 | C 语言 | 运算 | 优先级 | 运算限制条件 |
---|---|---|---|---|
++ | 后置自增 | 1 | 单目、右结合、各类型的变量 | |
-- | 后置自减 | 1 | 单目、右结合、各类型的变量 | |
- | - | 取负数 | 1 | 单目、右结合、各类型的表达式 |
× | * | 乘 | 2 | 双目、左结合、各类型的表达式 |
÷ | / | 除 | 2 | 双目、左结合、各类型的表达式。两个整型数相除,结果为整型,小数被截断。 |
% | 取余数 | 2 | 双目、左结合、整型及字符型,结果为整型 | |
+ | + | 加 | 3 | 双目、左结合、各类型的表达式 |
- | - | 减 | 3 | 双目、左结合、各类型的表达式 |
关系运算符:
- 如:
=
、<=
、==
、!=
,用于比较两个操作数的值,返回布尔值(真或假)。
运算符 | 运算符号的含义 | 实例 |
---|---|---|
>、>=、<、<= | 比较 2 个变量的大小,结果为 1(真)或 0(假) | score<=60 |
==、!= | 两个变量是否相等?比较 | 注意:实数比较相等的问题 |
- =、<=、==、!=中间是没有空格的
- =、<=、!=不能写作数学形式 ≥、≤、≠
- ==是相等关系比较运算符,=是赋值运算符。n == 0; 与 n = 0;意义不同,也不相等。
逻辑运算符:
&&
(逻辑与)、||
(逻辑或)、!
(逻辑非),用于执行逻辑运算。逻辑运算符具有短路特性,即当逻辑表达式的值已经确定时,不会继续计算未涉及的操作数。
类型 | 运算符 | 运算符号的含义 | 实例 |
---|---|---|---|
逻辑与运算 | && | 逻辑与运算同为1(真)结果才为 1(真) | d>=5 && d<=10 |
逻辑或运算 | | | | 逻辑或运算一个为1(真)结果为 1(真) | |
逻辑非运算 | ! | 逻辑非运算非1(真)为0(假),非0(假)为1(真) | |
逻辑向量 | &、|、^、 | 按位进行:与、或、异或 |
| a | b | a&&b | a | | b | !a |
| :---: | :---: | :---: | :---: | :---: |
| 0(假) | 0(假) | 0(假) | 0(假) | 1(真) |
| 0(假) | 1(真) | 0(假) | 1(真) | 1(真) |
| 1(真) | 0(假) | 0(假) | 1(真) | 0(假) |
| 1(真) | 1(真) | 1(真) | 1(真) | 0(假) |
- 注:整数为 0,表示逻辑量为假,否则为真,即,非零就为真。这是 C 的优点,也是缺点!
与运算(AND) :
- 符号:
&
或∧
- 定义:当且仅当两个输入都为 True 时,输出为 True。
- 示例:
True AND True = True
True AND False = False
False AND True = False
False AND False = False
或运算(OR) :
- 符号:
|
或∨
- 定义:只要有一个输入为 True,输出就为 True。
- 示例:
True OR True = True
True OR False = True
False OR True = True
False OR False = False
非运算(NOT) :
- 符号:
¬
或!
- 定义:将输入取反,即如果输入为 True,输出为 False;如果输入为 False,输出为 True。
- 示例:
NOT True = False
NOT False = True
异或运算(XOR) :
- 符号:
^
或⊕
- 定义:当且仅当两个输入不同(一个为 True,另一个为 False)时,输出为 True。
- 示例:
True XOR True = False
True XOR False = True
False XOR True = True
False XOR False = False
同或运算(XNOR) :
- 符号:
⊙
或≡
(在一些文献中,XNOR 也可以表示为!XOR
或XOR NOT
) - 定义:当且仅当两个输入相同时(都为 True 或都为 False),输出为 True。
- 示例:
True XNOR True = True
True XNOR False = False
False XNOR True = False
False XNOR False = True
这些逻辑运算在计算机科学、电子工程和数学等领域有着广泛的应用。例如,在计算机编程中,它们用于控制结构(如条件语句和循环)、数据验证和位操作等。在数字电路中,它们通过逻辑门(如 AND 门、OR 门、NOT 门等)实现,以构建复杂的电路系统。
逻辑运算体系
逻辑运算也遵循一定的运算规律。
例如:
结合律(Associativity):
x | ( y | z ) = ( x | y ) | z ,以及 x & ( y & z ) = ( x & y ) & z
交换律(Commutativity):
x | y = y | x ,以及 x & y = y & x
分配律(Distributivity):
x & ( y | z ) = ( x & y ) | ( x & z )
恒等律(Identity):
x | 0 = x ,x & 1 = x
归零律(Annihilator):
x & 0= 0
逻辑运算最小体系
第四个(异或)定义是多余的,可以用前三个定义,推导出来,例如:
- A⊕B= (A|B)\&(\sim A | \sim B)
- A⊕B= \sim(\sim A\&\sim B)\&\sim (A \& B)
- A⊕B= (A\&\sim B)|(\sim A \& B)
进一步,与、或、非是否可以归纳为两个运算呢?
例如,或运算可以归纳为非和与两种运算,
- 事实上, A|B= \sim (\sim A \& \sim B) 。
- 同样, A\&B = \sim (\sim A|\sim B) 。
逻辑计算中只需要两个符号,(非、与),或(非、或),这是逻辑运算的最小体系
证明:逻辑计算中只需要两个运算符
为了证明在逻辑计算中只需要两个运算符(例如“非”和“与”,或者“非”和“或”)就能完成所有逻辑运算,我们需要展示如何使用这两个运算符来模拟其他逻辑运算符(如“或”、“异或”等)。
- 使用“非”和“与”
- 模拟“或”运算: 给定两个变量A和B,要计算A或B(A OR B),我们可以使用德摩根定律(De Morgan's Laws)的逆用。德摩根定律告诉我们:
\neg(A \land B) = \neg A \lor \neg B 但我们需要A OR B,所以我们可以稍作变换:
A \lor B = \neg(\neg A \land \neg B) 这表示,我们可以通过对A和B取非,然后做与运算,再对结果取非来得到A或B。 - 模拟“异或”运算: 异或(XOR)运算可以通过与、或、非的组合来定义: A \oplus B = (A \land \neg B) \lor (\neg A \land B) 但既然我们只能用“非”和“与”,我们可以进一步转换:
A \oplus B = \neg((\neg A \land \neg B) \lor (A \land B)) 这里,我们先计算A和B同时为假或同时为真的情况,然后对这个结果取非。
- 使用“非”和“或”
- 模拟“与”运算: 类似地,我们可以使用德摩根定律来模拟与运算:
A \land B = \neg(\neg A \lor \neg B) - 模拟“异或”运算: 直接使用之前的异或表达式,但注意到它本质上已经包含了“与”和“或”,不过我们可以将其中的“与”用“非”和“或”来替换(如上所述,但直接转换稍显复杂)。更直接的方法是,利用异或的另一种等价表达式: A \oplus B = (A \lor B) \land \neg(A \land B) 这里,我们先计算A或B,然后计算A与B,对后者取非,最后将两者做与运算。
- 能否只用一个运算符来表达?
不能只用一个运算符来完成所有逻辑运算。原因是逻辑运算包括基本的真值表操作,如与、或、非,它们各自具有不同的真值表输出。没有足够的信息(即只有一个运算符)来区分这些不同的操作。例如,“非”运算只能反转一个值的真假,而“与”和“或”则涉及两个值的组合。因此,至少需要两个运算符来模拟所有基本的逻辑运算。
赋值符:
- 如:
=
,以及复合赋值运算符如+=
、-=
、*=
、/=
、%=
等。赋值表达式: 变量 = 表达式赋值运算符左边(称为左值)必须是指向内存单元的变量(或指针),不能是常量或值表达式;
赋值运算符右边(称为右值)是合法的 C 语言表达式。图示
例如: | |
---|---|
sum=sum+a | 合法 |
i++=5_i、a+b=c_4 | 非法 |
‘y’=x、x+1=y*a+3 | 非法 |
- 除了直接书写十进制数,也可以在表达式中直接写二、八、十六进制数。例如:
// C 语言中进制的书面表达
#include <stdio.h>
int main()
{
int x = 0101;//八进制(octal literal--用0开头)
int y=5; //十进制(直接写)
int w = 0x10; //十六进制(Hexdciaml---用0x开头)
int z = 0b010;//而进制(Binary---用0b开头)
printf("八进制的0101 = 十进制的: %d\n", x);
printf("八进制的0101+十进制5 = 十进制的:%d\n", y+x);
printf("十六进制0x10 = 十进制的:%d\n", w);
printf("二进制0b010 = 十进制的:%d\n", z);
printf("按16进制格式打印(八进制0101): %X\n", x);
return 0;
}
复合赋值运算符:
-
如:
+=
,-=
,*=
,/=
,%=
,<<=
,>>=
,&=
,∧=
,|=
变量 op= 表达式
等价于:变量 = 变量 op (表达式)
例如:a += b+3
等价于:a = a+(b+3)
位运算符:
- 程序中的任何数据在计算机内存中都是以二进制的形式储存的。 位运算就是直接对整型数(char、short、int、long)在内存的二进制位逐位进行测试、置位或移位操作。如:
<<
(左移)、>>
(右移)、&
(按位与)、|
(按位或)、^
(按位异或)、~
(按位取反),直接对整型数的二进制位进行操作。运算效率更高:例如,加密/解密
运算符 | 操作 | 示例(设 a=0x36,b=0xb3) |
---|---|---|
~ | 按位取反 | a : 00110110,~a : 11001001 |
<<、>> | 左移、右移 | a <<2 : 11011000,a>>3 : 00000110 |
& | 按位与 | a & b : 00110110 & 10110011=00110010 |
^ | 按位异或 | a ^ b : 00110110 ^ 10110011=10000101 |
| | 按位或 | a| b : 00110110 | 10110011=10110111 |
&=、^=、 | =、<<=、>>= | 按位复合赋值运算 |
- 位运算
- 左移运算: a << b 将 a 的各二进位全部左移 b 位,高位丢弃,低位补 0,其值相当于乘 2b。b 非负整数,且不超过 a 升格后的二进制位数
- 状态字译码: 标志位左移到最高位,再与最高位为 1 的 mask 做“与”运算,结果非 0 则标志位为 1。
- 整数快速乘法运算: 左移一位等效于乘 2。
- 右移运算:a >> b 将 a 的各二进位全部右移 b 位数,低位丢弃,高位填充符号位的值。其值相当于除 2b(取整)移位不同于循环,从一端移出的位并不送回到另一端去,移去的位永远丢失了,同时在另一端补符号位的值。例如:a=-100, b=-25
int a=-100,b;
b=a>>2;
printf(“a=%d,b=%d\n”,a,b);
-
位运算的用途
- 设备状态
- 位:用来保存设备的状态、操作权限等。计算机中常用特定的状态字管理和控制各种设备。
- 状态测试:读取状态字 status 的某标志位 mask 的值,进行判定。 status & mask 结果为 0 则该位为 0,非 0 则该位为 1。
- 状态设置:将某位清 0,status & ~mask;将某位置 1,status | mask
- 简单加密解密
- 原文与密钥异或后得到密文,密文与密钥异或后得到原文。
- 高效乘、除运算
- 左移 1 位乘 2,右移 1 位除 2
运算优先级与结合性
- 括号
()
具有最高优先级,用于改变运算顺序。 - 逗号运算符
,
优先级最低,用于连接两个表达式,并返回最后一个表达式的值。 - 运算符的结合性决定了当运算符具有相同优先级时,操作的顺序。例如,算术运算符和赋值运算符都是左结合的。
- 以下是运算优先级表格,包含了 C 语言运算符的名称、含义、说明、优先级、结合性,以及每个运算符的示例:
优先级 | 运算符 | 名称 | 含义 | 说明 | 结合性 | 示例 |
---|---|---|---|---|---|---|
1 | () | 圆括号 | 用于改变运算顺序或函数调用 | 括号内的表达式会优先计算 | 左 | (a + b) * c ,printf("%d", a) |
2 | [] | 下标运算符 | 用于访问数组元素或指针指向的值 | 表达式[索引]表示访问索引处的元素 | 左 | arr[i] ,ptr[j] |
2 | -> | 结构体指针成员访问 | 用于通过结构体指针访问成员 | 指针-> 成员表示访问指针所指向的结构体中的成员 | 左 | ptr->member |
2 | . | 结构体成员访问 | 用于通过结构体变量访问成员 | 结构体.成员表示访问结构体中的成员 | 左 | structVar.member |
3 | ++ | 自增运算符 | 使变量的值增加 1 | 可以作为前缀或后缀使用 | 右 | ++a (前缀),a++ (后缀) |
3 | -- | 自减运算符 | 使变量的值减少 1 | 可以作为前缀或后缀使用 | 右 | --b (前缀),b-- (后缀) |
3 | ! | 逻辑非运算符 | 对布尔值取反 | 如果操作数为真,结果为假;如果操作数为假,结果为真 | 右 | !flag (如果 flag 为真,则结果为假) |
3 | ~ | 位非运算符 | 对整数的每一位取反 | 0 变为 1,1 变为 0 | 右 | ~num (对 num 的每一位取反) |
3 | sizeof | 大小运算符 | 返回类型或变量所占的字节数 | 在编译时计算,结果类型为 size_t | 右 | sizeof(int) ,sizeof(var) |
... | ... | ... | ... | ... | ... | ... |
6 | * | 乘法运算符 | 计算两个数的乘积 | 适用于整数和浮点数 | 左 | a * b (计算 a 和 b 的乘积) |
6 | / | 除法运算符 | 计算两个数的商 | 适用于整数和浮点数,整数除法结果向下取整 | 左 | c / d (计算 c 除以 d 的商) |
6 | % | 求余运算符 | 计算两个数相除的余数 | 仅适用于整数 | 左 | e % f (计算 e 除以 f 的余数) |
7 | + | 加法运算符 | 计算两个数的和 | 适用于整数、浮点数和字符(ASCII 码值相加) | 左 | g + h (计算 g 和 h 的和) |
7 | - | 减法运算符 | 计算两个数的差 | 适用于整数、浮点数和指针(地址差) | 左 | i - j (计算 i 和 j 的差) |
8 | << | 左移运算符 | 将二进制位向左移动指定的位数 | 右侧用 0 填充,可用于乘以 2 的幂 | 左 | k << l (将 k 的二进制位向左移动 l 位) |
8 | >> | 右移运算符 | 将二进制位向右移动指定的位数 | 对于无符号数,左侧用 0 填充;对于有符号数,可能用符号位填充 | 左 | m >> n (将 m 的二进制位向右移动 n 位) |
9 | < | 小于运算符 | 比较两个数的大小 | 如果左侧小于右侧,结果为真 | 左 | o < p (如果 o 小于 p,则结果为真) |
9 | <= | 小于等于运算符 | 比较两个数的大小 | 如果左侧小于或等于右侧,结果为真 | 左 | q <= r (如果 q 小于或等于 r,则结果为真) |
9 | > | 大于运算符 | 比较两个数的大小 | 如果左侧大于右侧,结果为真 | 左 | s > t (如果 s 大于 t,则结果为真) |
9 | >= | 大于等于运算符 | 比较两个数的大小 | 如果左侧大于或等于右侧,结果为真 | 左 | u >= v (如果 u 大于或等于 v,则结果为真) |
10 | == | 等于运算符 | 比较两个数是否相等 | 如果两个数相等,结果为真 | 左 | w == x (如果 w 等于 x,则结果为真) |
10 | != | 不等于运算符 | 比较两个数是否不相等 | 如果两个数不相等,结果为真 | 左 | y != z (如果 y 不等于 z,则结果为真) |
11 | & | 位与运算符 | 对两个数的每一位进行与运算 | 只有当两个对应的位都为 1 时,结果位才为 1 | 左 | a & b (对 a 和 b 的每一位进行与运算) |
12 | ^ | 位异或运算符 | 对两个数的每一位进行异或运算 | 当两个对应的位不同时,结果位为 1 | 左 | c ^ d (对 c 和 d 的每一位进行异或运算) |
13 | | | 位或运算符 | 对两个数的每一位进行或运算 | 只要有一个对应的位为 1,结果位就为 1 | 左 | `e |
14 | && | 逻辑与运算符 | 对两个布尔值进行与运算 | 只有当两个操作数都为真时,结果才为真 | 左 | g && h (如果 g 和 h 都为真,则结果为真) |
15 | || | 逻辑或运算符 | 对两个布尔值进行或运算 | 只要有一个操作数为真,结果就为真 | 左 | `i |
16 | ? : | 三元运算符 | 根据条件选择两个值中的一个 | 条件 ? 值 1 : 值 2,如果条件为真,结果为值 1,否则为值 2 | 右 | k > 0 ? "Positive" : "Negative" (如果 k 大于 0,则结果为"Positive",否则为"Negative") |
17 | = | 赋值运算符 | 将右侧的值赋给左侧的变量 | 右侧表达式的值会存储在左侧变量中 | 右 | a = b (将 b 的值赋给 a) |
... | ...= | 复合赋值运算符 | 结合算术或位运算符进行赋值 | 例如 a += b 等同于 a = a + b | 右 | c += d (等同于 c = c + d) |
- 这些示例展示了每个运算符在 C 语言中的典型用法。请注意,实际代码中的变量名(如 a, b, c 等)和表达式可能会根据上下文而有所不同。
4. 数据类型及转换
- 在 C 语言中,数据类型分为基本数据类型、构造数据类型、枚举类型和指针类型等几大类。以下是 C 语言中一些常见的基本数据类型及其描述,以及数据类型转换的说明。
基本数据类型表格
类型 | 描述 | 典型大小(字节) | 取值范围(示例) |
---|---|---|---|
char |
字符类型,用于存储单个字符 | 1 | -27~27-1; -128 到 127 或 0 到 255(取决于编译器) |
signed char |
有符号字符类型,与char 通常相同,但显式表示有符号 |
1 | -27~27-1; -128 到 127 |
unsigned char |
无符号字符类型 | 1 | 0~28-1; 0 到 255 |
short |
短整型 | 2 | -215~215-1; -32,768 到 32,767 |
unsigned short |
无符号短整型 | 2 | 0~216-1; 0 到 65,535(无符号) |
int |
整型(通常与short 或 long 相同,具体取决于编译器和平台) |
4 | -231~231-1; -2,147,483,648 到 2,147,483,647 |
unsigned int |
无符号整型 | 4 | 0~232-1; 0 到 4,294,967,295(无符号) |
long |
长整型(通常按 4 字节) | 4 或 8(64 位系统通常为 8) | -231 |
unsigned long |
无符号长整型 | 4 或 8 | 0 |
long long |
更长的整型(C99 标准引入) | 8 | -263~263-1; -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 |
unsigned long long |
无符号更长的整型 | 8 | 0~264-1; 0 到 18,446,744,073,709,551,615(无符号) |
float |
单精度浮点型 | 4 | 3.4E±38F(6-7 个有效数字) -3.4 \times 10^{-38} \sim 3.4 \times 10^{38} |
double |
双精度浮点型 | 8 | 1.7E±308(15 个有效数字) -1.7 \times 10^{-308} \sim 1.7 \times 10^{308} |
long double |
扩展精度浮点型(具体精度取决于编译器和平台) | 8、12 或 16 | 3.4E±4932L(18-19 个有效数字,具体取决于实现) -1.7 \times 10^{-308} \sim 1.7 \times 10^{308} |
- 以下是一个关于C语言中常见转义序列及其意义的表格:
转义序列 | 意义 | ASCII码 | 示例输出 |
---|---|---|---|
\a |
报警(Alert) | 7 | 发出蜂鸣声(取决于终端) |
\b |
退格(Backspace) | 8 | 将光标向左移动一个位置 |
\f |
换页(Form Feed) | 12 | 将光标移动到下一页(取决于终端或打印机) |
\n |
换行(Newline) | 10 | 将光标移动到下一行的开头 |
\r |
回车(Carriage Return) | 13 | 将光标移动到当前行的开头 |
\t |
水平制表符(Tab) | 9 | 将光标向右移动一定数量的位置(通常是8个空格) |
\v |
垂直制表符(Vertical Tab) | 11 | 将光标向下移动一定数量的位置(取决于终端) |
\\ |
反斜杠(Backslash) | 92 | 输出一个反斜杠字符 |
\' |
单引号(Single Quote) | 39 | 输出一个单引号字符 |
\" |
双引号(Double Quote) | 34 | 输出一个双引号字符 |
\? |
问号(Question Mark) | 63 | 输出一个问号字符(在现代C中通常不需要转义) |
\0 |
空字符(Null Character) | 0 | 字符串结束符,不输出任何字符 |
\ddd |
八进制字符 | - | 输出对应的ASCII字符(ddd为三位八进制数)'\101'代表'A' |
\xhh |
十六进制字符 | - | 输出对应的ASCII字符(hh为两位十六进制数)'\x41'代表'A' |
注意:
\ddd
和\xhh
中的d
和h
分别代表八进制和十六进制数字,它们可以是0-7和0-9, a-f, A-F之间的字符。在实际使用时,需要替换为具体的数字。- 在现代C编译器中,问号字符(
?
)通常不需要转义,但在某些旧版本的编译器或特定上下文中,可能需要使用\?
来避免歧义。 - 空字符(
\0
)在C语言中用于表示字符串的结束,它不输出任何字符,但在内存中占用一个字节的空间,其ASCII码值为0。
数据类型转换
- 在 C 语言中,数据类型转换分为显式转换(强制类型转换)和隐式转换(自动类型转换)。
显式转换(强制类型转换)
- 显式转换使用类型转换运算符,将一种数据类型强制转换为另一种数据类型。语法如下:
(目标类型) 变量或表达式;
示例:
int a = 5;
float b = (float)a; // 将int类型变量a强制转换为float类型,赋值给float类型变量b
隐式转换(自动类型转换)
- 隐式转换发生在赋值、算术运算或比较运算中,当涉及不同数据类型的值时,编译器会自动将它们转换为一种共同类型。通常,转换规则遵循“数据类型的提升”(Promotion)规则即“低位向高位转换”,例如:
示例:
int a = 5;
double b = a; // 自动将int类型变量a转换为double类型,赋值给double类型变量b
- 需要注意的是,隐式转换有时会导致数据丢失或精度下降,因此在使用时应谨慎,特别是当涉及不同大小的整数类型或浮点类型时。
总结
C 语言提供了丰富的数据类型,以支持各种编程需求。了解每种数据类型的特性和大小,以及掌握数据类型之间的转换规则,对于编写高效、健壮的 C 程序至关重要。
- 整数类型会提升到足够大的类型(例如,
char
和short
通常会被提升为int
),字符型数据在运算时会被视为整型数据。 - 整数与浮点数进行运算时,整数会转换为浮点数。
float
类型在运算中会被提升为double
类型。- 在算术运算中,如果有
long double
参与,则所有其他浮点类型会被提升为long double
。
三、输入输出
输入输出点原理
1. 标准函数库
- C 语言通过标准输入输出函数库中的函数
printf()
和scanf()
实现数据的输入输出。 - 对于求平方根,指数,对数、三角函数等数学运算,C 语言中有标准库函数实现计算功能。编程时只要在表达式中正确调用库函数即可。
- 调用系统标准库函数之前,通常要用预编译命令将函数所在头文件包含到源程序中。
#include <stdio.h> // 标准输入输出函数头文件
#include <math.h> // 数学函数头文件常用数学计算库函数:
- 求 n 的平方根: sqrt(n)
- 求指数 ab: pow(a, b)
- 求绝对值 |x|: fabs(x)
- 求 x 正弦值: sin(x)
- 随机整数: rand()
- 调用库函数时,函数名必须正确,参数的类型和个数必须与系统规定一致。其中函数名由系统提供,不同 C 编译系统提供的函数名可能有差异,使用时需查阅相关手册。
2. 常用格式字符**
- 在 C 语言中,格式化输入输出时,
printf
和scanf
函数使用格式字符来指定数据的显示和输入格式。以下是一个关于常用格式字符的表格,包括它们的宽度和精度修饰符。
常用格式字符表格
格式字符 | 含义 | 示例 |
---|---|---|
%d |
十进制整数 | printf("%d", 42); |
%ld |
十进制整数长整型 long | printf("%ld", 42); |
%lld |
十进制整数长整型 long long | printf("%lld", 42); |
%u |
无符号十进制整数 | printf("%u", 42U); |
%x |
无符号十六进制整数(小写) | printf("%x", 2A); |
%X |
无符号十六进制整数(大写) | printf("%X", 2A); |
%o |
无符号八进制整数 | printf("%o", 52); |
%f |
浮点数(单精度实数 float) | printf("%f", 3.14159); |
%lf |
浮点数(双精度实数 double) | printf("%lf", 3.14159); |
%e |
浮点数(科学计数法) | printf("%e", 3.14159); |
%E |
浮点数(科学计数法,大写 E) | printf("%E", 3.14159); |
%g |
浮点数(根据数值自动选择) | printf("%g", 3.14159); |
%G |
浮点数(根据数值自动选择,大写 E) | printf("%G", 3.14159); |
%c |
单个字符 | printf("%c", 'A'); |
%s |
字符串 | printf("%s", "Hello"); |
%p |
指针地址(十六进制) | printf("%p", &var); |
%% |
百分号字符 | printf("%%"); |
宽度和精度修饰符
希望这个表格能帮助你更好地理解和使用 C 语言中的格式化输入输出。
修饰符 | 意义 |
---|---|
%md | 以宽度 m 输出整型数,不足 m 位数时左侧补以空格。 |
%0md | 以宽度 m 输出整型数,不足 m 位数时左侧补以 0(零)。 |
%-md | 以宽度 m 左对齐输出整型数,不足 m 位数时右侧补以空格 |
%m.nlf | 以宽度 m 输出实型数,小数位数为 n 位。 |
%ms | 以宽度 m 输出字符串,不足 m 位数时左侧补以空格。 |
- 宽度修饰符:用于指定输出字符的最小宽度。通过在格式字符前加一个数字或使用
*
来指定。- 示例:
printf("%5d", 42);
// 输出 " 42"(前面填充空格) - 使用
*
时,可以通过一个额外的int
参数指定宽度:printf("%*d", 5, 42);
// 同样输出 " 42"
- 示例:
- 精度修饰符:用于指定浮点数的小数位数或字符串的最大字符数。通过在格式字符前加
.
和一个数字来指定。- 示例:
printf("%.2f", 3.14159);
// 输出 "3.14" - 对于字符串:
printf("%.*s", 3, "Hello");
// 输出 "Hel"(最多 3 个字符)
- 示例:
3. 输出:printf()
printf()
函数用于向输出设备(如显示器)输出数据,可以使用格式控制符指定输出格式。- 格式化输出 printf()函数:
- printf 输出原理
- 格式:printf(“格式控制字符串”,输出列表)
- 格式控制字符串:由格式字符串和非格式字符串两部分组成
格式字符串:% 开头的字符串,指定输出项的格式。
非格式字符串:即普通字符,原样输出。 - 输出列表:要输出的数据,可以是常量,变量或表达式,多个输出项直接用逗号隔开。
- 格式控制字符串:由格式字符串和非格式字符串两部分组成
%d
格式符:以十进制的形式输出带符号整数 int
#include<stdio.h>
int main()
{
int a=-10;
double b=3.5;
char c='A';
printf("%d,%d,%d,%d,%d\n",10,a,c,3.5,b);
}
运行结果:
%d 格式输出字符型即输出其 ASCII 值
%d 格式输出实数型会出现错误。
* `%d`**格式符:以十进制的形式输出带符号整数 int**
#include<stdio.h>
int main()
{
long long a1=9999999999;
printf("%d,%ld,%lld",a1,a1,a1);
}
运行结果:
超出数据类型范围则出现“溢出”
%ld:用来输出 long 类型
%lld:用来输出 long long 类型
* `%f`**格式符:以小数形式输出单精度实数 float**
#include<stdio.h>
int main()
{
float a1=1;
double a2=1;
printf("%f,%f,%f\n",a1/3,-3.78,2.345e2);
printf("%lf,%lf,%lf\n",a2/3,-3.78,2.345e2);
}
运行结果:
%lf:用来输出 double 类型
%f 和 %lf 默认输出 6 位小数。
* `%f`**格式符:以小数形式输出单精度实数 float**
#include<stdio.h>
int main()
{
float b1=3333333333.3333333;
double b2=3333333333.3333333;
printf("%f\n%lf\n",b1,b2);
}
运行结果:
float 有效位 7 位
double 有效位 15 位
%c
格式符:输出单个字符
#include<stdio.h>
int main()
{
char c='0';
printf("%d,%c",c,c);
}
运行结果:
字符型按 %d 输出即输出其 ASCII 码值
4. 输入:scanf()
scanf()
函数用于从输入设备(如键盘)读取数据,并将其存储在指定的变量中。注意,scanf()
函数在读取字符或字符串时,需要特别注意缓冲区溢出和输入格式的问题。- scanf 输入原理
- 格式化输入 scanf()函数
- 格式:scanf(“格式控制字符串”,输入项地址列表)
- 格式控制字符串:由格式字符串和非格式字符串两部分组成
- 格式字符串:% 开头的字符串,指定输出项的格式。
- 非格式字符串:即普通字符,原样输入。
- 输入项地址列表:一个或多个变量的地址,以 & 开头的变量
- 格式控制字符串:由格式字符串和非格式字符串两部分组成
- 格式:scanf(“格式控制字符串”,输入项地址列表)
#include<stdio.h>
int main()
{
int a,b,c;
scanf("%d%d%d",a,b,c);
// scanf("%d%d%d",&a,&b,&c);
printf("%d,%d,%d",a,b,c);
}
// 程序编译没有问题,但执行会出现闪退,为什么?
// scanf函数中忘记&
- 注意事项一scanf 函数中忘记&为什么要用&? scanf 语句中是&a,而不是 a
&a 表达,是把输入的数存放到变量 a 地址代表的存储空间中
- 注意事项二使用空格或者回车符作为读入数据的分隔
#include<stdio.h>
int main()
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
printf("%d,%d,%d",a,b,c);
}
1.使用空格作为分隔
2.使用回车符作为分隔
- 注意事项三格式字符串中的非格式字符需要原样输入。对于 scanf 系列函数格式字符串:非格式字符要求用户输入时必须匹配这部分内容。
例如:
int num;
scanf("%d", &num); // 正确用法
这里 %d 表示程序期待一个整数输入。用户只需输入数字即可,无需考虑其他字符。
如果格式字符串中包含非格式字符,则用户输入时也必须包括这些字符。
例如:
int num;
scanf("Number: %d", &num); // 错误示例
在这种情况下,程序实际上等待用户键入 Number:
后跟一个整数。如果用户仅仅输入了数字,那么这次 scanf 调用可能不会成功读取数据。
注意事项使用 scanf 时尽量避免在格式字符串中加入额外的文字,因为这可能会导致输入处理上的困难。
- 注意事项四在按字符格式输入时候,会把空格或回车当做字符
#include<stdio.h>
int main()
{
int a;
char b;
double c;
scanf("%d%c%lf",&a,&b,&c);
printf("a=%d,b=%c,c=%lf\n",a,b,c);
}
输入:123 A 4.5
输出:a=123,b= ,c=0.000000
输入:123A4.5
输出:a=123,b=A,c=4.500000
在 C 语言中使用 scanf 函数进行输入时,空格、制表符和回车(换行)通常被视为分隔符。这意味着当你使用 %c 格式说明符来读取单个字符时,这些空白字符会被当作有效输入处理。不过,scanf 的行为依赖于具体的格式字符串和输入方式。例子
char ch;
scanf("%c", &ch);
假设用户直接按下回车键,程序将把回车符(\n
)存储到变量 ch
中。
char ch;
scanf(" %c", &ch); // 注意前面有一个空格
这里开头的空格告诉 scanf
忽略任何数量的空白字符,然后读取下一个非空白字符。这样即使用户先按下了几个空格或者回车,接着输入了一个字符,该字符也会被正确读入。
注意事项
- 如果你使用
%c
来读取一个字符,那么它会读取下一个未被跳过的字符,包括空格、制表符或换行符。- 如果你想忽略掉前导的空白字符(如空格、制表符、换行符等),可以使用空格作为格式字符串的一部分,例如:
- 当使用
%c
且不希望读入空白字符时,可以通过在%c
之前添加额外的空白字符来实现。 - 对于更复杂的输入处理,可能需要考虑使用
getchar()
函数逐字符读取,并手动过滤不需要的空白字符,或者使用fgets()
读取整行输入后再进行处理。 - 在处理包含空白字符的输入时要特别小心,因为这可能导致意外的结果,尤其是在连续调用
scanf
时。
4. 其他输入输出函数
要引入string.h头文件。
putchar( ) 输出1个字符
puts( ) 输出1个字符串
getchar() 输入1个字符,并回显
getch() 输入1个字符,不回显(头文件conio.h)
gets() 输入1个字符串,按回车结束