C语言基础

1、进制之间的转换

:::info

  1. 其他转十进制,原本的权位相加
    1. 如:20+21+… 80+81+… 160+161+…
    2. 十六进制,10a 11b 12c 13d 14e 15f
  2. 十进制转其他,除以其进制数逆向取余
  3. 八进制(421) 《=》 二进制(01) 《=》 十六进制(8421)
  4. 占用0~8个二进制位就是占用1个字节 、占用8~16个二进制位就是占用2个字节
  5. 转义字符:
    1. 八进制形式转义: ‘\ddd’ d代表的是 8 进制的方式 ,如:‘\101’ ----> ‘A’
    2. 十六进制形式转义:‘\xdd’ d代表是十六进制,如:‘\x43’ —> ‘C’
  6. 正整数:源码=反码=补码
    1. 源码:00000000 00001111 00010010 00000110 二进制位
    2. 反码:00000000 00001111 00010010 00000110
    3. 补码:00000000 00001111 00010010 00000110
  7. 负整数:
    1. 源码:10000000 00001011 10001111 01000111 符号位不变
    2. 反码:11111111 11110100 01110000 10111000 反码=源码取反
    3. 补码:11111111 11110100 01110000 10111001 补码=反码+1
  8. 数值是以补码存到数据类型的空间(int a = 10) 移位也是针对补码来操作
  9. 小数形式十进制转成二进制的方式:
    1. 第一步:(12.5)
      1. 整数部分:除以2逆向取余(1100)
      2. 小数部分:乘以2正向取整(.1)
    2. 第二步:
      1. 以整数1的指数形式连起来(1.1001 * 2^3)
    3. 第三步:存储形式位(1符号位+8指数位+23小数位)指数位:01111111
      1. 0 10000010 1001000000000…(12.5)
      2. 1 10000010 1001000000000…(-12.5)
  10. 小数形式进制转换:
  11. 小数形式任意进制转十进制的方式:
    1. 权位相加,小数位的是2-1、2-2…8-1、8-2…16-1、16-2
  12. 小数形式八进制转成二进制的方式:
    1. 补零凑对位 421
  13. 小数形式十六进制转成二进制的方式:
    1. 补零凑对位 8421
      :::

2、数据类型

:::info

  1. 整数类型分有符号(signed)和无符号(unsigned)
    1. signed(默认)修饰最高位符号位
    2. unsigned 修饰最高位数据位
  2. 小数默认double,整数默认int
  3. 小于int类型的会自动转成int再运算,不同类型会自动向高精度转换再运算
  4. 补码溢出照加,只取所占用的字节数(short 取2byte=16bit)
  5. 浮点型没有反码和补码
    1. 常量:小数形式(12.5) 指数形式:1e2(1*10^2)
    2. 二进制的小数形式比较大小时,后面可以补零(0.111 = 0.1110)
  6. float的存储形式:
    1. 1个符号位+8个指数位+23个小数位(指数位固定是01111111,代表0)
  7. double类型的存储形式:
    1. 1个符号位+11个指数位+52个小数位
  8. double类型转换成int类型会断尾(12.9 ——> 12)
  9. char类型最高位都是0,如果当整型使用,要用signed定义
    1. signed char x = -10;
    2. unsigned char x = 20;
  10. 字符串类型双引号里面隐藏一个’\0’字符
  11. #define PI 3.14 宏定义没有分辟空间,编译阶段被宏展开还原
  12. 基本类型: (低精度向高精度),既可以自动转换,也可以强制转换
  13. char — unsigned char — short — unsigned short — int — unsigned int — long — unsigned long — long long — unsigned long long — float — double
    :::

3、输入和输出函数

printf输出格式 %d ----- 以 int 方式打印
%hd ----- 以 short 方式打印
%hhd ----- 以 signed char 方式 打印
%ld ----- 以 long 的方式打印
%lld ----- 以 long long 的方式 打印

%u ----- 以 unsigned int 方式打印
%hu ----- 以 unsigned short 方式打印
%hhu ----- 以 unsigned char 方式 打印
%lu ----- 以 unsigned long 的方式打印
%llu ----- 以 unsigned long long 的方式 打印

%f ----- 以 float 方式打印 //6位小数, 四舍五入操作
%lf ----- 以 double 方式打印 //6位小数, 四舍五入操作
%g / %G -----

%o ----- 以 八进制 的形式 打印出来
%#o ----- 以 八进制 的形式 打印出来(带标志位)

%x / %X ----- 以 十六进制 的形式 打印出来
%#x / %#X ----- 以 十六进制 的形式 打印出来(带标志位)

%c ----- 以 char 字符的方式打印出来

%s ----- 以 字符串 的形式打印
%p ----- 打印指针
:::info

  1. %nd:n表示宽度、负号左对齐,正号右对齐
  2. %m.nf:m表示宽度,n表示小数点后长度
  3. scanf遇到空格、Tab、回车键都会结束
  4. getchar:
    1. getchar只有遇到回车键才会接收,获取回车键是用’\n’接收
    2. getchar()也能清空缓冲区,清空所有缓冲区把getchar放while循环,while(getchar != ‘\n’){};
  5. gets():
    1. 读取一整行的字符串,丢弃’\n’
  6. puts():
    1. 显示字符串,会加上换行符’\n’,遇到’\0’才会停止输出
  7. fgets(str,length,stdin)
    1. 读取length-1长度的字符串,保留’\n’
  8. fputs(str,stdout)
    1. 显示字符串,不会加换行符’\n’
      :::

