11·C语言---一些杂乱无章但是值得说说的问题


一、操作系统到底是个什么东西?

1· 先说说裸机程序:
代码量小,功能简单,所有代码都和直接目的有关,没有服务型代码(服务型代码就是 服务其他代码的 代码)。

2· 操作系统类似于 管理阶级。
操作系统的代码本身并不直接产生价值,它的主要任务就是管理所有资源,它主要为 直接产生价值,直接劳动的程序(各种应用程序)提供服务。所以操作系统既是管理者也是服务者。

3· 极简单的功能,使用简单的CPU(例如单片机)的产品才会选择用裸机开发;一般复杂性的产品选择基于操作系统来开发

4· 操作系统的调用通道:API函数
操作系统负责管理和资源调配,应用程序负责具体的直接劳动,他们之间的接口就是API函数。当应用程序需要使用系统资源(例如内存,CPU,硬件操作)时就通过API向操作系统发出申请,然后操作系统响应申请帮助应用程序执行功能。

5· c库函数 于 API的关系
单纯的API只是提供了很简单并且没有任何封装的服务函数,应用程序用API接口函数有点麻烦就把API接口函数进行了封装,于是形成了c库函数。

由于c库函数就是对API接口函数的封装,所以有时完成一个功能,既可以用API接口函数实现,也可以用c库函数实现,比如读写文件,API接口函数是:open write read close;c库函数里面有:fopen fwrite fread fclose .

fopen 的本质就是用open实现的,只是进行了封装,封装的目的:添加缓冲机制,具体的知识会在网络编程那一块出现。

6· 不同平台下(windows 、 Linux、裸机)下的库函数的差异
6·1 不同操作系统API 是不同的,但是都能完成所有的任务,只是完成一个任务的API不同。

6·2 库函数在不同操作系统下也不同,但是相似度要更高一些。这是认为的,因为在封装API的时候意识到方便性,即使在不同的操作系统下,看见相似的c库函数也能大致明白其函数的功能。所以在封装API的时候尽量的使用了同一套接口。虽然相似但是还是有差异,导致的问题是:在一个操作系统上写的应用程序不可能在另一个操作系统上编译运行,于是 可移植性 的概念就出来了。

6·3 跨操作系统可移植平台:QT,Java语言。

7· 操作系统的意义:软件体系的分工
在有操作系统的基础上,我们做一个产品就可用分为2部分:一部分人负责做操作系统(开发驱动);一部分人负责用操作系统实现功能(开发应用)。
实际上 上层应用层的功能进一步复杂化后又分了好多层。

二、 main函数的返回值究竟返回给谁?

前导:

正常的main函数定义
int main (void)

int main (int argc, char const  *argc[] )

int main (int argc, char const **argc)

标准C语言输出是 int类型的。所以下面的定义方式是错误的:main函数的返回值为void。

错误定义main函数
void main (void)

这种定义其实是存在的,只是存在于一些单片机的编程里面。
因为有些编译器是可以接受这个定义方法,但是有些编译器就不行

1· 函数为什么需要返回值?
函数的本质就是数据加工器。既然是加工,那就需要原材料,所以原材料就是输入型参数;加工的结果就是得到一个产品,那产品就是输出型参数

因为函数需要对外输出数据(函数运行的一些结果值)因此需要返回值。

2· main函数被谁调用?

2·1 main函数是特殊的,首先这个名字是特殊的。因为C语言规定了main函数是整个程序的入口。其他的函数只有直接或者间接被main函数所调用才能被执行,如果没有被main直接或者间接调用,则这个函数在整个程序中无用。

2·2 main函数从某种角度来看就代表了整个程序,因为main函数的开始意味着整个程序开始执行,main函数的结束意味着整个函数的结束。所以,谁执行了这个程序,谁就调用了main。那到底谁执行了这个程序?程序有几种被执行(也就是调用main函数)的方法?

2·3 Linux 系统下 一个新程序执行的本质
(1)表面来看,Linux中执行命令:. / xxx 就可以执行一个程序。
(2)还可以通过shell脚本调用执行一个程序。
(3)还可以在程序中去调用执行一个程序(fork exec)

总结:我们可以有很多种方法去执行一个程序,但是本质上是相同的。Linux中一个新程序的执行本质上就是一个进程的创建、加载、运行、消亡。

