C语言快速入门(二)

95 阅读13分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 18 天,点击查看活动详情

运算符

基本运算符

  • 加法运算符:+
  • 减法运算符:-
  • 乘法运算符:*
  • 除法运算符:/
  • 赋值运算符:=

加减乘不做阐述,对应的就是数学中的运算

下面介绍除法运算和取模运算

#include <stdio.h>int main() {
    int a = 19;
    int b = 3;
    printf("除法运算结果%d\n取模运算结果%d", a / b, a % b);
    // 除法运算结果6 (除法表示整除部分)
    // 取模运算结果1 (取模表示余数部分)
}

运算符优先级

在数学中,加减运算的优先级是没有乘除运算优先级高的,所以我们需要先计算那些乘除法,最后再来进行加减法的计算,而C语言中也是这样,运算符之间存在优先级概念。我们在数学中,如果需要优先计算加减法再计算乘除法,那么就需要使用括号来提升加减法的优先级,C语言也可以:

#include <stdio.h>int main() {
    int a = 20, b = 10;
    printf("%d", (a + a) * b);   //优先计算 a + a 的结果,再乘以 b
}

如果遇到多重数学运算的,例如[1 - (3 + 4)] x (-2 ÷ 1),那么在c语言中可以用如下方法实现

#include <stdio.h>int main() {
    printf("%d", (1 - (3 + 4)) * (-2 / 1));   //其实写法基本差不多,只需要一律使用小括号即可
}

总结一下优先级:() > + - (做符号表示,比如-9) > * / % > + - (做加减运算) > =

自增自减运算符

int a = 10;
++a;   //使用自增运算符,效果等价于 a = a + 1

并且它也是有结果的,除了做自增运算之外,它的结果是自增之后的值:

#include <stdio.h>
​
int main() {
    int a = 10;
    //int b = a = a + 1;  等价于下方代码
    int b = ++a;
    printf("%d", b);
}

当然我们也可以将自增运算符写到后面,和写在前面的区别是,它是先返回当前变量的结果,再进行自增的,顺序是完全相反的:

#include <stdio.h>int main() {
    int a = 10;
    int b = a++;   //写在后面和写在前面是有区别的
    printf("a = %d, b = %d", a, b);
}

简单记忆:自增运算符++在前,那么先自增再出结果;自增运算符++在后,那么先出结果再自增

那要是现在我们想自增其他的数字呢?我们可以使用复合赋值运算符:

#include <stdio.h>int main() {
    int a = 10;
    int b = a += 5; // 得到的结果是在自增之后的
    printf("a = %d", b);
}

复合赋值运算符不仅仅支持加法,还支持各种各样的运算:

#include <stdio.h>int main() {
    int a = 10;
    a %= 3;   
    printf("a = %d", a);
}

当然,除了自增操作之外,还有自减操作:

#include <stdio.h>int main() {位运算符
    int a = 10;
    int b = 5 * --a; // 优先计算--a 再计算乘法
    printf("b = %d", b); // b = 45
}

注意自增自减运算符和+-做符号是的优先级一样,仅次于()运算符

位运算符

前面我们学习了乘法运算符*,当我们想要让一个变量的值变成2倍,只需要做一次乘法运算即可,但是我们现在可以利用位运算来快速进行计算:

int a = 10;
a = a << 1;   //也可以写成复合形式 a <<= 1

得出的结果也是20,那么实际上<<是让所有的bit位进行左移操作。

  • 10 = 00001010 现在所以bit位上的数据左移一位 00010100 = 20

我们在十进制中,做乘以10的操作:22乘以10那么就直接左移了一位变成220,而二进制也是一样的,如果让这些二进制数据左移的话,那么相当于在进行乘2的操作。位运算可以移动多位,也是很好理解的,比如:

#include <stdio.h>
​
int main() {
    int a = 6;
    a = a << 2;   //让a左移2位,实际上就是 a * 2 * 2a * 2的平方,由此得出 a<<n 的运算结果就是 a * 2的n次方
    printf("a = %d", a);
}

当然能左移那肯定也可以右移:

#include <stdio.h>int main() {
    int a = 6;
    a = a >> 1;   //右移其实就是除以2的操作
    printf("a = %d", a);
}

当然除了移动操作之外,我们也可以进行按位比较操作,先来看看按位与操作:

#include <stdio.h>int main() {
    int a = 6, b = 4;
    int c = a & b;   //按位与操作
    printf("c = %d", c);
}

按位与实际上也是根据每个bit位来进行计算的:

  • 4 = 00000100
  • 6 = 00000110
  • 按位与实际上就是让两个数的每一位都进行比较,如果两个数对应的bit位都是1,那么结果的对应bit位上就是1,其他情况一律为0
  • 所以计算结果为:00000100 = 4

