C基础知识整理学习

C基础学习

1. 正数的补码是它本身、负数的补码是它的绝对值取反、+1
2. ASCII码为0 的字符是 NULL
3. char型和整型一样、都是以补码的形式存储、它其实可以是 -128 ~ 127 所表示的范围、unsigned char0~255
4. float其实不是精确的数值
5. 不同形式的00'\0''0'"0" 分别是常量0、空字符NULL、字符0、字符串06. 常量:整型常量、实型常量、字符常量、字符串常量、标识常量define
7. gcc预处理:-E、

常量

#define PI 3.14 
#define ADD (2+3)
#define MAX(a, b) a > b ? a : b
#define的内容会在预处理的时候解析处理

变量

用来保存特定内容、在程序执行过程中值随时会发生变化的量
[存储类型] 数据类型 标识符 = 值
          type name = value
数据类型:基本数据类型 + 构造类型
存储类型:auto static register extern(说明型)

默认auto:自动分配空间、自动回收空间
register:寄存器类型、只能用来定义局部变量、不能定义全局变量、只能定义32位大小的数据类型
默认auto、放在栈空间、
register存放在CPU、存取速度快
static:静态型、自动初始化为0值或者空值、并且其变量的值有继承性、
        修饰变量或者函数时、也把变量或者函数的作用域限制在本文件内
extern:说明型、说明有一个定义在外部、意味着不能改变被说明的变量的值或者类型
auto类型若不分配初始值、值的大小是不确定的、static一定会被初始化为0值

变量的生命周期和作用范围
1)全局变量的作用范围:从变量定义开始、到程序执行结束
   局部变量的作用范围:从局部变量定义开始、到语句块执行结束

   定义全局变量其实就是多个模块会公用某个变量、一个模块对变量的修改为影响其它模块的使用
   __FUNCTION__ 当前所在函数、gcc提供
2static 作用域、定义开始到本文件执行结束

运算符和表达式

1. sizeof 其实不是关键字、而是运算符、用来测试字符或者数据类型的长度
2. 强制类型转换、转换的是中间过程、而不是变量本身
3. 位运算应用
   1)将操作数的某一位置1、其它位不变 num | 1 << n
   2)将操作数第n为清0 num & ~(1 << n)
   3)测试第nnum & 1 << n
   4) 从一个指定宽度的数中取出其中的某几位 ??

输入/输出

分为标准IO 和 系统调用IO
1. 格式化输入输出函数 scanfprintf
2. 字符串输入输出函数:getchar、putchar
3. 字符串输入输出函数:gets(!)、puts()
格式控制修饰符  
m数据宽度 若int a = 123printf("%4d") 会输出空格+123  printf("%2d") 会输出123、也就是宽度不够时、忽略、宽度过宽时、补空格
n小数点后的位数、四舍五入

两个名字相同的函数、语言未知、如何判定是重载实现的还是变参实现的 ?
多传几个参数进去、如果在编译时报语法错误、则是重载实现的
如果编译时不报错、在使用时发现不符合预期、则是用变参实现的
exec族是变参实现的

缓冲机制:
当缓冲机制为行缓冲时、\n强制刷新缓冲区
数组名、本身就是地址常量
scanf必须按照format原样输入
1. scanf使用%s输入字符串时、无法检测输入字符的长度是否已越界
2. 放在循环里、必须校验scanf的返回值(如果要求输入%d(int)、实际输入%c(char)、程序会一直输出上次放到缓冲区的内容)
回车的ascii的码值是10
scanf和getchar连用时、回车本来是想结束scanf的输入、缺被getchar接收、可以使用抑制符*吃掉一个字符、或者用getchar吃掉

scanf("%d", &i);
// scanf("%*c*c", &ch);
getchar();
scanf("%c", &ch)
gets(danger): 不检测输入是否越界
fgets保证不出错、单不能保证接收你需要输入的全部、可能想输入10个、只被接收了5个
getline可以接收一个完整的串、也不会越界、因为使用的是动态内存来实现的
所以gets可以使用fgets或者getline来代替、注意getline不是c88或者c99标准库、值存在gunlib里

CFLAGS += -lm 在进行gcc编译时、添加-lm选项
C中进行混合数据类型的判断时、要让数据精度一致

