种一棵树,最好的时间是10年前,其次是现在。

大家好,这里是anduin,这次的主题依旧是初识C语言,和上次不同,这次我们主要讲两大块,对于一些知识,我们选择略讲,但是对于一些我觉得重要的知识我会对其进行精讲并做一些补充,其余的知识我会逐渐在以后一一讲解。话不多说,接下来就开始初识C语言吧!(以下程序均在vs2022中编译)


目录

10. 操作符

算数操作符

/ 操作符

小科普

% 操作符

% 操作符对正数

% 操作符对负数

移位操作符

位操作符

赋值操作符

单目操作符

关系操作符

例1

例2

 逻辑操作符

条件操作符

逗号表达式

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

11. 常见关键字

auto

typedef

本质

使用方法

作用

register

存储金字塔

寄存器存在的本质

register 修饰变量

小科普

结语



10. 操作符

算数操作符

+        –        *        /        %

算数操作符中的+ – *和平常运算方法相似,我们在这边重点介绍 / 和 %运算符


/ 操作符

为了让我们的讲解更加具有针对性,我们分正数负数两部分进行讲解:

/ 操作符对正数

#includeint main(){int a = 7 / 2;printf("%d\n", a);//3}

分析:对于上方代码当我们用平时的逻辑,那么这个答案应该是3.5,可在程序中编译运行,结果是 3,我们不禁产生疑惑是不是因为你打印的结果是整形的缘故,其实本身结果还是3.5呢?让我们用代码验证:

#includeint main(){float f = 7 / 2;printf("%f\n",f);//3.000000}

可当我们再次进行编译运行时,结果是3.000000,因此我们可以得出结论:

除号两端的操作数如果都是整数,执行的是整数除法,至少有一个操作数是浮点数,执行的才是浮点数的除法!!!

对于正数是这样进行运算的,那么负数呢?


/ 操作符对负数

#includeint main(){int i = -2.9;int j = 2.9;printf("%d\n",j);//-2printf("%d\n",j);//2return 0;}

对于负数运算,我们打印的值,结果为 -2 ,和正数运算规律相似,我们可以看出它们运算都是向0取整的,那么如何理解呢?对于取整,当正负数进行运算时,并不会出现四舍五入的情况,无论正数负数都是向0方向取整!(-2.9向零取整得到2)

如下图所示:


小科普

在C语言中,是不是只有一种取整方式?如果有它们分别是怎么实现的?接下来我们来一一了解:

trunc – 取整函数

#includeint main(){ printf("%d\n",(int)trunc(-2.9));//-2printf("%d\n",(int)trunc(2.9));//2}

Tips:由于trunc函数默认接收的返回值为%f,但是我们这边想输出的是整数所以强制类型转化成int类型。

floor – 地板取整

#include#includeint main(){printf("%.lf\n",floor(-2.9));//-3printf("%.lf\n",floor(-2.1));//-3printf("%.lf\n",floor(2.9));//2printf("%.lf\n",floor(2.1));//2}

因为其取整方式和函数名,我们称它为地板取整,什么意思呢,我们编译结果发现负数-2.9的值变成了-3,对于正数2.9的值变成了2

我们可以观察到无论是正数还是负数,在用floor函数进行取整的时候都是变小,我们观察其本质可以总结为-∞取整,如下图所示:

ceil – +∞取整

#includeint main(){printf("%.lf\n",ceil(-2.9));//-2printf("%.lf\n",ceil(-2.1));//-2printf("%.lf\n",ceil(2.9));//3printf("%.lf\n",ceil(2.1));//3}

当我们编译运行程序,可以观察到负数-2.9变为-2-2.1变为-2,正数2.9变为32.1变为3

我们可以观察到ceil函数的取整,结果都会在原来的基础上变大一个整数位,我们总结出该取整方式为+∞取整,如下图所示:

round – 四舍五入取整

是的你没有看错,与我们平常思维方式相同的四舍五入取整来了,上代码:

#includeint main(){printf("%.lf\n",round(-2.9));//-3printf("%.lf\n",round(-2.1));//-2printf("%.lf\n",round(2.9));//3printf("%.lf\n",round(2.1));//2}

对于这种取整方式,没什么好说的,就是按照我们平时的思路来,例如当值2.9时,值为3

当值为2.1时,值为2。在当前情况下,凡取整的值大于等于2.5,得出的结果都会变为3


% 操作符

概念:如果a和d是两个自然数,d非零,可以证明存在两个唯一的整数 q 和 r,满足 a = q*d + r 且0 ≤ r < d。其中,q 被称为商,r 被称为余数。


对于此部分我们依旧是从正数负数两方面进行剖析:

% 操作符对正数

#includeint main(){int a = 10;int d = 3;printf("%d\n",a%d);//1//因为:a=10,d=3,q=3,r=1 0<=r 10=3*3+1return 0;}-

代码:基于概念,我们可以理解当前代码的意思(详情见注释)。但我们还有另一种理解方式:% 操作符,关注的是除法后的余数,例如代码中的10%3,我们也可以理解为商3余1,相对于概念,这种方式更加容易接受一些。

