循环语法的理解
for循环是一种常用的循环结构,它适合于在已知循环次数的情况下重复执行相同的代码块。for循环语法如下:
for(条件初始化; 条件判定; 条件更新){
//业务代码
}
while循环是另一种常用的循环结构,它适合于在不知道循环次数的情况下重复执行代码块,只需要在每次循环前先检查一次循环条件。 while循环语法如下:
条件初始化
while(条件判定){
//业务更新
条件更新
}
do-while循环和while循环很类似,它们的区别在于do-while循环的判断是在循环体执行完后才进行的,因此它保证了循环体至少能够被执行一次。 do-while循环语法如下:
条件初始化
do{
条件更新
}while(条件判定);
三种循环对应的死循环写法场景
while(1)
{
while语句
}
for(;;)
{
for语句;
}
do
{
do-while语句;
}while(1);
在C语言中,每个程序都有三个默认打开的流,即标准输入流(stdin)、标准输出流(stdout)和标准错误流(stderr)。它们分别对应键盘输入、屏幕输出和错误信息输出,可以在程序中使用它们来读取或写入数据,或者输出一些提示信息、错误信息等。在C语言中,我们可以使用各种标准输入输出函数(如printf、scanf、fgets等)来访问这些默认的流,从而实现程序的输入输出。同时,这三个标准流也可以在程序中进行重定向,例如将标准输出流重定向到文件上,从而将程序的输出内容保存到文件中。
在图片的代码中,为什么我们的代码没有文件操作,却会打开这些流呢?
实际上,你使用标准输入输出函数时,代码中并没有显式的文件操作代码,因为标准库已经将文件操作封装到标准输入输出函数中了。标准输入输出函数是使用FILE数据类型来操作标准输入输出流的。标准输入、输出、错误输出描述符在程序开始执行时就已经被打开了,因此当你使用标准输入输出函数时,实际上是操作这些默认打开的流,而不需要显式地打开文件。
例如,当你使用printf函数将输出内容写入标准输出流(通常是控制台屏幕)时,实际上是写入到了stdout指向的流。同样,当你使用scanf函数从标准输入流(通常是键盘输入)读取输入内容时,实际上是从stdin指向的流读取数据。
了解上面的内容后,我们再来了解一下getchar函数。
getchar函数是一个标准输入函数,用于从标准输入流(stdin)中读取一个字符。
它的函数原型如下:
getchar函数不需要任何参数,当从标准输入流中读取到一个字符时,它会将该字符作为返回值返回给调用者。如果从流中读取到文件结束标志EOF(End-Of-File),则返回EOF。
因此,我们可以根据getchar函数的返回值来判断是否读取到有效字符。 在使用getchar函数时,需要注意以下几点:
1. 当程序使用getchar函数时,它会一次读取一个字符,如果想要读取多个字符,需要使用循环语句(如while、for等)结合getchar函数来实现。
2. 在读取字符时,getchar函数会自动忽略空白字符(如空格、制表符、回车等),直到读取到一个非空白字符为止。如果想要读取空白字符,可以使用scanf函数等其他读取函数。
我们接下里就来看看循环语法和getchar函数的应用
#include <stdio.h> #include <windows.h> int main() { while (1) { //while的死循环写法 char c = getchar(); if ('#' == c) { break;//结束死循环 } printf("%c\n", c); } system("pause"); return 0; }
getchar的返回值类型为什么是int,而不是char。
实际上,C语言规定getchar函数的返回值类型为int,而不是char。这是为了能够表示EOF(End-Of-File)值,其值宏定义为-1。
在标准输入流中,如果读取到文件结束标志EOF,getchar函数会返回EOF,以便程序能够正确地判断文件是否已经读取完毕。如果将getchar函数的返回值类型定义为char,那么EOF就无法被正确地表示,因为EOF是一个int类型的常量。例如,如果getchar函数的返回值类型是char,那么当从标准输入流中读取到EOF时,getchar函数会返回char类型的-1。此时,程序就无法正确地判断是读取到了EOF,还是实际上读取到了字符-1。
此外,在一些特殊情况下,比如运行在宽字符环境下时,一个字符可能需要占用多个字节,此时将返回值类型设置为 int,可以避免数据截断。例如:当我们读取到1 0000 0000,而读取char类型只有八个比特位,此时读取的就是0000 0000在其合法范围内,那么就会读取错误的数据。
因此,C语言规定getchar函数的返回值类型为int,以便在读取到EOF时能够正确地返回EOF值,让程序能够正确地判断文件是否已经读取完毕。
总结:使用getchar一定不能忘记你自身输入的回车符(`\n`)。
break关键字
`break` 关键字是C语言中用于控制循环语句的关键字,它用于立即退出当前循环,并跳到循环语句后面的代码中执行。
continue关键字
`continue` 关键字是C语言中用于控制循环语句的关键字,它可以用于跳过当前循环中剩余的语句,直接进入下一次循环。
continue跳转的位置
总结:while语句和do-while语句判断条件在的位置就是continue跳转的位置,而for语句的条件更新在的位置就是continue跳转的位置。
goto关键字
`goto`关键字是一种跳转语句,它可以用于无条件地跳转到程序中的某个标记(label)处继续执行,但仅仅是本代码块内,不可跨函数、文件使用。
#include <stdio.h> #include <windows.h> int main() { int i = 0; START: printf("[%d]goto running ... \n", i); Sleep(1000); ++i; if (i < 10) { goto START; } printf("goto end ... \n"); system("pause"); return 0; }
这是一段 C 语言的程序代码。它使用了 goto 语句来实现循环的功能。程序开始时,定义了一个计数器变量 i,并初始化为 0。然后使用标签 START,打印输出一条信息,暂停程序执行一秒钟,计数器变量自加 1。接着判断计数器变量 i 是否小于 10,如果小于 10,则跳转到标签 START 处重新执行循环;否则,打印输出一条结束信息,暂停程序执行并等待用户按下任意键结束程序。总的来说,这段程序使用了 goto 语句来实现了一个简单的循环结构,但是因为 goto 容易导致程序流程混乱,使用时要谨慎。
void的用法以及理解
在C语言中,void是一种特殊的数据类型,表示“无类型”或“空类型”。
void是否可以定义变量
在C语言中,void是一种“空类型”,它没有具体的值或大小,因此不能定义为变量类型,也不能用作变量的数据类型。
很明显,在vs和gcc编译器中是错误的,禁止运行。我们再用sizeof求一下它所占的空间。
很明显,在vs编译器空间大小为0,自然也就不能开辟空间。
在gcc编译器下,大小又是多少呢?
很明显,在vs编译器空间大小为1,但是在vs编译器上显示大小为0,这不是矛盾嘛?
实际上,这是编译对void类型做出的不同解释:
1.在vs编译器空间大小为0,自然定义变量也是错误的。
2.在gcc编译器空间大小虽然为1,但是它定义变量却是错误的。按照常量说有了空间就可以定义变量,但是这里不可以,因为void在gcc编译器只仅作为一个占位符看待,所以也不能定义变量。
总结:void本身就被编译器解释为空类型,强制的不允许定义变量
为何 void 不能定义变量
定义变量的本质:开辟空间
而void作为空类型,理论上是不应该开辟空间的,即使开了空间,也仅仅作为一个占位符看待。所以,既然无法开辟空间,那么也就无法作为正常变量使用,既然无法使用,编译器干脆不让他定义变量。
在vs2013中,sizeof(void)=0
在gcc中,sizeof(void)=1(但编译器依旧理解成,无法定义变量)
void的应用场景
1.用于函数的返回值类型。在C语言中,函数可以返回各种不同类型的数据,但是有些函数并不需要返回任何值,这时候就可以使用void类型的函数。void函数不需要使用return语句,因为它们不返回任何值。
但是自定义函数,没有返回值,如果不写void,会让阅读你代码的人产生误解:他是忘了写,还是想默认int?这样就会导致代码的体验感变差。
总结:void作为函数返回值,代表不需要,这里是一个"占位符"的概念,是告知编译器和给阅读源代码看的,建议程序如果没有返回值的时候,将返回值类型写成void。
2.用于函数参数类型。当一个函数不需要接受任何参数时,可以使用void作为函数参数类型。例如,int main(void) 是一个不接受任何参数的函数。
总结:如果一个函数没有参数,将参数列表设置成void,是一个不错的习惯,因为可以将错误明确提前发现,另外,阅读你代码的人,也一眼看出,不需要参数。相当于"自解释"。
3.用于指针类型。在C语言中,void*指针可以指向任何类型的数据,但是它不能被直接解引用,必须先进行类型转换才能使用。因此,void指针常用于作为通用指针类型,用于传递指针参数,例如: ```c void print(void *data, size_t size);
void 指针
void不能定义变量,那么void*呢?
#include <stdio.h> #include <windows.h> int main() { void* p = NULL; //可以 system("pause"); return 0; }
void*定义变量no error!
为什么void*可以呢?
因为void*是指针,是指针,空间大小就能明确出来,有空间就可以创建变量。
void* 能够接受任意指针类型
#include <stdio.h> int main() { void* p = NULL; int* x = NULL; double* y = NULL; p = x; //虽然类型不同,但是编译器并不报错 x - int* p = y; //同上 y - double* return 0; }
`void*` 可以接受任意指针类型是因为C语言中所有指针类型的大小都是相同的。指针的大小和所指向的数据类型没有关系,它只与寻址空间的大小有关,通常为4个字节或8个字节。因此,任意类型的指针都可以转换成 `void*` 类型指针,因为这种转换不会改变指针所占用的大小和指向的内存空间。
当我们反过来将void*赋给x,y呢?
#include <stdio.h> int main() { void* p = NULL; int* x = NULL; double* y = NULL; p = x; //虽然类型不同,但是编译器并不报错 x - int* p = y; //同上 y - double* x = p; y = p; return 0; }
同样没有报错,甚至连警告都没有。
总结:void*可以被任何类型的指针接收,同时也可以接收任意指针类型(常用)。
void * 定义的指针变量可以进行运算操作吗?
#include <stdio.h> int main() { void* p = NULL; p++; //报错 p += 1; //报错 return 0; }
`void*` 指针类型是一个通用的指针类型,用于通用的指针操作,它本身是一个无类型指针,不直接支持指针运算。
由于 `void*` 指针类型无法知道它指向的内存的数据类型和占用多少字节,所以在使用 `void*` 类型指针时,不能进行指针运算。若对 `void*` 类型指针进行加、减、乘、除、位运算等操作是没有意义的,并且这样的操作通常会导致编译错误。
再来看一下在gcc编译器的环境下是否可以?
因为在gcc编译器下void空间大小为1,自然就可以进行运算操作。p本身就是地址(十六进制),int强制转为整形,然后再以十进制打印出来结果。
void*指针可以直接解引用吗?
#include<stdio.h> int main() { int a = 10; void* p = (void*)&a; //由于void*可以接受任意类型的地址 //也可以写成void* p = &a; *p; return 0; }
我们发现在gcc和vs编译器下都出现了问题。
总结:void*指针是一种无类型指针,它不能直接解引用。因为void*指针并不知道它所指向的内存区域的数据类型,所以不能像其他指针那样通过解引用来获取值。