Linux中执行一个程序其实就是创建一个新进程,然后把这个程序丢进这个进程中去执行直到结束。新进程是怎么来的? 在Linux中进程都是被它的父进程fork出来的。
命令行本身就是一个进程,在命令行下 . / xxx 执行一个程序,其实这个新进程是作为命令行进程的一个子进程去执行。所以,一个程序被它的父进程调用。所以,main函数被所在子进程的父进程调用。

main函数的父进程 要 main函数 的返回值干什么?
父进程调用子进程执行一个任务,子进程执行完后通过main函数的返回值返回给父进程一个答复。这个答复一般是表示子进程的任务执行结果完成还是执行错误(0表示成功,-1表示错误)。

2·4 获取main函数的返回值 实践
shell 脚本执行程序获取程序返回并且打印出来。
Linux shell 中用 $? 这个符号来存储和表示上一次程序执行的结果

#!/bin/sh
./a.out
echo $?

三、 argc 、argv 与main函数的传参

1· 谁给main函数传参
main函数所在子进程的父进程给main函数传参,并且接收main函数的返回值。

2· 为什么要给main函数传参
(1)main函数不传参于是可以的,也就是说父进程调用子程序 并且 给子程序传参不是必须的。int main (void) 这种定义方式就是表示不必要给main传参。
(2)传参的目的就是:在不改变源程序的基础上,使程序具有灵活性。

3· 表面上:给main函数传参是怎样实现的?
(1)通过argc和argv这两个C语言预定参数来实现
(2)argc 是int类型,表示运行程序的时候给main传了几个参数;argv 是一个字符串数组,这个数组用来存储多个字符串,每个字符串就是我们给main函数传的一个参数。argv[ 0 ] 就是给main函数传的第一个参数,argv[1]就是给main函数传的第二个参数…

argv[0] 一般就是 . / a.out

4· 本质上:给main函数传参是怎样实现的?
(1)程序调用有多种方法本质上都是父进程fork一个子进程,然后子进程和一个程序绑定起来去执行(exec函数族),我们在exec的时候可以给他同时传参。
(2)程序调用时可以被传参(就是main的传参)是操作系统层面的支持完成的。

5· 给maini传参需要注意什么?
(1)main函数传参都是通过字符串传进去的
(2)程序被调用时传参,各个参数之间都是通过空格来间隔的
(3)在程序内部如果要使用argv,那么一定要先检验argc。

四、 void类型的本质

1· C语言属于强类型语言
(1)编程语言分2中:强类型语言 和 弱类型语言。强类型语言中所有的变量都有自己固定的类型,这个类型有 固定的 内存占用和解析方法;弱类型语言中没有类型的概念,所有变量全部都是一个类型(一般都是字符串的),程序在用的时候在根据需要来处理变量。

(2)C语言就是强类型语言,C语言中所有的变量都有明确的类型。因为C语言中的一个变量都要对应内存中的一段内存,编译器需要这个变量的类型来确定这个变量占用内存的字节数和这一段内存的解析方法。

2· 数据类型的本质含义
(1)数据类型的本质含义就决定数据的内存占用以及解析方法。
(2)得出结论:C语言中变量必须有确定的数据类型,如果一个变量没有确定的类型(就是所谓的无类型)会导致编译器无法给这个变量分配内存,也无法解析这个变量对应的内存。因此,不可能有没有类型的变量。
(3)C语言中不可能没有类型的变量,却可以有 没有类型的内存。在内存还没有和具体的变量相绑定之前,内存就可以没有类型。实际上纯粹的内存就是没有类型的。

3· void 类型的本质
(1)void类型的正确含义:不知道类型,暂时没有确定类型。
(2)void a ;定义了一个void 类型的变量,含义就是说a是一个变量,而且a肯定有确定的类型,只是目前我还不知道a的类型,还不确定,所以先标记为 void。

4· 为什么需要void类型
(1)什么情况下需要void类型
在描述一段还没有具体使用的内存时需要使用void类型。