数组

1. 一维数组
   1) 定义:[存储类型] 数据类型 标识符 [下标]
   2)初始化:
      可以不初始化(值不确定、是它指向的内存空间的值)
      全部初始化(用大括号包括)
      部分初始化(未被初始化的值会被初始化为0) 
      使用static定义 就算全部没有初始化也会被全部被初始化化为0
   3)元素引用
   4)数组名  是表示地址的常量、也是数组的起始位置
   5)数组越界
   应用:
   1)冒泡排序  2)选择排序  3)删除发求质数  4)进制转换
2. 二维数组
应用:1)行列互换 2)最大值及其下标 3)求二维数组中各行和各列的和  4)
3. 字符数组
  可以使用单个字符或者字符串常量来初始化
  会有一个结束标记\0
  先放到进程环境的输入缓冲区、然后再写入指定空间
  strlen是以\0为标记、以后的字符不计算  sizeof是实际占用的大小、包含\0

指针

1. 变量与地址
   变量名其实是给用户用的、地址是给计算机用的、计算机凭借地址值找到某个空间
   变量名就是抽象出来的某块地址的名称
2. 指针与指针变量
   指针变量:保存指针的变量
   指针:具有指向作用的地址
3. 直接访问与间接访问
   指针变量所占的地址空间大小是确定的、64位平台是占8个字节、32位是占4个字节
   但是不同类型的指针在运算时、意义不同、int *i= &x 会到i所指向的空间取4个字节、double *会取8个字节 char *会取1个字节
   int *p = &i
   type name = value
   type是int *  name是p、意义是 定义一个指针类型的变量p、将i的地址赋值给p
4. 空指针与野指针
   NULL是define的一个宏、值为0、*p = NULL 也就是将指针指向起始地址为0的一个空间,它不分配给任何人使用、在不确定将指针指向何处时、可以先赋为空指针
   野指针是所指向的空间是不确定的、或者压根就没有指向
   为了防止野指针的产生、可以在定义指针变量时、给初值NULL  int *i = NULL; 在有指向时再赋值即可
   void * 可以接收任何类型的指针、也可以赋值给任意类型、在不确定要操作的值类型时可以使用void *
   viod *p = NULL 
5. 空类型
6. 定于与初始化的书写规则
7. 指针与运算
  &  * 关系运算  ++ --
8. 指针与数组
  int a[3] = {1, 2, 3}
  int *p = a; p指向a的起始地址、与a的区别是:p是变量、a是常量

  int a[2][3] = {{1,2,3}, {4,5,6}}  a是在行上移动的指针  *(a+i)变成列指针
  int *p p是在列上移动的指针
   char str[] = "hello";
    // str = "world"; // 错误的写法、数组名是地址常量
    strcpy(str, "wrld"); // 新的字符串比原空间大时、会产生core dump·
    puts(str);
