C 语言基础 2 - 运算与输入输出

一、 引言

C 语言是一种功能强大、灵活多变的编译型语言,广泛应用于系统编程、嵌入式开发等领域。运算与选择结构是 C 语言编程的基础,它们构成了程序逻辑的核心。

C 语言是一种编译型语言,从创建 C 程序到运行出最终结果,包括:编辑、编译、链接和运行四个过程。

1. 程序设计语言与自然语言的对比

  • 自然语言
    • 种类:世界上有5651种语言,汉语、英语是主流。
    • 特点:
      • 汉语:象形文字,二维空间表达(上下左右),笔画组成一个字,二义性较强,需要标点符号分割句子。
      • 英语:拼音文字,一维空间表达(从左到右),句与句之间用标点符号分割,单词含义人为定义。
  • 程序设计语言
    • 目的:表达机械化可执行的算法。
    • 范围:程序设计语言可以描述的范围,远远小于自然语言的描述范畴。
    • 特点:上下文无关,机器仅关注当前语句的语义并按此执行。需要严格的词法、语法要求。

2. 程序设计语言的基本要素

  • 基本语句
    • 赋值语句:将表达式的计算结果放到某个变量里。
    • 判断(选择)语句:根据表达式的真假决定执行后续的哪个语句。
    • 循环语句:根据表达式的真假决定是否循环执行某些语句。

3. 程序设计过程

  • 转换:把自然语言描述的算法转变为程序语言的语义、语法和词法,让机器能够无歧义地解释和执行。
  • 程序员需知
    • 理解自然语言与程序语言的差别。
    • 熟悉所用编程语言的词法、语法和语义。
    • 使用程序语言的词法、语法和语义表达自然语言的思想。
  • 辅助工具:流程图,用于图形化表达算法,更好的完成自然语言到程序语言的转换。

4. 保留字与标识符

  • 保留字
    • 定义:编程时不能用于其他用途的字,如C语言中的intfloat等。
    • 作用:防止误用,导致程序出错。
    • C语言中的保留字(也称为关键字)是语言本身定义的一些具有特殊意义的标识符,不能用作变量名、函数名或任何其他标识符。以下是C语言标准(C99和C11)中的保留字列表:
保留字 用途
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. 数据类型、常量和变量

  • 数据类型:定义变量时,要指定其数据类型,如intdouble等。机器会根据数据类型为变量分配相应的地址和空间。
  • 常量:程序中其值不能改变的量,编译系统不会为常量分配存储空间。常量通常用于给变量赋值,如#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 也可以表示为 !XORXOR 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

逻辑运算最小体系

第四个(异或)定义是多余的,可以用前三个定义,推导出来,例如:

  1. A⊕B= (A|B)\&amp;(\sim A | \sim B)
  2. A⊕B= \sim(\sim A\&amp;\sim B)\&amp;\sim (A \&amp; B)
  3. A⊕B= (A\&amp;\sim B)|(\sim A \&amp; B)

进一步,与、或、非是否可以归纳为两个运算呢?
例如,或运算可以归纳为非和与两种运算,

  • 事实上, A|B= \sim (\sim A \&amp; \sim B)
  • 同样, A\&amp;B = \sim (\sim A|\sim B)

逻辑计算中只需要两个符号,(非、与),或(非、或),这是逻辑运算的最小体系

证明:逻辑计算中只需要两个运算符

为了证明在逻辑计算中只需要两个运算符(例如“非”和“与”,或者“非”和“或”)就能完成所有逻辑运算,我们需要展示如何使用这两个运算符来模拟其他逻辑运算符(如“或”、“异或”等)。

  • 使用“非”和“与”
  1. 模拟“或”运算: 给定两个变量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。
  2. 模拟“异或”运算: 异或(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同时为假或同时为真的情况,然后对这个结果取非。
  • 使用“非”和“或”
  1. 模拟“与”运算: 类似地,我们可以使用德摩根定律来模拟与运算:
    A \land B = \neg(\neg A \lor \neg B)
  2. 模拟“异或”运算: 直接使用之前的异或表达式,但注意到它本质上已经包含了“与”和“或”,不过我们可以将其中的“与”用“非”和“或”来替换(如上所述,但直接转换稍显复杂)。更直接的方法是,利用异或的另一种等价表达式: 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,其值相当于乘 2bb 非负整数,且不超过 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);

  • 位运算的用途

    1. 设备状态
    • 位:用来保存设备的状态、操作权限等。计算机中常用特定的状态字管理和控制各种设备。
    • 状态测试:读取状态字 status 的某标志位 mask 的值,进行判定。 status & mask 结果为 0 则该位为 0,非 0 则该位为 1。
    • 状态设置:将某位清 0,status & ~mask;将某位置 1,status | mask
    1. 简单加密解密
    • 原文与密钥异或后得到密文,密文与密钥异或后得到原文。
    1. 高效乘、除运算
    • 左移 1 位乘 2,右移 1 位除 2

运算优先级与结合性

  • 括号 () 具有最高优先级,用于改变运算顺序。
  • 逗号运算符 , 优先级最低,用于连接两个表达式,并返回最后一个表达式的值。
  • 运算符的结合性决定了当运算符具有相同优先级时,操作的顺序。例如,算术运算符和赋值运算符都是左结合的。
  • 以下是运算优先级表格,包含了 C 语言运算符的名称、含义、说明、优先级、结合性,以及每个运算符的示例:
优先级 运算符 名称 含义 说明 结合性 示例
1 () 圆括号 用于改变运算顺序或函数调用 括号内的表达式会优先计算 (a + b) * cprintf("%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 整型(通常与shortlong 相同,具体取决于编译器和平台) 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) -231231-1 或 -263263-1; -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807
unsigned long 无符号长整型 4 或 8 0232-1 或 0264-1; 0 到 18,446,744,073,709,551,615(无符号)
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'

注意

  1. \ddd\xhh 中的 dh 分别代表八进制和十六进制数字,它们可以是0-7和0-9, a-f, A-F之间的字符。在实际使用时,需要替换为具体的数字。
  2. 在现代C编译器中,问号字符(?)通常不需要转义,但在某些旧版本的编译器或特定上下文中,可能需要使用 \? 来避免歧义。
  3. 空字符(\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 程序至关重要。

  • 整数类型会提升到足够大的类型(例如,charshort 通常会被提升为 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 语言中,格式化输入输出时,printfscanf 函数使用格式字符来指定数据的显示和输入格式。以下是一个关于常用格式字符的表格,包括它们的宽度和精度修饰符。

常用格式字符表格

格式字符 含义 示例
%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(“格式控制字符串”,输入项地址列表)
      • 格式控制字符串:由格式字符串和非格式字符串两部分组成
        • 格式字符串:% 开头的字符串,指定输出项的格式。
        • 非格式字符串:即普通字符,原样输入。
      • 输入项地址列表:一个或多个变量的地址,以 & 开头的变量
#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个字符串,按回车结束

备注:

因个人习惯和能力所限,该文档内容若存在表述不合理或错误之处,请大家留言多多指正

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明