进制转换:
1、为什么使用二进制、八进制、十六进制?
因,因此就把二进制数据转换成八进制,方便记录到文档中
随着CPU的位数的不断增加,已经到目前的64位,所以八进制不再能够满足需求,因此发展出现在的为目前的CPU只能识别高低两种电平,只能对二进制数据进行计算
二进制虽然能够直接被计算机识别,但是不方便人去书写和记录十六进制,由于历史原因八进制还不能退出历史舞台
2、十进制转二进制(其它进制)
求余法:
用2不停地对数据求余,然后继续对商求余,直到商为0结束,在过程中得到的余数(逆序)就是该数据的二进制
127 % 2 1
63 % 2 1
31 % 2 1
15 % 2 1
7 % 2 1
3 % 2 1
1 % 2 1
0
0111 1111
求权法:
让数据从高位n位开始,数据 - 2^(n-1) ,如果够减,那么第n位为1,否则为0,直到减完为止
134
128 64 32 16 8 4 2 1
1 0 0 0 0 1 1 0
手算:73 62 121 49
3、二进制转十进制
每位乘以权位2^(n-1),求和
10011001 128+16+8+1
4、二进制转八进制
从低位起,每三个二进制位对应一个八进制位
二进制:10 101 110 111 100
八进制:2 5 6 7 4
5、二进制转十六进制
从低位起,每四个二进制位对应一个十六进制位
二进制: 10 1011 1011 1100
十六进制: 2 B B C
6、不同进制在程序的显示:
在C代码中,以0开头的数据是八进制数据,以0x/0X开头的是十六进制数据
%x 让数据以十六进制显示
%o 让数据以八进制显示
%#x %#o 显示出数据对应的进制前缀
7、原码、反码、补码
原码:数据的二进制
反码:
正数的原码就是反码
负数的反码是:它的原码符号位不变,其它位按位求反
补码:
所有数据在计算机中,都是以补码形式存储
正数的原码就是补码
负数的补码:
1、转换成二进制,得到原码
2、原码符号位不变,其余按位求反,得到反码
3、反码+1,得到补码
-127
1111 1111 原码
1000 0000 反码
1000 0001 补码
8、补码如何转成数据:
先确定是有符号还是无符号
1、无符号\有符号且最高位为0,补码直接转成十进制
2、有符号的且最高位为1
a、补码-1,得到反码
b、符号位不变,其余位按位求反,得到原码
c、原码转十进制数
1111 1111 补码
1111 1110 反码
1000 0001 原码
-1
127+1 = -128
0111 1111 + 1
1000 0000 -128
for(uint8_t i=0; i<256; i++)
{
printf("a");
}
位运算符:
& | ~ ^ << >>
A & B 按位相与
1010 1110 A 0xAE
0111 1100 B 0x7C
--------------
0010 1100 C 0x2C
A | B 按位相或
1010 1110 A 0xAE
0111 1100 B 0x7C
--------------
1111 1110 C
~A 按位求反
1010 1110 A 0xAE
-----------
0101 0001
A ^ B 按位异或 相同为零,相异为一
1010 1110 A 0xAE
0111 1100 B 0x7C
--------------
1101 0010
A << n 按位左移n位,左边超出的丢弃,右边补0
1010 1110 A << 3
0111 0000
A >> n 按位右移n位,右边超出的丢弃,左边补符号位
1010 1110 A >> 3
1111 0101
注意:只要式子中出现了位运算符,必须转换成二进制补码再进行运算
函数:Function
一段具有某一项功能的代码集合,是C语言管理代码的最小单位
把代码封装成一个个函数,方便管理和调用代码
1、函数分类:
标准库函数:
C语言标准委员会以函数形式提供的一些基础功能,都被封装在libc.so库中,并且分在了不同的文件中,需要使用时只要把对应的头文件导入即可(例如stdio.h...),然后通过具体的 函数名(参数) 即可完成调用
#include <time.h>
time_t time(time_t *tloc);
功能:获取自1970-1-1 0:0:0 到调用时总共过了的秒数
用法:time_t sec = time(NULL);
#include <stdlib.h>
int rand(void);
功能:获取一个随机数
注意:目前任何编程语言和系统都没有真正的随机数,C编译器是把从0~极大值范围的数值打乱后,存储到一块固定内存中,然后从里面取所谓的随机数
void srand(unsigned int seed);
功能:种随机种子,设置从随机数内存的某个位置开始取随机数,为了实现类似真随机的效果,seed位置一般使用time(NULL)来设置
int system(const char *command);
功能:执行系统命令command
例如:system("clear");
系统函数:
是操作系统以函数形式提供的一些功能接口
但是系统函数不是真正的函数
第三方库函数:
一些开源或收费的第三方代码
GitHub
md5
JSON 序列化和反序列化
100 "100" "100" 100
glog 谷歌日志系统
XML 配置文件解析程序
自定义函数:
为了更好地管理代码,减少代码冗余,把代码封装成自定义函数
函数声明:
函数声明的目的为了告诉其他的调用者,该函数的调用格式
返回值类型 函数名(形参类型1 形参名,形参类型2 形参名,...);
1、C语言中函数名一般全部小写,可以用下划线分隔
2、如果不需要参数时,建议写void,不要空着
3、如果没有返回值,就写void
函数定义:
函数的具体实现
返回值类型 函数名(形参类型1 形参名,形参类型2 形参名,...)
{
// 函数体
return [val];
}
函数调用:
函数名(实参1,实参2);
注意:返回值会放在调用函数语句这里,应该用变量接收或者直接显示,否则再也拿不到
使用函数需要注意的问题:
函数的隐式声明:
在函数调用前没有任何该函数的声明或定义,那么就会产生隐式声明
要避免产生隐式声明,那么就需要在函数调用前有函数声明或函数定义
注意:如果在函数调用前完成了函数的定义,那么函数声明可以省略
函数传参
1、函数中定义的变量属于该函数,出了该函数就不能再被别的函数直接使用
2、实参与形参之间是以赋值的方式进行传递数据的,并且是单向值传递
3、return语句其实是把返回值数据放入公共区域内存中(调用者和被调用者都可以访问),调用者会从该区域获取返回值;如果不写return语句,该区域会是一个随机的垃圾数据,调用者也能拿到返回值但是无意义。
4、数组作为函数的参数传递时,数组的长度会丢失,需要额外增加一个变量把数组的长度传递过去
void func(int arr[],int len);
int arr[10];
func(arr,10)
5、数组作为参数传递时,是"址传递",相当于调用者与函数共享数组
设计函数的准则:
1、一般一个函数最好不要超过50行,确保一个函数只负责完成一项功能,降低出错概率,提高可读性
2、数据一般要由调用者提供,只把结果返回给调用者,确保函数的通用性
3、考虑调用者提供的非法数据,可以先判断后使用,也可以通过注释或说明来写明情况,提高函数的健壮性
进程映像:
程序:存储在磁盘上的可执行文件(二进制文件、脚本文件)
进程:正在系统中运行的程序
进程映像:进程的内存分布情况
text 代码段:(代码段+只读段)
存储的是二进制指令、常量,权限是只读,如果强制修改会产生段错误
data 数据段:
初始化的全局变量、初始化过的静态局部变量 (初始化非0值)
bss 静态数据段:
未初始化的全局变量、未初始化的静态局部变量
在该段内存中的数据在程序开始前会自动清理为0
stack 栈:
局部变量和块变量,会随着程序的运行不断地申请、释放,由操作系统管理,使用方便,内存小
heap 堆:
该段内存由程序员手动管理,使用麻烦,足够大
局部变量和全局变量:
全局变量:定义在函数外的变量
存储位置:data(初始化) 或者 bss(未初始化)
生命周期:程序开始到程序结束
使用范围:程序的任意位置都可以使用
局部变量:定义在函数内的变量
存储位置:stack 栈内存
生命周期:从函数开始到函数结束
使用范围:只能在该函数内使用
块变量:定义在if/for/while等语句块内的变量
存储位置:stack 栈内存
生命周期:从语句块开始到语句块结束
使用范围:只能在语句内使用
注意:同名的局部变量会屏蔽同名的全局变量
同名的块变量会屏蔽同名的全局、局部变量
因此建议全局变量首字母大写,局部变量全部小写
类型限定符:
auto
用于定义自动申请、自动释放的变量(局部变量),不加就代表加了
注意:在C11语法标准中用于自动类型识别
auto num = 10; //int
auto num = 3.13;//double
注意:不用用它修饰全局变量
extern
用于声明外部变量,意思是告诉编译器此变量在程序的其他地方已经定义了,先让程序通过编译,如果在链接时找不到该变量依然会报错
不建议在extern时赋值,它只是声明
static
改变存储位置:
改变局部变量的存储位置,由stack改为data(初始化)或者bss(未初始化)
延长生命周期:
延长局部变量的生命周期,直到程序结束才释放
限制作用范围:
限制全局变量的使用范围,限制只能在本文件内使用
注意:使用static修饰全局变量,可以防止该变量被别的文件使用,以及防止命名冲突
const
"保护"变量的值不被显式地修改
注意:如果通过内存进行修改,还是可以改的
注意:使用const修饰data段数据,那么该数据会存储到text段中,如果强制修改会段错误
volatile
C编译器会对普通变量的取值进行"取值优化",只要在使用变量过程中该变量没有显式改变,那么编译器会直接使用上一次的结果,而不会每次都去内存读取数据
加上volatile修饰,让编译器不要对该变量进行"取值优化"
一般在驱动编程、硬件编程、多线程编程时使用
volatile int num = 10;
if(num == num)
{
// 可能为假
}
register
存储介质:
硬盘->内存->高级缓存->寄存器->CPU
申请把变量的存储介质由内存改为寄存器,但是由于寄存器数量有限,不一定百分百成功
注意:寄存器变量不能取地址
typedef
类型重定义
在定义变量前,加上typedef,那么原本的变量名就变成了这种数据类型,可以像数据类型一样定义变量
typedef int num;
num n1;