4、运算符

:::info

  1. 没给变量赋值不一定没有值,随机赋垃圾值
  2. 逻辑 与或非:&& || !
    1. 判断表达式真假时候,遇到&0则为假,遇到||1则为真
  3. 位运算:
    1. 位与& 同为1才是1
    2. 位或| 有1就是1
    3. 异或^ 不同就是1
    4. 取反~ 取反后为负数的话,要转成源码,符号位不参加计算
      :::
		00000011 00100000  ~ 800
    	======================
源码:	11111100 11011111   ---》 
反码: 	11111100 11011110   ---》
源码: 	10000011 00100001
	某个位置(从第0位开始数)左移补0,右移补符号位(除了逻辑算术补1)
		置1用位或,运算里补0除了目标位置为10用位与,运算里补1除了目标位置为0
	00000000 00000000 00011110 00100010  	
	00000000 00000000 00000000 00001000   |      a = a | (1<<3)   //  a |= 1<<3;
	---------------------				  		
	00000000 00000000 00011110 00101010	

	00000000 00000000 00011110 00100010 
	11111111 11111111 11111111 11011111    &     a =  a & (~(1<<5))  // a &= ~(1<<5) 
	------------------------
	00000000 00000000 00011110 00000010 
括 : 括号: ()  []  ->  .
单 : 单目: ++  --  ~  !  sizeof()  (强转)  &(取值)   * 
术 : 算术: +  -  * /  %  
移 : 移位: <<   >> 
关 : 关系: >  >=  <  <=  == != 
位 :位运算: &  |  ^ 
逻 : 逻辑: &&   || 
三 : 三目:  ?:
赋 : 赋值: =  +=  -=  .....
逗 : 逗号: ,
<< : 左移	左移末尾补0	相当于乘以2^n

>> : 右移	正数左边补0,负数左边补1	相当于除以2^n

5、for循环

:::info

  1. for循环(每行控制条件i<=n,每个控制条件j<=i)
    1. 直三角:
      1. 根据第i行输出多少个*
    2. 反直三角:
      1. 根据总行数n输出n-1空格,再根据第i行输出多少个*
    3. 等腰三角:
      1. 根据总行数n输出n-1空格,再根据第i行按奇数输出*的个数
    4. 倒三角:
      1. (空格)第一行不输出,第二行开始每行输出一个空格,j<=i-1
      2. (星号)按顺序输出倒数的奇数行k<=i
    5. 菱形:
      1. 正三角(遍历总行数的一半)
      2. 倒三角(根据第i行输出多少个空格,再按奇数的倒叙输出,n-2*i)
  2. 跳出循环:
    1. break跳出一个循环
    2. continue跳出一次循环
  3. 循环思想:
    1. 交叉符号相加用s = -s
    2. %10==5 : 找出的是个位数为5的
    3. /10==5 :找出的是十位数为5的
      :::
	goto用来跳出多层for循环
	标签:  ***;
		***;
		goto 标签;

6、指针