除了按位与之外,还有按位或运算:

int a = 6, b = 4;
int c = a | b;
  • 4 = 00000100
  • 6 = 00000110
  • 按位与实际上也是让两个数的每一位都进行比较,如果两个数对应bit位上其中一个是1,那么结果的对应bit位上就是1,其他情况为0。
  • 所以计算结果为:00000110 = 6

还有异或和按位非(按位否定):

int a = 6, b = 4;
int c = a ^ b;    //^不是指数运算,表示按位异或运算,让两个数的每一位都进行比较,如果两个数对应bit位上不同时为1或是同时为0,那么结果就是1,否则结果就是0,所以这里的结果就是2
a = ~a;   //按位否定针对某个数进行操作,它会将这个数的每一个bit位都置反,0变成1,1变成0

按位运算都是操作数据底层的二进制位来进行的。

逻辑运算符

逻辑运算符用于计算真和假

#include <stdio.h>int main() {
    int a = 10;
    _Bool c = a < 0;    //我们现在想要判断a的值是否小于0,我们可以直接使用小于符号进行判断,最后得到的结果只能是1或0
    printf("c = %d", c);  
}
#include <stdio.h>int main() {
    char c = 'D';
    printf("c是否为大写字母:%d", c >= 'A');    //由于底层存储的就是ASCII码,这里可以比较ASCII码,也可以写成字符的形式
}

在C语言中,0一般都表示为假,而非0的所有值(包括正数和负数)都表示为真

现在我们的判断只能判断一个条件,也就是说只能判断c是否是大于等于'A'的,但是不能同时判断c的值是否是小于等于'Z'的,这时,我们就需要利用逻辑与和逻辑或来连接两个条件了:

#include <stdio.h>int main() {
    char c = 'D';
    printf("c是否为大写字母:%d", c >= 'A' && c <= 'Z');   //使用&&表示逻辑与,逻辑与要求两边都是真,结果才是真
}

又比如现在我们希望判断c是否不是大写字母:

#include <stdio.h>int main() {
    char c = 'D';
    printf("c是否不为大写字母:%d", c < 'A' || c > 'Z');   //使用||表示逻辑或,只要两边其中一个为真或是都为真,结果就是真
}

当然我们也可以判断c是否为某个字母:

#include <stdio.h>int main() {
    char c = 'D';
    printf("c是否为字母A:%d", c == 'A');    //注意判断相等时使用==双等号
    printf("c是否不为字母A:%d", c != 'A');  //判断不相等也可以使用
}

我们也可以对结果直接取反

#include <stdio.h>int main() {
    int i = 20;
    printf("i是否不小于20:%d", !(i < 20));   //使用!来对结果取反,注意!优先级很高,一定要括起来,不然会直接对i生效
}

!如果直接作用于某个变量或是常量,那么会直接按照上面的规则(0表示假,非0表示真)非0一律转换为0,0一律转换为1。

下面引入一个运算符:三目运算符,又称条件运算符。它是唯一有3个操作数的运算符,一般格式为条件 : 结果a : 结果b。在C语言中,结果a结果b的类型必须一致。

对于a ? b: c,先计算条件a,然后进行判断。如果a的值为true,计算b的值,运算结果为b的值;否则,计算c的值,运算结果为c的值。

下面举个例子来看一下:

#include <stdio.h>int main() {
    int i = 0;
    char c = i > 10 ? 'A' : 'B';    //三目运算符格式为:expression ? 值1 : 值2,返回的结果会根据前面判断的结果来的
    //这里是判断i是否大于10,如果大于那么c的值就是A,否则就是B
    printf("%d", c);
}

流程控制

分支语句 - if

if语句的标准格式如下:

if(判断条件) {
    执行的代码
}

当然如果只需要执行一行代码的话,可以省略花括号:

if(判断条件)
  一行执行的代码   //注意这样只有后一行代码生效,其他的算作if之外的代码了
#include <stdio.h>int main() {
    int i = 0;
    if(i > 20) {    //我们只希望i大于20的时候才执行下面的打印语句
        printf("Hello World!");
    }
    printf("Hello World?");   //后面的代码在if之外,无论是否满足if条件,都跟后面的代码无关,所以这里的代码任何情况下都会执行
}

我们需要判断某个条件,当满足此条件时,执行某些代码,而不满足时,我们想要执行另一段代码,我们就可以结合else语句来实现:

#include <stdio.h>int main() {
    int i = 0;
    if(i > 20) {
        printf("Hello World!");   //满足if条件才执行
    } else {
        printf("LBWNB");   //不满足if条件才执行
    }
}

但多数情况下,会出现多个判断条件,我们需要使用else if来实现

#include <stdio.h>int main() {
    int score =  2;
    if(score >= 90) {
        printf("优秀");
    } else if (score >= 70) {
        printf("良好");
    } else if (score >= 60){
        printf("及格");
    } else{
        printf("不及格");
    }
}

