3 C 语言运算符深度解析:算术、关系、逻辑、赋值及 sizeof

目录

1 运算符分类

2 算术运算符与算术表达式

2.1 算术运算符的使用

2.2 左操作数和右操作数

3 关系运算符与关系表达式

3.1 关系运算符的使用

3.2 常量左置防错

3.3 数值判断的正确写法

4 逻辑运算符与逻辑表达式

4.1 逻辑运算符的使用

4.2 闰年的判断

4.3 短路运算

5 赋值运算符

5.1 左值与右值

5.2 赋值操作的限制

5.3 复合赋值运算符

5.4 复合赋值运算符的运算规则

6 sizeof 运算符

6.1 语法格式

6.2 对基本数据类型和变量的使用

6.3 对数组使用

6.4 对指针使用

7 运算符优先级表

8 复杂表达式的计算过程

9 本章判断题 

10 OJ 练习

10.1 课时3作业1

10.2 课时3作业2


1 运算符分类

C 语言提供了 13 种类型的运算符,如下所示:

        (1)算术运算符( + - * / % )
        (2)关系运算符( > < == >= <= != )
        (3)逻辑运算符( ! && || )
        (4)位运算符( << >> ~ | ^ & ) ——   高级课程时讲解
        (5)赋值运算符( = 及复合赋值运算符)
        (6)条件运算符( ? : ) ——   高级课程时讲解
        (7)逗号运算符( , ) ——   高级课程时讲解
        (8)指针运算符( * 和 & ) ——   讲指针时讲解
        (9)求字节数运算符( sizeof () ) ——   前面章节已用过
        (10)强制类型转换运算符((类型)) ——   前面章节已讲解
        (11)分量运算符( . -> ) ——   讲结构体时讲解
        (12)下标运算符( [ ] ) ——   讲数组时讲解
        (13)其他(如函数调用运算符()) ——   讲函数时讲解

        条件、逗号、位、自增自减运算符在高级阶段进行详细讲解。


2 算术运算符与算术表达式

2.1 算术运算符的使用

        算术运算符包含 “+”、“-”、“*”、“/” 和 “%”,当一个表达式中同时出现这 5 种运算符时,先进行乘(*)、除(/)、取余(%)运算 ,取余也称取模,后进行加(+)、减(-)运算,也就是乘、除、取余运算符的优先级高于加、减运算符

        除 “%” 运算符外,其余几种运算符既适用于浮点型数又适用于整型数。当操作符 “/” 的两个操作数都是整型数时,它执行整除运算,在其他情况下执行浮点型数除法。(前面章节已说明)

        “%” 为取模运算符(只适用于整型操作数),它接收两个整型操作数,将左操作数除以右操作数,但它的返回值是余数而不是商

        由算术运算符组成的式子称为算术表达式,表达式一定有一个值。

#include <stdio.h>

int main() {
    int a = 10, b = 3;
    float c = 5.0, d = 2.0;
    
    // 单独使用各个算术运算符
    printf("加法: %d + %d = %d\n", a, b, a + b); // 13
    printf("减法: %d - %d = %d\n", a, b, a - b); // 7
    printf("乘法: %d * %d = %d\n", a, b, a * b); // 30
    printf("除法(整型): %d / %d = %d\n", a, b, a / b); // 3
    printf("除法(浮点型): %.2f / %.2f = %.2f\n", c, d, c / d); // 2.50
    printf("取余: %d %% %d = %d\n", a, b, a % b); // 1
    
    // 综合使用算术运算符
    int result = 4 + 5 * 2 - 6 / 3 + 11 % 4;  // 4 + 10 - 2 + 3 = 15
    printf("综合表达式结果: %d\n", result);  // 15
    
    // 注意事项  
    // 1. 整数除法时,结果会向下取整(即舍弃小数部分)。
    // 2. 运算符的优先级:乘、除、取余 大于 加、减。可以使用括号改变运算顺序。
    // 3. % 运算符只适用于整型操作数。
    // 4. 浮点数运算可能产生精度问题,注意结果可能不是完全精确。
    
    return 0;
}

