C语言测试第一轮【错题涉及知识点】 内核原理{ 原码与反码{ 2^8 = [-128,-127,-126...0...+125,+126,+127][-128,-127,-126...0...+125,+126,+127] 补码 = [10000001,11111111~00000000,01111111][10000001,11111111~00000000,01111111] //如果溢出赋值了+128 --> 128%127 == 1 --> -128 (从左向右第一个) //如果溢出赋值了-129 --> -129%-128 == -1 --> 127 (从右向左第一个) //-128的补码 == 1000 0000 == 负数最小开头 //-127的补码 == 1000 0001 == 负数最小开头+1 //-126的补码 == 1000 0010 == 负数最小开头+2 // -1的补码 == 1111 1111 == 负数倒数第一个 // +0的补码 == 0000 0000 == 正数最小开头 // +1的补码 == 0000 0001 == 正数最小开头+1 // +2的补码 == 0000 0010 == 正数最小开头+2 //知道取值范围,能直接写出补码,补码没有-0,-0就是-128,只有+0 //补码解决了符号位不能参与运算的问题,解决了-0与+0的问题 //补码 --> 原码(补码求补码) //用户提供原码 --> 补码计算 --> 原码交给用户 //1个字节 --> 2^7=128 --> [-128,127] //2个字节 --> 2^15=32768 --> [-32768,32767] //4个字节 --> 2^31=2147483648 --> [-2147483648,2147483647] 例题:-1+2=1 -1 = 11111111(补码) +2 = 00000010(补码) 结果:00000001(补码)--> 00000001(原码) 例题:-2+1=-1 -2 = 11111110 (补码) +1 = 00000001 (补码) 结果:11111111 (补码)-->10000001 (原码) } 内存分配{ 16位平台{ char 1个字节8位 short 2个字节16位 int 2个字节16位 //* long 4个字节32位 地址/指针 2个字节16位 } 32位平台{ char 1个字节8位 short 2个字节16位 int 4个字节32位 long 4个字节32位 long long 8个字节64位 地址/指针 4个字节32位 } 64位平台{ char 1个字节 short 2个字节 int 4个字节 //* long 8个字节 long long 8个字节 指针/地址 8个字节 //* } 内存分区{ 栈区{ “函数参数”,“函数内的局部变量” //栈空间默认初始化为随机值 //栈区的地址分配是从内存的高地址开始向地地址分配 //用户变量存放在栈区,内容存放在其他区,所以才需要指针指向目标 例题{ int i=0x22222222; char szTest[]=0x61616161; func(I, szTest); 请问刚进入func函数时,参数在栈中的形式可能为 (左侧为地址,右侧为数据) 0x0013FCF0 0x0013FCF8 //函数入口地址 0x0013FCF4 0x22222222 //先放栈顶,从高地址-->低地址放 0x0013FCF8 0x61616161 //函数参数从右向左入栈,从左向右出栈 } } 堆区{ 又称树,由程序员分配和释放,若程序员忘记释放,进程结束时,自动销毁 //程序结束时由操作系统回收 //堆空间默认初始值为0 } bss段{ “未初始化的全局变量”,“未初始化的静态变量” BSS是Block Started by Symbol的简称,BSS段属于静态内存分配, 静态变量仅在第一次创建时初始化一次,之后自动跳过初始化语句,系统将自动将其值设置为0 动态内存分配是在程序运行时分配内存,静态内存在装入时分配内存 } 数据段{ 用户初始化过的bss段就数据段 } 代码段{ “函数体的二进制代码”,属于只读,不可更改,字符串常量就放在代码段 } } 写入内存与读取内存{ 大端与小端的存放与读取数据{ //数据都是从低地址向高地址存放,从高字节的一头读出数据,不是从必须从高地址读 //一般计算机都是小端模式,从低字节放,从右向左放,从左向右取,是属于栈操作; 大端 = 先放高字节,先放前面 0x1234 --> |0x12(低地址)|0x34(低地址+1)| 小端 = 先放低字节,先放后面 0x1234 --> |0x34(低地址)|0x12(低地址+1)| } } } } 数组{ 二维数组{ int a[3][3] --> 0 1 2 3 4 5 6 7 8 //二维数组类似与一栋楼,a[3][3]表示三层楼,每层楼三间房1,2,3,a表示楼下入口 //“+”表示找到,“*”表示进入,“[]”==“+”+“*” //必须先进入楼层再进入房间==先取行内容,再取列内容 //1个*表示进入楼层走廊,2个**表示进入房间,一个[]表示进入楼层走廊,2个[][]表示进入房间 //a[1][1] ,找到2楼,进入2楼,找到2号间,进入2号间 //*(*(a+1)+1) ,找到2楼,进入2楼,找到2号间,进入2号间 //a[1]+1 ,找到2楼,进入2楼,找到2号间 //a[1] ,找到2楼,进入2楼,找到0号间 //a[1]+0 ,找到2楼,进入2楼,找到0号间 //*(a+1) ,找到2楼,进入2楼 //*((int*)(&a+1)-1) ,&a+1根据a整栋楼的地址,找到a旁边一栋楼的地址, 再返回减1一个房间的地址等于a的最后一个房间地址 } } 运算 { 优先级{ } ++{ ++只能作用于变量,数字或表达式都是错(有符号的都叫表达式例如1+1,a++) i++:先自增,再返回自增之前的值 ++i:先自增,再返回自增之后的值 题{ a++b (x) a+++b == (a++)+b == a+(++b) (√) a++++b (x) i=1;(++i)+(++i)+(i++)+(i++) = 2+3+3+4; //不同编译器对++运算的规定不一样,不同编译器结果不一样,实际最好不用++; char a[] = “hello” ;a++; -->地址常量不能自加 } } 表达式{ 后缀表达式{ a - (b * c + d) / e = bc* --> bc*d+ --> bc*d+e/ --> abc*d+e/- a+b*c-(d+e)/f = --> de+ --> de+f/ =abc*+def/- --> bc* --> abc*+ } 逗号表达式{ (a=2,b=3,b++,a+b) 《==》 a=2;b=3;b++;a+b; //最后值为a+b //逗号表达式中间必须有表达式 } } sizeof{ //sizeof属于运算符 //sizeof(void)提示“不允许使用不完整的类型”,报错 //sizeof中的任何运算都不会执行,比如sizeof(++i); ++i是不会执行的 //sizeof()可以不统计到\0,如果有要算上\0; //strlen()直到统计到\0为止,但是不算上\0 例1{ int getSize(int data[]) { return sizeof(data); } int main(){//64位机子 int data1[] = {1,2,3,4,5}; int* data2 = data1; int size1 = sizeof(data1); //20字节 int size2 = sizeof(data2); //8字节 int size3 = getSize(data1);//8字节 } 强制转换{ 无符号号整数和有符号整数相加,有符号整数转化为无符号整数 float型转换为int型,小数点后的数位丢失 int型转换为float型,整数位可能丢失,浮点表示的整数范围比int小 2字节强转成1字节只是取最第低字节 11111111 00000000 --> 00000000 比如(bool*)(int*)1024 --> 10000000000 --> 自取最后第一个位==0 } 标识符{ 标识符 = 标识符开头(“字母”,”_“,"") + 标识符中间(“字母”,”_“,"")+标识符中间(“字母”,”“,"","数字") //只是开头不能用数字,其他一模一样 } 位运算{ ^ 异或 = 加;C中没有同或 %要求两边都是整数 关系运算符具有左结合性,(’a'<c<=’z’)先求’a'<c的值,即为0(假),再求0<=’z’的值,即为1(真) 逻辑运算符两侧运算对象的数据类型可以是任何类型的数据 } 逻辑运算{ 0&&n++; //n++不会执行 1||n++; //n++不会执行 } 赋值运算{ i*=j+8 <==> i = i*(j+8) } 运算优先级{//括号 > 单目运算 > 算数运算 > 箭头运算 > 位运算> 逻辑运算 > 赋值运算 //从左向右依次运算 } } } 函数{ 函数传递参数{ C语言中实参和形参都是值复制传递,只能传递“数据的值”与“地址的值”,指针是不能传递的 各自使用独立的存储单元,两者井水不犯河水,所以实参和虚参各占一个独立的存储单元 子函数自己定义的栈空间地址返回给调用函数,地址内容会发生变化,不能得到想要的值! } } 库函数{ 函数{ 例题1{ int fun(int i = 1, int j = 2) //函数在定义时,可以默认有初值 { return (i + j); //则fun(3)的值为:3+2=5 //则fun()的值为:1+2=3 } } 例题2{ f(printf("a"),printf("b"),printf("c"));-->cba//参数从右边执行从右边入栈 printf("%c\n",a,b); -->a //从左向右出栈 } } scanf{ %s得到一个连续的字符串,遇到空格、回车会自动终止 scanf将所有读到的内容送入缓冲区队列,再根据所需出队列,赋值给地址 scanf("%d%c",&a,&b); //键盘输入10A,将”10A“存放在缓冲队列,按照需求出队数字10给a地址,出队A给b地址 } fork{ 父子进程都是用的虚拟地址空间。num地址的值相同,都是一样的逻辑地址 例题1{ int main(void) { fork(); printf("hello\n"); fork(); printf("hello\n"); } -->打印6个hello //第一次fork,一共两个进程,打印两次 //第二次fork,一共四个进程,打印四次 //所以一共6个 } } main{ argc:程序名和参数个数之和 argv[0]记录的是程序名,系统自动加载进去的,后面的argv[i]记录的输入参数 } printf{ %# //带符号输出 %0 //补0 %5 //至少输出5位 %.2 //浮点数小数点后四舍五入输出2位,只输出2个字符 //%5.2,只输出2个字符其余三个用空格,右对齐 %- //左对齐 %+ //默认右对齐 %* //跳过不读 %e //“实数e整数”或“实数E整数”,e或E前后的实数和整数(可以带符号:e+4)都"不能省略", 小数点前后的数字都可以不写 %l //long %f //double } malloc{ malloc和free申请和释放的虚拟堆内存。并不是物理内存。 malloc函数是动态内存分配,属于执行阶段进行内存分配,地址重定位 } strcat{ strcat(p1+2,p2+1); //将p2+1开始的字符串添加到p1+2字符串的末尾(覆盖p1+2后的\0"),返回以p1+2为首的字符串,即"cdBCD" } strcpy{ strcpy(str+2,strcat(p1+2,p2+1)); //将"cdBCD"copy到str+2位置上,并覆盖后面内容,返回str地址 } fopen{ //r读,w写,b二进制文件,a追加,t文本文件,+读写 //只有w,w+,每次会清空 //只有r,r+必须要求文件存在 //只有a,a+会保留文件原先的内容 r 以只读方式打开文件,该文件必须存在。 r+ 以读/写方式打开文件,该文件必须存在。 rb+ 以读/写方式打开一个二进制文件,只允许读/写数据。 rt+ 以读/写方式打开一个文本文件,允许读和写。 w 打开只写文件,若文件存在则长度清为 0,即该文件内容消失,若不存在则创建该文件。 w+ 打开可读/写文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件。 a 以附加的方式打开只写文件。写入的数据会被加到文件尾,即文件原先的内容会被保留 a+ 以附加方式打开可读/写的文件。写入的数据会被加到文件尾后,即文件原先的内容会被保留 wb 以只写方式打开或新建一个二进制文件,只允许写数据。 wb+ 以读/写方式打开或建立一个二进制文件,允许读和写。 wt+ 以读/写方式打开或建立一个文本文件,允许读写。 at+ 以读/写方式打开一个文本文件,允许读或在文本末追加数据。 ab+ 以读/写方式打开一个二进制文件,允许读或在文件末追加数据。 } gets{ gets是得到一整行的字符串;//从输入缓冲区全部赋值给用户 } getchar{ 得到一个字符; } strlen{ //strlen()直到统计到\0为止,但是不算上\0 b[] = {'x', 'y', 'z'}; strlen(b); --> b没有'\0',strlen一直向后搜索,结果大于等于3! } } 结构体{ 结构体{ 结构体变量之间的copy赋值直接”=“ char data[0]柔性数组,它只能放在结构体末尾,是申明一个长度为0的数组,支持对于数组data的越界访问 可以使得这个结构体是可变长的,这是在C99才加入标准的,可以构造出变成结构体,如缓冲区,数据包等等 struct {int a; double b; char c;} A; sizeof(A)的返回值是3×8=24 } union{ } enum{ } } 数据类型{ register{ 寄存器变量,该变量值在寄存器中存放 } 字符{ 数字赋值给char类型时不用加单引号 ‘%%’ = %
} } 编译{ 预处理{//粗暴的替换 #include<> //只从标准库文件目录下搜索 #include"" //首先从用户工作目录下开始搜索,然后搜索整个磁盘。 #define INT_PTR int* --> INT_PTR a,b; --> int*a,b //b是整形 typedef int*int_ptr; --> int_ptr c,d; --> int*c,int*d #define N 3 #define Y(n)((N+1)*n) z=2*(N+Y(5+1)) == 2×(3+((3+1)×5+1))== 28 //宏定义时,必须对参数用()进行包起来的原因,控制计算优先级,而不是傻乎乎的替换 } 编译{ char相当于signed char或者unsigned char,但是这取决于编译器! } } 指针{ 没有初始化的野指针不能参与运算 指针为NULL,没有指向任何变量,不能对该指针进行操作 int(*p)[4] //()把*p包起来表示这是一个指针,后面【】表示数组,所以表示一个指针指向数组 int(*P)(void) ;//表示函数指针 int* p(void); //表示函数 } 其他{ 时间复杂度{ 程序运行了n次才能完,所以时间复杂度就是O(n), 比如:for(i=0;i<n;i++) --> O(n) //需要运行n次才结束 for(i=0;i<n*n;i++)--> O(n*n)//需要运行n×n次才结束 } 线程{ ++a和printf不是原子指令,可随时被打断 好东西才会被大家一起享用嘛,所以高速设备一般被设置成共享设备。 } 其他{ x = x & (x – 1); == 求x二进制表示中含有1的个数。 编译错误分为编译错误和链接错误 } }