C语言细节总结

来源:互联网 发布:js appendchild的用法 编辑:程序博客网 时间:2024/05/20 03:46

由传智扫地僧C语言提高篇整理而来 传智播客

数据类型 & 变量

  1. 数据类型的本质: 固定大小内存块的别名。
  2. 变量名的本质: 一段连续内存空间的别名。通过变量名向内存读写数据,而不是向变量读写数据。
    对数据类型取别名: typedef
    对变量(内存空间)取别名: C++引用

内存四区模型、函数调用模型(栈)

  1. 堆区: 动态内存申请与释放。malloc() free()
  2. 栈区: 自动分配释放,存放函数的参数值,局部变量的值
    • 开口可以向上或向下
    • 无论栈的开口向上还是向下, 数组buf的增长方向都是向上(地址增大) 栈的生长方向和buf的生长方向是两个概念。在编译时,buf所代表的内存空间编号就已经确定了。
  3. 全局区(静态区): 全局变量和静态变量。包括全局初始化区、全局未初始化区。常量字符串
  4. 代码区

指针

  1. 指针变量和它所指向的内存块是两个不同概念。
    • free()后,野指针。释放了所指的内存空间,但指针变量本身没有重置为NULL。
  2. 指针也是一种数据类型,其类型是所指向的内存空间的数据类型。(解决指针的步长问题,上述第2点)
    • 指针的步长,由所指的内存空间的数据类型决定。
  3. 通过指针间接赋值是指针存在的最大意义。
    变量取地址赋给形参,在被调函数中通过 *p 修改变量。
    • 函数调用时,通过n级指针做形参,修改n-1级指针的值(返回n-1级指针的值!!!)
      在子函数中,将变量的值传递出去,通过一级指针,即一级指针做形参;
      在子函数中,将一级指针的值传递出去,通过二级指针,即二级指针做形参;
      在子函数中,将二级指针的值传递出去,通过三级指针,即三级指针做形参;
    • 指针做函数参数,具有输入、输出特性。
      指针做输入:主调函数分配内存;指针做输出:被调函数中分配内存。
      用指针传递运算结果,return语句用于表示函数运行状态
  4. 二级指针做输入三种内存模型
    • 指针数组。
    • 二维数组。
    • malloc()分配内存,存放指针(程序员自己打造内存空间) eg: p = (char **)malloc(sizeof(char *) * num);
  5. 多级指针避免野指针方法
    • 分配成功,memset()置0;
    • 释放时,判断指针是否为NULL,非NULL则通过free()释放,释放完后指针置NULL.
  6. 一般应用禁用malloc/new
  7. 内存泄漏检测工具: mtrace、memwatch
  8. 在C语言中,const修饰的变量,通过指针可以修改。(C编译警告,C++编译报错)不同于C++的地方

数组

  1. 数组名 & 指针步长
    • 数组名: 数组首元素的地址; 数组名+1: 第二个元素的地址
    • &数组名: 整个数组的地址; &数组名+1: 数组最后一个元素之后的地址
    • 数组名是常量指针。Why? 保证数组所占内存能够安全释放。
  2. 数组做函数形参,本质为指针,在子函数中sizeof(数组名)返回的是指针占用的字节,而非原数组占用的字节。数组名作形参,退化为指针
  3. 数组类型定义 eg: typedef int (Array)[10];
  4. 数组指针的定义
    • 通过数组类型定义 Array *p;
    • 声明一个数组指针类型 eg: typedef int (*p)[10];
    • 直接定义 int (*p)[10];
  5. 多维数组名的本质:数组指针
    • 二维数组 a[3][5]
      • a+i: 第i行的地址 相当于二级指针
      • *(a+i): 第i行首元素的地址 相当于一级指针
      • *(a+i)+j: 相当于&a[i][j]
      • *( *(a+i)+j) = a[i][j] 或: a[i] = *(a+i); a[i][j] = *(a+i)[j] = *(*(a+i)+j) []结合性:从左到右
  6. 数组和指针的等价关系
    • 一维数组 char a[10] —–> 指针 char *a
    • 指针数组 char *a[10] —–> 指针的指针 char **a (数组名是首元素的地址,元素为指针)
    • 二维数组 char a[10][5] —–> 数组的指针 char (*a)[5] (二维数组可以看作是一维数组,因而看作指针,又因为每个元素是数组,所以是数组指针)

字符串

  1. C语言没有字符串类型,通过字符数组模拟字符串,以’\0’(0)结尾。
  2. 字符串的内存分配。堆、栈、数据区。指针和buf的区别
    • char buf[5] = “abcd”; // “abcd”为于常量区,buf数组位于栈区,常量区的”abcd”拷贝至buf中。
    • char *p = “abcd”; // “abcd”位于常量区,指针指向 ‘a’的地址。
  3. 字符串相关函数
    • strstr() 从母串中查找子串,返回字串第一次出现的位置。
  4. 递归实现字符串反转
    • 参数入栈、出栈,逆序打印
    • 递归和全局变量。递归结果存入全局变量。 strncat(dest, src, 1)
    • 递归和非全局变量。指针做函数参数。

结构体

  1. 定义结构体
    • 定义类型的同时定义变量
    • 匿名类型、定义变量
    • 通过类型定义
  2. 类型重定义
  3. ‘.’是寻址操作,计算偏移量,在CPU中进行,没有操作内存。
  4. 结构中嵌套结构体、结构体指针、自身类型的指针
  5. 结构体中嵌套指针时,深拷贝和浅拷贝问题
    • 浅拷贝:仅把指针变量的值拷贝,没有对指针所指的内存空间的数据进行拷贝。
    • 深拷贝:显式分配内存进行拷贝。
  6. 成员的偏移量
    定义一个结构体变量,分配内存时,会按成员顺序固定分配。一旦结构体定义下来,结构体中成员的内存布局就定下来了。

链表

  1. 链表由节点(结构体)构成,每个节点包含数据域指针域两部分。指针域中存储下一个节点的地址。
    eg: 单向链表: 头指针 -> 节点 -> 节点 -> … -> 节点(指针为NULL)
  2. 分类:
    • 带头指针 & 不带头指针
    • 节点的组织形式: 单向链表、双向链表、循环链表
    • 静态链表 & 循环链表
  3. 传统链表 & 非传统链表(linux内核链表、通用链表)
    • 链表算法和数据类型进行分离 (C++中模板类)
    • 通用链表将链表节点(区别于业务节点)设置为节点的第一个域,避免了求偏移量

文件操作

  1. 按照字符读写 fgetc() fputc()
  2. 按照行读写 fgets() fputs()
  3. 按照块读写 fread() fwrite()