注意:

        整数除法当使用 / 运算符进行整数除法时,结果会向下取整到最接近的整数,这意味着任何小数部分都会被舍弃

        运算符优先级:算术运算符的优先级是固定的,乘、除、取余的优先级高于加、减。如果需要改变默认的运算顺序,可以使用括号 ()

        取模运算符% 取模运算符只能用于整型操作数。如果尝试对浮点数使用 %,编译器会报错。

        浮点数精度浮点数运算可能会遇到精度问题,因为计算机中的浮点数表示是基于二进制的,某些十进制小数在二进制中可能无法精确表示(浮点数在计算机中存储的是其近似值)。因此,进行浮点数运算时,结果的精度可能不是完全精确的。

        类型提升(隐式类型转换):在表达式中混合使用整型和浮点型时,整型操作数会被提升为浮点型,然后执行浮点型运算,这可能会影响结果的类型和精度。

2.2 左操作数和右操作数

        在 C 语言中,操作数是指参与运算的数据项或表达式的值。当我们在 C 语言中执行一个算术运算(如加法、减法、乘法、除法和取模)时,我们通常会涉及到至少两个操作数:左操作数和右操作数。

  • 左操作数二元运算符(即需要两个操作数的运算符)的左侧的操作数
  • 右操作数二元运算符右侧的操作数

        假设我们有以下表达式:

int a = 5;  
int b = 3;  
int sum = a + b;

        在这个例子中,语句 int sum = a + b; 的 + 是一个二元运算符,意味着它接受两个操作数来执行操作。

  • 左操作数是 a,其值被指定为 5。
  • 右操作数是 b,其值被指定为 3。

        这个二元运算符 + 的作用是将两个操作数(这里是 a 和 b)相加,并将结果赋值给变量 sum。因此,sum 的值将会是 8。


3 关系运算符与关系表达式

3.1 关系运算符的使用

        关系运算符 “>”、“<”、“==”、“>=”、“<=”、“!=” 依次为大于、小于、等于、大于等于、小于等于和不等于

        由关系运算符组成的表达式称为关系表达式关系表达式的值只有,对应的值为 1 0 。由于 C 语言中没有布尔类型,所以在 C 语言中 0 值代表假,非 0 值即为真。例如,关系表达式 3 > 4 为假,因此整体值为 0 ,而关系表达式 5 > 2 为真,因此整体值为 1。

        【关系运算符的优先级】 低于 【算术运算符】,运算符的优先级的详细情况见本文目录: 7 运算符优先级表。

#include <stdio.h>

int main() {
    int a = 5, b = 3, c = 2, d;
    
    // 演示关系运算符
    printf("%d > %d is %d\n", a, b, a > b); // 5 > 3 是真,输出 1
    printf("%d < %d is %d\n", a, b, a < b); // 5 < 3 是假,输出 0
    printf("%d == %d is %d\n", a, a, a == a); // 5 == 5 是真,输出 1
    printf("%d >= %d is %d\n", a, b, a >= b); // 5 >= 3 是真,输出 1
    printf("%d <= %d is %d\n", a, b, a <= b); // 5 <= 3 是假,输出 0
    printf("%d != %d is %d\n", b, c, b != c); // 3 != 2 是真,输出 1
    
    // 演示关系运算符与算术运算符的优先级
    d = (a + b) > (c * 2); // 先进行算术运算,再进行关系比较
    printf("(%d + %d) > (%d * 2) is %d\n", a, b, c, d); // (5 + 3) > (2 * 2) 是真,输出 1
    
    // 如果不加括号,算术运算符会先于关系运算符执行
    // 但这里由于表达式的自然结构,加不加括号结果一样
    // 只是为了说明优先级的概念
    // 【关系运算符的优先级】 低于 【算术运算符】
    d = a + b > c * 2; // 这实际上和上面加了括号的表达式效果一样,因为这里运算符的自然结合顺序也是先进行算术运算
    printf("Without parentheses, (%d + %d) > (%d * 2) is %d\n", a, b, c, d); // 输出同样是 1
    
    return 0;
}