if这类的语句(包括我们下面还要介绍的三种)都是支持嵌套使用的,比如我们现在希望低于60分的同学需要补习,0-30分需要补Java,30-60分需要补C,这时我们就需要用到嵌套:

#include <stdio.h>int main() {
    int score =  2;
    if(score < 60) {   //先判断不及格
        if(score > 30) {   //在内层再嵌套一个if语句进行进一步的判断
            printf("学习C++");
        } else{
            printf("学习Java");
        }
    }
}

分支语句 - switch

前面我们介绍了if语句,我们可以通过一个if语句进行条件判断,然后根据对应的条件,来执行不同的逻辑,当然除了这种方式之外,我们也可以使用switch语句来实现,它更适用于多分支的情况,格式如下:

switch (目标) {   //我们需要传入一个目标,比如变量,或是计算表达式等
  case 匹配值:    //如果目标的值等于我们这里给定的匹配值,那么就执行case后面的代码
    代码...
    break;    //代码执行结束后需要使用break来结束,否则会继续进行到下一个case继续执行代码
}

比如现在我们要根据学生的等级进行分班,学生有ABC三个等级:

#include <stdio.h>int main() {
    char c = 'A';
    switch (c) {  //这里目标就是变量c
        case 'A':    //分别指定ABC三个匹配值,并且执行不同的代码
            printf("A级同学,成绩优秀");
            break;   //执行完之后一定记得break,否则会继续向下执行下一个case中的代码
        case 'B':
            printf("B级同学,成绩良好");
            break;
        case 'C':
            printf("C级同学,无药可救");
            break;
    }
}

switch可以精准匹配某个值,但是它不能进行范围判断,比如我们要判断分数段,这时用switch就难以实现。

当然除了精准匹配之外,其他的情况我们可以用default来表示:

switch (目标) {
    case: ...
    default:
        其他情况下执行的代码
}

比如:

#include <stdio.h>int main() {
    char c = 'A';
    switch (c) {
        case 'A':
            printf("A级同学,成绩优秀");
            break;
        case 'B':
            printf("B级同学,成绩良好");
            break;
        case 'C':
            printf("C级同学,无药可救");
            break;
        default:   //其他情况一律就是下面的代码了
            printf("默默无闻的路人甲");
    }
}

当然switch中可以继续嵌套其他的流程控制语句,比如if:

#include <stdio.h>int main() {
    char c = 'A';
    switch (c) {
        case 'A':
            if(c == 'A') {    //嵌套一个if语句(此举显得多余,仅作为演示)
                printf("去尖子班!");   
            }
            break;
        case 'B':
            printf("去平行班!");
            break;
    }
}

循环语句 - for

我们在某些时候,可能需要批量执行某些代码:

#include <stdio.h>int main() {
    printf("我想去的从来不是月球,而是有你的地方");  
    printf("我想去的从来不是月球,而是有你的地方");  
    printf("我想去的从来不是月球,而是有你的地方");  
}

将一个同样的句子打印三遍,显得非常的多余,我们可以通过for循环来实现此操作,for循环的基本格式如下

for (表达式1;表达式2;表达式3) {
    循环体
}

我们来介绍一下:

  • 表达式1:在循环开始时仅执行一次。
  • 表达式2:每次循环开始前会执行一次,要求为判断语句,用于判断是否可以结束循环,若结果为真,那么继续循环,否则结束循环。
  • 表达式3:每次循环完成后会执行一次。
  • 循环体:每次循环都会执行循环体里面的内容,直到循环结束。

一个标准的for循环语句写法如下:

//比如现在我们希望循环4次
for (int i = 0; i < 4; ++i) {
    //首先定义一个变量i用于控制循环结束
    //表达式2在循环开始之前判断是否小于4
    //表达式3每次循环结束都让i自增一次,这样当自增4次之后不再满足条件,循环就会结束,正好4次循环
}
#include <stdio.h>int main() {
    //比如现在我们希望循环4次
    for (int i = 0; i < 4; ++i) {
        printf("%d, ", i);
    }
}

这样,利用循环我们就可以批量执行各种操作了。

注意,如果表达式2我们什么都不写,那么会默认判定为真:

#include <stdio.h>int main() {
    for (int i = 0; ; ++i) {   //表达式2不编写任何内容,默认为真,这样的话循环永远都不会结束
        printf("%d, ", i);
    }
}

所以,如果我们想要编写一个无限循环,其实什么都不用写就行了:

#include <stdio.h>int main() {
    for (;;) {   //什么都不写直接无限循环,但是注意,两个分号还是要写的
        printf("Hello World!\n");   //这里用到了\n表示换行
    }
}

