操作符详解

文章目录

  • 操作符的分类
  • 1.算数操作符
    • - 加法(+): 将两个操作数相加。
    • - 减法(-): 从第一个操作数中减去第二个操作数。
    • - 乘法(*): 将两个操作数相乘。
    • - 除法(/): 将第一个操作数除以第二个操作数。
    • - 模运算(%): 返回两个操作数相除的余数。
  • 2. 赋值操作符:=和复合赋值
    • 示例代码
  • 3.单目操作符
    • 常见的单目操作符
      • 1. 取反操作符(!)
      • 2. 取地址操作符(&)
      • 3. 解引用操作符(*)
      • 4. 正负号操作符(+,-)
      • 5. 自增和自减操作符(++,--)
        • 前置自增和自减
          • 示例代码
        • 后置自增和自减
          • 示例代码
  • 4.关系操作符概览- `==`:等于`!=`:不等于 `>`:大于 `<`:小于 `>=`:大于等于`<=`:小于等于
    • 示例1:基本比较
    • 示例2:关系操作符在控制流中的应用
      • 给你个例子{ 问题:我们输入一个年龄,如果年龄在18~36,我们输出青年}
  • 5.条件操作符条件操作符有三个组成部分,形式为:`条件 ? 表达式1 : 表达式2`。
    • 优势
    • 示例1:基本用法
    • 示例2:嵌套条件操作符
    • 示例3:与函数结合
    • 注意事项
  • 6.逻辑操作符
    • 逻辑AND (`&&`)
    • 逻辑OR (`||`)
    • 逻辑NOT (`!`)
      • 示例1:使用逻辑AND和逻辑OR
      • 示例2:使用逻辑NOT
    • 注意事项
  • 二进制和进制转换
    • 二进制转十进制
    • 十进制转二进制
    • 示例代码
      • 十进制转二进制
    • 二进制转八进制
    • 二进制转十六进制
  • 原码、反码与补码
    • ***小贴士***
      • 为什么使用补码?
      • 补码的计算
      • 补码的实例分析
  • 7.位操作符概览
    • 位AND(&)
    • 位OR(|)
    • 位XOR(^)
    • 位NOT(~)
      • 示例代码
        • 位AND`&`的使用
        • 位OR`|`和位XOR`^`的使用
        • 位NOT`~`的使用
  • 8.移位操作符
    • 左移操作符
    • 右移操作符
    • ***一道变态的面试题***
      • 使用加减法
      • 使用位操作(XOR)
    • *练习一下噢*
  • 9.逗号表达式
    • 示例:变量初始化
    • 示例:`for`循环中的逗号表达式
      • 也要注意一下
  • 10.下标访问与函数调用
    • 下标访问
      • 示例:数组的声明和下标访问
    • 函数调用
      • 示例:函数的声明和调用
  • 11.理解结构体和成员访问操作符
    • 结构的声明
      • 示例:声明一个结构
    • 结构变量的定义和初始化
      • 示例:定义和初始化结构变量
    • 结构成员访问操作符
      • 直接访问结构成员
    • ***深入探讨一下C语言中结构体嵌套初始化调用***
      • 方法一:依次初始化
      • 方法二:统一初始化
      • 方法三:指针初始化
      • 方法四:C风格初始化
      • 间接访问结构成员
        • 分析
  • 12.操作符的优先级和结合性
      • 操作符的优先级
      • 操作符的结合性
      • 代码示例分析
  • 13.表达式求值:从理解到优化
    • 表达式求值
    • 算术转换
    • 问题表达式解析
    • 以下是一个简单的C语言代码示例,用于展示算术转换和表达式解析的概念:
  • 就这样吧,不想写了

在这里插入图片描述

操作符的分类

在C语言中,操作符可以分为几大类:

  • 算术操作符+, -, *, /, % 等。
  • 关系操作符==, !=, <, >, <=, >=
  • 逻辑操作符&&, ||, !
  • 位操作符&, |, ^, ~, <<, >>
  • 赋值操作符=, +=, -=, *=, /=, %=等。
  • 特殊操作符:逗号,, 条件?:, 下标[], 函数调用(),结构成员访问.->等。

下面我们先简单介绍一下其中比较简单的操作符

1.算数操作符

