C语言进阶——一文带你深度了解“C语言关键字”(中篇5)

 

这篇文章记录我深度学习C语言关键字知识,旨在分享记录心得,希望我的分享对你有所帮助!

目录

一、do、while、for关键字

 (一)、break与continue

(二)、循环语句的注意点

 二、goto关键字

三、void关键字

(一)、void  a?

(二)、void 修饰函数返回值和参数

(三)、void指针 

(四)、void 不能代表一个真实的变量

四、结语 


一、do、while、for关键字

while 循环:先判断 while 后面括号里的值,如果为真则执行其后面的代码;否则不执行。 while(1)表示死循环。

那问题来了——死循环有什么用呢? 

看一个例子吧:

比如你开发一个系统要日夜不停的运行,但是只有操作员输入某个特定的字符‘#’才可以停下来。

while(1)
{
    if( ‘#’== GetInputChar() )
    {
        break;
    }
}

 (一)、break与continue

break 关键字很重要,表示终止本层循环。现在这个例子只有一层循环,当代码执行到break 时,循环便终止。
如果把 break 换成 continue 会是什么样子呢? continue 表示终止本次(本轮) 循环。当代码执行到 continue 时,本轮循环终止,进入下一轮循环。
while( 1)也有写成 while(true) 或者 while(1==1) 或者 while((bool) 1)等形式的,效果一样。


do-while 循环:先执行 do 后面的代码,然后再判断 while 后面括号里的值,如果为真,循环开始;否则,循环不开始。其用法与 while 循环没有区别,但相对较少用。
for 循环: for 循环可以很容易的控制循环次数,多用于事先知道循环次数的情况下。


留一个问题:在 switch case 语句中能否使用 continue 关键字?为什么?

(二)、循环语句的注意点

 【注意点1】:

在多重循环中,如果有可能,应当将最长的循环放在最内层,最短的循环放在最外层,以减少 CPU 跨切循环层的次数。

【注意点2】:

建议 for 语句的循环控制变量的取值采用“半开半闭区间”写法。
半开半闭区间写法和闭区间写法虽然功能是相同,但相比之下,半开半闭区间写法写法更加
直观。

【注意点3】:

不能在 for 循环体内修改循环变量,防止循环失控。

【注意点4】: 

循环要尽可能的短,要使代码清晰,一目了然。

如果你写的一个循环的代码超过一显示屏,那会让读代码的人发狂的。解决的办法由两个:第一,重新设计这个循环,确认是否这些操作都必须放在这个循环里;第二,将这些代码改写成一个子函数, 循环中只调用这个子函数即可。 一般来说循环内的代码不要超过 20行。

【注意点5】:  

把循环嵌套控制在 3 层以内。
国外有研究数据表明,当循环嵌套超过 3 层,程序员对循环的理解能力会极大的降低。如果你的循环嵌套超过 3 层,建议你重新设计循环或是将循环内的代码改写成一个字函数。

 二、goto关键字

1、本人主张慎用或者禁用该关键字

自从提倡结构化设计以来, goto 就成了有争议的语句。首先,由于 goto 语句可以灵活跳转,如果不加限制,它的确会破坏结构化设计风格;其次, goto 语句经常带来错误或隐患。它可能跳过了变量的初始化、重要的计算等语句,例如:

struct student *p = NULL;
…
goto state;
p = (struct student *)malloc(…); //被 goto 跳过,没有初始化
⋯

⋯

state:
//使用 p 指向的内存里的值的代码
⋯
如果编译器不能发觉此类错误,每用一次 goto 语句都可能留下隐患。

三、void关键字

大家肯定听过一句话——“色即是空,空即是色”……

(一)、void  a?

void 的字面意思是“空类型”, void *则为“空类型指针”, void *可以指向任何类型的数据。
void 几乎只有“注释”和限制程序的作用,因为从来没有人会定义一个 void 变量,看看下面
的例子:
void a;
Visual C++6.0 上,这行语句编译时会出错,提示“illegal use of type 'void'”。不过,即使
void a 的编译不会出错,它也没有任何实际意义。


void 真正发挥的作用在于:
(1) 对函数返回的限定;
(2) 对函数参数的限定。

众所周知, 如果指针 p1 和 p2 的类型相同, 那么我们可以直接在 p1 和 p2 间互相赋值;
如果 p1 和 p2 指向不同的数据类型,则必须使用强制类型转换运算符把赋值运算符右边的
指针类型转换为左边指针的类型。

 例如:
float *p1;
int *p2;
p1 = p2;

其中 p1 = p2 语句会编译出错,提示“'=' : cannot convert from 'int *' to 'float *'”,必须改为:
p1 = (float *)p2;


而 void *则不同,任何类型的指针都可以直接赋值给它,无需进行强制类型转换:
void *p1;
int *p2;
p1 = p2;

但这并不意味着, void *也可以无需强制类型转换地赋给其它类型的指针。因为“空类型”可
以包容“有类型”,而“有类型”则不能包容“空类型”。

(二)、void 修饰函数返回值和参数

【规则1】如果函数没有返回值,那么应声明为 void 类型

在 C 语言中,凡不加返回值类型限定的函数,就会被编译器作为返回整型值处理。但是许多程序员却误以为其为 void 类型。例如:
add ( int a, int b )
{
        return a + b;
}
int main(int argc, char* argv[]) //甚至很多人以为 main 函数无返回值
//或是为 void 型的
{
        printf ( "2 + 3 = %d", add ( 2, 3) );
}
程序运行的结果为输出: 2 + 3 = 5


这说明不加返回值说明的函数的确为 int 函数。
因此,为了避免混乱,我们在编写 C 程序时,对于任何函数都必须一个不漏地指定其类型。如果函数没有返回值,一定要声明为 void 类型。这既是程序良好可读性的需要,也是编程规范性的要求。另外,加上 void 类型声明后,也可以发挥代码的“自注释”作用。所谓的代码的“自注释”即代码能自己注释自己

【规则2】如果函数无参数,那么应声明其参数为 void

在 C++语言中声明一个这样的函数:
int function(void)
{
        return 1;
}
则进行下面的调用是不合法的: function(2);
因为在 C++中,函数参数为 void 的意思是这个函数不接受任何参数。
但是在 Turbo C 2.0 中编译:
#include "stdio.h"
fun()
{
        return 1;
}
main()
{
        printf("%d",fun(2));
        getchar();
}
编译正确且输出 1,这说明,在 C 语言中,可以给无参数的函数传送任意类型的参数,但是在 C++编译器中编译同样的代码则会出错。在 C++中,不能向无参数的函数传送任何参数,出错提示“'fun' : function does not take 1 parameters”。
所以,无论在 C 还是 C++中,若函数不接受任何参数,一定要指明参数为 void。

(三)、void指针 

【注意点1】千万小心又小心使用 void 指针类型。

按照 ANSI(American National Standards Institute)标准,不能对 void 指针进行算法操作
即下列操作都是不合法的:
void * pvoid;
pvoid++; //ANSI:错误
pvoid += 1; //ANSI:错误


ANSI 标准之所以这样认定,是因为它坚持:进行算法操作的指针必须是确定知道其指向数据类型大小的。也就是说必须知道内存目的地址的确切值。
例如:
int *pint;
pint++; //ANSI:正确
但是大名鼎鼎的 GNU(GNU's Not Unix 的递归缩写)则不这么认定,它指定 void *的算法操作与 char *一致。因此下列语句在 GNU 编译器中皆正确:
pvoid++; //GNU:正确
pvoid += 1; //GNU:正确
在实际的程序设计中,为符合 ANSI 标准,并提高程序的可移植性,我们可以这样编写实现同样功能的代码:
void * pvoid;
(char *)pvoid++; //ANSI:正确; GNU:正确
(char *)pvoid += 1; //ANSI:错误; GNU:正确
GNU 和 ANSI 还有一些区别,总体而言, GNU 较 ANSI 更“开放”,提供了对更多语法的支持。但是我们在真实设计时,还是应该尽可能地符合 ANSI 标准。

【注意点2】如果函数的参数可以是任意类型指针,那么应声明其参数为 void *。

典型的如内存操作函数 memcpy 和 memset 的函数原型分别为:
void * memcpy(void *dest, const void *src, size_t len);
void * memset ( void * buffer, int c, size_t num );


这样,任何类型的指针都可以传入 memcpy 和 memset 中,这也真实地体现了内存操作函数的意义, 因为它操作的对象仅仅是一片内存, 而不论这片内存是什么类型。 如果 memcpy和 memset 的参数类型不是 void *, 而是 char *, 那才叫真的奇怪了!这样的 memcpy 和 memset明显不是一个“纯粹的,脱离低级趣味的”函数!

下面的代码执行正确:

 例子: memset 接受任意类型指针
int IntArray_a[100];
memset (IntArray_a, 0, 100*sizeof(int) ); //将 IntArray_a 清 0
例子: memcpy 接受任意类型指针
int destIntArray_a[100], srcintarray_a[100];
//将 srcintarray_a 拷贝给 destIntArray_a
memcpy (destIntArray_a, srcintarray_a, 100*sizeof(int) );
有趣的是, memcpy 和 memset 函数返回的也是 void *类型,标准库函数的编写者都不是一
般人。

(四)、void 不能代表一个真实的变量

【注意点1】void 不能代表一个真实的变量。

因为定义变量时必须分配内存空间,定义 void 类型变量,编译器到底分配多大的内存呢。
下面代码都企图让 void 代表一个真实的变量,因此都是错误的代码:
void a; //错误
function(void a); //错误
void 体现了一种抽象,这个世界上的变量都是“有类型”的,譬如一个人不是男人就是女人(人妖不算)。


void 的出现只是为了一种抽象的需要, 如果你正确地理解了面向对象中“抽象基类”的概念,也很容易理解 void 数据类型。正如不能给抽象基类定义一个实例,我们也不能定义一个 void(让我们类比的称 void 为“抽象数据类型”)变量。


void 简单吧?到底是“色”还是“空”呢?

四、结语 

关于本次的“C语言关键字”进阶篇知识分享就到此结束了。希望我的分享对你有所帮助!敬请关注,持续更新中~~~

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小小_扫地僧

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值