C语言学习笔记(二)

寒假时在b站上挑了个C语言的课程,花了一个多月看完了,也算是入门C语言了吧。

在看那个课时,讲师很多次强调,一定要多写博客,来记录自己的学习。

显然,以我的自制力,课程看完了,博客是一篇没写。

现在C语言算是入门了,但还是缺乏巩固。

于是乎,便打算写几篇C语言学习的总复盘,用于巩固昔日所学。

后面我会花几天,尽力去复盘学到的知识结合自己为数不多的做题经验,写成博客,并坚持更新下去。

这篇是第二章

上一篇很幸运的被选为“每天最佳新人”了,还是很惊喜的。

书接上回,这次主要复盘运算符

运算符

运算符分为以下几类

算数运算符、关系运算符、逻辑运算符、位运算符、赋值运算符、指针运算符、其它运算符

就像日常使用的加减乘除那些数学运算一样,这些运算符也都是有优先级的,优先级高的运算符会优先计算。

以下是C语言中运算符优先级的一个简要排序,从高到低:

优先级

  1. 圆括号 ()(包括函数调用和强制类型转换)

  2. 后缀自增/自减运算符 ++--

  3. 一元运算符 +-(作为负号时)、!(逻辑非)、~(按位取反)、&(取地址)

  4. 乘法 *、除法 /、求余 %

  5. 加法 +、减法 -

  6. 左移 <<、右移 >>

  7. 关系运算符 <><=>===!=

  8. 按位与 &

  9. 按位异或 ^

  10. 按位或 |

  11. 逻辑与 &&

  12. 逻辑或 ||

  13. 条件运算符(三目运算符)? :

  14. 赋值运算符 = 以及扩展赋值运算符 +=-=*=/=%=, <<=>>=&=^=|=

  15. 逗号 ,(逗号表达式)

注意,虽然优先级规定了运算符的计算顺序,但在有相同优先级的运算符出现时,还需要考虑运算符的结合性。大多数二元运算符都是从左到右结合,但赋值运算符和逻辑或运算符是从右到左结合的。圆括号可以用来改变运算的优先级和结合性,确保某个子表达式的计算顺序。

算术运算符

+和-我想没有什么讲的必要了,我们着重讲解下除(/)和取余(%)

/除

当是两个整数进行/运算时

int a=10;
int b=3;
int result1;

result1=a/b;//得到10/3,但由于result是整数,故实际的到的是3

如果希望得到小数结果,那么a、b至少有一个应该为浮点数类型

float a=10.0;
float b=3.0;
float result2;

result2=a/b;//得到10/3,由于result2为浮点数类型,故实际得到的是3.33333

但是如果是在直接进行实数运算时

float a=2;
float b=a+3/2;//b的值为3.000000
float c=a+3.0/2;//c的值为3.50000

即,直接进行实数运算,如果希望保留小数位,那么运算时要加上“.0”

%取余

取余。就是得到除后的余数。

即,10➗3=3...1 —— 10/3=3,10%3=1

int x=10;
int y=3;
int x/y;//得到 3
int x%y;//得到 1

取余常用于判断一个数是否为偶数

if(a%2==0)
{printf("偶数");}
//即,如果一个数对2取余,得到0,即这个数除以二没有余数,那么就说明这个数是偶数

因此更准确说,取余最大作用就是判断一个数是不是另一个数的倍数

a%n==0//就是说a为n的倍数
c==a/n;//c得到a是n的几倍

那么根据这个思路,就可以通过取余,每隔几位数进行一次操作。

取余也常用于单独拿出一个数的个位/十位/百位。这个在单片机的数码管、显示屏上常用。

int a=5614;
int gewei=a%10;//4
int shiwei=a%100-gewei;//14-4=10
int baiwei=a%1000-shiwei-gewei;//614-10-4=600

因此如果将取余(%)和除(/)结合在一起,就能通过循环提取一个数的每一位

int a=114514;
while(a)
{
//将每一位放到数组中
arr[i++]=a%10;
//每一位相加
sum=sum+a%10;
a/=10;
}

通过这个,我们就可以将数据分块处理。

++/--自增/自减

自增/自减,一般默认是自增1/自减1,即 i++ 等价于 i=i+1。

自增自减有前置和后置的区别,即 i++ 和 ++i 是存在区别的。

i++:后缀自增,先返回当前变量的值,然后再增加变量的值。

++i:前缀自增,先增加变量的值,再返回增加后变量的值。

int i=1;
while(--i);
printf("%d",i);//输出0

int j=1;
while(j--);
printf("%d",j);//输出-1

关系运算符

就是很常见的大小比较。但有一些细节需要注意

例如在for循环中使用<还是<=

for(int i=0;i<10;i++)//方式一
for(int i=1;i<=10;i++)//方式二

这两种都是进行了10次循环,差别是对i的初始化,是0还是1。

方式一更适合数组,因为数组下标是从0开始,即arr[i];

方式二更能直观地看到循环了几次,从1到10十次。

还有==

相信大家在这个上面没少犯错。在条件判断中,==经常被不小心写成=