在写代码时候一定会涉及到计算,C语言中为了简便计算,提供了一系列操作符。其中一组操作符叫:算数操作符,分别是+, -, *, /, `%,都是双目操作符。

- 加法(+): 将两个操作数相加。

- 减法(-): 从第一个操作数中减去第二个操作数。

#include <stdio.h>

int main() 
{
    int x = 4 + 22;  // 加法
    int y = 61 - 23;// 减法
    printf("Sum: %d\n", x);
    printf("Difference: %d\n", y);
   
    return 0;
}

在这里插入图片描述

- 乘法(*): 将两个操作数相乘。

#include <stdio.h>

int main() 
{
    int num = 5;
    int product = num * num;// 乘法
    
   
    printf("Product: %d\n", product);
  
    return 0;
}

在这里插入图片描述

- 除法(/): 将第一个操作数除以第二个操作数。

除法两端如果是整数,执行的是整数除法,得到的结果也是整数

#include <stdio.h>
int main()
{
    float x = 6 / 4;
    int y = 6 / 4;
    printf("%f\n", x);
    printf("%d\n", y);
    return 0;

}

上面示例中,尽管x的类型是float(浮点数),但是6/4的结果是1.0而不是1.5。原因在于c语言里面的整除法是整除,只会返回整数部分,丢弃小数部分。
如果希望得到的是浮点数,两个运算符至少一个是浮点数。这时候c语言会进行浮点数除法。

#include <stdio.h>
int main()
{
    float x = 6.0 / 4;
    
    printf("%f\n", x);
    
    return 0;

}

在这里插入图片描述
不难看出,6.0/4表示进行浮点数除法,得到的结果是1.5
再看一个例子

#include <stdio.h>
int main()
{

    int score = 5;
    score = (score / 20) * 100;
    return 0;
}

上面的代码,你可能会觉得score 会等于25,但是实际上score会等于0,这是因为score /20是整除,会得到一个数字0,所有乘以100后也是0
为了得到预想的结果,可以将除数20改为20.0
,让整除变成浮点除。
在这里插入图片描述

在这里插入图片描述
提一下
%f%lf打印的时候小数点后默认打印6位小数。
%.1f小数点后一位
%.2f小数点后两位

- 模运算(%): 返回两个操作数相除的余数。

这个运算符只能用于整数,不能用于小数。

#include <stdio.h>
int main()
{
    int x = 6 % 4;//2
    return 0;
}

负数求模的规则是:第一个运算符的正负号决定结果运算符。
在这里插入图片描述

2. 赋值操作符:=和复合赋值

  • 赋值(=): 将右侧表达式的值赋给左侧的变量。
  • 加等(+=): 等同于x = x + y
  • 减等(-=): 等同于x = x - y
  • 乘等(*=): 等同于x = x * y
  • 除等(/=): 等同于x = x / y
  • 模等(%=): 等同于x = x % y

示例代码

#include <stdio.h>

int main() 
{
    int x = 10;//初始化

    // 基本赋值
    x = 20;
    printf("x = %d\n", x);
    
    //连续赋值
    int a, b, c, d = 10;

    a = b = c = d;

    printf("a: %d\n", a);
    printf("b: %d\n", b);
    printf("c: %d\n", c);
    printf("d: %d\n", d);
    //虽然连续赋值能够使代码更简洁,
    //但过度使用或在复杂的表达式中使用可能会降低代码的可读性, 
    //适当使用,避免使代码难以理解。


    // 加等
    x += 10; // 等同于 x = x + 10
    printf("x += 10: %d\n", x);

    // 减等
    x -= 5; // 等同于 x = x - 5
    printf("x -= 5: %d\n", x);

    // 乘等
    x *= 2; // 等同于 x = x * 2
    printf("x *= 2: %d\n", x);

    // 除等
    x /= 4; // 等同于 x = x / 4
    printf("x /= 4: %d\n", x);

    // 模等
    x %= 3; // 等同于 x = x % 3
    printf("x %%= 3: %d\n", x);

    return 0;
}

3.单目操作符

单目操作符(Unary Operators)是只需要一个操作数进行运算的操作符。
这种操作符直接作用于它们旁边的一个值上,对其进行修改或者返回一个新的值。

常见的单目操作符

1. 取反操作符(!)

取反操作符!用于逻辑运算,它将其后的布尔值反转。如果操作数为0(假),则返回1(真);如果操作数为非0值(真),则返回0(假)。

#include <stdio.h>

int main() 
{
    int a = 0, b = 1;
    printf("!0 = %d\n", !a); // 输出: !0 = 1
    printf("!1 = %d\n", !b); // 输出: !1 = 0
    return 0;
}

2. 取地址操作符(&)

取地址操作符&返回变量的内存地址。这在指针编程中非常有用,它允许程序直接访问和修改内存中的数据。

#include <stdio.h>

int main() 
{
    int x = 10;
    printf("Address of x: %p\n", &x); // 输出变量x的地址
    return 0;
}

3. 解引用操作符(*)

与取地址操作符相对的是解引用操作符*,它用于访问指针指向的内存地址中存储的值。这是实现动态数据结构如链表和树的关键操作。

#include <stdio.h>

int main() 
{
    int x = 10;
    int *ptr = &x;
    printf("Value of x via ptr: %d\n", *ptr); // 输出: 10
    return 0;
}

4. 正负号操作符(+,-)

虽然正号(+)操作符在C语言中不改变操作数的值,但负号(-)操作符将数字的符号反转。

#include <stdio.h>

int main()
 {
    int a = 5;
    printf("Positive: %d, Negative: %d\n", +a, -a); // 输出: Positive: 5, Negative: -5
    return 0;
}

5. 自增和自减操作符(++,–)

  • 自增操作符(++:使变量的值增加1
  • 自减操作符(--:使变量的值减少1

这两个操作符都有前置和后置两种形式,其区别主要在于表达式的值(即操作的结果)和操作执行的时机。

前置自增和自减

前置自增(++var)和自减(--var)操作符首先增加或减少变量的值,然后返回变量的新值作为表达式的值。

示例代码
#include <stdio.h>

int main() 
{
    int var = 5;
    int result;

    result = ++var; // 先自增,然后赋值
    printf("After Prefix Increment: var=%d, result=%d\n", var, result);

    result = --var; // 先自减,然后赋值
    printf("After Prefix Decrement: var=%d, result=%d\n", var, result);

    return 0;
}

在这里插入图片描述

后置自增和自减

后置自增(var++)和自减(var--)操作符首先返回变量的当前值作为表达式的值,然后增加或减少变量的值。

示例代码
#include <stdio.h>

int main() 
{
    int var = 5;
    int result;

    result = var++; // 先赋值,然后自增
    printf("After Postfix Increment: var=%d, result=%d\n", var, result);

    result = var--; // 先赋值,然后自减
    printf("After Postfix Decrement: var=%d, result=%d\n", var, result);

    return 0;
}

在这里插入图片描述

从示例中不难看出,前置形式直接修改变量的值,然后将新值用作表达式的结果。而后置形式则保留变量原始的值用作表达式的结果,之后才执行增减操作。
这些区别虽细微,但在循环、条件语句以及值的计算中可能会对程序的行为产生重大影响。还是要清楚的。

4.关系操作符概览- ==:等于!=:不等于 >:大于 <:小于 >=:大于等于<=:小于等于

关系操作符是构建逻辑判断和条件控制的基础。在C语言中,这些操作符允许我们比较两个值,并根据比较结果返回真(非零)或假(零)

C语言中的关系操作符包括:

  • ==:等于
  • !=:不等于
  • >:大于
  • <:小于
  • >=:大于等于
  • <=:小于等于

这些操作符用于比较两个操作数的大小或等价性,返回值为1(真)或0(假),这在决策制定、循环控制等方面非常有用。

示例1:基本比较

#include <stdio.h>

int main() 
{
    int a = 5, b = 10;

    printf("a == b: %d\n", a == b); // 检查a是否等于b
    printf("a != b: %d\n", a != b); // 检查a是否不等于b
    printf("a > b: %d\n", a > b);   // 检查a是否大于b
    printf("a < b: %d\n", a < b);   // 检查a是否小于b
    printf("a >= b: %d\n", a >= b); // 检查a是否大于等于b
    printf("a <= b: %d\n", a <= b); // 检查a是否小于等于b

    return 0;
}

这个例子展示了关系操作符最基础的用法,即直接比较两个整数。输出将是一系列01,代表假和真。

示例2:关系操作符在控制流中的应用

关系操作符在决策制定(如if语句)和循环控制(如whilefor循环)中非常重要。

#include <stdio.h>

int main()
 {
    int score = 75;

    // 使用关系操作符在控制流中做决策
    if (score >= 90)
     {
        printf("Excellent\n");
    } else if (score >= 60) 
    {
        printf("Good\n");
    } else
     {
        printf("Need Improvement\n");
    }

    // 使用关系操作符控制循环
    int count = 1;
    while (count <= 5)
     {
        printf("Count: %d\n", count);
        count++;
    }

    return 0;
}

这个例子中,关系操作符帮助程序根据不同的条件执行不同的代码块,并在循环中作为结束条件的一部分。
注意一下
1.相等运算==和赋值运算=是两个不一样的操作符,不要混淆哦,有时候,可能会不小心出现下面代码,它可以运行,但很容易出现意料之外的 结果。

if(x = 3)···

上面例子中,原意是x==3,但是不小心写成了x= 3,它的返回值为3,所以if的判断总为真。
为了防止这些错误,有个大佬告诉我他说,他们喜欢将变量写在等号右边噢

if (3==x)
if (3 = x)
//报错

2.多个操作符不宜连用

i <j <k

上面示例,连续使用两个<,这是合法表达式不会报错,但是不会达到你想要的效果,即不是保证j在i和k中间 ,因为它是从左往右计算的,所以实际执行的是下面的表达式

(i <j)<k

上面式子中,i和j返回0或1与变量k进行比较,如果想要判断j是否在i和k之间
,要这么写

i<j&&j<k

给你个例子{ 问题:我们输入一个年龄,如果年龄在18~36,我们输出青年}

如果我们这么写
在这里插入图片描述
就是我们上面说的,先拿18和10 比较,18<10为假,返回0,0与36比较,0<36为真,所以打印了“青年”,逻辑上有问题,那么应该如何改正呢,

在这里插入图片描述

在这里插入图片描述

5.条件操作符条件操作符有三个组成部分,形式为:条件 ? 表达式1 : 表达式2

其工作原理如下:

  • 首先评估条件(位于?前面的表达式),如果条件为真(非零),则操作符的结果是表达式1的值。
  • 如果条件为假(零),则操作符的结果是表达式2的值。

简而言之,条件操作符允许在一个简单的表达式中执行“如果-那么-否则”的逻辑。

优势

  • 代码简洁:使用条件操作符可以减少必须写的代码行数,尤其是在赋值和简单条件判断中。
  • 提高可读性:对于简单的条件逻辑,使用条件操作符可以使意图更明显,易于理解。
  • 内联赋值:条件操作符允许在赋值语句中直接根据条件选择值。

示例1:基本用法

假设我们需要根据用户的分数(score)来判断他们是否通过了测试:

#include <stdio.h>

int main() 
{
    int score = 85;
    char *result = score >= 60 ? "Passed" : "Failed";
    printf("You have %s the exam.\n", result);
    return 0;
}

示例2:嵌套条件操作符

条件操作符也可以嵌套使用,以处理更复杂的条件逻辑:

#include <stdio.h>

int main() 
{
    int score = 85;
    char *grade = score >= 90 ? "A" :
                  score >= 80 ? "B" :
                  score >= 70 ? "C" :
                  score >= 60 ? "D" : "F";
    printf("Your grade is %s.\n", grade);
    return 0;
}

示例3:与函数结合

条件操作符非常适合与函数调用结合使用,以基于条件执行不同的函数:

#include <stdio.h>

void celebrate() 
{
    printf("Congratulations!\n");
}

void console() 
 {
    printf("Better luck next time.\n");
}

int main() 
{
    int score = 85;
    score > 60 ? celebrate() : console();
    return 0;
}

注意事项

  • 优先级:条件操作符的优先级较低,因此在复杂表达式中使用时可能需要括号来确保正确的评估顺序。
  • 可读性:虽然条件操作符对于简化代码很有用,但过度使用或在复杂逻辑中使用可能会降低代码的可读性。

6.逻辑操作符

在C语言中,逻辑操作符是构建程序逻辑的基础。它们允许我们将多个条件组合在一起,执行复杂的判断。本文旨在详细探讨C语言中的三个逻辑操作符——逻辑AND (&&)、逻辑OR (||)、逻辑NOT (!)——并通过示例展示如何有效使用这些工具。

逻辑AND (&&)

逻辑AND操作符用于组合两个条件,只有当两个条件都为真(非零)时,整个表达式的结果才为真。

逻辑OR (||)

逻辑OR操作符也用于组合两个条件,但只要其中一个条件为真(非零),整个表达式的结果就为真。

逻辑NOT (!)

逻辑NOT操作符用于反转一个条件的真假。如果条件为真(非零),逻辑NOT操作的结果为假(0);如果条件为假(0),操作的结果为真(非零)。

示例1:使用逻辑AND和逻辑OR

假设我们需要判断一个人是否符合投票的条件:年龄必须大于等于18岁且是注册选民。

#include <stdio.h>

int main()
 {
    int age = 20;
    int isRegisteredVoter = 1; // 假设1为真,0为假

    if (age >= 18 && isRegisteredVoter) 
    {
        printf("You are eligible to vote.\n");
    } else 
    {
        printf("You are not eligible to vote.\n");
    }
    
    return 0;
}

示例2:使用逻辑NOT

在某些情况下,我们可能需要检查一个条件不满足的情况。例如,提示用户只有在未满足某个条件时才需要采取行动。
(上面我们已经在单目操作符中介绍过,这里再提一下)

#include <stdio.h>

int main()
 {
    int passwordEnteredCorrectly = 0; // 假设0为假

    if (!passwordEnteredCorrectly)
     {
        printf("Access denied. Please try again.\n");
    }
    
    return 0;
}

注意事项

  • 短路行为:逻辑AND和逻辑OR操作符都有“短路”行为。例如,如果逻辑AND的第一个条件为假,则不会评估第二个条件,因为整个表达式已经确定为假。
  • 优先级:逻辑NOT有比逻辑AND和逻辑OR更高的优先级。当你在同一个表达式中混合使用它们时,适当使用括号来避免逻辑错误。

再介绍剩下的操作符前,先补充一个知识
在深入理解位操作符之前,我们是需要掌握二进制和进制转换的基础知识的。

二进制和进制转换

二进制是一种基数为2的数制,使用两个符号0和1来表示数值。
在计算机科学中,二进制占据核心地位,因为计算机内部所有的数据和指令都是以二进制的形式存储和处理的。
二进制数相对于我们日常使用的十进制数,其优势在于简化了计算机电路的设计——只需处理两种状态:开或关。


在这里插入图片描述

二进制转十进制

二进制转十进制的过程是将每个二进制位(bit)乘以其对应的二进制位权重(2的幂),然后将结果求和。

十进制转二进制

十进制转二进制通常使用除以2的方法,不断除以2并记录余数,然后逆序排列余数得到二进制数
在这里插入图片描述

示例代码

十进制转二进制

#include <stdio.h>

void decimalToBinary(int n)
{
    int binaryNum[32];
    int i = 0;
    while (n > 0)
    {
        binaryNum[i] = n % 2; // 1  0   1  1   1   1   1(binaryNUM)
        n = n / 2;            //62 31  15  7   3   1   0(n)
        i++;                  //1  2   3   4   5   6   7
    }                         //0  1   2   3   4   5   6(数组标号)
    for (int j = i - 1; j >= 0; j--)
        printf("%d", binaryNum[j]);
}

int main()
 {
    int number = 9;
    printf("Decimal: %d\n", number);
    printf("Binary: ");
    decimalToBinary(number);
    printf("\n");
    return 0;
}
#include <stdio.h>

// 十进制转二进制函数示例
void decimalToBinary(int n) 
{
    for (int i = 31; i >= 0; i--) 
    {
        int k = n >> i;//后面会讲到
        if (k & 1)
            printf("1");
        else
            printf("0");
    }
    printf("\n");
}

int main()
 {
    int number = 9;
    printf("Decimal: %d\nBinary: ", number);
    decimalToBinary(number);
    return 0;
}

二进制转八进制

在这里插入图片描述

二进制转十六进制

在这里插入图片描述

原码、反码与补码

在计算机中,整数的二进制可以用原码、反码和补码来表示。
有符号位整数的三种表示方法均有符号位数值位两部分组成,二进制序列中,最高位的0被当作符号位,其余都是数值位
符号位用0表示“正”,1表示“负”
正整数的原 反 补码都相同
负整数的三种表示方法各不同

  • 原码:直接将一个数值转换为二进制表示。
  • 反码:将源码除了符号位,其他所有的位取反。
  • 补码:反码的基础上加1。
    补码得到也可以使用取反,+1的操作

小贴士

对于整型数据来说,它们在计算机内存中的存放形式是以补码(Two’s Complement)的方式。
这种表示法不仅解决了原码和反码在表示负数时存在的问题,如符号位的处理和0的多种表示方式,而且还使得加法和减法运算统一化,简化了计算机的硬件实现。

为什么使用补码?

  1. 避免正负零:原码表示法中,+0和-0有不同的表示,这在计算时可能会造成不必要的麻烦。补码方式只有一个零的表示,即所有位都是0。
  2. 统一加减法:使用补码,减法运算可以转换为加法运算,这样计算机只需要实现加法器即可处理所有的加减运算,简化了硬件设计。
  3. 扩展数值范围:在相同的位数下,补码表示法能表示的整数范围比原码和反码大。例如,在8位二进制中,补码能表示的范围是-128到+127。

补码的计算

对于一个正整数,它的补码与其二进制表示相同。对于一个负整数,它的补码是其绝对值的二进制表示的反码(每一位取反)加1。例如,对于8位的整数:

  • +5 的二进制表示为 0000 0101,

  • 补码也是 0000 0101。

  • -5 的补码计算步骤如下:

    • -5 的二进制表示为 1000 0101。
    • 取反得到 1111 1010。
    • 加1得到 1111 1011,这就是-5的补码表示。

补码的实例分析

考虑一个8位的情况,整数-1在内存中的表示如下:

  1. +1的二进制表示为0000 0001。
  2. 取反得到1111 1110。
  3. 加1得到1111 1111,这是-1的补码表示。

因此,-1在计算机中以1111 1111的形式存储。

补码不仅解决了表示负数的问题,而且还简化了算术运算,使计算机的硬件实现更加高效。

7.位操作符概览

C语言提供了六种位操作符:

  • &:位AND
  • |:位OR
  • ^:位XOR(按位异或)
  • ~:位NOT(按位取反)
  • <<:左移
  • >>:右移

位AND(&)

位AND操作符用于对两个数值的每个位执行AND操作。如果两个相应的位都是1,则结果位为1;否则为0。

位OR(|)

位OR操作符对两个数值的每个位执行OR操作。如果两个相应的位中至少有一个为1,则结果位为1;如果都是0,则结果位为0。

位XOR(^)

位XOR操作符对两个数值的每个位执行异或操作。如果两个相应的位一个为1另一个为0,则结果位为1;如果两个相应的位都是0或都是1,则结果位为0。

位NOT(~)

位NOT操作符对数值的每个位执行取反操作。1变为0,0变为1。

它们的操作数必须是整数
直接上代码

示例代码

位AND&的使用
#include <stdio.h>

int main()
 {
    unsigned int a = 12; // 二进制: 1100
    unsigned int b = 10; // 二进制: 1010
    unsigned int result = a & b; // 结果: 1000 (8)
    printf("Result of %d & %d = %d\n", a, b, result);
    return 0;
}

规则:&两个是1才是1,有0就是0

位OR|和位XOR^的使用
#include <stdio.h>

int main() 
{
    unsigned int a = 12; // 二进制: 1100
    unsigned int b = 10; // 二进制: 1010
    unsigned int resultOr = a | b; // 结果: 1110 (14)
    unsigned int resultXor = a ^ b; // 结果: 0110 (6)
    printf("Result of %d | %d = %d\n", a, b, resultOr);
    printf("Result of %d ^ %d = %d\n", a, b, resultXor);
    return 0;
}

|有1就是1,全是0才是0
^相同为0,相异为1

位NOT~的使用
#include <stdio.h>

int main() 
{
    unsigned int a = 12; // 二进制: 1100
    unsigned int resultNot = ~a; // 结果: ...11110011 (取决于unsigned int的大小)
    printf("Result of ~%d = %u\n", a, resultNot);
    return 0;
}

~就是按位取反
注意
a^a——全0;
a^a——还是a

#include <stdio.h>

int main() {
    unsigned int a = 5;  // 0101
    unsigned int b = 9;  // 1001
    printf("a & b = %d\n", a & b); // 位与
    printf("a | b = %d\n", a | b); // 位或
    printf("a ^ b = %d\n", a ^ b); // 位异或
    printf("~a = %d\n", ~a); // 位非
    return 0;
}

8.移位操作符

移位操作符<<>>可以用于对二进制位进行左移或右移操作。
移位操作符的操作数只能是整数

左移操作符

移位规则:左边丢弃,右边补0;
在这里插入图片描述

在这里插入图片描述

右移操作符

移位规则:首先右移运算分为两种
1.逻辑右移:左边用0填充,右边丢弃
2.算数右移:左边用该值的符号位填充,右边丢弃

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
**注意:**对于移位运算符,不要移动负数位,这是未定义的

int num = 10;
num>>-1;//error

一道变态的面试题

不创建第三个变量,实现两个整数的交换

使用加减法

这种方法的基本思想是利用加法和减法来累积两个数的总和,并通过相减得到另一个数。这种方法可能会在数值过大时导致整数溢出。

#include <stdio.h>

int main() 
{
    int a = 10, b = 20;

    printf("Before swap: a = %d, b = %d\n", a, b);

    // 交换a和b的值
    a = a + b;
    b = a - b; // Now b is original value of a
    a = a - b; // Now a is original value of b

    printf("After swap: a = %d, b = %d\n", a, b);

    return 0;
}

使用位操作(XOR)

这种方法使用了XOR位操作的性质。XOR(异或)操作有一个特性:任何数和自己做异或运算的结果都为0,任何数和0做异或运算的结果都是数本身。此方法不会因为数值过大而导致溢出问题。

#include <stdio.h>

int main()
 {
    int a = 10, b = 20;

    printf("Before swap: a = %d, b = %d\n", a, b);

    // 交换a和b的值
    a = a ^ b;
    b = a ^ b; // Now b is original value of a
    a = a ^ b; // Now a is original value of b

    printf("After swap: a = %d, b = %d\n", a, b);

    return 0;
}

以上两种方法都能实现不使用临时变量交换两个整数的值,但使用位操作的方法更为通用,因为它避免了可能的溢出问题。

练习一下噢

1.求一个整数在存储中的二进制的1的个数
下面我将通过展示Brian Kernighan算法来实现我的目的,它的基本思想是反复地清除最低位的1,直到整数变为零,过程中清除的1的个数就是该整数二进制表示中1的总数。

#include <stdio.h>

int countBits(int n)
 {
    int count = 0;
    while (n)
    {
        n &= (n - 1); // 清除最低位的1
        count++;
    }
    return count;
}

int main()
 {
    int num;
    printf("Enter an integer: ");
    scanf("%d", &num);
    
    int bits = c.
    0ountBits(num);
    printf("The number of 1s in the binary representation of %d is: %d\n", num, bits);
    
    return 0;
}

2.二进制位置为0或1
下面我们以13为例 ,将二进制序列的第五位修改为1,然后再返回

要知道修改一个整数的二进制表示中的特定位,我们可以使用位操作符。
具体来说,为了将第5位改为1,我们可以创建一个只在第5位为1的掩码(mask),然后使用位OR操作符(|)将这个掩码应用到原始数字上。在这个上下文中,位的计数通常从右到左开始,从第0位开始计数。

我将演示如何将数字13(二进制为0000 1101)的第5位改为1(注意,第5位是从右边数的第六个位,二进制为0010 1101,十进制为45):
在这里插入图片描述

#include <stdio.h>

int main() 
{
    int a = 13;
    a = a |(1<<4);
    printf("a = %d\n",a);
    a = a & ~(1<<4);
    printf("a = %d\n",a);
    return 0;
}

在这里插入图片描述

在C语言中,逗号表达式是一种使用逗号运算符 , 来分隔两个或多个表达式的方式,其中每个表达式都会被求值,但整个逗号表达式的结果是最右边表达式的值。逗号表达式常用于需要在单个语句中顺序执行多个操作的场合,尤其是在for循环和变量初始化中。尽管它的使用不如其他语言结构频繁,理解其工作原理对于编写更紧凑、高效的C代码非常有帮助。

9.逗号表达式

逗号表达式的一般形式如下:

(expression1, expression2, ..., expressionN)

在这个表达式中,expression1expressionN-1 会按顺序被求值,但它们的结果会被丢弃,整个逗号表达式的结果是 expressionN 的值。

示例:变量初始化

逗号表达式可以在单个语句中初始化多个变量,尤其是在for循环中。

#include <stdio.h>

int main() 
{
    int a = 1, b = 2;
    int sum;

    sum = (a++, b++, a + b);

    printf("a = %d, b = %d, sum = %d\n", a, b, sum);

    return 0;
}

在这个例子中,ab 首先被初始化为1和2。接着,逗号表达式 (a++, b++, a + b) 被执行,其中 ab 都被增加了1,然后计算 a + b 的值作为整个逗号表达式的结果赋给 sum。因此,输出将会是:

a = 2, b = 3, sum = 5

示例:for循环中的逗号表达式

逗号表达式在for循环中尤其有用,可以在循环的一部分中执行多个操作。

#include <stdio.h>

int main() 
{
    for (int i = 0, j = 10; i != j; i++, j--)
     {
        printf("i = %d, j = %d\n", i, j);
    }
    return 0;
}

这个例子中,在for循环的初始化部分同时声明了两个变量ij,而在循环的迭代部分使用逗号表达式执行了两个操作:i++j--。这让我们能够在单个循环中向两个方向迭代,直到ij相遇。

也要注意一下

虽然逗号表达式可以让代码更紧凑,但它也可能使代码更难读懂。因此,在决定使用逗号表达式时,应该考虑代码的可读性和维护性。

10.下标访问与函数调用

下标访问

在C语言中,数组是一种基础的数据结构,用于存储相同类型的数据序列。数组中的每个数据项可以通过下标访问。下标通常从0开始,这意味着数组中的第一个元素的下标是0,第二个元素的下标是1,依此类推。

示例:数组的声明和下标访问

#include <stdio.h>

int main()
 {
    int numbers[] = {10, 20, 30, 40, 50};
    int size = sizeof(numbers) / sizeof(numbers[0]);

    for (int i = 0; i < size; i++) 
    {
        printf("Element at index %d is %d\n", i, numbers[i]);
    }

    return 0;
}

在这个例子中,我们声明了一个整型数组numbers,并初始化了5个元素。通过sizeof(numbers) / sizeof(numbers[0])计算得到数组的大小size。然后,我们使用for循环和下标访问每个元素,并打印出来。

函数调用

函数是C语言中用于封装代码的单元,使得代码更加模块化和可重用。通过函数,我们可以将代码划分为逻辑块,每个块执行特定的任务。

示例:函数的声明和调用

#include <stdio.h>

int sum(int a, int b)
 {
    return a + b;
}

int main() 
{
    int result = sum(5, 10);
    printf("The sum is %d\n", result);
    return 0;
}

在这个例子中,我们定义了一个名为sum的函数,它接受两个整数参数,返回它们的和。在main函数中,我们调用了sum函数并打印结果。

在上述例子中,当main函数调用sum函数时,程序执行流程跳转到sum函数,执行完成后再返回到main函数继续执行。在调用sum函数时,参数510被传递给sum,并且sum的返回值被赋给result变量。

函数调用不仅仅是跳转和执行代码那么简单,它还涉及到参数传递、返回值处理和调用栈管理。当函数被调用时,其参数会被压入调用栈,函数执行完成后,返回值会被传递回调用者,并且相关的栈帧会被清理。这个过程支持了C语言中函数的嵌套调用和递归调用。

11.理解结构体和成员访问操作符

结构体是一种复合数据类型,允许我们将多个不同类型的数据项组合成一个单一的实体。
这种特性让结构体成为组织和管理相关数据的强大工具,特别是在处理较为复杂的数据模型时。

结构的声明

结构的声明定义了一个新的数据类型,指定了结构中包含的成员和每个成员的类型。结构的声明使用struct关键字。

示例:声明一个结构

struct Person 
{
    char name[50];
    int age;
    float salary;
};

注意分号不能丢噢,在分号里面可以直接免去类型声明直接定义变量
在这个例子中,我们声明了一个名为Person的结构,它包含三个成员:一个字符数组name用于存储名字,一个整数age表示年龄,和一个浮点数salary表示薪水。

结构变量的定义和初始化

一旦声明了结构,就可以定义该结构的变量,并进行初始化。

示例:定义和初始化结构变量

struct Person person1 = {"Alice", 30, 50000.0};

这里,我们定义了一个Person结构的变量person1,并在定义时初始化成员值。

结构成员访问操作符

C语言提供了两种操作符来访问结构成员:点操作符(.)用于直接访问,箭头操作符(->)用于间接访问。

直接访问结构成员

当你有一个结构变量时,可以使用点操作符.来访问其成员。

#include <stdio.h>

int main()
 {
    struct Person person1 = {"Bob", 25, 30000.0};
    printf("Name: %s\n", person1.name);
    printf("Age: %d\n", person1.age);
    printf("Salary: %.2f\n", person1.salary);

    return 0;
}

深入探讨一下C语言中结构体嵌套初始化调用

摘要:
结构体是C语言中一种非常重要的数据结构,通过结构体的嵌套定义,可以构建更加复杂和
在C语言中,结构体可以嵌套定义,即一个结构体中包含另一个结构体作为成员。这种结构体的嵌套定义可以帮助我们组织复杂的数据结构,使得数据之间的关系更加清晰。
下面我将通过几种方法来演示结构体嵌套初始化的调用方式,并分析其实现原理。

方法一:依次初始化

依次初始化是最直观的一种方式,按照结构体成员的顺序依次为每个成员赋值。下面是一个示例代码:

struct Date
 {
    int year;
    int month;
    int day;
};

struct Goods 
{
    int num;
    struct Date in_time;
    struct Date pro_time;
};

int main() 
{
    struct Goods goods1 =
     {
        11,
        {2012, 1, 1},
        {2011, 12, 1}
    };
    
    // 输出初始化结果
    // 省略部分代码
}

在上面的示例中,我们先初始化一个Goods结构体变量goods1,并依次为每个成员赋值,包括嵌套结构体Date的成员。

方法二:统一初始化

从C99标准开始,C语言支持结构体的统一初始化方式,可以在创建结构体变量时使用大括号{}对所有成员进行统一初始化。下面是统一初始化的示例代码:

struct Goods goods2 = {11, {2012, 1, 1}, {2011, 12, 1}};

通过统一初始化的方式,我们可以更加简洁地初始化嵌套结构体的成员,提高了代码的可读性和维护性。

方法三:指针初始化

除了直接初始化方式外,我们还可以通过指针来初始化结构体及其嵌套结构体的成员。下面是一个使用指针初始化的示例代码:

struct Goods goods3;
struct Date in_time = {2012, 1, 1};
struct Date pro_time = {2011, 12, 1};

goods3.num = 11;
goods3.in_time = in_time;
goods3.pro_time = pro_time;

通过指针初始化的方式,我们可以更加灵活地控制结构体成员的赋值过程,适用于一些复杂的初始化逻辑。

方法四:C风格初始化

C风格的初始化方式使用键值对的方式进行初始化,使得每个成员的赋值更加明确。但需要注意的是,C风格初始化方式不支持结构体嵌套。下面是一个C风格初始化的示例代码:

struct Goods goods4 = {.num = 11, .in_time = {2012, 1, 1}, .pro_time = {2011, 12, 1}};

间接访问结构成员

如果你有一个指向结构的指针,则使用箭头操作符->来访问结构的成员。

#include <stdio.h>

int main()
 {
    struct Person person1 = {"Charlie", 28, 40000.0};
    struct Person *ptr = &person1;

    printf("Name: %s\n", ptr->name);
    printf("Age: %d\n", ptr->age);
    printf("Salary: %.2f\n", ptr->salary);

    return 0;
}

在这个例子中,我们首先定义了一个Person类型的变量person1,然后创建了一个指向person1的指针ptr。通过ptr,我们使用->操作符来访问person1的成员。

分析

使用结构体可以把相关的数据组织在一起,使得代码更加清晰和易于管理。通过结构体,我们可以创建更复杂的数据模型,如链表、树等。

  • 直接访问操作符.:当你直接操作结构体变量时使用。它简单直接,用于访问具体实例的成员。
  • 间接访问操作符->:当你通过指针操作结构体时使用。它简化了指针访问成员的语法,使得代码更加简洁。

12.操作符的优先级和结合性

式。正确理解和运用操作符的优先级和结合性规则可以帮助程序员编写更加清晰和准确的代码。

操作符的优先级

C语言中的操作符根据优先级的不同,会在表达式中按照一定的顺序执行。以下是一些常见操作符的优先级列表(由高到低):

  1. 后缀操作符:() [] -> .
  2. 一元操作符:+ - ! ~ ++ – (type) * & sizeof
  3. 乘除模操作符:* / %
  4. 加减操作符:+ -
  5. 移位操作符:<< >>
  6. 关系操作符:< <= > >=
  7. 相等操作符:== !=
  8. 位与操作符:&
  9. 位异或操作符:^
  10. 位或操作符:|
  11. 逻辑与操作符:&&
  12. 逻辑或操作符:||
  13. 条件操作符:? :
  14. 赋值操作符:= += -= *= /= %= >>= <<= &= ^= |=

在表达式中,操作符的优先级决定了执行顺序,优先级高的操作符先执行,优先级相同的操作符根据结合性决定执行顺序。

操作符的结合性

操作符的结合性指的是相同优先级的操作符在表达式中的执行顺序。C语言中,大部分操作符都是从左向右结合的,即先结合左侧的操作数,然后再结合右侧的操作数。例如,加法操作符+就是一个从左向右结合的操作符。

但是,也有一些操作符是从右向左结合的,比如赋值操作符=和逗号操作符,。它们会先结合右侧的操作数,然后再结合左侧的操作数。

代码示例分析

让我们通过一个简单的代码示例来理解操作符的优先级和结合性:

#include <stdio.h>

int main()
 {
    int result;
    int a = 5, b = 3, c = 2;

    result = a * b + c;
    printf("Result 1: %d\n", result);  // 结果为 17

    result = a * (b + c);
    printf("Result 2: %d\n", result);  // 结果为 25

    result = a % b / c;
    printf("Result 3: %d\n", result);  // 结果为 2

    result = a += b *= c;
    printf("Result 4: %d\n", result);  // 结果为 11

    return 0;
}

在上面的示例代码中,可以利用不同的操作符组合来演示操作符的优先级和结合性。通过调整操作符的位置和添加括号,可以改变表达式的计算结果,从而体会操作符优先级和结合性的影响。

操作符优先级

13.表达式求值:从理解到优化

表达式求值

是编程中一个基础且重要的概念。它涉及到如何解析、计算并返回表达式的值。在C语言中,表达式求值涉及到算术转换、运算符优先级和结合性等多个方面。

算术转换

是指在计算表达式时,编译器会对操作数进行类型转换。例如,在进行加法运算时,如果一个操作数是整数,另一个操作数是浮点数,那么编译器会先将整数转换为浮点数再进行运算。这种转换是隐式的,我们无需显式地指定转换类型。

问题表达式解析

理解并解析问题表达式是表达式的核心任务。C语言提供了许多运算符(如+、-、、/等)以及一些特殊的运算符(如+=、-=、=、/=等)来进行操作。解析一个表达式就是识别出这些运算符和操作数,并按照运算符的优先级和结合性进行计算。

以下是一个简单的C语言代码示例,用于展示算术转换和表达式解析的概念:

#include <stdio.h>

int main()
 {
    int a = 5;
    float b = 3.5;
    int result;

    // 算术转换示例
    result = a + b * 2; // a将被转换为浮点数,b保持为整数
    printf("Result: %f\n", result); // 结果为8.5,符合预期

    // 问题表达式解析示例
    result = a + (b = 2) * 3; // 先解析括号内的表达式,再对b进行赋值,最后进行乘法运算
    printf("After b = 2: %d\n", b); // b被设置为2,打印出2
    printf("Result: %d\n", result); // 结果为6,符合预期
    return 0;
}

就这样吧,不想写了

如果你认真的看到了这,那你真的,我哭死
写的我要碎掉了
如果能帮到你的话,那就太酷啦
在这里插入图片描述
在这里插入图片描述

  • 44
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 16
    评论
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

老衲在深渊

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

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

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

打赏作者

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

抵扣说明:

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

余额充值