第8讲 - C语言关键字(8)

先写一串代码:

#define _CRT_SECURE_NO_WARNINGS 1
int main()
{
	while (1)
	{
		int c = getchar();
		if ('#' == c)
		{
			break;
		}
		putchar(c);
	}
	return 0;
}

我们对putchar进行解释:把一个字符显示到我们到标准输出,也就是显示器上

这段代码有什么用呢?

答:可以检验我们输入到键盘上或者显示器上的内容是否都是字符,比如我们输入1234,假如我们输入的不是字符的话,if语句首先不满足条件,执行putchar函数,因为putchar函数能够把一个字符显示到我们的显示器上,假如我们输入到键盘上的内容并不是字符是,putchar函数是无显示的,所以证明了我们输入到键盘上或者显示器上的内容全部都是字符

我们对代码进行优化

#define _CRT_SECURE_NO_WARNINGS 1
int main()
{
	while (1)
	{
		int c = getchar();
		if ('#' == c)
		{
			break;
		}
		putchar(c);
	}
	printf("\nbreak out\n");
	return 0;
}

我们进行编译

 由此看见,使用break时,我们会跳出整个循环。

当我们使用continue时

#define _CRT_SECURE_NO_WARNINGS 1
int main()
{
	while (1)
	{
		int c = getchar();
		if ('#' == c)
		{
			continue;
		}
		putchar(c);
	}
	printf("\nbreak out\n");
	return 0;
}

我们进行运行

由此可以知道continue的意思:continue的意思是跳过本次循环,也就是跳过一次循环

注意:continue跳过本次循环后,再次来到循环的判断位置,在这里就是到while循环的判定处

这里,我们出现一个问题:是不是对于所有的循环,continue都会再次来到循环的判断?

我们进行来举几个例子:

首先是do while循环

int main()
{
	do{
		printf("hello\n");
		if (flag)
		{
			continue;
		}
	}  while (cond)
	return 0;
}

在do while循环内部的if循环中,看见continue,continue过后,程序又从哪里开始运行呢

答:到循环的判断条件处,也就是while(cond)

接下来就是for循环

int main()
{
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("hello world\n");
		if (flag)
		{
			continue;
		}
	}
	return 0;
}

对于for循环,continue的意思是跳过本次循环,然后跳转到for循环的条件更新出,也就是i++。

我们再举一个continue的例子

#include<Windows.h>
int main()
{
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("continue before :%d\n", i);
		if (i == 5)
		{
			printf("continue\n");
			continue;
		}
		printf("continue after:%d\n", i);
		Sleep(500);
	}
	return 0;
}

还是之前提到的问题:continue之后代码运行到什么位置?

答:这里需要假设,有三种情况,第一种情况运行到for循环内部的第一个printf

我们对这种情况进行解释:因为我们没有经过for循环,而i始终等于5,所以我们死循环打印continue并执行continue

第二种情况:continue之后代码运行到条件判断处,也就是i<10处

我们对这种情况进行解释:因为我们的i始终=5<10,所以我们始终执行if语句内部的内容,所以也是死循环

第三种情况:continue之后代码运行到条件更新处

我们对这种情况进行解释:我们continue之后,代码运行到i++处,所以i进行条件更新后,变成6,再进行条件判定,所以可以执行完毕整个for循环,我们进行编译看一下代码

代码并不是死循环,所以对于for循环内部的continue,跳过本次循环后,跳转到for循环的条件更新处

总结:continue对于不同的循环语句,跳过循环后跳转到不同的位置:对于while循环和do while循环语句,continue跳过循环后跳到while循环的条件判定处,对于for循环,continue跳过循环后跳到for循环的条件更新处。

在多层循环中,最长的循环放在最外层,最短的循环放在最内层,以减少cpu跨切循环次数

两个理由,第一个:cpu在存储数据的过程中,会不断缓存数据,在我们两个不同位置的代码来回切换的过程中,我们的cpu也是从缓存到过期来回跳转,这就导致我们的cpu在存储数据的过程中花费了太长的时间

