C - 基础总结

1. 程序运行模式

当我们将一个程序交给CPU去执行的时候,CPU只会执行main函数中的代码,别的地方的代码是不会执行的,因此如果想要在CPU中执行程序就必须要在main函数中从上到下一句一句执行代码,并且只有在上一句执行完毕之后才会执行下一句。 mian是程序的入口,当mian函数中的代码执行完毕之后就会自动结束,所以也是出口。

2. 计算机三大件

CPU : 中央处理器,处理数据的,负责计算,协调其他硬件相互和谐的工作。

内存 :存储数据 ,临时,效率高,通过电路存储,电子式。

硬盘 :存储数据 ,永久存储,效率低,效率和转速有关,机械式。

3. 程序是如何运行的

程序本质就是一对指令,程序存储在硬盘之中,当双击点开之后,CPU会先将程序复制到内存之中,然后CPU再去读取内存中程序的指令。这是因为在内存中CPU的读取效率更高。 例如:播放器播放电影,播放时是运行在内存中的,但是电影文件确实保存在硬盘中的,当双击电影打开播放器,播放器运行在内存中,然后播放器在去分段的读取电影文件。

或者英雄联盟这样的大型游戏也不是将整个游戏拷贝到内存中,而是只将exe文件拷贝到CPU中,然后需要什么文件就加载哪些文件,不用的就会释放掉。

4. 程序处理数据

  1. 要不要存? 程序自己是否要把这些数据存储起来,对于用户操作产生的数据,或者一会儿要用的数据可以存储起来,以便以后显示方便快捷。
  2. 存到哪? 存储到内存中,因为程序自己就在内存当中
  3. 如何将数据存储在内存之中? 先在内存中找一个位置,将数据放到这个位置中,当想要使用数据的时候,只要找到这个位置就可以了。 而每一个位置在内存中都有一个独一无二地址,16进制表示,0x开头。通过这个地址找到这个位置,然而地址非常难记,所以给位置起个别名,通过别名找到位置。 因此在开辟空间的同时,需要指定空间的别名和类型,而变量就是内存中用来存储数据的空间,那么变量名就是变量所代表的那块空间的别名。所以,变量的本质就是内存中存储数据的那块儿空间。 声明一个变量,实际上就是在内存中开辟一块指定类型和别名的空间

5. 内存中的五大区域

内存当中分为五大区域 为什么要分区个区域?虽然每一个区域都是用来存储数据的,但是不同的数据存储在不同的区域,这样不仅方便系统的管理,也可以使系统更快,更明确的找到要找的地址。

  1. 栈 - 专门用来存储局部变量,所有的局部变量都是声明在栈区域当中。
  2. 堆 - 允许程序员手动的从堆申请空间来使用。程序员可以自己申请指定字节数的空间。
  3. BSS段 - 用来存储未初始化的全局变量和静态变量,声明一个全局变量,如果我们没有初始化,在程序运行最开始的时候,这个变量没有初始化时是存储在BSS段,初始化之后,全局变量和静态变量就会被放到常量区。
  4. 数据段/常量区 - 用来存储已经初始化的全局变量、静态变量,还有常量数据
  5. 代码段 - 用来存储程序的代码/指令。

注意:我们堆中申请的字节空间,如果我们不主动释放,那么系统是不会释放的,除非程序结束 在堆中申请字节空间的步骤 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个字节空间。 一般会在我们申请的空间后面扩容,但是如果我们申请的空间后面被占用了,或者不够我们扩容的空间,就会重新去寻找一块新足够的空间申请,并将原来的数据拷贝过来,原来的空间将被释放。

注意:我们只能操作我们申请到的字节空间,如果贸然操作其他字节空间,很有可能修改掉系统的数据,造成严重问题。

6. fputs()函数

作用: 将字符串数据输出到指定的流中。 流: 标准输出流->控制台。文件流 --> 磁盘上的文件。 使用格式: 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()函数将这个文件关闭。

7. fget()函数

作用: 从指定的流中读取字符串。 这个流可以是标准输入流-->控制台,也可以是文件流。 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);  

8. const修饰基本数据类型的变量

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修饰指针参数,这样函数内部只会使用我们的值 绝对改不了参数的值。

