C语言入门学习 --- 5.操作符

文章目录

第五章操作符

本章将主要介绍:

1.各种操作符的讲解

2.表达式求值

1.操作符的分类

算术操作符

移位操作符

位操作符

赋值操作符

单目操作符

关系操作符

条件操作符

逻辑操作符

逗号表达式

下标引用、函数调用及结构成员

2.算术操作符

c 复制代码
+ - * / %

%操作符的两个操作数必须是整数,返回的是整除之后的余数。

3.移位操作符

c 复制代码
<< 左移操作符
>> 右移操作符
注意:移位操作符的操作数只能为整数。
3.1左移操作符

移位规则:

左边被抛弃,右边会随之补0

c 复制代码
    int a = 10;
    a << 1;
    00000000 00000000 00000000 00001010 //a在内存中的二进制  32位
    00000000 00000000 00000000 00010100 //a左移一位后的结果
3.2右移操作符

移位规则:

1.算术右移

右边丢弃,左边补原符号位

2.逻辑右移

右移丢弃,左边补0

注:不要移动负数位,这个是标准未定义的。

4.位操作符

c 复制代码
&  //按位与
|  //按位或
^  //按位异或
它们的操作数必须是整数

一道面试题:

不能创建临时变量(第三个变量),实现两个数的交换。

c 复制代码
#include <stdio.h>

int main()
{
    int a = 10; //00000000 00000000 00000000 00001010
    int b = 20; //00000000 00000000 00000000 00010100
    a = a ^ b;  //00000000 00000000 00000000 00011110
    b = a ^ b;  //00000000 00000000 00000000 00001010 a^b^b
    a = a ^ b;  //00000000 00000000 00000000 00010100 a^b^a
                //3^3  011 ^ 011 -> 000
                //0^3  000 ^ 011 -> 011
    printf("a=%d,b=%d\n",a,b);
    return 0;
}

练习:

求一个整数存储在内存中的二进制中1的个数。

c 复制代码
#include <stdio.h>

int main()
{
    int a = 13;
    int n = 1;
    int count = 0;
    for (int i = 0;i < 32;i++)
    {
        if (n & (a >> i))
        {
            count++;
        }
    }
    printf("%d", count );
    return 0;
}

5.赋值操作符

c 复制代码
  int height = 160;
    height = 180;    //如果对值不满意,可以赋值
    int weight = 70;
    weight = 50;

    //赋值操作符可以连续使用,如:
    int x = 10;
    int y = 5;
    int z = 2;
    z = y = x + 5;   //连续赋值

    //同样的语义:
    y = x + 5;
    z = y;
5.1复合赋值符

+=

-=

*=

%=

/=

》=

《=

&=

|=

^=

c 复制代码
  int a = 50;
    a = a + 5;
    a += 5;  //符合赋值
    //与其他运算符一样的道理,这样更加简洁。

6.单目操作符

6.1介绍
c 复制代码
+        //正值

-        //负值

!       //逻辑反

&        //取地址

sizeof   //操作数的类型长度(单位为字节)

~        //对一个数进行二进制按位取反

++       //前置,后置++

--       //前置,后置--

*        //间接反问操作符

(类型)   //强制类型转换
c 复制代码
    int a = 10;

    printf("%d\n", sizeof(a));    //4
    printf("%d\n", sizeof(int));  //4
    printf("%d\n", sizeof a);      //4
    printf("%d\n", sizeof int); //语法错误

sizeof可以求变量(类型)所占空间的大小。

6.2 sizeof和数组

7.关系操作符

c 复制代码
<
<=
>
>=
!=  //判断是否不相等
==  //判断是否相等

注意:=为赋值,==为判断相等,在编程过程中,不要写错了。

8.逻辑操作符

c 复制代码
&&  //逻辑与
||  //逻辑或

区分逻辑与和按位与

区分逻辑或和按位或

c 复制代码
1 & 2 --->  0
1 && 2 -->  1


1 | 2 --->  3
1 || 2 -->  1

一道面试题:

c 复制代码
#include <stdio.h>

int main()
{
    int i = 0, a = 0, b = 2, c = 3, d = 4;
    i = a++ && ++b && d++;
    //i = a++||++b||d++; //输出结果为:1 3 3 4
    printf("a = %d\nb = %d\nc = %d\nd = %d\n", a, b, c, d);
    //输出结果为:1 2 3 4
    //后置++,先使用再++,a为假后面不管为什么都为假,所以后面没有参与运算。
    return 0;
}

9.条件操作符

