当我们将一个程序交给CPU去执行的时候,CPU只会执行main函数中的代码,别的地方的代码是不会执行的,因此如果想要在CPU中执行程序就必须要在main函数中从上到下一句一句执行代码,并且只有在上一句执行完毕之后才会执行下一句。
mian是程序的入口,当mian函数中的代码执行完毕之后就会自动结束,所以也是出口。
CPU : 中央处理器,处理数据的,负责计算,协调其他硬件相互和谐的工作。
内存 :存储数据 ,临时,效率高,通过电路存储,电子式。
硬盘 :存储数据 ,永久存储,效率低,效率和转速有关,机械式。
3. 程序是如何运行的
程序本质就是一对指令,程序存储在硬盘之中,当双击点开之后,CPU会先将程序复制到内存之中,然后CPU再去读取内存中程序的指令。这是因为在内存中CPU的读取效率更高。
例如:播放器播放电影,播放时是运行在内存中的,但是电影文件确实保存在硬盘中的,当双击电影打开播放器,播放器运行在内存中,然后播放器在去分段的读取电影文件。
或者英雄联盟这样的大型游戏也不是将整个游戏拷贝到内存中,而是只将exe文件拷贝到CPU中,然后需要什么文件就加载哪些文件,不用的就会释放掉。
-
程序自己是否要把这些数据存储起来,对于用户操作产生的数据,或者一会儿要用的数据可以存储起来,以便以后显示方便快捷。 存储到内存中,因为程序自己就在内存当中
- 如何将数据存储在内存之中?
先在内存中找一个位置,将数据放到这个位置中,当想要使用数据的时候,只要找到这个位置就可以了。
而每一个位置在内存中都有一个独一无二地址,16进制表示,0x开头。通过这个地址找到这个位置,然而地址非常难记,所以给位置起个别名,通过别名找到位置。
因此在开辟空间的同时,需要指定空间的别名和类型,而变量就是内存中用来存储数据的空间,那么变量名就是变量所代表的那块空间的别名。所以,变量的本质就是内存中存储数据的那块儿空间。
声明一个变量,实际上就是在内存中开辟一块指定类型和别名的空间
5. 内存中的五大区域
为什么要分区个区域?虽然每一个区域都是用来存储数据的,但是不同的数据存储在不同的区域,这样不仅方便系统的管理,也可以使系统更快,更明确的找到要找的地址。
- 栈 - 专门用来存储局部变量,所有的局部变量都是声明在栈区域当中。
- 堆 - 允许程序员手动的从堆申请空间来使用。程序员可以自己申请指定字节数的空间。
- BSS段 - 用来存储未初始化的全局变量和静态变量,声明一个全局变量,如果我们没有初始化,在程序运行最开始的时候,这个变量没有初始化时是存储在BSS段,初始化之后,全局变量和静态变量就会被放到常量区。
- 数据段/常量区 - 用来存储已经初始化的全局变量、静态变量,还有常量数据
- 代码段 - 用来存储程序的代码/指令。
注意:我们堆中申请的字节空间,如果我们不主动释放,那么系统是不会释放的,除非程序结束
如何在堆区申请指定字节数的字节空间呢?C语言提供了三个函数用来申请空间。这三个函数声明在<stdlib.h>
的系统头文件中。
表示在堆内存中申请参数个连续的字节空间,返回值是
void *
表示没有类型的指针。并且返回的是创建的空间中的第一个字节的地址。
那么我们应该使用什么类型的指针变量来保存malloc()返回的地址?
用什么类型去接受,那要看你想要如何去操作申请的这些字节空间。如果你想要4个字节4个字节去操作那么就要用int *
类型去接收,如果想8个字节8个字节的去错做那么就用double *
类型去接收。
所以: int *p = malloc(24); 就相当于在堆内存中创建了一个长度为6的整型数组
1.在堆区申请的字节空间是从低地址向高地址申请,每次申请的字节地址都是从0开始的,并且每次申请的空间不一定是连续的。但是每一次申请的指定个字节,这些字节一定是连续的。
2.在堆区申请的字节里面也是有值的,值是垃圾值,并且值不会自动清零。
3.在向堆区申请字节空间的时候,有可能会申请失败,如果申请失败,返回的指针就是NULL值,所以我们在申请空间之后,最好进行判断 if(p1) 看是否申请成功。
4.申请的空间使用完毕之后,一定要进行释放 free(p1); 如果没有free(),那么需要等程序结束之后这些空间才会被释放。
calloc() 作用:向堆区申请指定字节数的空间。
参数2.每一个单位的字节数
同样有可能申请失败。与malloc()的优势,calloc()申请的字节,申请完之后,系统会将申请到的空间自动清零
当我们申请的字节空间不够用的时候,我们可以使用realloc() 扩容。
一般会在我们申请的空间后面扩容,但是如果我们申请的空间后面被占用了,或者不够我们扩容的空间,就会重新去寻找一块新足够的空间申请,并将原来的数据拷贝过来,原来的空间将被释放。
注意:我们只能操作我们申请到的字节空间,如果贸然操作其他字节空间,很有可能修改掉系统的数据,造成严重问题。
作用: 将字符串数据输出到指定的流中。
流: 标准输出流->控制台。文件流 --> 磁盘上的文件。
使用格式: fputs(要输出的字符串,指定的流);
1). 使用fputs函数将字符串数据输出到标准输出流,也就是控制台
2). 将字符串存储到文件中.
a. 要先声明1个文件指针,指向磁盘上的文件。使用fopen函数可以创建1个指向文件的指针。
fopen函数的两个参数:
第1个参数: 文件的路径,代表创建的指针就指向这个文件。
第2个参数: 操作文件的模式,你要对这个文件做什么操作,必须要告诉他。
当操作模式是"w"的时候,如果文件不存在, 就会创建这个文件,如果文件存在,就会将原来的文件替换掉。
当操作模式是"a"的时候,如果文件存在则追加。如果不存在就创建这个文件。
b. 使用fputs函数将字符串写入到指定的文件流中。
c. 写完之后,一定要记得使用fclose()
函数将这个文件关闭。
作用: 从指定的流中读取字符串。
这个流可以是标准输入流-->控制台,也可以是文件流。
1). 使用fgets函数从标准输入流中读取数据。
使用fgets函数从控制台接收用户输入字符串,scanf函数gets函数也可以实现这个功能。
fgets(要将字符串存储到哪1个数组中,最多接收多少个长度的字符串,指定流);
第2个参数: 如果参数为n 那么函数最多就接收n-1个长度的字符串,这个参数一般情况下和第1个参数数组的长度一致。
第3个参数:流,stdin: 代表标准输入流,也就是键盘流从控制台输入。
为什么fgets函数是安全的?
1. 如果输入的字符串的长度大于等于了第2个参数n,只会接收前面的n-1个,然后最后1个自动是'\0'。这样,就不会崩溃。
2. 如果输入的字符串的长度刚好等于n-1那就是刚好的。
3. 如果输入的字符串的长度小于了n-1,那么就会将我们最后输入的换行字符'\n'一并的接收。然后后面才是'\0'结束符。
2). 使用fgets函数从文件流中读取数据:
就是读取磁盘上文件的内容.
// 1. 创建1个读取文件的文件流.
// 2. 准备1个字符数组.准备存储读取到的字符串数据.
// 3. 使用fgets函数从指定的文件流中读取.
8. const修饰基本数据类型的变量
const是1个关键字,是来修饰变量的,也就是说在声明变量的同时,可以使用const关键字来修饰。
一般情况下来说,被const修饰的变量具备一定程度上的不可变性,被const修饰的变量我们叫做只读变量。
不同的数据类型的变量是用来保存不同类型的数据的。而结构体是我们自己定义的数据类型。并指定这个数据类型的变量由哪些小的变量和成的。
这代表我们新创建了一个数据类型,这个数据类型的名称叫做 struct Student
这个新类型是由1个char * 类型,一个int 类型,一个float类型的
小的标量联合而成的。然而只有类型是不够的,还需要根据类型声明变量。
声明结构体类型的变量:
** 结构体变量的初始化**
意义:为结构体变量中的小变量赋值。严格意义上是将地址赋值给小变量。结构体变量中的小变量就叫做结构体的成员。
初始化语法 使用点语法。
结构体变量名称.成员 = 数据;
当要保存一个数据,但是发现这个数据需要由其他小变量组成的,这个时候先使用结构体类自定义这个数据类型由哪些小变量合成的,然后在根据这个结构体类型声明变量,来保存数据。
-
一定要先声明结构体类型,然后在声明结构体变量。
-
结构体变量也是变量所以可以批量声明。
-
定义结构体名称要求每一个单词的首字母大写。
-
可以在声明结构体类型的时候声明结构体变量。
- // 匿名结构体只能在声明结构体的同时创建变量,并且不能单独的声明变量
- 先声明变量,在使用点语法一个一个赋值。
- 在声明结构体变量的同时,就为结构体变量的成员初始化。(最常用)
- 只初始化部分成员,按顺序。
结构体变量成员的默认值
声明一个结构体变量,如果没有给结构体变量成员赋值,那么成员是有值的,是垃圾值。只要在声明结构体变量的同时,为一个成员变量初始化,整个结构体就会自动初始化为0,就不在是垃圾值了。
一般情况下结构体类型都是定义在函数外面,已让所有函数都可以使用。
结构体变量之间的相互赋值
相同结构体类型的结构体变量是可以赋值的。
结构体变量之间赋值的原理:
将结构体变量中的每一个成员的值,拷贝过来复制一份,然后重新赋值给目标结构体变量中对应的成员。结构体变量之间的赋值是值传递。
struct 结构体类型名称 数组名[数组长度];
可以存储5个strut 结构体类型名称 的结构体
或者直接在声明结构体数组的时候,为结构体赋值。
使用sizeof计算出数组占用的总字节数/ 每一个元素占用的字节数
声明了一个pStu指针变量,这个指针变量的类型是struct Student *。
使用。可以使用指针间接的访问结构体变量。
(*结构体指针名).成员 结构体指针名->成员结构体是可以嵌套的,在一个结构体内部声明另外一个结构体即可。
结构体是自定义的数据类型,当然可以作为参数,结构体作为参数传值是值传递,如果想要在函数中修改结构体变量的值,可以使用结构体指针。
结构题类型完全可以作为函数的返回值,在返回的时候直接将结构体变量返回即可。如果返回结构体变量的地址,需要将结构体创建在堆区。
变量的取值只能是指定的几个值当中的任意一个,除此之外其他不行,需要自己定义具备限定取值的类型。
作用:支持先创建一种数据类型,这个数据类型的变量的取值被限定。
这个数据类型的名称叫做 **enum Type **。可以声明这个类型的变量,这个变量中就只能存储这其中指定的任意一个。
enum 枚举类型名称 变量名 = 枚举类型限定的取值之一。
一般定义在函数外,每一个枚举值都对应一个整形数,默认为0,依次递增。枚举类型的变量,无论什么类型 都占据4个字节。而枚举变量中真正存储的是,枚举值对应的整形的数。所以使用%d输出枚举的值。
所以也可以直接为枚举变量赋值整形变量。但是一般不建议这么做 ,可读性降低。命名规范 首字母大写,每一个单词的首字母大写
枚举值名称以枚举类型名开头
作用: 为一个已经存在的数据类型取别名。
因此当数据类型很长的时候既可以为这个数据类型取一个短一点的别名。
C语言从编写到编译、链接、执行的流程
- 先执行原文件中的预处理指令,如果有文件包含指令,就将文件的内容拷贝到写指令的地方。
- 检查语法是否符合规范,符合就生成.o目标文件,就是.c 对应的二进制指令。如果不符合语法规范,就报错不生成.o目标文件。为.o的目标文件添加启动代码
告诉编译器要调用的函数在什么地方
调用的时候去正确的地方找实现 - 链接成功以后.out文件运行即可。
预处理指令以#开头,并且都是在编译之前执行。
- 宏定义:可以将一段代码定义为一个标识,使用这个标识就代表这段代码。
- 条件编译指令:只编译指定的C代码为二进制指令。
会将C代码中使用宏名的地方替换成宏值 过程叫做宏替换
宏值可以是任意语句,定义宏的时候,并不会去检查与法,只有当完成了宏替换的时候,才会去检查替换以后的代码是否符合语法规范。
如果宏值是一个表达式,那么宏值并不是表达式的值,而是表达式本身。
如果宏值当中包括一个变量名,那么在使用这个宏之前必须保证这个变量已经存在。
无法通过赋值符号位宏赋值。因为宏根本就不是变量。
从定义宏的地方开始,后面的所有地方都可以使用这个宏。就算这个宏定义在这个大括弧里面,在这个后面,哪怕是大括弧的后面都可以使用。
默认情况下,宏从定义的地方一直到文件结束都可以使用,#undef
可以让宏提前失效
字符串优先,也就是字符串中不会识别宏。系统不会认为是一个宏,而认为是字符串的一部分。
宏的层层替换。一个宏值中可以使用另外一个宏名。
#define
预处理指令 在预编译的时候会把宏明替换成宏值,typedef
运行的时候才会执行。
#define
可以将任意的C代码取一个表示名, typedef
只能为数据类型取名字。
在定义宏的时候,宏名是可以带参数的。在这个宏值当中,可以直接使用这个参数。
// 如果使用的宏有参数,就必须在使用的时候为宏传值。先将参数赋值,然后在将宏值里面用到参数的地方替换为值,最后宏替换,将值替换为宏名。
- 宏不是函数,所以宏的参数不需要添加类型说明。
- 我们在定义宏的时候,编译器是如何区分宏名和宏值的。
#define 宏名 宏值
宏名中不可以有空格,与参数之间也不可以有空格。 - 为带参数的宏传值的时候,是本色传递,如果传递一个变量,并不是传递这个变量的值,而是直接传递的就是这个变量的串。
- 宏值一旦换行就认为宏定义结束了,需要使用 \ 来拼接宏
- 宏只适合于少量的代码。
- 预处理指令, 在预编译阶段执行。
- 作用:默认情况下,我们所有的C代码都会被编译为二进制代码,条件编译指令的作用,可以让编译器只编译部分的代码。
- 条件编译指令是一个预处理指令,在预处理阶段执行,而
if
语句是C代码,在程序运行的时候执行。 - if语句无论如何全都要被编译为二进制指令,条件编译指令:只会将符合条件的C代码编译为二进制指令。
- 条件编译指令参数必须为宏。
C语言当中的两个关键字,是用来修饰变量和函数的。
如果都没有修饰 默认是 extern 。
-
static修饰局部变量,那么这个变量就叫做静态变量,静态变量不在存储在栈区,而是存储在常量区中,当函数执行完毕之后,这个静态变量不会被回收。
当第一次执行这个函数的时候,就会将这个静态变量声明在常量区,第二次去执行这个函数的时候,声明静态变量的这句话就不会在执行了,而是直接略过,直接使用静态变量的值。
所以static修饰的静态变量,函数结束不会被回收,仍然存在,函数无论执行多少次,这个静态变量只有一份。
extern不能修饰局部变量。 -
一个全局变量,最完整的步骤也应该分为两步,1.先写全局变量的声明,只定义而不赋值。2.在写全局变量的定义,定义全局变量并初始化。
全局变量可以只有声明,如果这样的话,那么这个全局变量的值背会编译器自动的去实现,会将这个全局变量自动初始化为0。
全局变量也可以只有定义而没有声明,但是这个时候,这个全局变量的定义必须要在使用全局变量的函数的前面。
全局变量的声明要写在.h文件中,全局变量的实现要写在.c文件中。
如果将全局变量定义在模块之中,这个全局变量就必须要使用static或者extern来修饰
static修饰全局变量,这个全局变量只能在当前 模块访问。
extern修饰全局变量,这个全局变量就可以跨模块访问。 -
static修饰函数 函数只可以在当前模块访问
extern修饰函数 那么函数可以跨模块调用
文中如果有不对的地方欢迎指出。我是xx_cc,一只长大很久但还没有二够的家伙。
本文已在版权印备案,如需转载请访问版权印。