:::info

  1. 指针在内存中占用的字节数:

    1. int a定义4个字节空间在内存
    2. 每个字节对应32位操作系统的4个字节(门牌号)
    3. 每个字节按小端序存放,最低位作为该空间地址(&a)
    4. &a地址是int *指针类型,指针p = &a是将a空间地址最低位赋到p的4个字节空间里
    5. 该*p指针所占8个字节,再把p地址赋给指针pp,则pp为int **类型,**pp也是占8个字节
  2. 数组指针:

    1. arr 既是int[10]类型,也是int*类型
    2. 当arr是数组名:
      1. &arr,arr没有取值,会把arr当成是数组类型
      2. &arr+1; 会进入了CPU运算,arr被当成了指针类型,再&取址就是int(*arr)[10]
    3. 当arr是地址:
      1. arr+1移动的是4*1个字节
      2. &arr+1移动的是4*10个字节
    4. 数组指针和数组的区别:
      1. int(*arr)[10]数组的指针类型,这里arr是数组的首元素指针,占8个字节
      2. int arr[10]则是数组类型,arr数组是4*10个字节
    5. 数组指针:
      1. 一维数组array[3]指针是 int * ,首元素指针;即 int *p = array,指针移动跨度都为4
      2. int (*p)[] = &arr; 即数组指针指向的是整个数组
      3. int (*p)[] = arr; 这样写也是对的,不过取值时要取两次,数组的地址也是数组的首地址
      4. 二维数组array[3][4]指针是 int (*p)[4];即 int (*p)[4] = array,指针移动跨度都为16
    6. 多维数组作为返回值:
    7. int (*fun(void))[5][6]
  3. 指针地址:

    1. int*、char*、short*、int**、char**等等都是存放地址的类型,地址都是系统的寻址线组成
    2. 64位固定为8个字节,32为固定为4个字节
  4. 指针类型转换:

    1. 变量a,&a取出地址的类型是int*
    2. ((short )&a+1) 是先把int类型转成short类型,再+1则只移动2个字节
    3. 然后从低位往上取剩下的地址
  5. 指针分别在数组和函数的作用:

    1. 指针用在数组
      1. 可以通过移动指针来更改数组元素
    2. 指针用在函数
      1. 可以传入指针接收两变量的指针取值运算后得出的结果,而不用把结果返回
      2. 定义指针最好用基本类型,再取址变指针放到函数里
  6. 指针数组和二维数组:

    1. 指针数组:
      1. char *str[5],40个字节,显示效率高,但不能改变字符串字面量
      2. 类型都相同int *p = int p[] = int p[100]
    2. 二维数组:
      1. char str[5][40],200个字节,可更改字符串,因为它是拷贝的副本
      2. 类型都相同int (*p)[4][5] = p[][4][5] = p[2][4][5]
  7. 指针用法:

    1. 左值(指针变量)lvalue可以改变,右值(数组名)rvalue不能改变
    2. 野指针或者赋0或NULL都不能解引用
  8. 指针对字符串的含义:

    1. 一个指向字符串的指针赋给另一个指针时,不会把字符串拷贝给另一个指针,只是让另一个指针也指向该字符串
    2. 把字符串数组元素往前移动时(str[i]=str[i+1]),会把’\0’也前移了不用截取掉最后的元素
    3. 字符串只能用数组或者指针定义,并且字符串以’\0’结束,以及还有个0隔开
  9. 指针在数组和字符串的不同:

    1. 初始化数组是把字符串拷贝到数组中,初始化指针是把字符串地址拷贝给指针
    2. 数组的变量名是地址常量,不能改变
    3. 指针的指针名是变量,可以递增改变(str++)
  10. 函数指针:

  11. 函数指针作为函数名:

    1. int (*fp)(int,int) = fun;
    2. int ret = fp(3,4);
  12. 函数名默认就是指针,指针指向该函数

  13. 函数指针作为函数参数:

    1. int fun(int (*fp)(int,int),int,int) 该指针指向一个函数,相当于传入一个函数
  14. 函数指针作为返回值类型:

    1. int (*fun(char,int))(int int) 返回某个函数的指针,相当于返回某个函数
  15. 函数指针数组:

  16. int (*brr[5])(short)= {fun,fun2,fun3}; //保存的是函数指针

  17. const对指针的限制:

  18. const int a,相当于把a变成常量

  19. const int *p,修饰int *则不能通过解引用更改值,是指向常量的指针

  20. int * const p,修饰指针变量p则不能再用p指向其他

  21. const int * const p,同时修饰的话只能通过该指向的变量更改值
    :::
    指针与数组之间的关系:::info

  22. 元素指针:

    1. short arr[10];
    2. short *p = arr; // p 指向该数组的 第一个元素 short
  23. 数组指针:

    1. short brr[2][3];
    2. short (*p)[3] = brr; // p 指向 short [3]
      :::
      指针与指针之间的关系:::info
  24. 一级指针:

    1. int a = 10;
    2. int *p = &a; // p 指向 a空间
  25. 二级指针:

    1. int **pp = &p; // pp 指向 一级指针变量p
  26. 三级指针:

    1. int ***ppp = &pp; // ppp 指向 二级指针变量pp
      :::
      指针与函数之间的关系:::info
  27. 函数:

    1. short fun(short s,int c){}
  28. fun的类型:

    1. short (*)(short,int)
    2. short (*p)(short,int) = fun; //函数指针p
    3. p(10,20); // fun(10,20);
  29. 回调函数,一个函数作为另一个函数的参数

    1. fun(short (*b)(short,int)){}
    2. fun(a);
      :::
      指针与数组之间的关系:::info
  30. 一维数组:

    1. 函数实参:
      1. int arr[10]; // arr : int *
      2. fun(arr);
    2. 一维数组作为函数形参:
      1. fun(int *p)
      2. fun(int p[])
      3. fun(int p[10])
  31. 多维数组:

    1. 函数实参:
      1. int arr[2][3][4]; // int (*)[3][4]
      2. int arr[][3][4] ={{},{}};
      3. fun(arr);
    2. 多维数组作为函数形参:
      1. fun(int (*p)[3][4])
      2. fun(int p[][3][4])
      3. fun(int p[2][3][4])
  32. 一维数组作为返回值:

    1. int * fun(void) {
    2. int *arr = (int *)malloc(20); // static int arr[10];
    3. return arr; }
  33. 多维数组作为返回值:

    1. int (*fun2(void))[5][6] {
    2. static int arr[4][5][6];
    3. return arr; }