Warning:操作符两端的操作数必须是整数!!!


% 操作符对负数

int main(){int a = - 10;int d= 3;printf("%d\n",a%d);//-1}

 很显然,结果不满足定义中的0<=r<d,故后来就有了一个修订版的定义:

如果a和d是两个自然数,d非零,可以证明存在两个唯一的整数 q 和 r,满足 a = q*d + r , q 为整数,且0 ≤ |r| < |d|。其中,q 被称为商,r 被称为余数。

根据定义,这里的结果也能解释了,即-10 = (-3)*3+(-1)。

在C语言中,%本质取余,余数的大小,本质取决于商q的,而商都取决于除法运算的取整规则。

在对于正数的取余和取模在C语言中是相同的,我们发现对于向0取整向-∞的取整在正数上是同向的,但是对于负数上就完全不同了,因此我们平时遇到的大多都是整数取模,并没有做太多解释。


移位操作符

>>        <<(以后讲解)


位操作符

&         ^(以后讲解)


赋值操作符

=        +=        -=        *=        /=        &=        ^=        |=        >>=

由于赋值操作符便于理解,我们挑两个讲一下(详情见注释):

int main(){int a = 10;//创建变量,并初始化a = 20;//赋值a = a+5//等价于 a+=5a = a-5//等价于 a-=5}

单目操作符

!                 逻辑反操作

–                 负值

+                正值

&                取地址

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

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

—                前置、后置–

++              前置、后置++

*                 间接访问操作符(解引用操作符)

(类型)         强制类型转换

我们常见的操作符比如+都有两个操作数,表示为左操作数+右操作数,这种操作符叫做双目操作符

单目操作符其实就是只有一个操作数的。

在这里,对于!,-,+等操作符都便于理解,而对于&和*我打算之后在指针部分讲解,这里我们着重讲一下–,和++操作符(详情见注释):

#includeint main(){int a = 10;int b = ++a;//前置++,先++后使用//a=a+1,b=aprintf("a=%d b=%d\n",a,b);//11 11---------------------------------int a = 10;int b = a++;//后置++,先使用,后++//b=a,a=a+1printf("a=%d b=%d\n",a,b);//11 10---------------------------------int a = 10;int b = --a;//前置--,先--后使用//a=a-1,b=aprintf("a=%d b=%d\n",a,b);//9 9---------------------------------int a = 10;int b = a--;//后置--,先使用,后--//b=a,a=a-1printf("a=%d b=%d\n",a,b);//10 9}

关系操作符

>         >=         <        <=        !=(不相等)        ==(相等

前几个操作符,也就是对应的大于,大于等于,小于,小于等于等比较关系的操作符,这里我们讲一下后两个操作符。


例1

int main(){int a = 3;int b = 3;if(a!=b){printf("hehe\n");}if(a==b){printf("haha\n");}//结果为hehe}

运行结果如下: 

观察代码,很简单,判断a和b是否相等,如果相等则输出haha,若不相等输出hehe,因为c此处a和b都等于3,所以打印结果为hehe。


例2

#includeint main(){char arr1[] = "abcdef";char arr2[] = "abcdef";if(arr1==arr2){printf("==\n");}else{printf("!=\n");}}

我们知道用==和!=可以判断是否相等,那么对于字符串呢?他们是否相等,当我们编译后,发现它们是不相等的!因为arr1和arr2是字符串首元素地址,开辟的空间不同,所以地址当然不同,如果要比较两个字符串的大小可以用strcmp函数,接下来看修改后的代码:

#include#includeint main(){char arr1[] = "abcdef";char arr2[] = "abcdef";if(strcmp(arr1,arr2)==0){printf("==\n");}else{printf("!=\n");}}

再次运行

对于strcmp这个函数使用时需要应用头文件#include,如果返回的值为0,则两个字符串相等。


 逻辑操作符

&& – 逻辑与并且        || – 逻辑或或者

int main(){int a= 3;int b = 0;int c = a && b;printf("%d\n",c);//真为1,假为0if(a&&b){printf("hehe\n");}if(a||b){printf("haha\n);}}

运行得

分析:&&的返回值是这样规定的,如果两个数都为真,则值为1,反之则为0。

对于本代码由于a&&b为0,故不打印hehe,||的值是有一个为真则为真,值为1,反之为0。

这里由于a||b为真,所以打印haha。


条件操作符

exp1″ />

分析:条件操作符使用时先判断表达式1,如果表达式1结果为,则输出第一个值,否则为第二个值,改题中因为a>10,所以打印结果为3。当然,这段代码也可以用if        else语句来表示:

#includeint main(){int a = 10;int b = 0;if(a>5){b=3;}else{b=-3;}printf("%d\n", b);}

逗号表达式

exp1, exp2, exp3, …expN

#includeint main(){int a=3;int b=5;int c=0;int d= (a+=2,b=b-c+a,c=a+b);//a=5 b=10 c=5+10 printf("%d\n",d);}

分析:对于逗号表达式其特点为从左往右依次计算,整个表达式的结果是最后一个表达式的结果,因此在代码中d的值也就是15。


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

[]        ()        .        ->

对于这类操作符我们讲解一下[ ]操作符:

#includeint main(){int arr[10] = {0};arr[4]=5;return 0;}

分析:对于上述代码就是引用下标4,将arr[4]的位置上赋值5。

和一些操作符相同,[ ]也有两个操作数,例如arr 和 4是两个操作数,对于这个代码写成4[arr]也可以,但是这种写法不仅让人难以理解,而且比较挫,我们可千万不要写出这样的代码!!!


11. 常见关键字

C 语言标准中规定了32个关键字,而在之后C99的标准下又增加了五个关键字,anduin这次对大家进行归纳的是C标准中的32个关键字,我们根据其特性,可以把它分为几类:

  数据类型关键字(12个)

  • char:声明字符型变量或函数
  • short:声明短整型或函数
  • Int :声明整型变量或函数
  • long :声明长整型变量或函数
  • signed:声明有符号类型变量或函数
  • unsigned:声明无符号整形变量或函数
  • foat:声明浮点型变量或函数
  • double:声明双精度变量或函数
  • struct:声明结构体变量或函数
  • union:声明共用体(联合)数据类型
  • enum:声明枚举类型
  • void:声明函数无返回值或无参数,声明无类型指针

控制语句关键字(12个)

1.循环控制(5个)

  • for:一种循环语句
  • do:循环语句的循环体
  • while :循环语句的循环条件
  • break:跳出当前循环
  • continue:结束当前循环,开始下一轮循环

2.条件语句(3个)

  • if:条件语句
  • else:条件语句否定分支
  • goto:无条件跳转语句

3.开关语句(3个)

  • switch:用于开关语句
  • case:开关语句分支
  • default:开关语句中的”其他”分支

4.返回语句(1个)

  • return:函数返回语句(可以带参数,也可以不带参数)

5.存储类型关键字(5个)

  • auto:声明自动变量,一般不使用
  • extern:声明变量实在其他文件中声明
  • register:声明寄存器变量
  • static:声明静态变量
  • typedef:用以给数据类型取别名(但是该关键字被分到存储关键字分类中,虽然看起来没什么相关性)

注意:存储关键字,不可以同时出现,也就是说,在一个变量定义的时候,只能有一个。

6.其它关键字(3个)

  • const:声明只读变量
  • sizeof:计算数据类型长度
  • volatile:说明变量在程序执行中可被隐含地改变

这里anduin带大家有选择性的,讲解几个关键字,若没有讲到日后我们一一讲解:

auto

auto 从字面意思来看就是自动的意思 它也被成为自动变量

局部变量都是自动创建自动销毁的,所以局部变量都是auto修饰的。

int main(){auto int a = 10;//局部变量都是auto类型的,因此auto基本会被省略return 0;}

typedef

本质

本质:类型重命名


使用方法

#includetypedef unsignde int u_int;int main(){u_int x = 0; return 0;}

分析:当我们在写代码时,一些代码类型很长,例如无符号整型unsigned int或者结构体,指针等,这时我们就可以用typedef对它进行类型重命名,本段代码中就是用了typedef来重命名unsigned int为u_int。


作用

对类型重命名的一种解决方案,让我们在面临冗长的类型命名方面上更加简便,可以对一些不太好理解的数据类型进行简化。


register

我们先想想,数据在计算机上可以存放在哪里呢?

1.内存

2.硬盘

3.高速缓存

4.寄存器


存储金字塔


寄存器存在的本质

从硬件层面上,提高计算机的运算效率。当读取数据时,计算器先去寄存器中读取,如果没有读取到,再去高速缓存区中读取,最后才是内存,而且在cpu再读取寄存器中的数据时,内存->高数缓存区(cache),cache->寄存器,这个数据传递过程会持续进行,大大提高效率!


register 修饰变量

也就是相同的原理,尽量把所修饰变量,放入CPU寄存区中,从而达到提高效率的目的。

#includeint main(){register int a = 10;return 0;}

register 修饰什么变量

register可不是什么变量都适合修饰的,要知道寄存器的价格是很昂贵的,register所修饰的变量也得”精挑细选”一番。

  1. 局部变量(全局变量由于其特性会导致CPU寄存器被长时间占用)
  2. 不会被写入的(写入就需要写回内存,register本身就是快速读取,后续还要读取检测的话,就与原目的背道而驰了)
  3. 高频被读取的变量(俗话说把钱用在刀刃上,存入了寄存器,当然要经常使用的)
  4. 如果要使用,不要大量使用,寄存器的数量是有限的!

小科普

register修饰的变量,不能取地址!!!

#includeint main(){register int a = 0; printf("&a = %p\n", &a);return 0;}

编译运行:

 分析:因为register的作用,变量a已经被放入寄存器中了,你怎么取地址呢?


结语

以上就是初识C语言中篇的内容,虽然只有两个专题,但是内容其实也是很多的,希望我的文章对你有帮助,如果大家觉得anduin写的还可以的话,请点赞+收藏哦!你们的肯定就是对我最大的鼓励!

今天也是高考第一天,祝广大学子辛勤奋战在学海,汗水扬起成功帆!

愿大家都可以金榜题名!