9. 结构体

不同的数据类型的变量是用来保存不同类型的数据的。而结构体是我们自己定义的数据类型。并指定这个数据类型的变量由哪些小的变量和成的。 语法格式:

例:
struct Student
{
     char * name;
     int  age;
     float height;
};

这代表我们新创建了一个数据类型,这个数据类型的名称叫做 struct Student 这个新类型是由1个char * 类型,一个int 类型,一个float类型的小的标量联合而成的。然而只有类型是不够的,还需要根据类型声明变量。

声明结构体类型的变量:
struct Student Tom;

** 结构体变量的初始化** 意义:为结构体变量中的小变量赋值。严格意义上是将地址赋值给小变量。结构体变量中的小变量就叫做结构体的成员。

初始化语法 使用点语法。
结构体变量名称.成员 = 数据;
Tom.name = @"Tom";

什么时候使用结构体 当要保存一个数据,但是发现这个数据需要由其他小变量组成的,这个时候先使用结构体类自定义这个数据类型由哪些小变量合成的,然后在根据这个结构体类型声明变量,来保存数据。

结构体注意点

  1. 一定要先声明结构体类型,然后在声明结构体变量。
  2. 结构体变量也是变量所以可以批量声明。 struct Car{ } BMWCar,BCCar,KDLKCar;
  3. 定义结构体名称要求每一个单词的首字母大写。
  4. 可以在声明结构体类型的时候声明结构体变量。
  5. 匿名结构体 struct{ // 匿名结构体只能在声明结构体的同时创建变量,并且不能单独的声明变量 }car1;

结构体变量的初始化

  1. 先声明变量,在使用点语法一个一个赋值。
  2. 在声明结构体变量的同时,就为结构体变量的成员初始化。(最常用)
  3. 只初始化部分成员,按顺序。
  4. 也可以指定成员初始化。 struct Student jim = {.name = "jim",.age = 18};

结构体变量成员的默认值 声明一个结构体变量,如果没有给结构体变量成员赋值,那么成员是有值的,是垃圾值。只要在声明结构体变量的同时,为一个成员变量初始化,整个结构体就会自动初始化为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;
结构体指针名->成员 

结构体嵌套 结构体是可以嵌套的,在一个结构体内部声明另外一个结构体即可。

结构体与函数 作为参数 结构体是自定义的数据类型,当然可以作为参数,结构体作为参数传值是值传递,如果想要在函数中修改结构体变量的值,可以使用结构体指针。

作为返回值 结构题类型完全可以作为函数的返回值,在返回的时候直接将结构体变量返回即可。如果返回结构体变量的地址,需要将结构体创建在堆区。

10. 枚举

变量的取值只能是指定的几个值当中的任意一个,除此之外其他不行,需要自己定义具备限定取值的类型。 作用:支持先创建一种数据类型,这个数据类型的变量的取值被限定。

语法结构:
enum Type
{
     Type1,
     Type2,
     Type3,
};

这个数据类型的名称叫做 **enum Type **。可以声明这个类型的变量,这个变量中就只能存储这其中指定的任意一个。

声明枚举类型的变量。

enum 枚举类型名称 变量名 = 枚举类型限定的取值之一。

枚举作用域 一般定义在函数外,每一个枚举值都对应一个整形数,默认为0,依次递增。枚举类型的变量,无论什么类型 都占据4个字节。而枚举变量中真正存储的是,枚举值对应的整形的数。所以使用%d输出枚举的值。

所以也可以直接为枚举变量赋值整形变量。但是一般不建议这么做 ,可读性降低。命名规范 首字母大写,每一个单词的首字母大写 枚举值名称以枚举类型名开头

11.type define 类型定义

作用: 为一个已经存在的数据类型取别名。 例:typedef int clint; 为int数据类型去了一个别名,叫做clint。这个时候clint 完全等价于int 。 因此当数据类型很长的时候既可以为这个数据类型取一个短一点的别名。

typedefine 数据类型 数据类型别名;
typedefine 给结构体和枚举取别名

1.声明结构体类型的同时给结构体区别名
typedefine struct Student
{
    

} Student;

2.最常用是为声明匿名结构体的同时  取一个短别名。
typedefine struct 
{

} Student;

3.枚举同理
typedefine enum 
{

} Dreiction;

12. 预处理指令

C语言从编写到编译、链接、执行的流程 编译做的事情

  1. 先执行原文件中的预处理指令,如果有文件包含指令,就将文件的内容拷贝到写指令的地方。
  2. 检查语法是否符合规范,符合就生成.o目标文件,就是.c 对应的二进制指令。如果不符合语法规范,就报错不生成.o目标文件。
  3. 链接 为.o的目标文件添加启动代码 告诉编译器要调用的函数在什么地方 调用的时候去正确的地方找实现
  4. 链接成功以后.out文件运行即可。

预处理指令 预处理指令以#开头,并且都是在编译之前执行。

预处理指令类型

  1. 文件包含指令 #include。
  2. 宏定义:可以将一段代码定义为一个标识,使用这个标识就代表这段代码。
  3. 条件编译指令:只编译指定的C代码为二进制指令。

宏定义

#define 宏名 宏值
#define N 10 

会将C代码中使用宏名的地方替换成宏值 过程叫做宏替换

宏值可以是任意语句,定义宏的时候,并不会去检查与法,只有当完成了宏替换的时候,才会去检查替换以后的代码是否符合语法规范。 如果宏值是一个表达式,那么宏值并不是表达式的值,而是表达式本身。 如果宏值当中包括一个变量名,那么在使用这个宏之前必须保证这个变量已经存在。 无法通过赋值符号位宏赋值。因为宏根本就不是变量。

宏作用域 从定义宏的地方开始,后面的所有地方都可以使用这个宏。就算这个宏定义在这个大括弧里面,在这个后面,哪怕是大括弧的后面都可以使用。 默认情况下,宏从定义的地方一直到文件结束都可以使用,#undef可以让宏提前失效

#undef N 
解除宏定义,之后宏就不可以使用了 体现实效

字符串优先,也就是字符串中不会识别宏。系统不会认为是一个宏,而认为是字符串的一部分。 宏的层层替换。一个宏值中可以使用另外一个宏名。

#define 和typedef的区别 #define 预处理指令 在预编译的时候会把宏明替换成宏值,typedef运行的时候才会执行。 #define可以将任意的C代码取一个表示名, typedef只能为数据类型取名字。

带参数的宏 在定义宏的时候,宏名是可以带参数的。在这个宏值当中,可以直接使用这个参数。

#define N(a)
// 如果使用的宏有参数,就必须在使用的时候为宏传值。
N(10);

宏带参数替换的原理 先将参数赋值,然后在将宏值里面用到参数的地方替换为值,最后宏替换,将值替换为宏名。

使用带参数的宏注意点

  1. 宏不是函数,所以宏的参数不需要添加类型说明。
  2. 我们在定义宏的时候,编译器是如何区分宏名和宏值的。 #define 宏名 宏值 宏名中不可以有空格,与参数之间也不可以有空格。
  3. 为带参数的宏传值的时候,是本色传递,如果传递一个变量,并不是传递这个变量的值,而是直接传递的就是这个变量的串。
  4. 宏值一旦换行就认为宏定义结束了,需要使用 \ 来拼接宏
  5. 宏只适合于少量的代码。

条件编译指令

  1. 预处理指令, 在预编译阶段执行。
  2. 作用:默认情况下,我们所有的C代码都会被编译为二进制代码,条件编译指令的作用,可以让编译器只编译部分的代码。
  3. #if #elif #endif 必须要有 #endif

#ifif语句的区别

  1. 条件编译指令是一个预处理指令,在预处理阶段执行,而if语句是C代码,在程序运行的时候执行。
  2. if语句无论如何全都要被编译为二进制指令,条件编译指令:只会将符合条件的C代码编译为二进制指令。
  3. 条件编译指令参数必须为宏。
#ifdef 宏名        
如果定义了宏名的宏 就编译其中的代码
#ifndef 宏名 
如果没有定义宏名就来到这里。
#endif

13. static 和 extern

C语言当中的两个关键字,是用来修饰变量和函数的。 如果都没有修饰 默认是 extern 。

  1. static和extern修饰局部变量 static修饰局部变量,那么这个变量就叫做静态变量,静态变量不在存储在栈区,而是存储在常量区中,当函数执行完毕之后,这个静态变量不会被回收。 当第一次执行这个函数的时候,就会将这个静态变量声明在常量区,第二次去执行这个函数的时候,声明静态变量的这句话就不会在执行了,而是直接略过,直接使用静态变量的值。 所以static修饰的静态变量,函数结束不会被回收,仍然存在,函数无论执行多少次,这个静态变量只有一份。 extern不能修饰局部变量。
  2. static和extern修饰全局变量的效果 一个全局变量,最完整的步骤也应该分为两步,1.先写全局变量的声明,只定义而不赋值。2.在写全局变量的定义,定义全局变量并初始化。 全局变量可以只有声明,如果这样的话,那么这个全局变量的值背会编译器自动的去实现,会将这个全局变量自动初始化为0。 全局变量也可以只有定义而没有声明,但是这个时候,这个全局变量的定义必须要在使用全局变量的函数的前面。 全局变量的声明要写在.h文件中,全局变量的实现要写在.c文件中。 如果将全局变量定义在模块之中,这个全局变量就必须要使用static或者extern来修饰 static修饰全局变量,这个全局变量只能在当前 模块访问。 extern修饰全局变量,这个全局变量就可以跨模块访问。
  3. static和extern修饰函数的效果 static修饰函数 函数只可以在当前模块访问 extern修饰函数 那么函数可以跨模块调用

文中如果有不对的地方欢迎指出。我是xx_cc,一只长大很久但还没有二够的家伙。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏小二的折腾日记

面试总结-C++

堆、栈、自由存储区、全局/静态存储区、常量存储区 自由存储区存储malloc申请的内存 (1)从静态存储区域分配 。内存在程序编译的时候就已经分配好,这块内存在...

21910
来自专栏极客慕白的成长之路

JavaScript代码压缩细节

对于Javascript来说,提高网络下载的性能最直接的方法就是把JS文件体积减小。

10120
来自专栏逆向技术

C++反汇编第二讲,不同作用域下的构造和析构的识别

               C++反汇编第二讲,不同作用域下的构造和析构的识别 目录大纲:   1.全局(静态)对象的识别,(全局静态全局一样的,都是编译期间...

208100
来自专栏数据科学与人工智能

【Python环境】Python面试题汇总(二)

【 题目:031】 | 介绍一下except的用法和作用 try/except: 捕捉由PYTHON自身或写程序过程中引发的异常并恢复 e...

28150
来自专栏大数据挖掘DT机器学习

Python一些基础面试题目总结

1 Python是如何进行内存管理的? 答:从三个方面来说,一对象的引用计数机制,二垃圾回收机制,三内存池机制 一、对象的引用计数机制 pytho...

39360
来自专栏数据结构笔记

数据结构(一):什么是数据结构

数据的逻辑结构是从逻辑关系上描述数据(主要是相邻关系,比如栈、队列、链表等),它与数据的存储无关,是独立于计算机的。因此,数据结构可以看作从具体问题中抽象...

20940
来自专栏nummy

itertools模块详解

tee()创建的迭代器共享其输入迭代器,所以一旦创建了新迭代器,就不应该再使用远迭代器。

15030
来自专栏开源优测

python selenium2 - webelement操作常用方法

完整路径 C:\Python27\Lib\site-packages\selenium\webdriver\remote\webelement...

31850
来自专栏liulun

Nim教程【十五】【完结】

模版 模版是Nim语言中的抽象语法树,它是一种简单的替换机制,在编译期被处理 这个特性使Nim语言可以和C语言很好的运行在一起 像调用一个方法一样调用一个模版 ...

24080
来自专栏web后端

PHP json中的Malformed UTF-8 characters问题

一般情形下,json嵌套层级太深这种失败是罕见,但是又相对比较容易识别的;另外一种错误,是关于utf-8编码的,则情形相对比较复杂;

79160

扫码关注云+社区

领取腾讯云代金券