当前位置: 首页 > news >正文

C语言语句、语句分类及注释

文章目录

  • 一、语句和语句分类
  • 二、注释
    • 🍕注释是什么?为什么写注释?
    • 1. /**/的形式
    • 2. //的形式
    • 3. 注释会被替换
  • 三、随机数的生成
    • 1. rand函数
    • 2. srand函数
    • 3. time函数
    • 4. 设置随机数的范围
  • 四、C99中的变长数组
  • 五、问题表达式解析
    • 表达式1
    • 表达式2
    • 表达式3
    • 表达式4
    • 表达式5
  • 六、位段补充

一、语句和语句分类

C语言的代码是由一条一条的语句构成的,C语言中的语句可为以下五类:
🍑空语句
🍑表达式语句
🍑函数调用语句
🍑复合语句
🍑控制语句

(1) 空语句
空语句是最简单的语句,一个分号就是一条语句,即空语句。

#include<stdio.h>intmain(){;//空语句return0;}

空语句一般出现的地方是:这里需要一条语句但是这条语句不需要做任何事,就可以写一个空语句

(2) 表达式语句
表达式语句就是在表达式的后边加上分号。如下所示:

#include<stdio.h>intmain(){inta=20;intb=0;b=a+5;//表达式语句return0;}

(3) 函数调用语句
函数调用的时候,也会加上分号,就是函数调用语句。

#include<stdio.h>intAdd(intx,inty){returnx+y;}intmain(){printf("hehe\n");//函数调用语句intret=Add(2,3);//函数调用语句return0;}

(4) 复合语句
成对括号中的代码就构成一个代码块,也被称为复合语句

#include<stdio.h>voidprint(intarr[],intsz)//函数的大括号中的代码也构成复合语句{inti=0;for(i=0;i<sz;i++){printf("%d ",arr[i]);}}intmain(){inti=0;intarr[10]={0};for(i=0;i<10;i++)//for循环的循环体的大括号中的就是复合语句{arr[i]=10-i;printf("%d\n",arr[i]);}return0;}

(5) 控制语句
控制语句用于控制程序的执行流程,以实现程序的各种结构方式(C语言支持三种结构:顺序结构、选择结构、循环结构),它们由特定的语句定义符组成,C语言有九种控制语句。可分成以下三类:
🍊1.条件判断语句也叫分支语句:if语句、switch语句;
🍊2.循环执行语句:do while语句、while语句、for语句;
🍊3.转向语句:break语句、goto语句、continue语句、return语句。

二、注释

🍕注释是什么?为什么写注释?

注释是对代码的说明,编译器会忽略注释,也就是说,注释对实际代码没有影响。
注释是给程序员自己,或者其他程序员看的。
好的注释可以帮我们更好的理解代码,但是也不要过度注释,不要写没必要的注释。
当然不写注释可能会让后期阅读代码的人抓狂。

写注释一定程度上反应了程序员的素质,建议大家写必要的注释,在未来找工作的时候,写代码时留下必要的注释也会给面试官留下更好的印象。

C 语言的注释有两种表示方法。

1. /**/的形式

第一种写法是将注释放在/*…*/之间,内部可以分行
形式1:

/* 注释 */

🏐形式2:

/*
这是一行注释
*/

这种注释可以插在一行内部。

int fopen(char* s/* file name */, int mode);

上面示例中,/*file name*/用来对函数参数进行说明,跟在它后面的代码依然会有效执行。这种注释一定不能忘记写结束符号*/,否则很容易导致错误。

例如:
上面示例的原意是,第五行和第七行代码的尾部,有两个注释。但是,第一行注释忘记写结束符号*/,导致注释一延续到第三行才结束。

🍏🍏/**/ 这种注释不支持嵌套注释,从/*开始注释后,遇到第一个 */ 就认为注释结束了

2. //的形式

第二种写法是将注释放在双斜杠//后面从双斜杠到行尾都属于注释。这种注释只能是单行,可以放在行首,也可以放在一行语句的结尾。这是C99标准新增的语法。
🍆形式1:

//这是一行注释

🌽形式2:

int x = 1; //这也是注释

不管是哪一种注释,都不能放在双引号里面。放在双引号里面的注释符号,会成为字符串的一部分,会被解析成普通符号,失去注释作用。