3.2 常量左置防错

        在工作中,很多程序员容易不小心将两个等号写成一个等号,因此当判断整型变量 i 是否等于 3 时,我们可以写为 3 == i ,即把常量(如字面量或已定义的常量)写在前面而把变量写在后面。这是因为当不小心将两个等号写为一个等号时,变量在前面就会导致编译不通(编译器一般会给出提示),从而快速发现错误(这种写法属于华为公司内部的一条编程规范)。

        当错误地将 == 写成 = 时,如果常量在左侧,在大多数情况下会导致编译错误(编译器一般会给出提示),从而立即引起开发者的注意,如下图所示:

        如果将变量放在左侧,那么这行代码在语法上是合法的,但逻辑上是错误的,因为它实际上是在将常量赋值给变量,而不是在比较判断它们。这样的错误可能更难在编译时被发现,因为编译器不会报错,但程序的逻辑将不符合预期,如下图所示:

        因此,将常量放在比较操作符的左侧是一种防御性编程的技巧,有助于避免此类逻辑错误。

3.3 数值判断的正确写法

        在编写程序时,如果我们需要判断三个数是否相等,那么绝对不可以写为 if(5==5==5) ,这种写法的值无论何时都为假,为什么?因为首先 5==5 得到的结果为 1 ,然后 1==5 得到的结果为 0 。如果要判断三个变量 a、b、c 是否相等,那么不能写为 a==b==c ,而应写为 a==b && b==c 。下面来看一个错误示例:

#include <stdio.h>

// 不能用数学上的连续判断大小来判断某个数
int main()
{
    int a;

    while(scanf("%d",&a))  // 只要 scanf 函数成功读取到一个整数,就会一直执行循环
    {
        // 正确的判断方式应该是使用逻辑运算符 if(a > 3 && a < 10)
        if(3 < a < 10)  // 错误的判断方式,在 C 语言中这种连续比较是不正确的
        {
            printf("a 在 3 和 10 之间\n");
            fflush(stdout); // Clion 中加上这句代码,为了立马显示需要打印的内容
        }
        else
        {
            printf("a 不在 3 和 10 之间\n");
            fflush(stdout); // Clion 中加上这句代码,为了立马显示需要打印的内容
        }
    }
}

        输出结果如下所示:

        如果要判断变量 a 是否大于 3 且同时小于 10 ,那么不能写为 3 < a < 10 , 这种写法在数学上的确是正确的,但是在程序中是错误的。首先,无论 a 是大于 3 还是小于 3 , 对于 3 < a 这个表达式只有 1 或 0 两种结果。由于 1 和 0 都是小于 10 的,所以无论 a 的值为多少, 这个表达式的值始终为真,因此在判断变量 a 是否大于 3 且同时小于 10 时,要写成 a>3 && a<10 ,这才是正确的写法。


4 逻辑运算符与逻辑表达式

        逻辑运算符 “!”、“&&”、“||” 依次为逻辑非、逻辑与、逻辑或,这和数学上的与、或、非是一致的。

  • 运算符用于取反,即将真变为假,假变为真。
  • !! 是一个常见的技巧,表示双重否定(即肯定),结果就是本身的真假值。
  • && 运算符用于逻辑与操作,只有当两边的表达式都为真时,结果才为真。一假即假!
  • || 运算符用于逻辑或操作,只要两边的表达式中有一个为真,结果就为真。一真即真!

        逻辑非的优先级高于算术运算符,逻辑与和逻辑或的优先级低于关系运算符。

        简单记就是: ! > 算术运算符 > 关系运算符 > && > ||

        由逻辑运算符组成的表达式称为逻辑表达式,逻辑表达式的值只有,对应的值为 10

4.1 逻辑运算符的使用

        下面是一个包含逻辑运算符以及双重否定( !! )的程序。这个程序将演示如何使用这些逻辑运算符,并解释它们的行为:

#include <stdio.h>