:::
函数与指针之间的关系:::info

  1. 指针作为形参

    1. int a = 10;
    2. int *p = &a;
    3. fun§;
    4. ==============>
      1. fun(int *pp)
      2. fun(int pp[])
      3. fun(int pp[10])
    5. =============> 本质都是 int *
  2. 多级指针作为形参:

    1. int a = 10;
    2. int *p = &a;
    3. int **pp = &p;
    4. int ***ppp = &pp;
    5. fun(ppp);
    6. ============>
      1. fun(int ***x)
      2. fun(int **x[])
      3. fun(int **x[10])
    7. ====》 本质 :都是 int ***
  3. 指针作为函数体:

    1. 多级指针作为返回值:
      1. int ***fun(void) {
        1. int a;
        2. int *p = &a;
        3. int **pp = &p;
        4. int ***ppp = &pp;
        5. return ppp; }
          :::
          函数与函数之间的关系:::info
  4. 函数作为形参:====》 回调函数

    1. void xx(int a){} // xx : void (*)(int)
    2. fun(xx);
    3. =======> fun(void (*p)(int)){}
  5. 函数作为返回值 :

    1. typedef void (*sighandler_t)(int);
    2. sighandler_t signal(int signum, sighandler_t handler);
      :::
拆分: int *(*)[10]   (*)(int *)   pfun ;	
	  返回值为int (*)(int *)  形参为int *  函数指针变量 pfun 
	  int *(*)[10]    //数组指针指向 int * [10]
pfun函数返回值为数组指针int *(*)[10],参数为int *
fun : 类型: int *(*(*)(int *))[10]
{
	static int *arr[3][10];
	return arr;	
}
	int *(*(*p)(int *))[10]  = fun;

7、算法

:::info

  1. 求素数
    1. 用2到输入值除以2到输入值以内的数,能整除则跳出第二循环,否则会加到与输入值一样
    2. 所以判断如果第二循环里的数和输入值一样(i==j)输出i则是素数
  2. 斐波那契数列,1,1,2,3,5,8…
    1. 利用返回两个递归函数相加:f(n-1)+f(n-2)
  3. 快速排序:
    1. 比基准值大的放右边,比基准值小的放左边,递归分两部分进行
      1. 找出一个基准值(中间下标)
      2. i下标右移,找出比基准值大的值;j下标左移,找出比基准值小的值(可以等于基准值)
      3. (i<=j的时候)就把值互换,然后i++,j–
      4. 当i>j则以上就完成了一次互换循环
      5. 再将数组分成两部分,left到j为一部分,i到right为另一部分
      6. 当j>left时调用递归继续对这一部分进行互换,直到j=left;当i<right时同理
  4. 冒泡排序:
    1. 从左往右两两相比,把较大值放右边,每轮到最后的值是最大值
      1. 外层for循环控制比较的轮数,10个数则9轮
      2. i要从1开始(为了控制次数),小于length是9轮
      3. 内层for循环控制比较的次数,j下标则从0开始,小于length-i
      4. 第1轮最后到下标8,是比较9次
        :::

8、存储类型

:::info

  1. 存储类型:auto、extern、register、static、typedef

    1. auto(默认) 声明该局部变量在其作用域里覆盖了与之名称相同的变量(包括全局变量)
    2. extern(默认) 声明(全局变量、函数)为外部文件共享,多个文件一起编译才起作用
    3. register 声明为寄存器变量后无法再通过取址符取值
    4. static 声明为静态的局部变量,会一直保留着变量值,在程序结束后才释放内存
      1. static修饰全局变量、函数:只允许在当前文件起作用
      2. static修饰局部变量:初始化一次,存在于.data区一直保留此值,直到程序结束
    5. typedef 起别名
  2. 存储类型:

    1. 局部变量:
      1. 自动存储类变量
    2. 在程序运行时期内一直存在
      1. 静态存储类变量
    3. 可以在多个文件中使用
      1. 外部链接的静态存储变量
    4. 只限于在一个文件中使用
      1. 内部链接的静态存储变量
  3. 链接属性:

    1. external(外部链接属性)
      1. 标识符不管声明多少次、位于几个源文件表示的是同一个实体
    2. internal (内部链接属性)
      1. 在一个源文件中的声明都指向同一个实体,不同源文件的声明则是不同的实体
    3. none (空链接)
      1. 多个声明都是独立不同的实体
  4. 内存分布:

    1. 栈区:(读写权限)
      1. 存放局部变量,由操作系统来进行自动的分配/回收内存空间
    2. 堆区:(读写权限)
      1. 手动申请、手动回收,通过malloc函数或者new、delete运算符
    3. .bss段:(读写权限)
      1. 存放未初始化的静态局部变量和全局变量
    4. .data:(读写权限)
      1. 存放已经初始化的静态局部变量和全局变量
    5. .rodata段:(只读权限)
      1. 存放一些常量,如字符串常量
    6. .text代码段:(只读权限)
      1. 存放可执行代码
        :::
        static的用法:::info
  5. static有三种不同的用法:

    1. 修饰局部变量:生命周期变长,作用域没有变化
      1. 被static修饰的局部变量存储位置改变了,局部变量是存储在栈区内,而静态变量则是存储在静态区内,使得局部变量的生命周期变得和整个程序一样长,但作用域并没有变化,并在函数被调用过程中维持其值不变
    2. 修饰全局变量:与外部隔绝起来
      1. 全局变量是具有外部链接属性的,而static修饰的全局变量的时候就把这个外部链接属性变成了内部链接属性,只能在文件内部使用
    3. 修饰函数:与外部隔绝起来
      1. static修饰函数其实和全局变量差不多,因为函数也具有外部链接属性,被static修饰后就变成内部链接属性了,就只能在该源文件内部使用,将于外部隔绝起来
        :::