#include<stdio.h>intmain(){printf("//hello /* world */ ");return0;}

程序运行结果:
上面示例中,双引号里面的注释符号,都会被视为普通字符,没有注释作用。

3. 注释会被替换

编译时, 注释会被替换成一个空格, 所以 min/*这里是注释*/Value 会变成 min Value ,而不是minValue。

三、随机数的生成

1. rand函数

C语言提供了一个函数叫rand,这个函数可以生成随机数,函数原型如下:

intrand(void);

rand函数会返回一个伪随机数,这个随机数的范围是在0~RAND_MAX之间,这个RAND_MAX的大小是依赖编译器实现的,但是在大部分编译器上是32767。

rand函数的使用需要包含一个头文件是:stdlib.h
测试一下rand函数,这里多调用几次,产生5个随机数:

#include<stdio.h>#include<stdlib.h>intmain(){printf("%d\n",rand());printf("%d\n",rand());printf("%d\n",rand());printf("%d\n",rand());printf("%d\n",rand());return0;}

程序运行结果:
我们先运行一次,看看结果,然后再运行一次,多运行次呢?
可以看到虽然一次运行中产生的5个随机数是相对随机的,但是下一次运行程序生成的结果和上一次一模一样,这就说明有问题。

如果深入了解一下,就不难发现,其实rand函数生成的随机数是伪随机的伪随机数不是真正的随机数,是通过某种算法生成的随机数。真正的随机数的是无法预测下一个值是多少的。而rand函数是对一个叫“种子”的基准值进行运算生成的随机数。之所以前面每次运行程序产生的随机数序列是一样的,就是因为rand函数生成随机数的默认种子是1。如果要生成不同的随机数,就要让种子是变化的。

2. srand函数

C语言中又提供了一个函数叫srand,是用来初始化随机数的生成器,srand的原型如下:

voidsrand(unsigned int seed);

程序中在调用 rand 函数之前先调用 srand 函数,通过 srand 函数的参数seed来设置rand函数生成随机数时候的种子,只要种子在变化,每次生成的随机数序列也就变化起来了。也就是说给srand的种子如果是随机的,那rand就能生成随机数;在生成随机数的时候又需要一个随机数(种子),这咋办呢?看下面:

3. time函数

在程序中我们一般使用程序运行的时间作为种子,因为时间是时刻在变化的。在C语言中有一个函数叫 time,就可以获得这个时间,time函数原型如下:

time_ttime (time_t* timer);

🍇time 函数会返回当前的日历时间,其实返回的是1970年1月1日0时0分0秒到现在程序运行时间之间的差值,单位是。返回的类型是time_t,time_t类型本质上就是32位或者64位的整型类型。
🍇time函数的参数 timer 如果是非NULL指针的话,函数也会将这个返回的差值放在timer指向的内存中带回去。
🍇如果 timer 是NULL,就只返回这个时间的差值。time函数返回的这个时间差也被叫做:时间戳。time函数的使用需要包含头文件:time.h

如果只是让time函数返回时间戳,我们就可以这样写:

time(NULL); //调用time函数返回时间戳,这里没有接收返回值

那就可以让生成随机数的代码改写成如下:

#include<stdio.h>#include<stdlib.h>#include<time.h>intmain(){//使用time函数的返回值设置种⼦//因为srand的参数是unsigned int类型,我们将time函数的返回值强制类型转换srand((unsignedint)time(NULL));printf("%d\n",rand());printf("%d\n",rand());printf("%d\n",rand());printf("%d\n",rand());printf("%d\n",rand());return0;}

程序运行结果:
(注:截图只是当时程序运行的结果,你的运行结果不一定和这个一样)
srand函数是不需要频繁调用的,在一次运行的程序中调用一次就够了。

4. 设置随机数的范围

如果我们要生成0~99之间的随机数,方法如下:

rand() % 100; //余数的范围是0~99

如果要生成1~100之间的随机数,方法如下:

rand()%100+1; //%100得到的余数是在0~99之间(包括0和99),0~99的数字+1,范围就是1~100

如果要生成100~200的随机数,方法如下:

100 + rand()%(200-100+1); //余数的范围是0~100,加100后就是100~200

所以如果要生成a~b的随机数,方法如下:

a + rand()%(b-a+1)

四、C99中的变长数组

在C99标准之前,C语言在创建数组的时候,数组大小的指定只能使用常量常量表达式,或者如果我们初始化数组的话,可以省略数组大小。

int arr1[10];
int arr2[3+5];
int arr3[ ] = {1,2,3,4,5};

省略数组大小这种情况,虽然[ ]中没指定元素个数,但是编译器会根据你后面初始化元素的多少来确定数组的大小。

这样的语法限制,让我们创建数组就不够灵活,有时数组大了浪费空间,有时数组小了又不够用。
C99中给了一个变长数组(variable-length array,简称VLA)的新特性,允许我们可以使用变量指定数组的大小。

int n = a+b;
int arr[n];

上面示例中,数组 arr 就是变长数组,因为它的长度取决于变量n的值,编译器没法事先确定,只有运行时才能知道 n 是多少。
变长数组的根本特征,就是数组长度只有运行时才能确定,所以变长数组不能初始化。它的好处是程序员不必在开发时,随意为数组指定一个估计的长度,程序可以在运行时为数组分配精确的长度。有一个比较迷惑的点,变长数组的意思是数组的大小是可以使用变量来指定的,在程序运行的时候,根据变量的大小来指定数组的元素个数,而不是说数组的大小是可变的。数组的大小一旦确定就不能再变化了。

遗憾的是在VS2022上,虽然支持大部分C99的语法,但没有支持C99中的变长数组语法,没法测试;但可以在gcc编译器上测试下面的代码:

#include<stdio.h>intmain(){intn=0;scanf("%d",&n);//根据输入的数值确定数组的大小intarr[n];inti=0;for(i=0;i<n;i++){scanf("%d",&arr[i]);}for(i=0;i<n;i++){printf("%d ",arr[i]);}return0;}

五、问题表达式解析

表达式1

我们都知道,通过运算符优先级可以知道一个表达式优先级高的部分要比优先级低的先执行。但不能知道有相同优先级的几个部分会不会同时执行,还是有先后顺序?比如:

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

表达式1在计算的时候,由于*比+的优先级高,只能保证,*的计算比+的早,但是优先级并不能决定第三个 * 比第一个 + 早执行。
所以表达式的计算机顺序就可能是:

1 a*b
2 c*d
3 a*b + c*d
4 e*f
5 a*b + c*d + e*f

或者

1 a*b
2 c*d
3 e*f
4 a*b + c*d
5 a*b + c*d + e*f

表达式2

//表达式2
c + --c;

同上,操作符的优先级只能决定自减 – 的运算在 + 运算的前面,但是我们并没有办法得知,+ 操作符的左操作数的获取(使用)在右操作数之前还是之后求值,所以结果是不可预测的,是有歧义的。

表达式3

#include<stdio.h>//表达式3intmain(){inti=10;i=i-----i*(i=-3)*i+++++i;printf("i = %d\n",i);return0;}

表达式3在不同编译器中的测试结果:(非法表达式程序的结果)
上面的表达式在VS2022中的运行结果为:

表达式4

#include<stdio.h>intfun(){staticintcount=1;return++count;}intmain(){intanswer;answer=fun()-fun()*fun();printf("%d\n",answer);//输出多少?return0;}

程序运行结果:

虽然在大多数的编译器上求得的结果都是相同的。
但是上述代码 answer = fun() - fun() * fun(); 中我们只能通过操作符的优先级得知:先算乘法,再算减法。
但三个fun函数的调用先后顺序却无法通过操作符的优先级确定,不一定就是按从左到右的顺序调用。

表达式5

#include<stdio.h>intmain(){inti=1;intret=(++i)+(++i)+(++i);printf("%d\n",ret);printf("%d\n",i);return0;}

VS2022的运行结果:
但如果你在gcc中运行上面的代码得到的结果就和上面VS的结果有差异。
看看同样的代码产生了不同的结果,这是为什么? 简单看一下汇编代码,就可以分析清楚
这段代码中的第一个 + 在执行的时候,第三个++是否执行,这个是不确定的,因为依靠操作符的优先级和结合性是无法决定第一个+和第三个前置 ++ 的先后顺序的。