int main() {
    int a = 1; // 真(在 C 中,非零值被视为真)
    int b = 0; // 假(在 C 中,0 被视为假)

    // 逻辑非(!)
    printf("!a = %d\n", !a); // 输出:!a = 0,因为 a 是真,非真为假
    printf("!b = %d\n", !b); // 输出:!b = 1,因为 b 是假,非假为真

    // 双重否定(!!)
    printf("!!a = %d\n", !!a); // 输出:!!a = 1,因为 !a 是 0(假),再取非就是真
    printf("!!b = %d\n", !!b); // 输出:!!b = 0,因为 !b 是 1(真),再取非就是假

    // 逻辑与(&&)
    printf("a && b = %d\n", a && b); // 输出:a && b = 0,因为 a 是真,但 b 是假,一假即假!
    printf("a && a = %d\n", a && a); // 输出:a && a = 1,因为 a 是真,真与真为真

    // 逻辑或(||)
    printf("a || b = %d\n", a || b); // 输出:a || b = 1,因为 a 是真,一真即真!
    printf("b || b = %d\n", b || b); // 输出:b || b = 0,因为 b 是假,假或假还是假

    // 混合使用
    printf("!!(a && b) = %d\n", !!(a && b)); // 输出:!!(a && b) = 0,因为 a && b 是 0(假),再取双重非还是假
    printf("!!(a || b) = %d\n", !!(a || b)); // 输出:!!(a || b) = 1,因为 a || b 是 1(真),再取双重非还是真

    return 0;
}

4.2 闰年的判断

        判断一个年份是否为闰年通常有以下两种常见的规则:

  1. 能被 4 整除但不能被 100 整除的年份为闰年。例如,2008 年是闰年,因为 2008 能被 4 整除但不能被 100 整除;1900 年不是闰年,因为它能被 100 整除但不能被 400 整除。

  2. 能被 400 整除的年份也是闰年。例如,2000 年是闰年,因为它能被 400 整除。

        下例中的代码是计算一年是否为闰年的例子,因为需要重复测试,所以我们用了一个 while 循环,而当输入为 0 时,就结束循环:

#include <stdio.h>

int main() {
    int year;  // 定义一个整型变量 `year` 用于存储输入的年份
    
    while ( scanf("%d", &year) && year != 0 ) {  // 只要能成功读取一个整数到 year 并且 year 不等于 0,就会一直执行循环
        if ( year % 4 == 0 && year % 100 != 0 || year % 400 == 0 )  // 判断年份是否为闰年的条件
        {
            printf("%d is leap year\n", year);  // 如果是闰年,输出相应信息
            fflush(stdout);  // 立即刷新标准输出缓冲区,确保输出内容立即显示
        } else {
            printf("%d is not leap year\n", year);  // 如果不是闰年,输出相应信息
            fflush(stdout);  // 立即刷新标准输出缓冲区,确保输出内容立即显示
        }
    }
    printf("Program exited.\n");  // 当循环结束(即输入为 0 时),输出程序已退出的信息
    return 0;
}

        输出结果如下所示:

提示:

        if (year % 4 == 0 && year % 100!= 0 || year % 400 == 0)

        和

        if ((year % 4 == 0 && year % 100!= 0) || year % 400 == 0)
        这两个表达式的逻辑是一样的,括号在这里主要是为了增强代码的可读性和明确运算的先后顺序,即使不加括号,其运算结果也是相同的。

        但是,在考研初始时,由于是人工阅卷,如果括号写得较多,不便于阅卷老师阅卷,避免可分,能不加括号就不加括号,如这句代码:if (((year % 4 == 0) && (year % 100!= 0)) || (year % 400 == 0)),括号很多,完全没必要!

        所以,常见运算符的优先级还是需要记住,这样可以避免冗余的括号。

4.3 短路运算

        在 C 语言中,逻辑运算符 &&(逻辑与)和 ||(逻辑或)可以实现短路运算。这意味着,如果逻辑表达式的第一部分已经足够确定整个表达式的值,那么第二部分将不会被评估(执行)

        下面是一个使用 && 运算符实现短路运算的例子,其中在短路运算符后面跟了 printf 打印语句,但由于短路的原因,部分打印语句可能不会被执行。

        printf 函数返回一个整数值,该值表示实际输出的字符数(不包括结尾的空字符 '\0' )。如果输出成功,这个返回值将大于或等于 0;如果发生错误,返回值将是负值

#include <stdio.h>