9、申请堆内存

:::info

  1. 申请动态内存:
    1. malloc(字节数) 返回void*指针指向在堆里的内存块
  2. 释放动态内存:
    1. free(指针) 无垃圾回收机制,要手动释放,与局部变量不同
  3. 内存泄露:
    1. 耗完了CPU内存或内存块丢失
  4. 申请内存的函数:
    1. malloc(字节数)
    2. calloc(个数,字节数) 自动初始化全为0
    3. realloc(ptr,新字节数) 将原内存块内容复制到新开辟的内存块,ptr==NULL时相当于malloc
      1. int *temp = realloce(ptr,新字节数);
  5. size 编译生成的文件
    :::
    内存管理:从低地址到高地址
  6. 代码段(text)——已确定的区域大小
  7. 数据段(rodata、data)——只读常量、字符串常量;全局变量、静态局部变量(已初始化的)
  8. bss段——全局变量、静态局部变量(未初始化的,自动初始化为0或NULL)
  9. 堆——(和其他区段一样从低地址到高地址存储)
  10. 动态库(未分配的堆空间)——用户共享
  11. 栈——(从高地址往低地址存储)
  12. 1G内核空间——高地址(命令行、环境变量)
    高级宏定义:#define
  13. #define SQUARE(x) ((x)*(x)) 求平方
  14. inline 加在函数前就是内联函数
  15. #define STR(s) # s 将STR()里的内容变成字符串
  16. #define STR(x,y) x ## y 将STR()里的内容连接成字符串
  17. #define SHOWLIST(…) printf(# VA_ARGS) 将SHOWLIST()里printf#后转成字符串
  18. #define PRINT(format,…) printf(# format,##VA_ARGS)
    清空前n个字节:清空新创建的指针未初始化产生的垃圾值
  19. bzero(指针,n):
    1. 清空前n个字节
  20. menset(指针,int,n):
    1. 将指针的前n位用int来填充
      1. int a = 0x11223344; 最低位是44
      2. int *a = “abcd”; 最低为a

10、结构体

:::info

  1. 结构体声明:
    1. struct 标签{ ; ; ;}(变量名)(初始化);
  2. 结构体初始化:
    1. struct 标签 变量名={ , , ,};
  3. 结构体变量赋值:
    1. .加属性=值
    2. 属性名:值
    3. 声明的变量名.属性=值
  4. 获取结构体属性值:
    1. 变量名.属性
    2. 指针->属性
  5. 结构体存储的字节数:
    1. 64位系统默认最大8个字节对齐,32位系统默认最大4个字节对齐
    2. 总大小计算(sizeof(结构体类型)):
      1. 默认以结构体中最大字节的类型对齐
    3. 自定义结构体的对齐方式:
      1. #pragma pack(n) n字节必须为2的n次幂 (注意:最大不超过系统最大字节数)
        :::

11、位域

:::info

  1. 位域:
    1. 限制变量值占用内存的位数,在声明结构体变量时加:位数
  2. 位域类型要为整型(char、short、int、unsigned int)
  3. 空域:
    1. 即在中间插入(同类型,无变量名,位数0)
    2. int : 0; 对齐空间剩余部分全部补0
  4. 无名域:
    1. 即在中间插入(同类型,无变量名,位数) unsigned:3; 按位数隔开两个位域
  5. 位域存储:
    1. 同类型两个变量值的位宽之和没超出该类型的字节数,则会紧挨着存储,即会压缩内存
    2. 位宽之和超出了该类型字节数则补0按最大类型对齐
    3. 不同类型的两个变量值位域则要看编译器是否采用压缩方式
    4. 如果两个位域字段中间插入非位域类型则不会压缩
      :::

12、共用体

:::info

  1. 共用体:
    1. 所有的成员共用一块空间
  2. 占用内存空间计算:
    1. 取占用空间最大的那个作为空间字节数
    2. 该空间的字节数是每个成员类型的整数倍
  3. 作用:
    1. 成员变量相互影响
    2. 修改某一个成员需要去影响其他成员
      :::

13、枚举

:::info

  1. 枚举格式:
    1. enum <枚举标签名字>{枚举常量,枚举常量};
    2. 下标默认按从0开始
  2. 作用:
    1. 限制取值范围
      :::

14、条件编译

:::info

  1. 条件编译只在编译过程中执行:
    1. #if 确定的值 #endif // 确定的值: 0 非0
    2. #if 值 #else #endif
    3. #if 值 #elif 值 #endif
    4. #ifdef 宏 #endif // 如果宏存在,就编译
    5. #ifndef 宏 #endif // 如果宏不存在,就编译
    6. #if defined(宏) && defined(宏) #endif // 如果多个宏同时存在,就编译
    7. #if defined(宏) || defined(宏) #endif // 只要有宏存在,就编译
      :::