总结:
即使有了操作符的优先级和结合性,我们写出的表达式依然有可能不能通过操作符的属性唯一确定表达式的计算路径,那这个表达式就是存在潜在风险的,建议大家不要写出特别复杂的表达式。

六、位段补充

在位段中,位段的成员必须是int、unsigned int或者signed int(包括char类型, char也属于整型家族)。上面的意思是如果使用位段,那这个结构体中的成员一般是同类型的。比如:

structS{int_a:2;int_b:5;int_c:10;int_d:30;};
structS{char_a:3;char_b:4;char_c:5;char_d:4;};

特别是位段成员如果出现在表达式中,则会进行整型提升,自动转换为int类型或者unsigned int类型。

在C语言中,位段是用来节省内存空间的。内存分配是遵循以下规则的:
🏉1.成员大小限制:位段中的成员大小不能超过机器定义的类型大小。例如,int类型在大多数平台上是32位,因此位段中的成员大小(bit位数)不能超过32位。
🏉2.内存对齐:位段的内存对齐是基于其中成员的最大类型来确定的。如果一个位段中包含多个成员,且这些成员的类型大小不同,则内存对齐将基于最大的成员类型
🏉3.内存分配:位段通常不直接分配内存,而是作为结构体的一部分。结构体在内存中的分配遵循常规的内存分配规则,即通过 malloc 函数分配内存,并由程序员使用free函数释放内存。

位段中是可以有不同的类型成员的,但如果位段的成员有不同类型的话,那位段大小的计算就比较复杂。建议位段成员都以相同类型出现。(只要是整型家族即可)

http://www.jsqmd.com/news/1389918/

相关文章:

  • 东营网站建设天锐科技为何成为当地企业数字化转型的首选服务商
  • 浏览器端 OCR 文字识别完整实战:从一次后端服务迁移说起
  • 深入理解String.dedent工作原理:ECMAScript提案技术细节剖析
  • 用Label Studio做数据标注:新手3步跑通第一个标注项目
  • DOM Distiller与Boilerpipe对比:谁才是网页蒸馏技术的王者?
  • PageView手势冲突解决方案:3种手势类型深度解析
  • Rufus 启动盘制作完整指南:三步搞定 Windows 11 安装盘与常见报错排查
  • KiteSQL未来路线图:SQL 2016支持与LLVM JIT优化展望
  • MathorCup B题解析:动态需求预测与库存优化在物流排班中的应用
  • 5分钟跑通一个能看、能信、能交差的多智能体框架:AgentScope 2.0实战手记
  • Shapiq性能优化技巧:处理大规模数据集的高效计算方法
  • 抖店店群自动化管理系统:DOM透视突破大促弹窗,毫秒级响应
  • ElasticSearch Paramedic核心功能详解:从集群健康到分片分配的全方位监控
  • MCC代码结构详解:从engine_mcc到mcc_model的关键模块解析
  • Loki 查询性能优化实战:从压缩存储到查询分片,把日志链路压到毫秒级
  • 探寻专业之路:如何选择可靠的皮肤外用产品供应商
  • Vim-Addon-Manager快速上手指南:5分钟打造你的高效Vim插件系统
  • win11桌面日历替代软件
  • FFmpeg6操作 RTMP参数详解
  • 鼠标点击间歇性失灵:从驱动冲突到微动老化的全链路排查指南
  • 揭秘青岛知名网站建设公司背后的选择逻辑与避坑指南
  • ArcaneGAN技术原理揭秘:基于FastAI U-Net与StyleGAN2的创新融合方案
  • 手把手教你如何利用godaddy网站建设教程从零开始搭建一个专业级个人网站
  • 2026多模态AI模型选型指南:从架构演进到工程落地
  • Threat Composer社区与支持:如何获取帮助与参与讨论
  • drawsvg 2.x迁移指南:从1.x版本平滑过渡到最新版的关键步骤
  • 机器人技术演进:从本体性能到通用智能的范式转移
  • 保姆级教程:四个步骤完成ERPNext自动化安装,v13到v16全版本一次搞定
  • 一台电脑远程操控鸿蒙真机:HOScrcpy 投屏工具从零上手实战,附 5 个避坑点
  • 制造业设备巡检自动化选型:数据采集能力横评 —— 深度解析多模态融合与端到端智能自动化路径