- 对于基本数据类型,要掌握:
- 各种数据类型的长度(任何数据类型的指针都是4字节长度,包括对象指针);
C语言中的六种基本的数据类型:short,int,long,char,float,double;
- 记住<char,unsigned char,unsigned int>这些数据类型的取值范围,如果越界,会是什么情况?比如unsigned char 的取值范围是0-255,定义一个char a = 255,对其a+1,结果为0,如果赋值为0,对其a-1,结果为255;
计算机中数据是以补码的方式存储的,当取值越界时,所取值的补码的溢出为将被舍去,并按照余下位来计算:
在char型中:-128 原码1 1000 0000 取反 1 0111 1111 补码 1 1000 0000 即1000 0000;
-129 原码1 1000 0001 取反 1 0111 1110 补码 1 0111 1111 即0111 1111;
故在计算机中-128-1 = 127;
2. 对union,struct的用法要掌握:
1)内存空洞:可能会struct里套union来计算长度,要记住union的长度特点,而且union也遵守字对齐,并且长度等于最长的数据成员长度;
结构体中各成员的起始地址不同,结构体变量在内存中的长度为各成员长度之和;
共用体中各成员的起始地址相同,共用体变量所占的内存长度为最长的成员的长度。
2)对于union第二个特点一定要领会,各个数据成员的存放地址是相同的,且造成的缺陷是会导致数据覆盖,例如,定义一个union nn{int a,char b[2]};nn.b[0] = 1;nn.b[1] = 10,那么printf(“nn.a = %d\n”,nn.a);结果是多少?在回答此类问题要注意大端还是小端!
0000 0000 0000 0000 0000 1010 0000 0001 = 2561
注:小端模式 -- 字数据的高字节对应高地址,低字节对应低地址;
大端模式 -- 字数据的高字节对应低地址,低字节对应高地址;
- 对于++,--的用法要掌握:
- 会计算(++i)+(++i)+(++i)+(++i) 以及 (i++)+(i++)+(i++)+(i++);
(++i) + (++i) + (++i) + (++i) = 2 + 2 + 3 + 4 = 11
(i++) + (i++) + (i++) + (i++) = 0 + 0 + 0 + 0 = 0
- 会计算int i = 2,i = i * ((i++)+(++i));打印i的值是多少?
i = i * ((i++)+(++i)) = 3 * (3 + 3) = 18
i++ = 19
- static的作用(C,C++):回答这个问题的时候,最好能加上在C++里的作用,比如说是修饰变量是类变量,修饰函数是类成员函数(这个标记为重点);
(1)在函数体内,一个被声明为静态的变量在这一函数被调用过程中维持其值不变(该变量存放在静态变量区)。
(2) 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。
(3) 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。
5.#define和typedef的比较;
(1)作用时间不同:#define是宏定义,发生在预处理阶段,只进行简单的字符串替换,不进行正确性检查;而typedef在编译阶段有效,有类型检查的功能;
(2)功能不同:#define不只是可以为类型取别名,还可以定义常量、变量、编译开关等;
typedef用来定义类型的别名,包括内部类型、自定义类型、机器无关的类型等;
(3)作用域不同:#define没有作用域的限制,只要是之前预定义过的宏,在以后的程序中都可以使用。而typedef有自己的作用域。
(4)修饰指针类型时作用不同:typedef int * pint; const pint p; //p的地址不可改;
#define int * Pint; const Pint P; //P指向的值不可改;
- #define和 const的比较;
(1) define宏是在预处理阶段展开;const常量是编译运行阶段使用。
(2) define宏没有类型,不做任何类型检查,仅仅是展开;const常量有具体的类型,在编译阶段会执行类型检查。
(3) 存储方式不同:define宏仅仅是展开,有多少地方使用,不会分配内存;
const常量会在内存中分配(可以是堆中也可以是栈中)。
- const定义的常量在程序运行过程中只有一份拷贝,而define定义的常量在内存中有若干个拷贝。
- #define 定义的宏函数和自定义函数的比较(优缺点);
(1)代码长度:#define宏:每次使用时,宏代码都被插入到程序中,程序的长度将大幅度增长;
函数:函数代码只出现于一个地方:每次使用这个函数时,都调用那个地方的同一份代码;
(2)执行速度:#define宏:更快
函数:存在函数调用、返回的额外开销;
(3)操作符优先级:#define宏:宏参数的求值是在所有周围表达式的上下文环境里,除非它们加上括号,否则邻近操作符的优先级可能产生不可预料的结果。
函数:函数参数只在函数调用时求值一次,它的结果值传递给函数。
(4)参数求值:#define宏:参数用于宏定义时,每次都将重新求值,由于多次求值,具有副作用的参数可能会产生不可预测的结果。
函数:参数在函数调用前只求值一次,在函数中多次使用参数并不会导致多次求值过程;
(5)参数类型:#define宏:宏与类型无关,只要参数的操作是合法的,它可以用于任何参数类型。
函数: 函数的参数是与类型有关系的,如果参数的类型不同,就需要使用不同的函数,即使它们执行的任务是相同的。
- #define定义宏函数的时候记着加括号,比如定义一个宏函数实现比较两个数的大小;
#defined Max(x,y) ((x>y) ? (x):(y))
- #define和枚举enum的区别;
(1)#define宏常量是在预编译阶段进行简单替换。枚举常量则是在编译的时候确定其值。
(2)一般在编译器里,可以调试枚举常量,但是不能调试宏常量。
(3)枚举可以一次定义大量相关的常量,而#define宏一次只能定义一个。
10.掌握数组指针的定义,指针数组的定义,函数指针的定义及使用,函数指针数组的定义;
(1)数组指针:int (*p)[ ];(一个指向数组的指针)
(2)指针数组:int *p[ ];(一个数组,数组内存放着指针)
(3)函数指针:int (*p)(int);(一个指向函数地址的指针)
(4)函数指针数组:int (*p[ ])(int);(一个数组,数组内存放着指向函数地址的指针)
11.内存的分配方式有几种?造成内存泄漏的原因以及怎么防止内存泄漏?
(1) 内存的分配方式:
1、从静态存储区域分配内存。程序编译的时候内存已经分配好了,并且在程序的整个运行期间都存在,例如全局变量。
2、在栈上创建。在执行函数时,函数内局部变量的存储单元可以在栈上创建,函数结束时这些存储单元自动被释放。
3、在堆上分配内存,亦称动态内存分配,程序在运行的时候用malloc函数或new运算符申请任意大小的内存,程序员要用free函数或delete运算符释放内存。
(2) 内存泄漏的原因:没有释放向系统申请的内存;
(3) 防止内存泄漏的方法:
1、良好的编码规范;
2、使用内存泄漏的检测工具(ccmalloc、Dmalloc等)
- 请简单描述Windows内存管理的方法;
内存管理有块式管理,页式管理,段式和段页式管理。现在常用段页式管理
块式管理:把主存分为一大块、一大块的,当所需的程序片断不在主存时就分配一块主存空间,把程序片断load入主存,就算所需的程序片度只有几个字节也只能把这一块分配给它。这样会造成很大的浪费,平均浪费了50%的内存空间,但是易于管理。
页式管理:把主存分为一页一页的,每一页的空间要比一块一块的空间小很多,显然这种方法的空间利用率要比块式管理高很多。
段式管理:把主存分为一段一段的,每一段的空间又要比一页一页的空间小很多,这种方法在空间利用率上又比页式管理高很多,但是也有另外一个缺点。一个程序片断可能会被分为几十段,这样很多时间就会被浪费在计算每一段的物理地址上。
段页式管理:结合了段式管理和页式管理的优点。把主存分为若干页,每一页又分为若干段。
- malloc与new的区别?Realloc等函数的作用?
(1)malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。
(2)对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc /free.
(3)因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以一个能完成清理与释放内存工作的运算符delete.注意new/delete不是库函数。
(4)C++程序经常要调用C函数,而C程序只能用malloc/free管理动态内存。
(5)new可以认为是malloc加构造函数的执行。New出来的指针是直接带类型信息的。而malloc返回的都是void指针;
- 位操作的掩码的使用:&(位与)、|(位或)、~(取反)、^(异或)、>>(位右移)、<<(位左移)
打开位:打开位置1,其它位置0,源数据 = 源数据 OR 位掩码。
关闭位:关闭位置0,其它位置1,源数据 = 源数据 AND 位掩码。
转置位:利用位与或的特性,0^B为B,1^1为0,1^1为0,就把想转置的位转置了。
a = a^b;
b = a^b; 利用异或运算,不设置第三个变量就可以实现整型变量值的交换;
a = a^b;
- Sizeof与strlen的区别;
(1)strlen是库函数,遇到’\0’结束,字符串长度不包括’\0’;sizeof是运算符,字符串长度包括’\0’;
(2)strlen的结果在运行的时候才能计算出来,用来计算字符串的长度,不是计算占内存的大小的;
(3)sizeof操作符的结果类型是size_t(int类型);
(4)sizeof可以用类型做参数,strlen只能用char*做参数,且必须是以’\0’结尾的。sizeof还可以用函数做参数(如sizeof(fun()) );
(5)sizeof后如果是类型必须加括弧,如果是变量名可以不加括弧;
(6)数组做sizeof的参数不退化(还表示的是数组),传递给strlen后退化为指针;
(7)当用于一个结构类型活变量时,sizeof返回实际的大小 ,sizeof操作符不能返回动态地分配了的数组或外部的数组的尺寸。
- 指针与数组的区别:(至少总结七条)
- 数组长度固定,指针可以动态分配内存。
- 指针可以随时指向任意类型的内存块,而数组可以在静态存储区被创建。
- 修改的内容不同。
(4)在内存中所占的字节数不同。
- 怎么杜绝野指针?
(1)良好的编码习惯;
(2)凡定义的指针变量初始化为NULL;指针变量被free或delet之后将其值赋为NULL;
- 按值传递与按地址传递
数据传递方式有两种方式:按值传递与按地址传递。
(1)按值传递参数:按值传递参数时,是将实在参数的值复制一个形式参数中,如果在调用过程中改变了形式参数的值,不会影响实在参数本身,即实在参数保持调用前的值不变。
(2)按地址传递:按地址传递参数时,把实在参数的地址传送给被调用过程,形式参数和实在参数共用内存的同一地址。在被调用过程中,形式参数的值一旦改变,相应实参的值也跟着改变。
- 源文件到可执行文件要经过那几个步骤?每个步骤做什么?比如说预处理都做什么工作,做好能够详细的阐述链接过程所要做的工作,一定要背下来并且理解;
源程序(*.c)— 预处理 — 编译(*.s) — 汇编(*.o) — 链接 — 可执行文件。
1.预处理过程主要处理的是#include、#define、#if、#else、#ifdef、#endif等指令以及处理注释、行号(用于调试)等工作;
(1)头文件展开;(2)宏替换;(3)条件编译;
2.编译:将源码编译为汇编代码;
3.汇编:将汇编代码汇编为目标代码;
4.链接:将目标代码链接为可执行文件;(静态链接,动态链接)