int main() {
    int a = 0;
    int b = 1;
    
    /* 使用 && 运算符的短路特性 */
    // if 括号里面的 printf 不会被打印,因为 a 是 0(假),&& 表达式的第一部分已经足以决定整个表达式的值
    if ( a && (printf("1. 前面表达式为假,我不会打印\n") > 0)) {
        // 这里面的代码块不会执行,因为 if 括号里面的条件为假
        printf("2. 我不会被打印");
    }
    
    // if 括号里面的 printf 会被打印,因为 !a 是 1(真),&& 表达式的第一部分还不足以决定整个表达式的值
    if ( !a && (printf("3. 前面表达式为真,我会打印\n") > 0)) {
        // 这里面的代码块会执行,因为 if 括号里面的条件为真
        printf("4. 我会被打印\n");
    }
    
    /* 使用 || 运算符的短路特性 */
    // if 括号里面的 printf 不会被打印,因为 b 是 1(真),|| 表达式的第一部分已经足以决定整个表达式的值
    if ( b || (printf("5. 前面表达式为真,我不会打印\n") > 0)) {
        // 这里面的代码块会执行,因为 if 括号里面的条件为真
        printf("6. 我会被打印\n");
    }
    
    // if 括号里面的 printf 会被打印,因为 || 表达式的第一部分是 0(假),所以需要评估第二部分来确定整个表达式的值
    if ( 0 || (printf("7. 前面表达式为假,我会打印\n") > 0)) {
        // 这里面的代码块会执行,因为 if 括号里面的条件为真
        printf("8. 我会被打印\n");
    }
    
    return 0;
}

        输出结果如下所示:


5 赋值运算符

        赋值运算符( = ) 在编程中用于将一个表达式的值赋给另一个变量。然而,这个操作受到左值和右值概念的限制。

        优先级: ! > 算术运算符 > 关系运算符 > && > || > 赋值运算符

5.1 左值与右值

        在计算机编程中,特别是 C 和 C++ 等语言中,左值(L-value)和右值(R-value)是两个重要的概念,它们源于赋值操作的上下文,但具有更广泛的含义。

        左值(L-value)左值是指可以出现在赋值表达式左侧的表达式。简单来说,左值代表了一个明确的内存位置,即它可以被赋予一个新值。在赋值操作中,左值用于存储右侧表达式计算出的结果。例如,在表达式 a = b + 25; 中,a 是一个左值,因为它代表了一个可以被赋新值的变量。

        右值(R-value)右值则是指可以出现在赋值表达式右侧的表达式右值通常表示一个具体的值或者一个临时对象它不能表示一个可以存储新值的内存位置。在上面的例子中,b + 25 是一个右值,因为它是一个表达式的结果,这个结果可以赋值给左值。但是,b + 25 不能作为左值,因为它并未标识一个特定的位置(并不对应特定的内存空间)。因此,b + 25 = a 这条赋值语句是非法的。

5.2 赋值操作的限制

        左值必须可修改赋值操作要求左侧必须是一个左值,即一个可以存储新值的内存位置。如果尝试将一个右值(如表达式的结果)放在赋值语句的左侧,编译器会报错,因为右值没有内存位置来存储新值。

        右值提供值:赋值操作的右侧可以是一个右值,它提供了要赋给左值的具体值。右值可以是字面量、表达式的结果、函数调用返回的值等。

示例与错误用法

  • 正确用法:a = b + 25;(a 是左值,b + 25 是右值)
  • 错误用法:b + 25 = a;(尝试将右值 b + 25 用作左值,这是不允许的)
  • 编译错误结果,如下图所示:

5.3 复合赋值运算符

        在 C 语言中,复合赋值运算符是一种简化的赋值方式,它将算术运算符或位运算符与赋值运算符(=)结合使用,以便在单个操作中完成值的计算和赋值。这些运算符包括 +=、-=、*=、/=、%= 等。下面是一张简明的表格,列出了这些复合赋值运算符及其功能:

运算符含义示例(假设 a = 5, b = 2)
+=加法赋值a += b; 等同于 a = a + b; 结果:a = 7
-=减法赋值a -= b; 等同于 a = a - b; 结果:a = 3
*=乘法赋值a *= b; 等同于 a = a * b; 结果:a = 10
/=除法赋值(整数除法时向下取整)a /= b; 等同于 a = a / b; 结果:a = 2
%=求余赋值(模运算)a %= b; 等同于 a = a % b; 结果:a = 1

        下面是一个简单的 C 语言程序示例,展示了如何使用这些复合赋值运算符:

#include <stdio.h>