if(a=0)//由于=是赋值,无论a为何值因此a必定被赋值为0,()中返回值仍为1,满足循环条件
if(a==0)//这才是把a和0做比较,只有当a==0时,才满足条件

但当写上a=0时,编译器往往不会报错。因此在处理a==0时,建议把0写到左边,即0==a,这样如果不小心写成0=a,那么编译器会报错,就能及时发现处理。

逻辑运算符

&&逻辑与

A&&B,当A和B同为真时结果才为真

短路逻辑:

A&&B&&C&&D,从左到右依次判断真假,若C为假,则不再判断D,即不再执行D内的语句

int a = 0;
int b = 1;

if (a != 0 && b --) {
    // 如果 a 为0,那么 b 是否为0并不重要,因为整个表达式注定为假
    // 所以在 a 为0的情况下,sth() 不会被调用
}
||逻辑或

A||B,当A和B任意为真时,结果为真;两者均为假,结果才为假。

短路逻辑:

A||B||C||D,从左到右以此判断,若A、B为假,C为真,则不再判断D

int x = -1;
int y = sth();

if (x > 0 || y) {
    // 如果 x > 0 为真,那么无需检查 y 的值,因为整个表达式已确定为真
    // 因此,在 x > 0 的情况下,sth() 不会被调用
}
!逻辑非

在嵌入式中经常会看到这样的语句:

while(!Sth_Init());

就是反复初始化一个外设,直到外设初始化成功,Init函数返回1,再由!1成为0。如果初始化不成功,则是返回0,再由!0转为1,循环继续。

^逻辑异或

A^B,在A和B只有一个为真时,结果为真;若同时为真或同时为假,则结果为假。

位运算符

位运算符是我在初次学习时最不重视的东西,但当做到一个个有趣的题时,我才意识到位运算符的魅力。

位运算符是在二进制的基础上进行运算的,即,如果你输入了一个十进制的数,编译器会把先转化为二进制,再进行计算。

所以这里先扯一下二进制

二进制

二进制就是逢二进一,只有0和1这两个数字。

二进制位其实就是比特(bit)位,比如int类型占2个字节,也就是16个bit位,因此最大的数为2^16。也就是16个空间,每个空间有0或1这两个数字。

如果要在程序中表示二进制,直接int a=00001000是不行的,常常要在数值前加上0b,即int a=0b00001000。同样对于八进制(加0)、十六进制(加0x)也有同样的规则

(C语言无法直接表示二进制!!!即0b00001001这样的不能直接写到程序里。这个要记住,0b只是为了在演示时声明)

请注意,0b00001000 形式的二进制字面量虽然在一些现代C编译器(如gcc和clang)中可用,但并不属于ANSI C标准的一部分。
在标准C中,推荐使用十六进制来间接表示二进制数,因为每四位十六进制数正好对应一个字节的二进制数,
转换关系直观且简单。如:
一个字节有8个bit位,即 0000 0000,这也就是一个字节的二进制数
如果一个十六进制为0x1A,也就是十进制的26,对应二进制 0001 1010。每四位一组是因为四位二进制最大值正好为15,到16进1
0001-1010
 1----A
正好和十六进制的两位对应

其实如果这样对应八进制数字也可以,那么就是三位一组 00 000 000。26对应八进制032
00-011-010
0---3---2

上面这些其实在嵌入式中应用的更多。

所以为了展现二进制的这种层次,下面对于二进制的表示都会通过每四位一组分开

二进制特点:

若为偶数,则尾项为0,若为奇数,则尾项为1。因此可以通过与1按位与来判断其奇偶

if(y&1)//y为偶数,则不执行,为奇数则执行
//1实质上就是 0000 0001
//如 0010 1101 & 0000 0001----0000 0001,为奇数
//如 0010 1100 & 0000 0001----0000 0000,为偶数
if(y%2)//类似于与2取余,为偶数,则不执行,为奇数则执行
&按位与

1&1==1,1&0==0,0&0==0

按位与有以下这些常用功能:

  1. 提取特定位:

    //掩码操作
    int value = 0b11111010; // 假设value是我们感兴趣的特定几位数据混合在一起
    int mask  = 0b00000111; // 创建一个掩码,只想保留最后三位
    int extracted_bits = value & mask; // 提取最后三位,结果为0b00000010
  2. 清除特定位:

    int flags = 0b00100101; // 初始化标志变量
    // 若要清除第3位
    flags &= ~(1 << 2); // flags现在为0,因为在掩码中第3位变成了0,与操作后该位变为0
    //1<<2是将1左移2位,即0000 0001<<2---0000 0100,在通过取反得到 1111 1011
  3. n=n&(n-1)可以不断将后面的1依次变为0,

    即在每次循环时
    0101 1001
    0101 1000
    0101 0000
    0100 0000
    0000 0000

这样就可以通过循环次数,计算一个二进制数中1的个数

/*
输入一个整数 n ,输出该数32位二进制表示中1的个数。其中负数用补码表示。
*/
class Solution {
public:
    int NumberOf1(int n) {
        int res = 0;
        //当n为0时停止比较
        while(n){  
            n &= n - 1;
            res++;
        }
        return res;
     }
};
|按位或