15、函数调用过程

:::info

  1. 九步函数调用过程:
    1. 保存返回地址
    2. 程序流程转移到被调函数的地址处
    3. 保存调用方的栈底
    4. 切换到被调用函数的栈底
    5. 为局部变量分配空间
    6. 开始执行函数体代码
    7. 释放分配的局部变量空间
    8. 恢复调用方的栈底
    9. 返回保存地址
      :::

16、变参函数

:::info

  1. void va_start(va_list ap, last);
    1. 使用该函数 ap 指向 第一个参数 last
  2. type va_arg(va_list ap, type);
    1. ap 往后移动一个,获取 其 数据
    2. type 获取的数据类型
    3. 返回该数据
  3. void va_end(va_list ap);
    1. 完成清理工作
      :::
/*【第一步】:定义一个使用省略号的函数原型 */
double sum(int lim, ...)  
{
 /*【第二步】:声明一个va_list类型的变量ap */
 va_list ap;    
 double sum = 0;  // 用于保存参数之和
 int i;
 /*【第三步】:使用va_start把变量ap初始化为参数列表 */
 va_start(ap, lim); 
 for (i = 0; i < lim; i++)
 {
   /*【第四步】: 使用va_arg访问参数列表。 
   这里第二个参数是double类型,传入的不定参数就应是double类型
   */
   sum += va_arg(ap, double);  
 }
 /*【第五步】:使用va_end完成清理工作 */
 va_end(ap);  
 
 return sum;
}

17、递归

:::info

  1. 三步走:
    1. 明确函数的作用
    2. 找出递归结束的条件
    3. 找出递归的等价关系式
      :::
int f(int n){
    // 1.先写递归结束条件
    if(n <= 2){
        return 1;
    }
    // 2.接着写等价关系式
    return f(n-1) + f(n - 2);
}
int f(int n){
    //f(0) = 0,f(1) = 1,f(2) = 2等价于 n<=2时,f(n) = n。
    if(n <= 2){
        return n;
    }
    ruturn f(n-1) + f(n-2);
}

18、跟踪调试宏

:::info

  1. 当前代码行的行号:(表示为十进制整型常量)
    1. LINE
  2. 当前代码的文件名(全路径):(表示字符串型常量)
    1. FILE
  3. 当前代码所在的函数名:(表示字符串常量)
    1. func or FUNCTION
  4. 转换的日历日期,(表示为Mmm dd yyyy 形式的字符串常量)
    1. DATE
  5. 转换的时间,(表示"hh:mm:ss"形式的字符串型常量)
    1. TIME
  6. 指示c++编译器:
    1. __cplusplus
      :::
#include <stdio.h>
int main(void)
{
        printf("%d\n",__LINE__);
        printf("%s\n",__DATE__);
        return 0;
}

19、变长数组

:::info

  1. 概念:
    1. 可以使用变量指定数组的长度,一旦创建了变长数组,它的大小则保持不变
  2. 特点:
    1. 变长数组VLA只能是局部变量数组
    2. 变长数组VLA在定义的时候不能初始化
    3. 变长数组VLA必须是自动存储类别,即不能使用extern或static存储类别说明符
    4. 数组的长度在变量的整个生命周期中是不可变的(不等于动态数组)
  3. 注意:
    1. 使用const变量定义的数组,就不能指定数组的长度,也就不再是变长数组了
    2. 变长数组占用的内存空间会被自动释放,不需要使用free()
      :::
#include <stdio.h>

int main(void)
{
	int a=0;
	int b=0;
	scanf("%d %d",&a,&b);
	char array[a][b];
	printf("sizeof(array)=%d\n",sizeof(array));
	return 0;
}
//输入4 5 输出20

20、动态数组

:::info

  1. 概念:
    1. 使用动态内存分配malloc来实现动态数组
      1. int* const a = (int*)malloc(sizeof(int)*n);
  2. 与const配合:(只能指向这个获取的空间,完全符合数组的性质)
    1. const修饰符在星号之后,则表示指针在被声明后指向内容可以变,指向地点不能变
  3. 注意:
    1. const可以去掉,但要保证使用时不改变a的指向
    2. 用malloc()创建的数组不局限在一个函数内访问
      :::