int main() {
    int a = 5;
    int b = 2;

    // 使用复合赋值运算符
    a += b;  // a = a + b
    printf("a += b: a = %d\n", a);  // 7

    a -= b;  // a = a - b
    printf("a -= b: a = %d\n", a);  // 5

    a *= b;  // a = a * b
    printf("a *= b: a = %d\n", a);  // 10

    a /= b;  // a = a / b
    printf("a /= b: a = %d\n", a);  // 5

    a = 10;  // 重置a的值
    a %= b;  // a = a % b
    printf("a %= b: a = %d\n", a);  // 0

    return 0;
}

5.4 复合赋值运算符的运算规则

        在复合赋值运算符(如 +=、-=、*=、/=、%= 等)中,等号(=)后面的表达式首先被计算为一个整体的值,然后将这个值与等号左边的变量进行相应的算术或位运算,并将结果赋值回该变量。

        以 a += b + 2; 为例,这里的计算过程可以分为两步:

        首先计算等号右边的表达式 b + 2。假设 b 的值是已知的(比如 b = 3),则 b + 2 的结果是 5。然后,将这个结果 5 与 a 的当前值进行加法运算,即 a 的当前值(假设为 10)加上 5,得到 15。最后,将计算结果 15 赋值回 a,更新 a 的值为 15。如下代码所示:

#include <stdio.h>

int main() {
    int a = 10;
    int b = 3;
    printf("a += b + 2 = %d", a += b + 2); //a += b + 2 = a + (b + 2) = 15

    return 0;
}

        这种运算方式简化了代码,使得在需要更新变量值时不必显式地写出变量名两次,例如,a = a + b + 2; 可以更简洁地写为 a += b + 2; ,但是这样写代码可能不是很直观。


6 sizeof 运算符

        很多同学会认为 sizeof 是一个函数,这种理解是错误的,实际 sizeof 是一个运算符

        sizeof 是 C 语言中的一个运算符,用于获取某个类型或变量在内存中所占用的字节数。这个运算符在编译时就被求值,因此它不会计算变量在运行时所实际占用的空间大小(比如,如果某个指针指向了一个大型数组,sizeof 运算符作用于该指针时,只会给出指针本身的大小,而不是它所指向的数组的大小)。

6.1 语法格式

size_t sizeof(type_name);  
size_t sizeof expression;

当 sizeof 的操作数是一个类型名时,必须加括号。
当 sizeof 的操作数是一个表达式(变量名、数组名、结构体/联合体成员访问表达式等)时,括号是可选的(但通常不加)。

建议都加上括号,确保万无一失!
  • type_name 可以是任何数据类型,包括基本数据类型(如 int、float、char 等)和用户定义的数据类型(如结构体、联合体等)。
  • expression 可以是任何变量名、类型名或类型表达式。
  • sizeof 运算符返回一个 size_t 类型的值,这个类型是一个无符号整数类型,足够表示内存中对象的大小,printf 输出时,使用 %zu 格式说明符

6.2 对基本数据类型和变量的使用

#include <stdio.h>

int main() {
    int  i = 10;
    
    // 对变量使用,可以用括号
    printf("sizeof (i) = %zu bytes\n", sizeof (i));  // sizeof i = 4 bytes
    // 对变量使用,也可以不用括号
    printf("sizeof i = %zu bytes\n", sizeof i);  // sizeof i = 4 bytes
    
    // 对数据类型使用,必须加括号
    printf("sizeof(char) = %zu bytes\n", sizeof(char));  // 1 bytes
    printf("sizeof(unsigned char) = %zu bytes\n", sizeof(unsigned char));  // 1 bytes
    printf("sizeof(short) = %zu bytes\n", sizeof(short));  // 2 bytes
    printf("sizeof(unsigned short) = %zu bytes\n", sizeof(unsigned short));  // 2 bytes
    printf("sizeof(int) = %zu bytes\n", sizeof(int)); // 4 bytes
    printf("sizeof(unsigned int) = %zu bytes\n", sizeof(unsigned int)); // 4 bytes
    printf("sizeof(long) = %zu bytes\n", sizeof(long)); // 4 bytes
    printf("sizeof(unsigned long) = %zu bytes\n", sizeof(unsigned long)); // 4 bytes
    
    printf("sizeof(float) = %zu bytes\n", sizeof(float)); // 4 bytes
    printf("sizeof(double) = %zu bytes\n", sizeof(double)); // 8 bytes
    
    printf("sizeof(long int) = %zu bytes\n", sizeof(long int)); // 通常与 sizeof(long) 相同,  4 bytes
    printf("sizeof(long long) = %zu bytes\n", sizeof(long long)); // 8 bytes
    printf("sizeof(long double) = %zu bytes\n", sizeof(long double)); // 16 bytes
    return 0;
}