当然,我们也可以在循环过程中提前终止或是加速循环的进行,这里我们需要认识两个新的关键字:break,continue

for (int i = 0; i < 10; ++i) {
    if(i == 5) break;   //比如现在我们希望在满足某个条件下提前终止循环,可以使用break关键字来跳出循环
    printf("%d", i);
}

可以看到,当满足条件时,会直接通过break跳出循环,循环不再继续下去,直接结束掉。

我们也可以加速循环:

for (int i = 0; i < 10; ++i) {
    if(i == 5) continue;   //使用continue关键字会加速循环,无论后面有没有未执行完的代码,都会直接开启下一轮循环
    printf("%d", i);
}

虽然使用break和continue关键字能够更方便的控制循环,但是注意在多重循环嵌套下,它只对离它最近的循环生效:

for (int i = 1; i < 4; ++i) {
    for (int j = 1; j < 4; ++j) {
        if(i == j) continue;    //当i == j时加速循环
        printf("%d, %d\n", i, j);
    }
}

可以看到,continue仅仅加速的是内层循环,而对外层循环没有任何效果,同样的,break也只会终结离它最近的:

for (int i = 1; i < 4; ++i) {
    for (int j = 1; j < 4; ++j) {
        if(i == j) break;    //当i == j时终止循环
        printf("%d, %d\n", i, j);
    }
}

循环语句 - while

前面我们介绍了for循环语句,我们接着来看第二种while循环,for循环要求我们填写三个表达式,而while相当于是一个简化版本,它只需要我们填写循环的维持条件即可,比如:

#include <stdio.h>int main() {
    while (1) {   //每次循环开始之前都会判断括号内的内容是否为真,如果是就继续循环
        printf("Hello World!\n");   //这里会无限循环
    }
}

相比for循环,while循环更多的用在不明确具体的结束时机的情况下,而for循环更多用于明确知道循环的情况,比如我们现在明确要进行循环10次,此时用for循环会更加合适一些,又比如我们现在只知道当i大于10时需要结束循环,但是i在循环多少次之后才不满足循环条件我们并不知道,此时使用while就比较合适了。

#include <stdio.h>int main() {
    int i = 100;   //比如现在我们想看看i不断除以2得到的结果会是什么,但是循环次数我们并不明确
    while (i > 0) {   //现在唯一知道的是循环条件,只要大于0那么就可以继续除
        printf("%d, ", i);
        i /= 2;   //每次循环都除以2
    }
}

while也支持使用break和continue来进行循环的控制:

int i = 100;
while (i > 0) {
    if(i < 30) break;
    printf("%d, ", i);
    i /= 2;
}

我们可以反转循环判断的位置,可以先执行循环内容,然后再做循环条件判断,这里要用到do-while语句:

#include <stdio.h>int main() {
    do {  //无论满不满足循环条件,先执行循环体里面的内容
        printf("Hello World!");
    } while (0);   //再做判断,如果判断成功,开启下一轮循环,否则结束
}

实战:寻找水仙花数

“水仙花数(Narcissistic number)也被称为超完全数字不变数(pluperfect digital invariant, PPDI)、自恋数、自幂数、阿姆斯壮数或阿姆斯特朗数(Armstrong number),水仙花数是指一个 3 位数,它的每个位上的数字的 3次幂之和等于它本身。 例如:1^3 + 5^3+ 3^3 = 153。”

实现打印1000以内的水仙花数。

#include <stdio.h>
int main() {
//    1000以内的水仙花数
    for (int i = 100; i < 1000; ++i) {
        int a = i % 10, b = i / 10 % 10, c = i / 10 / 10;
        if (a * a * a + b * b * b + c * c * c == i) {
            printf("1000以内的水仙花数为%d\n", i);
        }
    }
}

实战:打印九九乘法表

#include <stdio.h>
int main() {
//  99 乘法表  
    for (int i = 1; i <= 9; ++i) {
        for (int j = 1; j <= i; ++j) {
            printf("%d * %d = %d\t", i, j, i * j);
        }
        printf("\n");
    }
}

实战:斐波那契数列(一)

斐波那契数列指的是这样一个数列:**1、1、2、3、5、8、13、21、34、……*在数学上,斐波那契数列以如下被以递推的方法定义:F(0)=0,F(1)=1, F(n)=F(n - 1)+F(n - 2)(n ≥ 2,n ∈ N

斐波那契数列:1,1,2,3,5,8,13,21,34,55,89...,实际上从第三个数开始,每个数字的值都是前两个数字的和。

#include <stdio.h>
int main() {
//  斐波那契数列
    int target = 20, result;
​
    int a = 1, b = 1, c;
    for (int i = 2; i < target; ++i) {
        c = a + b;
        a = b;
        b = c;
    }
    result = c;
​
    printf("%d", result);
}