第二个理由:局部性原理,意思就是代码呈现很密集的情况下,代码的效率会大大提高,因为我们的程序在加载的过程中,默认会把一个代码附近的代码也会加载到系统当中的,所以代码越密集,代码的执行效率越高。

for循环语句尽量要写成前闭后开型区间

理由有两个

第一个:前闭后开型区间的循环次数简单直观,就是两个区间端点值相减,例如

for (i = 0; i <10; i++)
	{

这个语句的循环此时就是10-0为10 ,假如我们写成前闭后闭型区间时,我们的循环次数就是两个区间的端点值+1,例如,

for (i = 0; i <=9; i++)
	{

我们的循环次数就是9-0+1也就是10

第二个:下标计算更加方便,例如

for (i = 0; i <10; i++)
	{

假如我们根据for循环的循环次数,创建一个数组,注意数组的首元素也就是arr[1]对应的是i=0,arr[10]对应的是i=9,把整个数组都包裹完全了

for循环内,注意不要有浮点型的数据

goto语句的介绍

我们举一个例子

int main()
{

	goto end;
	printf("hello 1\n");
	printf("hello 2\n");
	printf("hello 3\n");
end:
	printf("hello 4\n");
	printf("hello 5\n");
	printf("hello 6\n");
	return 0;
}

goto语句是这样,跳过goto end和标签end的代码,直接执行标签end以后的代码。

如图所示 ,上面的goto语句表示的是向下跳转,其实,goto语句是可以任意跳转的,接下来,我们实现一下往上跳转

int main()
{
	
end:
	printf("hello 1\n");
	printf("hello 2\n");
	printf("hello 3\n");
	goto end;
	printf("hello 4\n");
	printf("hello 5\n");
	printf("hello 6\n");
	return 0;
}

我们进行编译

 代码出现死循环,原因是什么?

答:代码每次运行到goto语句都会跳转到end标签处,end打印后会继续到goto语句,所以死循环

接下来,我们举一个goto语句应用的例子

int main()
{
	int i = 0;
start:
	printf("[%d] goto running \n", i);
	i++;
	if (i < 10)
	{
		goto start;
	}
	printf("goto end\n");
	return 0;
}

我们进行运行

 goto语句可不可以跨代码块使用

答:不能,我们进行检测

void fun()
{
start:
	printf("enter fun()");
}
int main()
{
	int i = 0;

	printf("[%d] goto running \n", i);
	i++;
	if (i < 10)
	{
		goto start;
	}
	printf("goto end\n");
	return 0;
}

我们把goto语句的标签start放在我们自定义的函数fun()里面,我们进行编译,如果goto语句可以跨代码使用,应该会打印出enter fun()

 可以发现,生成错误,所以:goto语句是不能跨代码块使用

很多公司禁止使用goto语句,不过这个问题我们还是灵活看待,goto在解决很多问题时有奇效的

下面提出一个问题:是否可用void来定义变量

我们写一个代码进行检测

int main()
{
	void x = 0;
}

我们进行编译

 所以是不能用void类型来定义变量的,那么原因是什么呢?

有人会说:因为void类型是空类型,定义变量时开辟的空间是未知的,所以不能用来定义变量。

这种说法是可以理解的,但是真正的原因是什么呢?

我们先写一个代码

int main()
{
	printf("%d", sizeof(void));
	return 0;
}

我们这里求的就是void类型的变量占据空间的大小,我们进行编译

 我们可以发现,结果为0,我们再在Linux系统上尝试一下 

 我们发现,在linux系统中void类型的变量却占据1个字节,所以void类型的变量开辟的空间不一定都是0,所以上面的说法不成立。真正正确的说法在下面总结处

总结:void本身就被编译器解释为空类型,强制的不允许定义变量

我们来写一个代码

void test()
{
	printf("hello test\n");
	return 1;
}
int main()
{
	test();
}

 我们的编译器是能编译过去的

但是只要我们创建了一个变量来接受这个函数的返回值

void test()
{
	printf("hello test\n");
	return 1;
}
int main()
{
	int a=test();
}

我们进行编译,可以发现

 代码报错,所以我们得出结论void类型的函数,正常情况下是能编译过去的,但是假如我们创建了一个变量来接受这个函数,就会报错。

我们把函数的返回值类型void去掉,可以发现

test()
{
	printf("hello test\n");
	
}
int main()
{
	test();
}
test()
{
	printf("hello test\n");
	
}
int main()
{
	int a= test();
}

无论是否创建变量接受函数的返回值,函数都不会报错

所以在c语言中,可以不带返回值,默认的返回值类型是int

那是不是就说明了以后我们在书写函数的时候,是不是可以不带返回值类型呢?

答:不可以,如果我们的函数没有返回值时,我们还是要在返回值的前面加上void类型

因为:在默认的情况下,函数的返回值是整型,在没有返回值的时候,返回值的类型也是整型,所以当别人看见你的代码,别人就会产生疑问:是这个忘写返回值了,还是这个函数是没有返回值的。

void修饰函数返回值有两个作用

1:一方面告诉代码的编写者不要添加返回值了

2:当你加上void类型,即使你有返回值,如果你不接受的情况下,编译是能编过的,但是只要你想要接受,或者存储返回值,编译就无法通过,起到检验错误的作用

我们再写一串代码

int test1()
{
	return 1;
}
int test2(void)
{
	return 1;
}
int main()
{
	test1(1, 2, 3, 4);
}

我们对代码进行分析:首先,第一个函数test1和第二个函数test2的区别在于test2函数没有参数的

我们对test1传参,进行编译,可以发现,代码正常运行

 

但是当我们把对test2函数传参时,在编译器vs2013版本下,依旧正常运行,但是代码产生警告

 总结:void充当函数的形参列表,告诉编译器和代码的编写者函数不需要传参

void不能用来创建变量,那么void*可以吗?

答:可以,原因如下:因为类型明确,void*就是一个指针,在32维平台下占四个字节,64位平台下占八个字节

写一串代码

int main()
{
	void*p = NULL;
	double*x = NULL;
	int *y = NULL;
	x = p;
	y = p;
}

我们进行编译,可以发现代码正确运行

总结:void型的指针可以用其他任何类型的指针来接受

我们再换一种写法

int main()
{
	void*p = NULL;
	double*x = NULL;
	int *y = NULL;
	x = p;
	y = p;

	p = x;
	p = y;
}

我们进行运行,依旧成功运行

总结:void型的指针可以接受任意类型的指针

提问:void类型的指针可以进行加减运算吗?

我们写一个代码进行检验

int main()
{
	int *p = NULL;
	p++;
	p--;
}

我们首先写一个整型指针,检验其是否可以进行加减运算

代码是可以运行的 ,接下来我们试试void类型的指针

int main()
{
	void *p = NULL;
	p++;
	p--;
}

可以发现

 产生错误,所以void型的指针是不能进行加减运算的

究其原因是:指针加减通常是从一个指针变量移位到另一个指针变量,所以涉及到指针类型的步长,因为void类型是没有步长的,也就不清楚加1减1移位的距离,所以不能进行加减运算

注意:在Linux系统中,void类型的指针是可以加减运算的,原因是因为在linux中,void类型的步长被认为是1

void类型的指针能够直接解引用吗?

答:不能

int main()
{
	void*p = NULL;
	*p;
}

我们进行编译:

 有人可能会认为p为空指针,所以不能解引用,所以我们换一个其他的

int main()
{
	int a = 10;
	void*p = &a;
	*p;
}

注意:我们之前有说过,void型的指针能够接受任意类型的指针变量,所以也能接受int类型的指针。我们进行运行

 依旧是非法寻址。

总结:void类型的指针不能直接解引用

为什么呢?

答:因为p的类型void类型,void类型的指针解引用后也是void类型的变量,因为void类型的变量是无法创建的,所以void型的指针不能直接解引用

注意:c语言中有字符串,但是没有字符串类型。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值