c 复制代码
exp1 ? exp1 : exp2
c 复制代码
  int a = 5;
    int b = 10;
    int c = 0;
    if (b > a)
    {
        c = b;
    }
    else
    {
        c = a;
    }

    c = a > b ? a : b;

10.逗号表达式

c 复制代码
exp1,exp2,exp3,....expn

逗号表达式:是用逗号隔开的多个表达式,它是从左往右依次执行,整个表达式的最后结果是最后一个表达式的结果。

c 复制代码
  //代码1
  int a = 1;
    int b = 3;
    int c = 0;
    c = (++a, b + a, b, a + 4);
    printf("%d",c); //C为6

  //代码2
  if(a = b/2,b = a+2, a>0)

  //代码3
  a = get();
    count(a);
    while (a > 0)
    {
        a = get();
        count(a);
    }

    //使用逗号表达式,改写成:
    while (a = get(), count(a), a > 0)
    {
        a = get();
        count(a);
    }

11.下标引用、函数调用和结构成员

1.[]下标引用操作符

操作数:一个数组名 + 一个索引值

c 复制代码
  int arr[3] = { 0 };  //创建个数组
    arr[2] = 2;             //引用下标操作符进行赋值
    //[]的两个操作数分别为arr和3.
2.()函数调用操作符

接受一个或者多个操作符,第一个操作符为函数名,剩余的操作数为传递给函数的参数。

c 复制代码
#include <stdio.h>

void Test()
{
    printf("Hello ");
}

void Test2(char *Str)
{
    printf("%s", Str);
}

int main()
{
    Test();
    Test2("World");
    return 0;
}
3.访问结构成员

. 结构体.成员名

-> 结构体指针->成员名

c 复制代码
#include <stdio.h>


struct Book
{
    char name[20];
    double price;
    char id[20];
};

int main()
{
    struct Book b = { "C语言", 58.00, "C15156206"};
    struct Book *pb = &b;

    /*printf("书名是:<<%s>>,价格是:%lf, 书本编号是:%s\n", b.name, b.price, b.id);*/
    /*printf("书名是:<<%s>>,价格是:%lf, 书本编号是:%s\n", (*pb).name,(*pb).price,(*pb).id);*/
    printf("书名是:<<%s>>,价格是:%lf, 书本编号是:%s\n", pb->name, pb->price, pb->id);

    return 0;
}

12.表达式求值

表达式的求值顺序由操作符的优先级和结合性决定。

有些函数表达式求值的时候需要转换成其他类型。

12.1隐式类型转换

C语言的整型算术运算总是至少以缺省整型类型的精度进行运算的。

为了获得精度,表达式中的字符和短整型操作数在使用之前会先转为普通整型,这种转换就被称为整型提升

整型提升有什么意义?

c 复制代码
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度
一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长
度。
通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令
中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转
换为int或unsigned int,然后才能送入CPU去执行运算。
c 复制代码
  char a, b, c;
    c = a + b;
  //b与c先提升为普通整型,再进行加法运算
  //运算完成后,结果将被截断,然后再存储在a中

如何进行进行整型提升:

按照变量的数据类型的符号位来进行提升的。

c 复制代码
  char a = 3;
    //00000000 00000000 00000000 00000011
    //00000011   -   a 
    char b = 127;
    //00000000 00000000 00000000 01111111
    //01111111   -   b
    char c = a + b;
    //00000000 00000000 00000000 00000011
    //00000000 00000000 00000000 01111111
    //00000000 00000000 00000000 10000010    //逢二进一

    //10000010   -   c
    //11111111 11111111 11111111 10000010    //补码
    //11111111 11111111 11111111 10000001    //反码  补码-1
    //10000000 00000000 00000000 01111110    //原码  反码取反
    //-126
    //a和b都是char类型,都没有达到一个int的大小,这里就要整型提

     printf("%d\n", c); //-126

  ///无符号整形提升,高位补0

例子:

c 复制代码
 //例1
 int main()
    {
        char a = 0xc5;
        short b = 0xc500;
        int c = 0xc5000000;
        if (a == 0xc5)
            printf("A");
        if (b == 0xc500)
            printf("B");
        if (c == 0xc5000000)
            printf("C");
        return 0;

 //例子中的a和b都需要进行整型提升,c不需要。
 //a,b进行了整型提升后变成了负数,所以表达式a == 0xc5,b == 0xc500的结果是假,c不进行整型提升,则表达式c == 0xc5000000的结果为真。
 //程序运行结果为C。
c 复制代码
//例2
int main()
{
 char c = 1;
 printf("%u\n", sizeof(c));
 printf("%u\n", sizeof(+c));
 printf("%u\n", sizeof(-c));
 return 0;
}

c只要参与表达式运算,就会进行整型提升,表达式+c就会进行整型提升,所以sizeof(+c)/(-c)是四个字节,sizeof©是一个字节。

12.2 算术转换

如果某个操作符的各个操作数不属于相同的类型,那么除非其中一个操作数转换成另一个操作数的类型,否则将无法执行操作。以下的层次体系称为寻常算术转换。

c 复制代码
long double
double
float
unsigned long int
long int
unsigned int
int

如果某个操作数的类型在上面列表中排名较低,那首先要转换为另外一个操作数的类型再执行运算。

c 复制代码
    float x = 3.1415;
    int y = x; //隐式转换,会丢失精度

    //算术转换要合理,不然会有一些问题
12.3操作符的属性

复杂表达式的求值会受到三个因素的影响。

1.操作符的优先级

2.操作符的结合性

3.是否控制求值顺序

两个相邻的操作符的执行先后,取决于它们的优先级。如果优先级相同,取决于它们的结合性。

有一些问题表达式:

c 复制代码
//表达式的求值部分由操作符的优先级决定。
a*b + c*d + e*f

注释:该代码在计算的时候,由于*比+的优先级高,只能保证,的计算比+早,但优先级不能决定第三个比第一个+早执行

所以表达式的计算顺序可能是:

c 复制代码
a*b->c*d->a*b+c*d->e*f->a*b + c*d + e*f
或者
a*b->c*d->e*f->a*b + c*d->a*b + c*d + e*f
c 复制代码
c + --c;

注释:操作符的优先级只可决定--的运算在+的运算前面,但并没有办法得

知,+操作符的左操作数的获取是在右操作数之前还是之后求值,所以结果不可预测,是有歧义的。

c 复制代码
//非法表达式
int main()
{
 int a = 10;
 a = a-- - --a * ( a = -3 ) * a++ + ++a;
 printf("a = %d\n", a);
 return 0;
}

该表达式在不同的编译器有不同的结果

编译器
42 Microsoft C 5.1
36 Dec VAX/VMS
21 Turbo C/C++ 4.5
---86 IBM PowerPC AIX 3.2.5
... ...
c 复制代码
int f()
{
     static int count = 1;
     return ++count;
}
int main()
{
     int a;
     a = f() - f() * f();
     printf( "%d\n", a);//输出-10
     return 0;
}          

这个代码也存在一定的问题。

虽然在大多数的编译器上得到的结果都是相同的。但是a = f() - f() * f();中我们只能通过操作符的优先级先算乘法,

再算减法。

函数的调用先后顺序无法通过操作符的优先级确定。

c 复制代码
#include <stdio.h>
int main()
{
 int i = 1;
 int ret = (++i) + (++i) + (++i);
 printf("%d\n", ret);
 printf("%d\n", i);
 return 0;
 //vs 12 4
 //gcc 10 4
}

相同的代码缺产生了不同的结果

该代码中第一个 + 执行的时候,第三个++是否在执行,这个是不确定的,因为依靠操作符的优先级和结合性是没法取决定第一个 + 和第三个++ 的执行先后顺序。

总结:写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那么该表达式存在一些问题的。

练习:

1.创建一个整型数组,完成对数组进行以下操作:

实现函数init() 初始化数组全为0

实现print() 打印数组的每一个元素

实现reverse() 函数完成数组元素的逆置

c 复制代码
#include <stdio.h>

void init(int arr[], int sz)
{
    for (int i = 0;i < sz; i++)
    {
        arr[i] = 0;
    }
}

void print(int arr[], int sz)
{
    for (int i = 0; i < sz; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

void reverse(int arr[], int sz)
{
    int left = 0;
    int right = sz - 1;
    int tmp = 0;
    while (left < right)
    {
        tmp = arr[left];
        arr[left] = arr[right];
        arr[right] = tmp;
        left++;
        right--;
    }
}

int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    int sz = sizeof(arr) / sizeof(arr[0]);

    reverse(arr, sz);
    print(arr, sz);

    init(arr, sz);
    print(arr, sz);

    return 0;
}

2.交换数组

将数组A中的内容和数组B中的内容进行交换。(数组大小一样)

c 复制代码
#include <stdio.h>

void swap(int arr1[],int arr2[], int sz)
{
    int tmp = 0;
    for (int i = 0;i < sz;i++)
    {
        tmp = arr1[i];
        arr1[i] = arr2[i];
        arr2[i] = tmp;
    }
}

void print(int arr1[], int arr2[], int sz)
{
    for (int i = 0;i < sz;i++)
    {
        printf("%d ", arr1[i]);
    }
    printf("\n");
    for (int i = 0;i < sz;i++)
    {
        printf("%d ", arr2[i]);
    }
}

int main()
{
    int arr1[] = { 3,13,23,33,43,53,63,73,83,93 };
    int arr2[] = { 2,12,22,32,42,52,62,72,82,92 };
    int sz = sizeof(arr1) / sizeof(arr1[0]);
    swap(arr1, arr2, sz);
    print(arr1, arr2, sz);
    return 0;
}

作业:

1.统计二进制中1的个数

写一个函数返回参数二进制中1的个数。

如:15 0000 1111 4个1

c 复制代码
#include <stdio.h>

//int Number(int b)
//{
//    int count = 0;
//    while (b)
//    {
//        if (b % 2 == 1)
//        {
//            count++;
//        }
//        b = b / 2;
//    }
//    return count;
//}

int Number(int b)
{
    int count = 0;
    int i = 0;
    for (i = 0;i < 32;i++)
    {
        if(((b >> i) & 1 )== 1)
    {
        count++;
    }
    }
    return count;
}

int main()
{
    int a = -1;
    //10000000 00000000 00000000 00000001   原码
    //11111111 11111111 11111111 11111110   反码
    //11111111 11111111 11111111 11111111   补码
    int ret = Number(a);
    printf("%d\n", ret);
    return 0;
}

2.求两个数二进制中不同位的个数

编程实现:两个int(32位)整数m和n的二进制表达中,有多少个位(bit)不同?

输入:1999 2999 输出:6

c 复制代码
#include <stdio.h>

int Number(int n)
{
    int count = 0;
    while (n)
    {
        n= n & (n - 1);
        count++;
    }
    return count;
}

int main()
{
    int a = 0;
    int b = 0;
    int i = 0;
    int count = 0;
    scanf("%d %d", &a, &b);
    /*for (i = 0;i < 32;i++)
    {
        if (((a >> i) & 1) != ((b >> i) & 1))
        {
            count++;
        }
    }*/
    int ret = a ^ b;
    count = Number(ret);
    printf("%d\n", count);
    return 0;
}

3.打印整数二进制的奇数位和偶数位

获取一个整数二进制序列中所有的偶数位和奇数位,分别打印二进制序列

c 复制代码
#include <stdio.h>

int main()
{
    int a = 0;
    int i = 0;
    scanf("%d", &a);

    //偶数
    for (i = 31;i >= 1;i -= 2)
    {
        printf("%d ",((a>>i) & 1));
    }

    printf("\n");

    //奇数
    for (i = 30;i >= 0;i -= 2)
    {
        printf("%d ", ((a >> i) & 1));
    }
    return 0;
}

4.交换两个变量(不创建临时变量)

不允许创建临时变量,交换两个整数的内容

c 复制代码
#include <stdio.h>

int main()
{
    int a = 10;
    int b = 20;
    printf("%d %d\n", a, b);

    a = a ^ b;
    b = a ^ b;
    a = a ^ b;

    printf("%d %d\n",a, b);
    return 0;
}

上一章:C语言入门学习 --- 4.数组

配套练习:

C语言练习题110例(一)
C语言练习题110例(二)
C语言练习题110例(三)
C语言练习题110例(四)
C语言练习题110例(五)
C语言练习题110例(六)
C语言练习题110例(七)
C语言练习题110例(八)
C语言练习题110例(九)
C语言练习题110例(十)
C语言练习题110例(十一)

相关推荐
你可以叫我仔哥呀2 分钟前
ElasticSearch学习笔记三:基础操作(一)
笔记·学习·elasticsearch
.Cnn6 分钟前
用邻接矩阵实现图的深度优先遍历
c语言·数据结构·算法·深度优先·图论
脸ル粉嘟嘟7 分钟前
GitLab使用操作v1.0
学习·gitlab
路有瑶台10 分钟前
MySQL数据库学习(持续更新ing)
数据库·学习·mysql
2401_8582861112 分钟前
101.【C语言】数据结构之二叉树的堆实现(顺序结构) 下
c语言·开发语言·数据结构·算法·
Beau_Will17 分钟前
数据结构-树状数组专题(1)
数据结构·c++·算法
迷迭所归处21 分钟前
动态规划 —— 子数组系列-单词拆分
算法·动态规划
爱吃烤鸡翅的酸菜鱼22 分钟前
Java算法OJ(8)随机选择算法
java·数据结构·算法·排序算法
寻找码源1 小时前
【头歌实训:利用kmp算法求子串在主串中不重叠出现的次数】
c语言·数据结构·算法·字符串·kmp
Matlab精灵1 小时前
Matlab科研绘图:自定义内置多款配色函数
算法·matlab