/*
    char *str = "hello";
    strcpy(str, "world"); // 是把原空间的常量覆盖、不允许
    str = "world"; // 把str这个指针指向world这个常量即可
    puts(str);

9. const与指针
   const把某些内容常量化
   #define在预编译阶段进行、不检测语法
   float pi = 3.14 用变量来保存常量值、但它的值可能被外部改变
   const float pi=3.14 把变量pi常量化、不允许修改
   与define比会进行语法检测

   const int *p; 是指针常量
   int const *p; 等价

   常量指针:(const int *p)
     指针的指向可以发生变化、但指针所指向的当前空间的值不能发生变化
   指针常量:(int *const p)
     指针的指向不能发生变化、但指针所指向的目标变量的值可以发生变化  
  const int *const p = &i; // 


     常量指针举例:
     int i = 1;
     const int *p = &i;
     i = 10; // 可以改变变量的值
     *p = 10; // 不可以、因为*p被const修饰、常量化了
     const修饰值不能变化、只是锁定通过这个名字不能发生变化、而不是目标变量的值不能变化、可以通过其它方式改变
     int j = 100;
     p = &j; // 可以、把p指向j的地址

     指针常量举例:
     int i = 1;
     int *const p = &i;
     *p = 10; // 可以、改变p指向地址的值
     p = &j; // 不允许、指针常量不允许修改指针的指向

10. 指针数组和数组指针
   数组指针:是指向数组的指针、本质是一个指针
   [存储类型] 数据类型 (*指针名)[下标] = 值
   auto      int (*p)[3]; ====  int[3] * p
   指向包含3个整型元素的数组的指针

   指针数组:
    [存储类型] 数据类型 * 数组名 [长度]
    int * arr[3]; -> type name === int *[3] arr
    包含3个指针变量的数组
    char * name[5] = {"follow me", "basic", "great", "ccc"}
    定义一个数组name、包含5个元素、每个元素是一个指向char 类型的指针


11. 字符指针和字符数组

函数

1. 函数的定义
   数据类型  函数名 ([形参说明表]) -- 数据类型 形参名, 数据类型  形参名, ...(可能是定长参数、也可能是变长参数)

2. 函数的传参
   1)值传递
   2)地址传递  需要在另外的函数中改变其它函数的值的时候、可以使用地址传参
   3)全局变量

3. 函数的调用
   return 0; // 结束当前函数
   echo $?; 输出上次命令的返回值
   一个进程的返回状态、是给它的父进程看的

   int main(int argc, char *argv[]) 
   int main(int argc, char **argv) --> **argc 其实就是 *argv[]就是一个argv的数组起始地址
   argc 是计数器、计算从终端传入的参数个数
   argv 是列表、用来保存从终端传入的所有参数(其实就是字符指针数组的首地址)
   每个char *指向一个参数字符串、最后以空指针NULL结束
   当前shell可以解析通配符

   1. 嵌套调用
   2. 递归调用
      何时跳出、检错、
      递归会有调用栈、递归层级太深的时候、会造成栈溢出(栈空间大小是一定的)
      传数组名的时候、其实是把数组第一个元素的地址传入
      void print_arr(int p[], int n)
      void print_arr(int *p, int n)
      在作为形参时、int *P 等价于 p[]
      在定义变量时、表示分配多大内存的空间 int a[] = {1, 2, 3}

      int a[N] = {1, 2, 3}
      在使用以下值作为实参时、对应的形参应该是?
      -> a      *a    a[0]  &a[3]   p[i]  p      *p   p+1
      -> int *  int   int    int *   int  int *  int  int *

4. 函数与数组
   二维数组
      int a[M][N] = {1, 2, 3, 4, 5, 6}
   1. 作为一个大的一维数组传入
      print_arr(&a[0][0], M*N) // 或者 *a, M*N | a[0]--> *(a+0)
      print_arr(*p, int n)
      // 这样无法区分行、列

   2. 作为二维数组传入、区分行、列
      print_arr(a, M, N)
      print_arr(int (*p)[N], int m, int n) // 数组的本质是 *(a+i)[j] 行列指针(一个指向数组的指针)
      printf("%4d ", *(*(p+i)+j));
      p其实就是一个一级指针、指向一个数组的起始位置
      或者写成
      print_arr(int p[][N], int m, int n) 
      printf("%4d ", p[i][j]);


   3. int a[M][N] = {...};
      int *p = *a; --> p相当于一个行指针、所以p[i] 相当于某个元素
      int (*q)[N] = a; --> q[i][j] ==> a[i][j]
   在使用以下值作为实参时、对应的形参应该是?
   -> a[i][j]  *(a+i)+j  a[i]+j  p[i]  *p   q[i][j]    *q     q          p+3      q+2
   -> int       int *    int *   int   int  int      int *   int (*)[N]  int *   int (*)[N]
 5. 函数与指针
    指针函数(之前的int func(arg) 可以称为整型函数)
        返回值 * 函数名(形参)
        eg int * fun(int)
    函数指针
        类型 (*指针名) (形参表)
        eg. int (*p)(int)
        指的是一个指针、它指向一个函数
        int (int, int) *p ==> int *p(int, int) 
    函数指针数组
        类型 (*数组名[下标]) (形参)
        eg. int (*arr[N])(int);
        数组arr包含N个元素、这n个元素都是指向函数的指针
        int (int, int) *funcp[2] ==> int (*funcp[2])(int, int)

   指向指针函数的函数指针数组
        int *(*funcp[M])(int)

构造类型

1. 结构体
   产生及意义:描述不同数据类型的结构
   类型描述
          struct 结构体名{
            数据类型 成员1;
            数据类型 成员2;
            ...
          }; // 分号必须有
          结构体的类型描述不占用任何空间、不能直接使用等号初始化
          int i; // int 是描述、不占用空间  i是变量、占用空间、struct一样、描述不占用空间、定义变量时才占用
          一般定义在函数体外

   嵌套定义
   定义变量(变量、数组、指针)、初始化及成员引用
   成员引用:变量名.成员名
   struct stu stu = {.math = 98, .chinese = 97} // 只给结构体中部分元素赋值
   或者  指针 -> 成员名
   struct stu *p; // 定义一个指向结构体的指针
   (*指针).成员名
   结构体数组
   struct st arr[2] = {{"zhang", 11, 98}, {"li", 12, 99}};
   注意:在给name赋值时、因为str[NAMESIZE]数组名的数组名str只是数组的起始位置、 不能直接使用 stu.str = "name"、可以使用 strcpy(stu.name, "name")

    struct simp_t
    {
        int i;
        char ch;
        float f; // 占用4个字节
        char ch1; //
    } __attribute__((packed)); // 告诉结构体不要进行内存对齐、在网络传输时常用

    函数传参(值、地址)


2. 共用体
   产生及意义:解决硬件空间不足、同一时刻、n个成员间只能有一个在使用
   类型描述
   union 共用体名
   {
     数据类型 成员名1;
     数据类型 成员名2;
     ...
   };

   嵌套定义
   定义变量(变量、数组、指针)
   初始化成员及引用
      变量名.成员名
     6位 指针名->成员名
   函数传参(值、地址)

   位域
   得到32位数字的值
   unsigned int32_t i = 0x11223344;
   i>>16(高1) + i & 0xFFFF(低16位)

   union{
     struct{
       uint16_t i; 
       uint16_t j; 
     }x;
     uint32_t y; 
   }a;

   给y赋值、可以得到x+y
3. 枚举值
   enum 枚举名称
   {
     成员1 ;
     成员2 ;
     ...
   };
   可以当成宏值来使用
   enum
   {
     STATE_RUNNING = 1;
     STATE_RUNNING ;
     STATE_OVER;
   };
   直接使用宏定义、在预编译时会被替换成常量值
   使用enum是结构体定义、不会在预处理是被替换掉

动态内存管理

1. auto修饰的变量在栈上  static修饰在静态区上, 根据全局还是局部、是否初始化再细化
2. 动态内存可以方便的根据需要来申请空间
   原则:谁申请谁来释放
3. void *malloc(size_t size); // 在堆上申请size个大小的空间、由于不知道用户怎么使用、只能返回void *(申请内存的起始地址),可以用任何类型的指针接收 
   realloc 从给定地址处查看是否具有申请的size字节、没有的话、从别处重新查找申请、返回新的空间地址、释放原空间地址、
   若是缩小、直接在原基础上从尾压缩

    #include <stdio.h>
    #include <stdlib.h>

    int main()
    {
        int *p; // 定义整型变量的指针、用以接收申请的内存
        p = malloc(sizeof(int)); //将申请的地址空间的初始值给p、返回值是void *,
    直接用p接收即可、若不包含头文件stdlib、编译器会看不到malloc的函数原型、默认>为int *、就会想要类型强转
        if (p == NULL){
             printf("malloc error.");
             exit(1);
        }

        *p = 10;
        printf("%d\n", *p);
        free(p);
        exit(0);
    }

    free之后空间还在、但是可能会重新分配
    free之后、要有把指针赋值为空的习惯

3. typedef 针对已有类型去设置新的数据类型(修改原有数据类型名)
   typedef 已有的数据类型 新名字;

   typedef int FUNC(int) 将参数为int、返回值为int的函数改名为FUNC -> int(int) FUNC
   eg. FUNC f; --> int f(int);

   typedef int *FUNCP(int);
   eg. FUNCP f; --> int *f(int) 给返回值为int *、参数为int的函数FUNCP改名

  typedef int *(*FUNCP)(int);
   eg. FUNCP f; --> int *(*f)(int) 定义指针变量p、它指向返回值为int *、参数为int的函数
   是指向指针函数的指针变量



猜你喜欢

转载自blog.csdn.net/njys1/article/details/81413438