1、进制之间的转换
:::info
- 其他转十进制,原本的权位相加
- 如:20+21+… 80+81+… 160+161+…
- 十六进制,10a 11b 12c 13d 14e 15f
- 十进制转其他,除以其进制数逆向取余
- 八进制(421) 《=》 二进制(01) 《=》 十六进制(8421)
- 占用0~8个二进制位就是占用1个字节 、占用8~16个二进制位就是占用2个字节
- 转义字符:
- 八进制形式转义: ‘\ddd’ d代表的是 8 进制的方式 ,如:‘\101’ ----> ‘A’
- 十六进制形式转义:‘\xdd’ d代表是十六进制,如:‘\x43’ —> ‘C’
- 正整数:源码=反码=补码
- 源码:00000000 00001111 00010010 00000110 二进制位
- 反码:00000000 00001111 00010010 00000110
- 补码:00000000 00001111 00010010 00000110
- 负整数:
- 源码:10000000 00001011 10001111 01000111 符号位不变
- 反码:11111111 11110100 01110000 10111000 反码=源码取反
- 补码:11111111 11110100 01110000 10111001 补码=反码+1
- 数值是以补码存到数据类型的空间(int a = 10) 移位也是针对补码来操作
- 小数形式十进制转成二进制的方式:
- 第一步:(12.5)
- 整数部分:除以2逆向取余(1100)
- 小数部分:乘以2正向取整(.1)
- 第二步:
- 以整数1的指数形式连起来(1.1001 * 2^3)
- 第三步:存储形式位(1符号位+8指数位+23小数位)指数位:01111111
- 0 10000010 1001000000000…(12.5)
- 1 10000010 1001000000000…(-12.5)
- 第一步:(12.5)
- 小数形式进制转换:
- 小数形式任意进制转十进制的方式:
- 权位相加,小数位的是2-1、2-2…8-1、8-2…16-1、16-2
- 小数形式八进制转成二进制的方式:
- 补零凑对位 421
- 小数形式十六进制转成二进制的方式:
- 补零凑对位 8421
:::
- 补零凑对位 8421
2、数据类型
:::info
- 整数类型分有符号(signed)和无符号(unsigned)
- signed(默认)修饰最高位符号位
- unsigned 修饰最高位数据位
- 小数默认double,整数默认int
- 小于int类型的会自动转成int再运算,不同类型会自动向高精度转换再运算
- 补码溢出照加,只取所占用的字节数(short 取2byte=16bit)
- 浮点型没有反码和补码
- 常量:小数形式(12.5) 指数形式:1e2(1*10^2)
- 二进制的小数形式比较大小时,后面可以补零(0.111 = 0.1110)
- float的存储形式:
- 1个符号位+8个指数位+23个小数位(指数位固定是01111111,代表0)
- double类型的存储形式:
- 1个符号位+11个指数位+52个小数位
- double类型转换成int类型会断尾(12.9 ——> 12)
- char类型最高位都是0,如果当整型使用,要用signed定义
- signed char x = -10;
- unsigned char x = 20;
- 字符串类型双引号里面隐藏一个’\0’字符
- #define PI 3.14 宏定义没有分辟空间,编译阶段被宏展开还原
- 基本类型: (低精度向高精度),既可以自动转换,也可以强制转换
- 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
- %nd:n表示宽度、负号左对齐,正号右对齐
- %m.nf:m表示宽度,n表示小数点后长度
- scanf遇到空格、Tab、回车键都会结束
- getchar:
- getchar只有遇到回车键才会接收,获取回车键是用’\n’接收
- getchar()也能清空缓冲区,清空所有缓冲区把getchar放while循环,while(getchar != ‘\n’){};
- gets():
- 读取一整行的字符串,丢弃’\n’
- puts():
- 显示字符串,会加上换行符’\n’,遇到’\0’才会停止输出
- fgets(str,length,stdin)
- 读取length-1长度的字符串,保留’\n’
- fputs(str,stdout)
- 显示字符串,不会加换行符’\n’
:::
- 显示字符串,不会加换行符’\n’
4、运算符
:::info
- 没给变量赋值不一定没有值,随机赋垃圾值
- 逻辑 与或非:&& || !
- 判断表达式真假时候,遇到&0则为假,遇到||1则为真
- 位运算:
- 位与& 同为1才是1
- 位或| 有1就是1
- 异或^ 不同就是1
- 取反~ 取反后为负数的话,要转成源码,符号位不参加计算
:::
00000011 00100000 ~ 800
======================
源码: 11111100 11011111 ---》
反码: 11111100 11011110 ---》
源码: 10000011 00100001
某个位置(从第0位开始数)左移补0,右移补符号位(除了逻辑算术补1)
置1用位或,运算里补0除了目标位置为1
置0用位与,运算里补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
- for循环(每行控制条件i<=n,每个控制条件j<=i)
- 直三角:
- 根据第i行输出多少个*
- 反直三角:
- 根据总行数n输出n-1空格,再根据第i行输出多少个*
- 等腰三角:
- 根据总行数n输出n-1空格,再根据第i行按奇数输出*的个数
- 倒三角:
- (空格)第一行不输出,第二行开始每行输出一个空格,j<=i-1
- (星号)按顺序输出倒数的奇数行k<=i
- 菱形:
- 正三角(遍历总行数的一半)
- 倒三角(根据第i行输出多少个空格,再按奇数的倒叙输出,n-2*i)
- 直三角:
- 跳出循环:
- break跳出一个循环
- continue跳出一次循环
- 循环思想:
- 交叉符号相加用s = -s
- %10==5 : 找出的是个位数为5的
- /10==5 :找出的是十位数为5的
:::
goto用来跳出多层for循环
标签: ***;
***;
goto 标签;
6、指针
:::info
-
指针在内存中占用的字节数:
- int a定义4个字节空间在内存
- 每个字节对应32位操作系统的4个字节(门牌号)
- 每个字节按小端序存放,最低位作为该空间地址(&a)
- &a地址是int *指针类型,指针p = &a是将a空间地址最低位赋到p的4个字节空间里
- 该*p指针所占8个字节,再把p地址赋给指针pp,则pp为int **类型,**pp也是占8个字节
-
数组指针:
- arr 既是int[10]类型,也是int*类型
- 当arr是数组名:
- &arr,arr没有取值,会把arr当成是数组类型
- &arr+1; 会进入了CPU运算,arr被当成了指针类型,再&取址就是int(*arr)[10]
- 当arr是地址:
- arr+1移动的是4*1个字节
- &arr+1移动的是4*10个字节
- 数组指针和数组的区别:
- int(*arr)[10]数组的指针类型,这里arr是数组的首元素指针,占8个字节
- int arr[10]则是数组类型,arr数组是4*10个字节
- 数组指针:
- 一维数组array[3]指针是 int * ,首元素指针;即 int *p = array,指针移动跨度都为4
- int (*p)[] = &arr; 即数组指针指向的是整个数组
- int (*p)[] = arr; 这样写也是对的,不过取值时要取两次,数组的地址也是数组的首地址
- 二维数组array[3][4]指针是 int (*p)[4];即 int (*p)[4] = array,指针移动跨度都为16
- 多维数组作为返回值:
- int (*fun(void))[5][6]
-
指针地址:
- int*、char*、short*、int**、char**等等都是存放地址的类型,地址都是系统的寻址线组成
- 64位固定为8个字节,32为固定为4个字节
-
指针类型转换:
- 变量a,&a取出地址的类型是int*
- ((short )&a+1) 是先把int类型转成short类型,再+1则只移动2个字节
- 然后从低位往上取剩下的地址
-
指针分别在数组和函数的作用:
- 指针用在数组
- 可以通过移动指针来更改数组元素
- 指针用在函数
- 可以传入指针接收两变量的指针取值运算后得出的结果,而不用把结果返回
- 定义指针最好用基本类型,再取址变指针放到函数里
- 指针用在数组
-
指针数组和二维数组:
- 指针数组:
- char *str[5],40个字节,显示效率高,但不能改变字符串字面量
- 类型都相同int *p = int p[] = int p[100]
- 二维数组:
- char str[5][40],200个字节,可更改字符串,因为它是拷贝的副本
- 类型都相同int (*p)[4][5] = p[][4][5] = p[2][4][5]
- 指针数组:
-
指针用法:
- 左值(指针变量)lvalue可以改变,右值(数组名)rvalue不能改变
- 野指针或者赋0或NULL都不能解引用
-
指针对字符串的含义:
- 一个指向字符串的指针赋给另一个指针时,不会把字符串拷贝给另一个指针,只是让另一个指针也指向该字符串
- 把字符串数组元素往前移动时(str[i]=str[i+1]),会把’\0’也前移了不用截取掉最后的元素
- 字符串只能用数组或者指针定义,并且字符串以’\0’结束,以及还有个0隔开
-
指针在数组和字符串的不同:
- 初始化数组是把字符串拷贝到数组中,初始化指针是把字符串地址拷贝给指针
- 数组的变量名是地址常量,不能改变
- 指针的指针名是变量,可以递增改变(str++)
-
函数指针:
-
函数指针作为函数名:
- int (*fp)(int,int) = fun;
- int ret = fp(3,4);
-
函数名默认就是指针,指针指向该函数
-
函数指针作为函数参数:
- int fun(int (*fp)(int,int),int,int) 该指针指向一个函数,相当于传入一个函数
-
函数指针作为返回值类型:
- int (*fun(char,int))(int int) 返回某个函数的指针,相当于返回某个函数
-
函数指针数组:
-
int (*brr[5])(short)= {fun,fun2,fun3}; //保存的是函数指针
-
const对指针的限制:
-
const int a,相当于把a变成常量
-
const int *p,修饰int *则不能通过解引用更改值,是指向常量的指针
-
int * const p,修饰指针变量p则不能再用p指向其他
-
const int * const p,同时修饰的话只能通过该指向的变量更改值
:::
指针与数组之间的关系:::info -
元素指针:
- short arr[10];
- short *p = arr; // p 指向该数组的 第一个元素 short
-
数组指针:
- short brr[2][3];
- short (*p)[3] = brr; // p 指向 short [3]
:::
指针与指针之间的关系:::info
-
一级指针:
- int a = 10;
- int *p = &a; // p 指向 a空间
-
二级指针:
- int **pp = &p; // pp 指向 一级指针变量p
-
三级指针:
- int ***ppp = &pp; // ppp 指向 二级指针变量pp
:::
指针与函数之间的关系:::info
- int ***ppp = &pp; // ppp 指向 二级指针变量pp
-
函数:
- short fun(short s,int c){}
-
fun的类型:
- short (*)(short,int)
- short (*p)(short,int) = fun; //函数指针p
- p(10,20); // fun(10,20);
-
回调函数,一个函数作为另一个函数的参数
- fun(short (*b)(short,int)){}
- fun(a);
:::
指针与数组之间的关系:::info
-
一维数组:
- 函数实参:
- int arr[10]; // arr : int *
- fun(arr);
- 一维数组作为函数形参:
- fun(int *p)
- fun(int p[])
- fun(int p[10])
- 函数实参:
-
多维数组:
- 函数实参:
- int arr[2][3][4]; // int (*)[3][4]
- int arr[][3][4] ={{},{}};
- fun(arr);
- 多维数组作为函数形参:
- fun(int (*p)[3][4])
- fun(int p[][3][4])
- fun(int p[2][3][4])
- 函数实参:
-
一维数组作为返回值:
- int * fun(void) {
- int *arr = (int *)malloc(20); // static int arr[10];
- return arr; }
-
多维数组作为返回值:
- int (*fun2(void))[5][6] {
- static int arr[4][5][6];
- return arr; }
:::
函数与指针之间的关系:::info
-
指针作为形参
- int a = 10;
- int *p = &a;
- fun§;
- ==============>
- fun(int *pp)
- fun(int pp[])
- fun(int pp[10])
- =============> 本质都是 int *
-
多级指针作为形参:
- int a = 10;
- int *p = &a;
- int **pp = &p;
- int ***ppp = &pp;
- fun(ppp);
- ============>
- fun(int ***x)
- fun(int **x[])
- fun(int **x[10])
- ====》 本质 :都是 int ***
-
指针作为函数体:
- 多级指针作为返回值:
- int ***fun(void) {
- int a;
- int *p = &a;
- int **pp = &p;
- int ***ppp = &pp;
- return ppp; }
:::
函数与函数之间的关系:::info
- int ***fun(void) {
- 多级指针作为返回值:
-
函数作为形参:====》 回调函数
- void xx(int a){} // xx : void (*)(int)
- fun(xx);
- =======> fun(void (*p)(int)){}
-
函数作为返回值 :
- typedef void (*sighandler_t)(int);
- 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
- 求素数
- 用2到输入值除以2到输入值以内的数,能整除则跳出第二循环,否则会加到与输入值一样
- 所以判断如果第二循环里的数和输入值一样(i==j)输出i则是素数
- 斐波那契数列,1,1,2,3,5,8…
- 利用返回两个递归函数相加:f(n-1)+f(n-2)
- 快速排序:
- 比基准值大的放右边,比基准值小的放左边,递归分两部分进行
- 找出一个基准值(中间下标)
- i下标右移,找出比基准值大的值;j下标左移,找出比基准值小的值(可以等于基准值)
- (i<=j的时候)就把值互换,然后i++,j–
- 当i>j则以上就完成了一次互换循环
- 再将数组分成两部分,left到j为一部分,i到right为另一部分
- 当j>left时调用递归继续对这一部分进行互换,直到j=left;当i<right时同理
- 比基准值大的放右边,比基准值小的放左边,递归分两部分进行
- 冒泡排序:
- 从左往右两两相比,把较大值放右边,每轮到最后的值是最大值
- 外层for循环控制比较的轮数,10个数则9轮
- i要从1开始(为了控制次数),小于length是9轮
- 内层for循环控制比较的次数,j下标则从0开始,小于length-i
- 第1轮最后到下标8,是比较9次
:::
- 从左往右两两相比,把较大值放右边,每轮到最后的值是最大值
8、存储类型
:::info
-
存储类型:auto、extern、register、static、typedef
- auto(默认) 声明该局部变量在其作用域里覆盖了与之名称相同的变量(包括全局变量)
- extern(默认) 声明(全局变量、函数)为外部文件共享,多个文件一起编译才起作用
- register 声明为寄存器变量后无法再通过取址符取值
- static 声明为静态的局部变量,会一直保留着变量值,在程序结束后才释放内存
- static修饰全局变量、函数:只允许在当前文件起作用
- static修饰局部变量:初始化一次,存在于.data区一直保留此值,直到程序结束
- typedef 起别名
-
存储类型:
- 局部变量:
- 自动存储类变量
- 在程序运行时期内一直存在
- 静态存储类变量
- 可以在多个文件中使用
- 外部链接的静态存储变量
- 只限于在一个文件中使用
- 内部链接的静态存储变量
- 局部变量:
-
链接属性:
- external(外部链接属性)
- 标识符不管声明多少次、位于几个源文件表示的是同一个实体
- internal (内部链接属性)
- 在一个源文件中的声明都指向同一个实体,不同源文件的声明则是不同的实体
- none (空链接)
- 多个声明都是独立不同的实体
- external(外部链接属性)
-
内存分布:
- 栈区:(读写权限)
- 存放局部变量,由操作系统来进行自动的分配/回收内存空间
- 堆区:(读写权限)
- 手动申请、手动回收,通过malloc函数或者new、delete运算符
- .bss段:(读写权限)
- 存放未初始化的静态局部变量和全局变量
- .data:(读写权限)
- 存放已经初始化的静态局部变量和全局变量
- .rodata段:(只读权限)
- 存放一些常量,如字符串常量
- .text代码段:(只读权限)
- 存放可执行代码
:::
static的用法:::info
- 存放可执行代码
- 栈区:(读写权限)
-
static有三种不同的用法:
- 修饰局部变量:生命周期变长,作用域没有变化
- 被static修饰的局部变量存储位置改变了,局部变量是存储在栈区内,而静态变量则是存储在静态区内,使得局部变量的生命周期变得和整个程序一样长,但作用域并没有变化,并在函数被调用过程中维持其值不变
- 修饰全局变量:与外部隔绝起来
- 全局变量是具有外部链接属性的,而static修饰的全局变量的时候就把这个外部链接属性变成了内部链接属性,只能在文件内部使用
- 修饰函数:与外部隔绝起来
- static修饰函数其实和全局变量差不多,因为函数也具有外部链接属性,被static修饰后就变成内部链接属性了,就只能在该源文件内部使用,将于外部隔绝起来
:::
- static修饰函数其实和全局变量差不多,因为函数也具有外部链接属性,被static修饰后就变成内部链接属性了,就只能在该源文件内部使用,将于外部隔绝起来
- 修饰局部变量:生命周期变长,作用域没有变化
9、申请堆内存
:::info
- 申请动态内存:
- malloc(字节数) 返回void*指针指向在堆里的内存块
- 释放动态内存:
- free(指针) 无垃圾回收机制,要手动释放,与局部变量不同
- 内存泄露:
- 耗完了CPU内存或内存块丢失
- 申请内存的函数:
- malloc(字节数)
- calloc(个数,字节数) 自动初始化全为0
- realloc(ptr,新字节数) 将原内存块内容复制到新开辟的内存块,ptr==NULL时相当于malloc
- int *temp = realloce(ptr,新字节数);
- size 编译生成的文件
:::
内存管理:从低地址到高地址 - 代码段(text)——已确定的区域大小
- 数据段(rodata、data)——只读常量、字符串常量;全局变量、静态局部变量(已初始化的)
- bss段——全局变量、静态局部变量(未初始化的,自动初始化为0或NULL)
- 堆——(和其他区段一样从低地址到高地址存储)
- 动态库(未分配的堆空间)——用户共享
- 栈——(从高地址往低地址存储)
- 1G内核空间——高地址(命令行、环境变量)
高级宏定义:#define - #define SQUARE(x) ((x)*(x)) 求平方
- inline 加在函数前就是内联函数
- #define STR(s) # s 将STR()里的内容变成字符串
- #define STR(x,y) x ## y 将STR()里的内容连接成字符串
- #define SHOWLIST(…) printf(# VA_ARGS) 将SHOWLIST()里printf#后转成字符串
- #define PRINT(format,…) printf(# format,##VA_ARGS)
清空前n个字节:清空新创建的指针未初始化产生的垃圾值 - bzero(指针,n):
- 清空前n个字节
- menset(指针,int,n):
- 将指针的前n位用int来填充
- int a = 0x11223344; 最低位是44
- int *a = “abcd”; 最低为a
- 将指针的前n位用int来填充
10、结构体
:::info
- 结构体声明:
- struct 标签{ ; ; ;}(变量名)(初始化);
- 结构体初始化:
- struct 标签 变量名={ , , ,};
- 结构体变量赋值:
- .加属性=值
- 属性名:值
- 声明的变量名.属性=值
- 获取结构体属性值:
- 变量名.属性
- 指针->属性
- 结构体存储的字节数:
- 64位系统默认最大8个字节对齐,32位系统默认最大4个字节对齐
- 总大小计算(sizeof(结构体类型)):
- 默认以结构体中最大字节的类型对齐
- 自定义结构体的对齐方式:
- #pragma pack(n) n字节必须为2的n次幂 (注意:最大不超过系统最大字节数)
:::
- #pragma pack(n) n字节必须为2的n次幂 (注意:最大不超过系统最大字节数)
11、位域
:::info
- 位域:
- 限制变量值占用内存的位数,在声明结构体变量时加:位数
- 位域类型要为整型(char、short、int、unsigned int)
- 空域:
- 即在中间插入(同类型,无变量名,位数0)
- int : 0; 对齐空间剩余部分全部补0
- 无名域:
- 即在中间插入(同类型,无变量名,位数) unsigned:3; 按位数隔开两个位域
- 位域存储:
- 同类型两个变量值的位宽之和没超出该类型的字节数,则会紧挨着存储,即会压缩内存
- 位宽之和超出了该类型字节数则补0按最大类型对齐
- 不同类型的两个变量值位域则要看编译器是否采用压缩方式
- 如果两个位域字段中间插入非位域类型则不会压缩
:::
12、共用体
:::info
- 共用体:
- 所有的成员共用一块空间
- 占用内存空间计算:
- 取占用空间最大的那个作为空间字节数
- 该空间的字节数是每个成员类型的整数倍
- 作用:
- 成员变量相互影响
- 修改某一个成员需要去影响其他成员
:::
13、枚举
:::info
- 枚举格式:
- enum <枚举标签名字>{枚举常量,枚举常量};
- 下标默认按从0开始
- 作用:
- 限制取值范围
:::
- 限制取值范围
14、条件编译
:::info
- 条件编译只在编译过程中执行:
- #if 确定的值 #endif // 确定的值: 0 非0
- #if 值 #else #endif
- #if 值 #elif 值 #endif
- #ifdef 宏 #endif // 如果宏存在,就编译
- #ifndef 宏 #endif // 如果宏不存在,就编译
- #if defined(宏) && defined(宏) #endif // 如果多个宏同时存在,就编译
- #if defined(宏) || defined(宏) #endif // 只要有宏存在,就编译
:::
15、函数调用过程
:::info
- 九步函数调用过程:
- 保存返回地址
- 程序流程转移到被调函数的地址处
- 保存调用方的栈底
- 切换到被调用函数的栈底
- 为局部变量分配空间
- 开始执行函数体代码
- 释放分配的局部变量空间
- 恢复调用方的栈底
- 返回保存地址
:::
16、变参函数
:::info
- void va_start(va_list ap, last);
- 使用该函数 ap 指向 第一个参数 last
- type va_arg(va_list ap, type);
- ap 往后移动一个,获取 其 数据
- type 获取的数据类型
- 返回该数据
- void va_end(va_list ap);
- 完成清理工作
:::
- 完成清理工作
/*【第一步】:定义一个使用省略号的函数原型 */
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
- 三步走:
- 明确函数的作用
- 找出递归结束的条件
- 找出递归的等价关系式
:::
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
- 当前代码行的行号:(表示为十进制整型常量)
- LINE
- 当前代码的文件名(全路径):(表示字符串型常量)
- FILE
- 当前代码所在的函数名:(表示字符串常量)
- func or FUNCTION
- 转换的日历日期,(表示为Mmm dd yyyy 形式的字符串常量)
- DATE
- 转换的时间,(表示"hh:mm:ss"形式的字符串型常量)
- TIME
- 指示c++编译器:
- __cplusplus
:::
- __cplusplus
#include <stdio.h>
int main(void)
{
printf("%d\n",__LINE__);
printf("%s\n",__DATE__);
return 0;
}
19、变长数组
:::info
- 概念:
- 可以使用变量指定数组的长度,一旦创建了变长数组,它的大小则保持不变
- 特点:
- 变长数组VLA只能是局部变量数组
- 变长数组VLA在定义的时候不能初始化
- 变长数组VLA必须是自动存储类别,即不能使用extern或static存储类别说明符
- 数组的长度在变量的整个生命周期中是不可变的(不等于动态数组)
- 注意:
- 使用const变量定义的数组,就不能指定数组的长度,也就不再是变长数组了
- 变长数组占用的内存空间会被自动释放,不需要使用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
- 概念:
- 使用动态内存分配malloc来实现动态数组
- int* const a = (int*)malloc(sizeof(int)*n);
- 使用动态内存分配malloc来实现动态数组
- 与const配合:(只能指向这个获取的空间,完全符合数组的性质)
- const修饰符在星号之后,则表示指针在被声明后指向内容可以变,指向地点不能变
- 注意:
- const可以去掉,但要保证使用时不改变a的指向
- 用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
- 概念:
- 零长度数组(柔性数组)应用在变长结构体中
- 在一个结构体的最后, 申明一个长度为0的数组, 就可以使得这个结构体是可变长的
- 特点:
- 长度为0的数组并不占用空间, 因为数组名本身不占空间
- 柔性数组只需要用一次malloc创建就可以
- sizeof求结构体大小时所求出的大小不包括柔性数组的大小
- 用法:
- 通过calloc、malloc或 者new等动态分配方式生成,在不需要时要用free()释放相应的空间
- 区别:
- 普通方法用指针创建数组需要两次malloc,因为malloc申请的内存位置是任意的,所以柔性数组可以减少内存碎片化
- 柔性数组申请的内存更加集中,有利于查找使用
:::
========================柔性数组创建=============================
#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
- gcc xx.c -o xx 过程:
- 预处理:
- 加载了头文件(将头文件的内容拷贝过来了)
- 处理了宏 (宏展开)
- 处理了注释 (将所有的注释全部忽略)
- 处理了条件编译
- 命令:gcc -E xx.c -o xx.i
- 编译:
- 将 程序编译称为汇编文件(.s)
- gcc -S xx.i -o xx.s
- 或者 gcc -S xx.c -o xx.s
- 将 程序编译称为汇编文件(.s)
- 汇编:
- 将 程序 汇编成为 目标文件(.o)
- gcc -c xx.s -o xx.o
- 或则 gcc -c xx.c -o xx.o
- 将 程序 汇编成为 目标文件(.o)
- 链接:
- 将 各个 目标文件 链接 成为 可执行文件(a.out)
- gcc xx.o yy.o zz.o -o app
- 或者 gcc xx.c yy.c zz.c -o app
- 将 各个 目标文件 链接 成为 可执行文件(a.out)
- 预处理:
- 查看目标文件和可执行文件:
- readelf xx.o -a
- readelf app -a
- 头文件:<> 和 “”
- <> : 可以去到 系统提供的 路径下 寻找 头文件 (/usr/include 等其他路径)#include<stdio.h>
- “” : 去到自己提供的路径下寻找,如果没有找到,就去系统提供的路径下寻找
- #include “…/…/xx/yy.h”
- #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
- 进入gdb调试:
- 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) return(return) 结束当前调用函数并返回指定值,到上一层函数调用处停止程序执行。
(gdb) jump(j) 使程序从当前要执行的代码处,直接跳转到指定位置处继续执行后续的代码。
(gdb) quit (q) 终止调试。
24、Makefile文件管理工具
:::info
- 特点:
- 时间戳管理机制,修改过的程序才编译,未修改的不编译
- 当生成的目标的时间 比 依赖的 时间 新 ,那么就不在编译
- 使用:
- make [标签]
- 去到一个 叫做 Makefile 的文件中 找寻 标签目标
- 如果没有标签,去到Makefile中找寻第一个目标
- make [标签]
- Makefile书写:
- 目标 :依赖
- [2tab] 命令
- 目标 :依赖
- 依赖不存在:
- 系统自动生成 : 系统检测到你需要 .o 文件,自动去寻找.c 生成.o
- 手动生成
:::
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)/∗.orm−rf./(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