#include<stdio.h>
#include<stdlib.h>
#include<memory.h>
int main(void)
{
    int m;
    scanf("%d", &m);
    int* const p = (int*)malloc(m*(sizeof(int)));
    memset(p, 0, m);//初始化,每个元素都为零
    int i;
    for (i=0;i<m; i++)//数组赋值
    {
        p[i] = i;
    }
    for (i = 0; i <m; i++)//打印数组
    {
        printf("%d,", p[i]);
    }
    free(p);
    return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
int main(void)
{
    int m, n;
    scanf("%d %d", &m,&n);
    int **p;
    p = (int**)malloc(m*(sizeof(int*)));//二级指针所在地址申请内存
    int i, j;
    for (i = 0; i<m; i++)
        p[i] = (int*)malloc(sizeof(int)*n);//一级指针所在地址申请内存
    for (i = 0; i < m; i++)
        for (j = 0; j < n; j++)
            p[i][j] = i + j;
    for (i = 0; i < m; i++)
    {
        for (j = 0; j < n; j++)
        {
            printf("%d %p   ", p[i][j], &p[i][j]);     //输出每个元素及地址,每行的列与列之间的地址时连续的,行与行之间的地址不连续  
        }
        printf("\n");
    }
    for (i = 0; i < m; i++) free(p[i]);     
    free(p);
    return 0;
}

21、柔性数组

:::info

  1. 概念:
    1. 零长度数组(柔性数组)应用在变长结构体中
    2. 在一个结构体的最后, 申明一个长度为0的数组, 就可以使得这个结构体是可变长的
  2. 特点:
    1. 长度为0的数组并不占用空间, 因为数组名本身不占空间
    2. 柔性数组只需要用一次malloc创建就可以
    3. sizeof求结构体大小时所求出的大小不包括柔性数组的大小
  3. 用法:
    1. 通过calloc、malloc或 者new等动态分配方式生成,在不需要时要用free()释放相应的空间
  4. 区别:
    1. 普通方法用指针创建数组需要两次malloc,因为malloc申请的内存位置是任意的,所以柔性数组可以减少内存碎片化
    2. 柔性数组申请的内存更加集中,有利于查找使用
      :::
========================柔性数组创建=============================
#include<stdio.h>                      
#include<stdlib.h>//或者是#include<malloc.h>动态内存函数的头文件                    
struct d
{                                                                                    
    int nb;  
    int nn;                                 
    int arr[];                 
};                                               
int main()                      
{
    struct d *p=(struct d*)malloc(sizeof(struct d)+5*sizeof(int));   //一次malloc
=========================普通用指针创建============================
    struct bb
{
    int a;
    int *arr;
};
    int main()
 {
     struct bb* p=(struct bb*)malloc(sizeof(struct bb));  //结构体指针malloc
     p->a=20;
     p->arr=(int*)malloc(5*sizeof(int));				//数组指针malloc
==========================定长数组创建==============================

#define MAX_BUFFER_SIZE 1024
typedef struct
{
      unsigned int buffsize;
      char buff[MAX_BUFFER_SIZE];
}SMsg;
        
SMsg *msg=(SMsg*)malloc(sizeof(SMsg));
msg->buffsize=16;

//用msg->buff来访问数据。
//使用完成后用free(msg);来释放

22、gcc编译器

:::info

  1. gcc xx.c -o xx 过程:
    1. 预处理:
      1. 加载了头文件(将头文件的内容拷贝过来了)
      2. 处理了宏 (宏展开)
      3. 处理了注释 (将所有的注释全部忽略)
      4. 处理了条件编译
        1. 命令:gcc -E xx.c -o xx.i
    2. 编译:
      1. 将 程序编译称为汇编文件(.s)
        1. gcc -S xx.i -o xx.s
        2. 或者 gcc -S xx.c -o xx.s
    3. 汇编:
      1. 将 程序 汇编成为 目标文件(.o)
        1. gcc -c xx.s -o xx.o
        2. 或则 gcc -c xx.c -o xx.o
    4. 链接:
      1. 将 各个 目标文件 链接 成为 可执行文件(a.out)
        1. gcc xx.o yy.o zz.o -o app
        2. 或者 gcc xx.c yy.c zz.c -o app
  2. 查看目标文件和可执行文件:
    1. readelf xx.o -a
    2. readelf app -a
  3. 头文件:<> 和 “”
    1. <> : 可以去到 系统提供的 路径下 寻找 头文件 (/usr/include 等其他路径)#include<stdio.h>
    2. “” : 去到自己提供的路径下寻找,如果没有找到,就去系统提供的路径下寻找
      1. #include “…/…/xx/yy.h”
      2. #include “stdio.h”
        :::
	-E		:  预处理 
	-S		:  编译 
	-c   	:  汇编 
	-o 		:  生成
	-v 		:  查看过程 
	-Wall 	:  Walling 严格编译 
	-On		:  优化程度 
		-O0 :  不优化 (cpu优化机制) 
		-O1 :  
		优化程度越高,编译时间越长,运行越快
	-g 		:  运行进行 gdb调试
	
gcc xx.c yy.c zz.c -o app -Wall -O0 -g 
目标文件: .o  , 用于链接,不能执行
	二进制文件,机器看得懂,你看不懂的
	
file xx.o
xx.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped

LSB	: 低位先行
MSB	:高位先行

stripped:将程序中的符号表的信息剔除掉了,这样子编译出来的可执行文件体积比较小;
not stripped:则反之,但是就是因为其保留了这些信息,所以便于调试。
	
==============
可执行文件 :
file app
app: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=9d53d0299daca1e7bbfaf2032450d7a55efbf628, not stripped
	例子:
		int a = 10; // 在内存中占用4个字节空间,该空间取名为a
		
		int b = a;  // 在内存中占用4个字节空间,该空间取名为b 
					// 将内存中a的值读取放入cpu寄存器中,
					// 将cpu寄存器中的a存入b中
			
		int c = a;	// 在内存中占用4个字节空间,该空间取名为c
					// 将cpu寄存器中的a存入c中
		
// 将内存中a变成了500

		int d = a;	// 在内存中占用4个字节空间,该空间取名为d
					// 将cpu寄存器中的a存入d中
					
===== 问题出来了:
	如果我采用非常规方式(硬件)修改了内存中a的值,将内存中a变成了500,d依旧是原来寄存器中的a的10
		
	volatile : 避免cpu优化,每一次取值操作都必须去 内存中 读取

===》 解决方法:
		
	volatile int a = 10;
	int b = a;
	int c = a;
	int d = a; 
			---> 这些a就没有优化,每一次都是去内存中读取的
			
struct xx{
	volatile int a :9;		// 硬件寄存器地址
	volatile int b :4;		// 硬件寄存器地址
	volatile char c : 2;	// 硬件寄存器地址
};

23、gdb调试器

:::info

  1. 进入gdb调试:
    1. gdb app
    • l : list 显示程序
    • r : run 运行
    • q : quit 退出
    • start : 启动
    • n : next 下一步 : 碰到函数,直接执行完函数(不进去)
    • s : step 下一步 : 碰到函数,进入函数里面去了
    • p : printf 打印某个变量的值
      • p a —> 打印a的值
    • b : breakpoint
      • b 23 —> 给23行打个断点
      • i b : info breakpoint 显示所有的断点
    • d num :删除断点
      • d 2 // 删除num为2的断点
    • c : continue
      :::
(gdb) break (b)	在源代码指定的某一行设置断点,其中xxx用于指定具体打断点位置
(gdb) run (r)	执行被调试的程序,其会自动在第一个断点处暂停执行。
(gdb) continue (c)	当程序在某一断点处停止后,用该指令可以继续执行,直至遇到断点或者程序结束。
(gdb) next (n)	令程序一行代码一行代码的执行。
(gdb) step(s)	如果有调用函数,进入调用的函数内部;否则,和 next 命令的功能一样。
(gdb) until (u)
(gdb) until (u) location	当你厌倦了在一个循环体内单步跟踪时,单纯使用 until 命令,可以运行程序直到退出循环体。
until n 命令中,n 为某一行代码的行号,该命令会使程序运行至第 n 行代码处停止。
(gdb) print (p)	打印指定变量的值,其中 xxx 指的就是某一变量名。
(gdb) list (l)	显示源程序代码的内容,包括各行代码所在的行号。
(gdb) finish(fi)	结束当前正在执行的函数,并在跳出函数后暂停程序的执行。
(gdb) returnreturn)	结束当前调用函数并返回指定值,到上一层函数调用处停止程序执行。
(gdb) jump(j)	使程序从当前要执行的代码处,直接跳转到指定位置处继续执行后续的代码。
(gdb) quit (q)	终止调试。

24、Makefile文件管理工具

:::info

  1. 特点:
    1. 时间戳管理机制,修改过的程序才编译,未修改的不编译
    2. 当生成的目标的时间 比 依赖的 时间 新 ,那么就不在编译
  2. 使用:
    1. make [标签]
      1. 去到一个 叫做 Makefile 的文件中 找寻 标签目标
      2. 如果没有标签,去到Makefile中找寻第一个目标
  3. Makefile书写:
    1. 目标 :依赖
      1. [2tab] 命令
  4. 依赖不存在:
    1. 系统自动生成 : 系统检测到你需要 .o 文件,自动去寻找.c 生成.o
    2. 手动生成
      :::
      Makefile使用```c
      CC = gcc
      OBJ = obj
      APP = app
      FLAGS = -Wall -O0 -g
      export CC OBJ APP FLAGS

DIRS = lcl lsy main $(OBJ)
.PHONY : all clear $(DIRS)

all : $(DIRS)
$(DIRS):
make -C $@

clear :
rm -rf ./ ( O B J ) / ∗ . o r m − r f . / (OBJ)/*.o rm -rf ./ (OBJ)/.ormrf./(APP)

```c
../$(OBJ)/lcl.o : lcl.c lcl.h
                $(CC) -c $< -o $@ $(FLAGS)
../$(OBJ)/main.o : main.c
                $(CC) -c $^ -o $@ $(FLAGS)
../$(APP) : lcl.o lsy.o main.o
                $(CC) $^ -o $@ $(FLAGS)
赋值操作:
	* 简单赋值
		:=  
	* 递归赋值 : 递归寻找 其中 使用的变量最后一次的值  
		=   
	* 条件赋值  : 如果 定义的该变量 事先 已经有了,该语句无效 
		?=	
	* 追加赋值
		+= 

如何使用变量:  $(变量)

如果不想执行过程 将 命令 显示出来 
	@echo "x : $(x)"  
	
=====================================
$@  :  目标 
$^  :  所有的依赖 
$<  :  第一个依赖

=====================================
make -C  文件夹 [目标] //  去执行 文件夹 里面的 Makefile 的 [目标] 

======================================
伪目标:
	不需要生成的目标 ,如果调用该目标,都是去执行该目录的 命令 
	
.PHONY :  伪目标    // 声明了  伪目标 

================================
共享 变量 :
	export 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值