6.3 对数组使用

        当 sizeof 应用于数组时,它返回整个数组所占的字节大小,即数组所有元素的大小之和。这意味着它返回的是数组的总大小,而不是数组的长度(即元素的个数)。如果想要得到数组的长度,需要自己计算,通常是通过 sizeof(数组) / sizeof(数组[0]) 来实现。

int arr[10];  
printf("%zu\n", sizeof(arr));  // 打印数组的总大小,int 是 4 字节,则为 40  
printf("%zu\n", sizeof(arr) / sizeof(arr[0]));  // 打印数组的长度,即 10

6.4 对指针使用

       当 sizeof 应用于指针时,它返回的是指针本身所占的字节大小,而不是指针所指向的数据的大小或长度。这个大小是固定的,取决于你的系统架构(比如 32 位系统下通常是 4 字节,64 位系统下通常是 8 字节)。

int *ptr = malloc(10 * sizeof(int));  
printf("%zu\n", sizeof(ptr));  // 打印指针的大小,不是 10*sizeof(int),而是指针本身的大小

7 运算符优先级表

        同一优先级的运算符,运算次序由结合方向所决定。
        简单记就是: ! > 算术运算符 > 关系运算符 > && > || > 赋值运算符


8 复杂表达式的计算过程

        前文我们了解到算术运算符的优先级高于关系运算符、关系运算符的优先级高于逻辑与和逻辑或运算符、相同优先级的运算符从左至右进行结合等 。

        简单记就是: ! > 算术运算符 > 关系运算符 > && > || > 赋值运算符

        那么对于表达式 5>3&&8<4-!0 的最终值是多少?计算过程是怎样的?

正确的计算过程如下图所示:

       由于 && 的短路性质 ,所以先计算 && 左边的表达式:5>3 逻辑值为 1,然后计算 && 右边的表达式:8<4-!0 。在表达式 8<4-!0 中,先进行非运算, !0 逻辑值为 1,然后进行算术运算, 4-1 值为 3,然后进行关系运算, 8 < 3 逻辑值为 0,最后进行逻辑运算,1 && 0 逻辑值为 0。

#include <stdio.h>

/* 程序验证结果 */
int main() {
    printf("%d\n", 5 > 3 && 8 < 4 - !0);  // 输出:0
    
    return 0;
}

        再来看这么一个例子:若 a= 2,b=3,c=4,则表达式 a+b<c&&b==c&&a||b+c&&b+c 的计算过程是怎样的?值为多少?

正确的计算过程如下所示:

        由于 && 的短路性质 ,所以先计算 && 左边的表达式 a+b<c,即 2+3<4 逻辑值为 0,所以 && 右边的表达式 b==c&&a 不会执行,即 || 左边的值为 0;现在计算 || 右边的表达式,对于表达式 b+c&&b+c 先进行从左到右的算符运算,然后进行逻辑与运算,即 7&&7,逻辑值为 1;最后进行逻辑或运算,即 0 || 1 ,逻辑值为 1。

#include <stdio.h>

/* 程序验证结果 */
int main() {
    int a = 2, b = 3, c = 4;
    printf("%d\n", a + b < c && b == c && a || b + c && b + c); // 1
    
    return 0;
}

        最后再来看这样一个例子:设有 int a=1,b=2,c=3,d=4,m=2,n=2; 执行 (m=a>b) && (n=c>d) 后 m 和 n 的值是多少?(m=a>b) && (n=c>d) 的结果是多少?计算过程是怎样的?