(2)void 的一个典型案例就是 malloc的返回值
malloc函数向堆管理器申请一段内存给当前进程使用,malloc返回的是一个指针,这个指针指向申请的那段内存。malloc刚申请的这段内存尚未用来存储数据,malloc函数也无法预知这段内存将来会存放什么类型的数据,所以malloc无法返回具体类型的指针,所以malloc就被规定返回一个void * 类型,告诉外部返回的是一段干净的内存空间,没有确定类型。所以我们在malloc之后可以给这段内存读写任意类型的数据----->所以可以强制类型转换成一个具体类型。

五、 C语言中的NULL

1· NULL在C / C++中的标准定义
(1)NULL不是C语言关键字,本质上是一个宏定义
(2)NULL的标准定义:

#ifdef _cplusplus    //条件编译   这里是C++
#define NULL 0
#else 
#define NULL (void *)0   //这里是 C语言
#endif

解释:
C++ 的编译环境中,编译器预先定义了一个宏_cplusplus,程序中可以通过条件编译判断当前的编译环境是C++的还是C的。
NULL的本质是 0 ,但是这个 0 不是一个数字解析,而是当一个内存地址来解析的,因为(void * )0 这个整体表示一个指针指向 0 这个地址,所以这个 0 其实是0x0000 0000 ,代表 0 地址。

2· 从指针角度理解NULL的本质
int * p ;
int * p = NULL;
p 是一个局部变量,分配在栈上的地址是由编译器决定的,p 的值是(void *) 0.实际上就是 0 ,意思是指针p 指向内存的 0 地址处,这时候的p就不是野指针。

为什么要让野指针指向地址0处?
因为在大部分cpu中,内存地址为0 的地址是不可以随便访问的(一般都是操作系统严密管控区域,所以应用程序不能随便访问)。所以野指针指向了这个 0 地址可以保证野指针不会随机乱指向其他不知道的地址。如果程序无意识的解引用指向0地址处的野指针就会触发段错误,这样就可以提示我们并且帮助我们找到程序中的错误。

3· 为什么需要NULL
(1)让野指针指向 0 地址处,安全。
(2)作为特殊标记。
if (NULL != p)
{
*p … //解引用指针

}
p = NULL;

4· 注意不要混用NULL 与 ’ \0 ’
(1)’ \0 ’ 和 ’ 0 ’ 和 0 和 NULL 的区别
‘\0’ :转义字符,对应ASCII编码值是0,本质就是0.
’ 0 ’ :一个字符,对应的ASCII编码值是48,本质是 48.
0 : 一个数字,就是 0 ,本质就是0.
NULL : 一个表达式,(void *)0,本质是0;

六、 运算中的临时匿名变量

1· C语言和汇编的区别
(1)C语言叫高级语言,汇编语言叫低级语言
(2)低级语言的意思是汇编语言和机器操作相对应,汇编语言只是cpu的机器码助记符。
(3)高级语言(C语言)对低级语言进行了封装

2· 强制类型转换的详细解释

float a = 12.33;
int b = (int)a;

强制类型转换的步骤:
1· 先在找到另外一个内存地方构建一个临时变量x,x的类型是int
2· 将float a 的值的整数部分赋值给x
3· 将x赋值给b
4· 销毁x

最后结果:a 还是 float 而且值保持不变,b是a的整数部分


#include <stdio.h>


int main (void)
{
	int a = 0;
	float b = 0;

	a = 10;
	b = a /3;

	printf("b = %f\n",b );

	return 0;
}

运行结果:
b = 3.000000
为什么不是3.333333333333333呢?
原因:因为 a 是int类型的,所以a / 3 就是int类型的,为 3
于是有一个临时变量 存储这个 3,然后将3赋值给b,但是b是float类型的,
所以会将3后面补全0后赋值给b

七、 顺序结构

1· 顺序结构说明CPU的工作状态,就是以时间轴来顺序执行所有的代码语句直到停机。

2· 选择结构 和 循环结构内部的顺序结构
if 与 switch case 等等 内部也是按照顺序结构来执行的

3· 编译过程中的顺序结构
(1)一个c程序有多个.c文件,编译的时候多个.c文件是独立分开编译的。每个c文件编译的时候,编译器是按照从前到后的顺序逐行进行编译。
(2)编译器编程时的顺序编译会导致函数 / 变量必须先定义 / 声明才能调用,这也是C语言中函数 / 变量声明的来源。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值