先写一串代码:
#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语言中有字符串,但是没有字符串类型。