正确的计算过程如下所示:

         由于 && 的短路性质 ,所以先计算 && 左边的表达式 (m=a>b) ,对于表达式 (m=a>b),先进行关系比较运算,a>b 逻辑值为 0,然后进行赋值运算,即 m=0; 由于 && 左边的表达式为 0,所以右侧表达式不执行。最终,m 的值为 0,n 的值不变还是为 2,整体表达式的结果为0。

#include <stdio.h>

/* 程序验证结果 */
int main() {
    int a = 1, b = 2, c = 3, d = 4, m = 2, n = 2;
    int ret = (m = a > b) && (n = c > d);
    printf("%d\n", m);   // 0
    printf("%d\n", n);   // 2
    printf("%d\n", ret); // 0
    
    return 0;
}

        通过上面几个例子,我们也可以看出来复杂表达式的计算相当不易,所以我们在编写代码时,尽量不要编写这么复杂的代码,能简化最好。 


9 本章判断题 

1、算术运算符包含 “+”、“-”、“*”、“/” 和 “%”,其中乘、除、取余运算符的优先级高于加、减运算符 ?

A. 正确         B. 错误

答案:A

解释:正确的,这个需要记住。


2、“%” 取余(也称为取模运算)可以用于浮点数 ?

A. 正确         B. 错误

答案:B

解释:取模运算只能用于整型数,不能用于浮点数。


3、关系运算符的优先级高于算术运算符 ?

A. 正确         B. 错误

答案:B

解释:关系运算符优先级是低于算术运算符的,记住这个对于初试大题编写是必须的。


4、代码编写 “int a = 5; if (3 < a < 10)” 这种编写方式是否正确 ?

A. 正确         B. 错误

答案:B

解释:在程序中是错误的。首先,无论 a 是大于 3 还是小于 3 ,对于 “3 < a” 这个表达式只有 1 或 0 两种结果。由于 1 和 0 都是小于 10 的,所以无论 a 的值为多少,这个表达式的值始终为真,因此在判断变量 a 是否大于 3 且同时小于 10 时,要写成 “a > 3 && a < 10”,这才是正确的写法。


5、C 语言中逻辑表达式的值是真或假,真的值是 1,假是 0 ?

A. 正确         B. 错误

答案:A

解释:正确的,这个需要记住。


6、逻辑非的优先级高于算术运算符,逻辑与和逻辑或的优先级低于关系运算符  ?

A. 正确         B. 错误

答案:A

解释:逻辑非是单目运算符,优先级高于算术运算符,逻辑与和逻辑或的优先级低于关系运算符。


7、int a = 1, b = 0; b + 2 = a; 这个运算可以正常编译通过 ?

A. 正确         B. 错误

答案:B

解释:b + 2 = a 无法编译通过,因为 b + 2 是表达式,不是变量,没有对应的内存空间,无法作为左值。


8、sizeof(char) 的值是 1 ?

A. 正确         B. 错误

答案:A

解释:sizeof 用来计算不同类型空间大小,char 类型占用一个字节空间大小。


9、int j = 1; j || printf("hello world\n"); 代码运行后,我们会看到“hello world”的打印输出 ?

A. 正确         B. 错误

答案:B

解释:由于 j 等于 1,为真。逻辑或的短路运算,当前一个表达式为真时,后面的表达式不会得到运行,因此 printf("hello world\n"); 不会得到运行,所以看不到其打印输出。


10 OJ 练习

10.1 课时3作业1

        这道题主要是练习关系运算符与逻辑与逻辑运算符,闰年的判断规则是可以被 4 整除同时不能被 100 整除,或者可以被 400 整除,在考研初试时,我们要记住运算符的优先级,不要加太多的括号,避免阅卷老师扣分。
#include <stdio.h>

int main() {
    int year;
    scanf("%d",&year);
    if(year % 4 == 0 && year % 100 != 0 || year % 400 == 0){
        printf("yes\n");
    }else{
        printf("no\n");
    }
    return 0;
}

10.2 课时3作业2

        这个题目主要考察的是 scanf 读取不同数据类型,同时不同的数据类型可以做算术运算,注意控制输出的数据格式。
#include <stdio.h>

int main() {
    int i;
    char j;
    float k;
    scanf("%d %c%f",&i,&j,&k); // 注意 %c 前面的空格
    printf("%.2f\n",i+j+k);    //保留两位小数
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Thanks_ks

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值