当我们将一个程序交给CPU去执行的时候,CPU只会执行main函数中的代码,别的地方的代码是不会执行的,因此如果想要在CPU中执行程序就必须要在main函数中从上到下一句一句执行代码,并且只有在上一句执行完毕之后才会执行下一句。 mian是程序的入口,当mian函数中的代码执行完毕之后就会自动结束,所以也是出口。
CPU : 中央处理器,处理数据的,负责计算,协调其他硬件相互和谐的工作。
内存 :存储数据 ,临时,效率高,通过电路存储,电子式。
硬盘 :存储数据 ,永久存储,效率低,效率和转速有关,机械式。
程序本质就是一对指令,程序存储在硬盘之中,当双击点开之后,CPU会先将程序复制到内存之中,然后CPU再去读取内存中程序的指令。这是因为在内存中CPU的读取效率更高。 例如:播放器播放电影,播放时是运行在内存中的,但是电影文件确实保存在硬盘中的,当双击电影打开播放器,播放器运行在内存中,然后播放器在去分段的读取电影文件。
或者英雄联盟这样的大型游戏也不是将整个游戏拷贝到内存中,而是只将exe文件拷贝到CPU中,然后需要什么文件就加载哪些文件,不用的就会释放掉。
内存当中分为五大区域 为什么要分区个区域?虽然每一个区域都是用来存储数据的,但是不同的数据存储在不同的区域,这样不仅方便系统的管理,也可以使系统更快,更明确的找到要找的地址。
注意:我们堆中申请的字节空间,如果我们不主动释放,那么系统是不会释放的,除非程序结束
在堆中申请字节空间的步骤 a. 申请 -> b. 使用 -> c. 释放
如何在堆区申请指定字节数的字节空间呢?C语言提供了三个函数用来申请空间。这三个函数声明在<stdlib.h>
的系统头文件中。
malloc()函数
malloc()
参数只有一个:size_t
类型的,也就是unsigned long
。 表示在堆内存中申请参数个连续的字节空间,返回值是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()
calloc() 作用:向堆区申请指定字节数的空间。 参数1.多少个单位 参数2.每一个单位的字节数 例:callco(3,sizeof(int)); // 表示申请3个4字节数的空间。 同样有可能申请失败。与malloc()的优势,calloc()申请的字节,申请完之后,系统会将申请到的空间自动清零
realloc()
realloc()作用:扩容。 当我们申请的字节空间不够用的时候,我们可以使用realloc() 扩容。 例:realloc(p1,4); 表示在p1后面申请4个字节空间。 一般会在我们申请的空间后面扩容,但是如果我们申请的空间后面被占用了,或者不够我们扩容的空间,就会重新去寻找一块新足够的空间申请,并将原来的数据拷贝过来,原来的空间将被释放。
注意:我们只能操作我们申请到的字节空间,如果贸然操作其他字节空间,很有可能修改掉系统的数据,造成严重问题。
作用: 将字符串数据输出到指定的流中。 流: 标准输出流->控制台。文件流 --> 磁盘上的文件。 使用格式: fputs(要输出的字符串,指定的流); 1). 使用fputs函数将字符串数据输出到标准输出流,也就是控制台 标准输出流: 控制台. stdout 2). 将字符串存储到文件中. a. 要先声明1个文件指针,指向磁盘上的文件。使用fopen函数可以创建1个指向文件的指针。
fopen函数的两个参数:
第1个参数: 文件的路径,代表创建的指针就指向这个文件。
第2个参数: 操作文件的模式,你要对这个文件做什么操作,必须要告诉他。
"w" --> write 代表要向这个文件写入内容。
"r" --> read 代表从这个文件中读取数据。
"a" --> apped 追加代表向这个文件中追加数据。
当操作模式是"w"的时候,如果文件不存在, 就会创建这个文件,如果文件存在,就会将原来的文件替换掉。
当操作模式是"a"的时候,如果文件存在则追加。如果不存在就创建这个文件。
b. 使用fputs函数将字符串写入到指定的文件流中。
fputs(字符串,文件指针);
c. 写完之后,一定要记得使用fclose()
函数将这个文件关闭。
作用: 从指定的流中读取字符串。 这个流可以是标准输入流-->控制台,也可以是文件流。 1). 使用fgets函数从标准输入流中读取数据。 使用fgets函数从控制台接收用户输入字符串,scanf函数gets函数也可以实现这个功能。
scanf的缺点
a. 不安全.
b. 输入的空格会被认为结束.
gets函数的缺点.
a. 不安全.
fgets函数的优点
a. 安全.
b. 空格也会一并接收.
语法:
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个读取文件的文件流.
FILE* pFile = fopen("/Users/Itcast/Desktop/abc.txt", "r");
// 2. 准备1个字符数组.准备存储读取到的字符串数据.
char content[50];
// 3. 使用fgets函数从指定的文件流中读取.
fgets(content, 50, pFile);
// 4. 读取成功:
printf("读取的内容是: %s\n",content);
// 5. 关闭文件流
fclose(pFile);
const是1个关键字,是来修饰变量的,也就是说在声明变量的同时,可以使用const关键字来修饰。
例: const int num = 10;
一般情况下来说,被const修饰的变量具备一定程度上的不可变性,被const修饰的变量我们叫做只读变量。
const修饰基本数据类型的变量 int、double、float、char。
1. const int num = 10;
这个时候.num变量的值只能去取值,而不能去修改.
2. int const num = 10;
效果同上.
const修饰数组.
1. const int arr[4] = {10,20,30,40};
数组的元素的值不能修改.
2. int const arr[4] = {10,20,30,40};
效果同上.
const修饰指针;
1. const int* p1 = #
无法通过p1指针去修改指针指向的变量的值,但是如果直接操作变量这是可以的,并且指针变量的值可以改,可以把另外1个变量的地址赋值给这个指针。
2. int const * p1 = #
效果同上
3. int * const p1 = #
p1的值不能修改,但是可以通过p1去修改p1指向的变量的值。
4. int const * const p1 = #
既不能修改p1的值,也不能通过p1去修改p1指向的变量的值。
const的使用场景.
1. const的特点:
被const修饰的变量,是只读变量,只能取值而不能改值。所以,const变量的值,至始至终都不会发生变化。因此当某些数据是固定的,在整个程序运行期间都不会发生变化,并且你也不允许别人去修改时,可以使用const来修饰这个变量。
3. 当函数的参数是1个指针的时候,函数的内部是有可能会修改实参变量的值的,那么这个时候,可以使用const修饰指针参数,这样函数内部只会使用我们的值 绝对改不了参数的值。
不同的数据类型的变量是用来保存不同类型的数据的。而结构体是我们自己定义的数据类型。并指定这个数据类型的变量由哪些小的变量和成的。 语法格式:
例:
struct Student
{
char * name;
int age;
float height;
};
这代表我们新创建了一个数据类型,这个数据类型的名称叫做 struct Student
这个新类型是由1个char * 类型,一个int 类型,一个float类型的
小的标量联合而成的。然而只有类型是不够的,还需要根据类型声明变量。
声明结构体类型的变量:
struct Student Tom;
** 结构体变量的初始化** 意义:为结构体变量中的小变量赋值。严格意义上是将地址赋值给小变量。结构体变量中的小变量就叫做结构体的成员。
初始化语法 使用点语法。
结构体变量名称.成员 = 数据;
Tom.name = @"Tom";
什么时候使用结构体 当要保存一个数据,但是发现这个数据需要由其他小变量组成的,这个时候先使用结构体类自定义这个数据类型由哪些小变量合成的,然后在根据这个结构体类型声明变量,来保存数据。
结构体注意点
结构体变量的初始化
结构体变量成员的默认值 声明一个结构体变量,如果没有给结构体变量成员赋值,那么成员是有值的,是垃圾值。只要在声明结构体变量的同时,为一个成员变量初始化,整个结构体就会自动初始化为0,就不在是垃圾值了。
结构体类型的作用域 一般情况下结构体类型都是定义在函数外面,已让所有函数都可以使用。
结构体变量之间的相互赋值 相同结构体类型的结构体变量是可以赋值的。 结构体变量之间赋值的原理: 将结构体变量中的每一个成员的值,拷贝过来复制一份,然后重新赋值给目标结构体变量中对应的成员。结构体变量之间的赋值是值传递。
结构体数组
struct 结构体类型名称 数组名[数组长度];
可以存储5个strut 结构体类型名称 的结构体
struct Student students[5];
数组的类型是struct Student,可以存储5个struct Student类型的变量。
结构体数组的初始化
students[0] = (struct Student) {"name",16}; // 需要转化为结构体类型。
或者直接在声明结构体数组的时候,为结构体赋值。
结构体数组长度的计算
使用sizeof计算出数组占用的总字节数/ 每一个元素占用的字节数
sizeof(students)/ sizeof(struct Student)
结构体指针
struct 结构体类型名称* 指针名;
struct Student* pStu;
声明了一个pStu指针变量,这个指针变量的类型是struct Student *。
这个指针就只能指向 struct Student
pStu = &stu1;
使用。可以使用指针间接的访问结构体变量。
(*pStu).name;
(*结构体指针名).成员
pStu -> name;
结构体指针名->成员
结构体嵌套 结构体是可以嵌套的,在一个结构体内部声明另外一个结构体即可。
结构体与函数 作为参数 结构体是自定义的数据类型,当然可以作为参数,结构体作为参数传值是值传递,如果想要在函数中修改结构体变量的值,可以使用结构体指针。
作为返回值 结构题类型完全可以作为函数的返回值,在返回的时候直接将结构体变量返回即可。如果返回结构体变量的地址,需要将结构体创建在堆区。
变量的取值只能是指定的几个值当中的任意一个,除此之外其他不行,需要自己定义具备限定取值的类型。 作用:支持先创建一种数据类型,这个数据类型的变量的取值被限定。
语法结构:
enum Type
{
Type1,
Type2,
Type3,
};
这个数据类型的名称叫做 **enum Type **。可以声明这个类型的变量,这个变量中就只能存储这其中指定的任意一个。
声明枚举类型的变量。
enum 枚举类型名称 变量名 = 枚举类型限定的取值之一。
枚举作用域 一般定义在函数外,每一个枚举值都对应一个整形数,默认为0,依次递增。枚举类型的变量,无论什么类型 都占据4个字节。而枚举变量中真正存储的是,枚举值对应的整形的数。所以使用%d输出枚举的值。
所以也可以直接为枚举变量赋值整形变量。但是一般不建议这么做 ,可读性降低。命名规范 首字母大写,每一个单词的首字母大写 枚举值名称以枚举类型名开头
作用: 为一个已经存在的数据类型取别名。
例:typedef int clint;
为int数据类型去了一个别名,叫做clint。这个时候clint 完全等价于int 。
因此当数据类型很长的时候既可以为这个数据类型取一个短一点的别名。
typedefine 数据类型 数据类型别名;
typedefine 给结构体和枚举取别名
1.声明结构体类型的同时给结构体区别名
typedefine struct Student
{
} Student;
2.最常用是为声明匿名结构体的同时 取一个短别名。
typedefine struct
{
} Student;
3.枚举同理
typedefine enum
{
} Dreiction;
C语言从编写到编译、链接、执行的流程 编译做的事情
预处理指令 预处理指令以#开头,并且都是在编译之前执行。
预处理指令类型
宏定义
#define 宏名 宏值
#define N 10
会将C代码中使用宏名的地方替换成宏值 过程叫做宏替换
宏值可以是任意语句,定义宏的时候,并不会去检查与法,只有当完成了宏替换的时候,才会去检查替换以后的代码是否符合语法规范。 如果宏值是一个表达式,那么宏值并不是表达式的值,而是表达式本身。 如果宏值当中包括一个变量名,那么在使用这个宏之前必须保证这个变量已经存在。 无法通过赋值符号位宏赋值。因为宏根本就不是变量。
宏作用域
从定义宏的地方开始,后面的所有地方都可以使用这个宏。就算这个宏定义在这个大括弧里面,在这个后面,哪怕是大括弧的后面都可以使用。
默认情况下,宏从定义的地方一直到文件结束都可以使用,#undef
可以让宏提前失效
#undef N
解除宏定义,之后宏就不可以使用了 体现实效
字符串优先,也就是字符串中不会识别宏。系统不会认为是一个宏,而认为是字符串的一部分。 宏的层层替换。一个宏值中可以使用另外一个宏名。
#define 和typedef的区别
#define
预处理指令 在预编译的时候会把宏明替换成宏值,typedef
运行的时候才会执行。
#define
可以将任意的C代码取一个表示名, typedef
只能为数据类型取名字。
带参数的宏 在定义宏的时候,宏名是可以带参数的。在这个宏值当中,可以直接使用这个参数。
#define N(a)
// 如果使用的宏有参数,就必须在使用的时候为宏传值。
N(10);
宏带参数替换的原理 先将参数赋值,然后在将宏值里面用到参数的地方替换为值,最后宏替换,将值替换为宏名。
使用带参数的宏注意点
#define 宏名 宏值
宏名中不可以有空格,与参数之间也不可以有空格。条件编译指令
#if #elif #endif
必须要有 #endif
#if
和if
语句的区别
if
语句是C代码,在程序运行的时候执行。#ifdef 宏名
如果定义了宏名的宏 就编译其中的代码
#ifndef 宏名
如果没有定义宏名就来到这里。
#endif
C语言当中的两个关键字,是用来修饰变量和函数的。 如果都没有修饰 默认是 extern 。
文中如果有不对的地方欢迎指出。我是xx_cc,一只长大很久但还没有二够的家伙。