1|1==1,1|0==1,0|0==0

按位或有以下常用功能:

  1. 设置特定位:

    int flags = 0; // 初始化标志变量0b00000000
    // 若要设置第3位(从0开始计数)
    flags |= (1 << 2); // flags现在为0b00001000
  2. 旋转位:

    int rotate_left(int n, int shift) {
        return ((n << shift) | (n >> (sizeof(n) * CHAR_BIT - shift))) & ((1 << (sizeof(n) * CHAR_BIT)) - 1);
    }
    
    int rotate_right(int n, int shift) {
        return ((n >> shift) | (n << (sizeof(n) * CHAR_BIT - shift))) & ((1 << (sizeof(n) * CHAR_BIT)) - 1);
    }
^按位异或

0^1==1,1^1==0,0^0==0

异或操作符有一些很妙的性质:

  1. 自反性:任何数与自身的异或结果总是0。即 a ^ a = 0

而任何数^0,结果为原数。

  1. 交换律:异或运算符满足交换律,即 a ^ b = b ^ a

  2. 结合律:异或运算符满足结合律,即 (a ^ b) ^ c = a ^ (b ^ c)

  3. 异号得1,同号得0:如同上面的真值表所示,异或操作实际上是检查两个位是否不同。

  4. 异或运算可以用来交换两个变量的值而不需额外的临时变量(称为无临时变量交换):a ^= b; b ^= a; a ^= b; 这三个表达式执行完毕后,ab 的值将会互换。

通过以上性质就可以实现下面这些有用的功能:

  • 检测一个数的奇偶性:如果一个数与1进行异或运算,结果为1,则该数为奇数,否则为偶数,因为二进制下最后一个比特位(最低位)为1代表奇数,为0代表偶数。

    if(x^1)//x为偶数,即最低位为0,则执行语句
  • 翻转特定位:如果想要翻转一个整数的特定位(例如第n位),可以对该整数与 1 << n 进行异或运算,其中 1 << n 是一个仅第n位为1的数。

    int flags = 0b00100101; // 初始化标志变量
    // 若要清除第3位
    flags ^= (1 << 2); //flags变为0b00100001,第三位被翻转
  • 查找仅出现一次的数字:在一个数组中,如果只有一个数字出现奇数次,其余数字都出现偶数次,可以对数组中的所有元素进行异或操作,结果将是那个唯一出现奇数次的数字。如果有两个数字各出现一次,通过异或操作可以得到这两个数字的异或结果,然后通过位操作分离出这两个数字。

    /*
    用C语言函数指针实现以下内容: 一个数组中只有两个数字是出现一次,其他所有数字都出现了两次。
    编写一个函数找出这两个只出现一次的数字。
    例如:
    有数组的元素是:1,2,3,4,5,1,2,3,4,6
    只有5和6只出现1次,要找出5和6.
    */
    
    #include <stdio.h>
    // 定义异或操作的函数
    int xor_operation(int a, int b) {
        return a ^ b;
    }
    
    // 函数指针类型声明
    typedef int (*XorFunc)(int, int);
    
    // 主要逻辑函数:找出数组中只出现一次的两个数字
    void find_single_numbers(int arr[], int size, XorFunc xor_func, int *num1, int *num2) {
        int single_num = 0;
        for (int i = 0; i < size; ++i) {
            single_num ^= arr[i];
        }
    
        // 找出最低位为1的比特位
        int bit_mask = 1;
        while ((single_num & bit_mask) == 0) {
            bit_mask <<= 1;
        }
    
        // 分离出两个只出现一次的数字
        int num1_val = 0, num2_val = 0;
        for (int i = 0; i < size; ++i) {
            if ((arr[i] & bit_mask) == 0) {
                num1_val ^= arr[i];
            } else {
                num2_val ^= arr[i];
            }
        }
        // 输出结果
        *num1 = num1_val;
        *num2 = num2_val;
    }
    
    int main() {
        int arr[] = {1, 2, 3, 4, 5, 1, 2, 3, 4, 6};
        int size = sizeof(arr) / sizeof(arr[0]);
        int num1, num2;
        XorFunc xor_func_ptr = xor_operation;
        find_single_numbers(arr, size, xor_func_ptr, &num1, &num2);
        printf("只出现一次的数字是:%d 和 %d\n", num1, num2);
        return 0;
    }
>>右移

如果要让一个数不断自减直至为0,最常见的就是让它在每次循环时减一。

如果二进制数末尾为0,是偶数,>>1就相当于÷2。

如果二进制数末尾为1,为奇数,>>1就相当于-1再÷2。

if(x){
    x>>=1;//不断向右移位,直到为0。
}
<<左移

在讲&和|时都有在代码中看到<<。这就是<<最常用的功能,就是把1移向某个特定的位

1<<3是向左移动3位,由00000001变为00001000

>>左移和<<右移都有一个需要注意的点,就是你会发现在进行移位时,会导致某一边的数据缺失。这个就涉及到补位的知识了,在后面数据类型转换会讲到。

OK,写不少了,这一节也就到这里吧。如有错误,感谢指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值