learn_C_deep_8 (循环语法的理解、void的用法以及理解)

简介: learn_C_deep_8 (循环语法的理解、void的用法以及理解)

循环语法的理解


       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*指针并不知道它所指向的内存区域的数据类型,所以不能像其他指针那样通过解引用来获取值。

相关文章
|
存储 负载均衡 算法
Nacos注册表解读
Nacos注册表解读
|
自然语言处理 编译器 程序员
【Qt底层之 元对象的编译】Qt 元对象系统及其编译流程解析
【Qt底层之 元对象的编译】Qt 元对象系统及其编译流程解析
756 4
|
机器学习/深度学习 人工智能 自然语言处理
利用AI辅助工具提高软件测试效率与准确性
【2月更文挑战第16天】 在快速发展的软件行业中,测试工作是确保产品质量的关键环节。然而,传统的测试方法往往耗时且容易遗漏错误。本文介绍了一种结合人工智能(AI)技术的测试辅助工具,旨在提升软件测试的效率和准确性。通过引入智能化的缺陷预测、自动化测试用例生成以及实时反馈机制,该工具能够显著减少人力资源消耗,同时提高发现潜在问题的能力,为软件测试领域带来革新。
|
Java 定位技术 Android开发
【Android App】利用腾讯地图获取地点信息和规划导航线路讲解及实战(附源码和演示视频 超详细必看)
【Android App】利用腾讯地图获取地点信息和规划导航线路讲解及实战(附源码和演示视频 超详细必看)
742 1
|
安全 Java API
SpringCloud Gateway路由转发规则
`Spring`在因`Netflix`开源流产事件后,在不断的更换`Netflix`相关的组件,比如:`Eureka`、`Zuul`、`Feign`、`Ribbon`等,`Zuul`的替代产品就是`SpringCloud Gateway`,这是`Spring`团队研发的网关组件,可以实现限流、安全认证、支持长连接等新特性。
SpringCloud Gateway路由转发规则
|
机器学习/深度学习 人工智能 自然语言处理
一文读懂“大语言模型”
本文基于谷歌云的官方视频:[《Introduction to Large Language Models》](https://www.youtube.com/watch?v=zizonToFXDs&t=525s&ab_channel=GoogleCloudTech) ,整理而成,希望对大家入门大语言模型有帮助。
17081 1
一文读懂“大语言模型”
|
传感器 人工智能 物联网
浅谈物联网工程专业:技术融合与未来发展
浅谈物联网工程专业:技术融合与未来发展
|
机器学习/深度学习 人工智能 TensorFlow
《深入探讨:AI在绘画领域的应用与生成对抗网络》
《深入探讨:AI在绘画领域的应用与生成对抗网络》
354 0
|
人工智能 BI
5.2.2_并行进位加法器
计算机组成原理之并行进位加法器
440 0
5.2.2_并行进位加法器
|
JavaScript 数据可视化 前端开发
大厂经验(一):一套 Web 自动曝光埋点技术方案
超过10年互联网行业技术工作经验总结。
4009 0
大厂经验(一):一套 Web 自动曝光埋点技术方案