无锡嘉饰茂建设网站的公司,网站构建技术,本地网站模版批量修改网站字符,wordpress页面打不开1.谈谈你对C内存分配的理解1.1 还是的先看看C对内存分为哪几个区#xff1f;1、栈区#xff08;stack#xff09;— 由编译器自动分配释放 #xff0c;存放函数的参数值#xff0c;局部变量的值等。其操作方式类似于数据结构中的栈。想知道为什么效率高吗#xff1f;因为…1.谈谈你对C内存分配的理解1.1 还是的先看看C对内存分为哪几个区1、栈区stack— 由编译器自动分配释放 存放函数的参数值局部变量的值等。其操作方式类似于数据结构中的栈。想知道为什么效率高吗因为关于栈的操作如push集成在处理器的指令集中效率很高但是分配的内存容量有限。2、堆区heap — 一般由程序员分配释放 若程序员不释放程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事分配方式倒是类似于链表。3、全局区静态区static—全局变量和静态变量的存储是放在一块的初始化的全局变量和静态变量在一块区域 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 4、文字常量区—常量字符串就是放在这里的。5、程序代码区—存放函数体的二进制代码。1.2 再给面试官谈谈new/delete和malloc/free的区别 1、像我们的new/delete和malloc/free就是在上面所说的堆区上操作。程序员申请了内存用完要记得释放否则就内存泄露了。而且多次申请释放会造成堆区碎片这个需要注意下。2、new/delete是操作符而malloc/free是函数。前者可以被重载。前者可以有构造函数和析构函数。前者返回的某种类型对象的指针后者返回VOID指针。2.基于问题1深入谈谈堆和栈1、申请方式栈函数内的局部变量int a 10;堆new / malloc2、申请后的系统响应栈只要申请的空间大小栈的剩余空间栈就分配。堆因为newmalloc都是C封装的里面做了这样的事首先明白windows有这么一个记录空闲空间地址的链表C遍历该链表先找到第一个空闲空间大小大于程序员申请的空间大小的地址堆节点将该堆节点从链表中删除把该节点的空间分配给程序。对于大多数系统会在这块内存空间中的首地址处记录本次分配的大小这样代码中的delete语句才能正确的释放本内存空间。由于找到的堆结点的大小不一定正好等于申请的大小系统会自动的将多余的那部分重新放入空闲链表中。3、申请大小栈默认是1M还是2M堆看系统的虚拟内存有多大了。请记住堆是一个节点为空闲内存空间的链表。。堆是向高地址扩展的数据结构是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的自然是不连续的而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见堆获得的空间比较灵活也比较大。4、申请效率栈速度快pushpop等指令都内置在处理器了能不快吗堆相对慢为什么慢可以看2就知道它在申请的时候干了多少事还容易产生内存碎片。不过容量大操作方便。5、存储内容栈依次是主函数中后的下一条指令地址然后是函数的各个参数在大多数的C编译器中参数是由右往左入栈的函数中的局部变量堆程序员随意安排。6、存取效率先看看这段代码int main(){ char a 1; char c[] 1234567890; char *p 1234567890; a c[1]; a p[1]; return 0; } 这是在函数内char数组c是在栈上。char *p 1234567890 其中虽然*p也是在栈上但是1234567890在常量区。再看看它们对应的汇编代码10: a c[1]; 00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh] 0040106A 88 4D FC mov byte ptr [ebp-4],cl 11: a p[1]; 0040106D 8B 55 EC mov edx,dword ptr [ebp-14h] 00401070 8A 42 01 mov al,byte ptr [edx1] 00401073 88 45 FC mov byte ptr [ebp-4],al 可以看到在栈上的c[]只要两行就能取到内容而p要先去把常量区的内容放到edx中再存取。说明纯栈操作效率是要高一些的。7、总结看看一个经典代码就什么都懂了//main.cpp int a 0; //全局初始化区 char *p1; //全局未初始化区 main() { int b; //栈 char s[] abc; //栈 char *p2; //栈 char *p3 123456; //123456\0在常量区p3在栈上。 static int c 0 //全局静态初始化区 p1 (char *)malloc(10); p2 (char *)malloc(20); //分配得来得10和20字节的区域就在堆区。 strcpy(p1, 123456); //123456\0放在常量区编译器可能会将它与p3所指向的123456优化成一个地方。 } [cpp] view plain copy //main.cpp int a 0; //全局初始化区 char *p1; //全局未初始化区 main() { int b; //栈 char s[] abc; //栈 char *p2; //栈 char *p3 123456; //123456\0在常量区p3在栈上。 static int c 0 //全局静态初始化区 p1 (char *)malloc(10); p2 (char *)malloc(20); //分配得来得10和20字节的区域就在堆区。 strcpy(p1, 123456); //123456\0放在常量区编译器可能会将它与p3所指向的123456优化成一个地方。 } 3.谈谈这strcpymemcpy和sprintf三者的区别共同点都是拷贝东西。效率排行 memcpy strcpy sprintf操作对象memcpy是两块内存之间的操作strcpy 是两个字符串对象之间的操作sprintf是任意类型转到字符串的操作。4.C多态机制先上代码[cpp] view plain copy#include iostream.h class animal { public: void sleep() { coutanimal sleependl; } void breathe() { coutanimal breatheendl; } }; class fish:public animal { public: void breathe() { coutfish bubbleendl; } }; int main() { fish fh; animal *pAnfh; pAn-breathe(); return 0; } [cpp] view plain copy #include iostream.h class animal { public: void sleep() { coutanimal sleependl; } void breathe() { coutanimal breatheendl; } }; class fish:public animal { public: void breathe() { coutfish bubbleendl; } }; int main() { fish fh; animal *pAnfh; pAn-breathe(); return 0; } 运行结果是内存模式子类fish的内存模型是先animal然后再自己增加的部分。因为animal *pAnfh;fish对象被强制更改成父类animal对象指针指向fish的上半部分那不就是一个animal对象了吗?所以pAn-breathe()就是父类的breathe()。更深入的了解机制早绑定和晚绑定早绑定C在编译的时候要确定每个对象调用的函数的地址这就是早绑定。晚绑定编译的时候先不决定等到运行的时候再根据情况确定要调用的函数地址这就是晚绑定。什么东西支持晚绑定这个机制呢虚函数虚函数机制每个类对象有个虚表指针vptr这个指针指向类所属的虚表vtable。当我们把父类animal的breathe()设置为虚函数子类fish即使被强制转换成animal对象也是没关系的。因为pAn实际指向的就是fish对象因此vptr指向的是fish的vtable所以vptr指向的vtable中的breathe()函数就是fish对象的breathe()函数。正确的多态代码[cpp] view plain copy#include iostream.h class animal { public: void sleep() { coutanimal sleependl; } virtual void breathe() { coutanimal breatheendl; } }; class fish:public animal { public: void breathe() { coutfish bubbleendl; } }; int main() { fish fh; animal *pAnfh; pAn-breathe(); return 0; } [cpp] view plain copy #include iostream.h class animal { public: void sleep() { coutanimal sleependl; } virtual void breathe() { coutanimal breatheendl; } }; class fish:public animal { public: void breathe() { coutfish bubbleendl; } }; int main() { fish fh; animal *pAnfh; pAn-breathe(); return 0; } 运行结果再一次深入了解vptr和vtable每个含有virtual function的类中都隐式包含着一个静态虚指针vfptr指向该类的静态虚表vtablevtable中的表项指向类中的每个virtual function的入口地址每个类内部都有一个虚表无论类型怎么被转换虚表指针vptr都是固定的。fish被转换成animal也无所谓vptr指向的永远是fish的vtable关于虚表1、每一个类都有虚表。2、虚表可以继承如果子类没有重写虚函数那么子类虚表中仍然会有该函数的地址只不过这个地址指向的是基类的虚函数实现。如果基类3个虚函数那么基类的虚表中就有三项虚函数地址派生类也会有虚表至少有三项如果重写了相应的虚函数那么虚表中的地址就会改变指向自身的虚函数实现。如果派生类有自己的虚函数那么虚表中就会添加该项。3、派生类的虚表中虚函数地址的排列顺序和基类的虚表中虚函数地址排列顺序相同。5.C的虚函数和纯虚函数1、后者不可实例化虚函数和纯虚函数可以定义在同一个类中含有纯虚函数的类被称为抽象类而只含有虚函数的类不能被称为抽象类抽象类不可实例化。2、后者不可被直接使用虚函数可以被直接使用也可以被子类重载以后以多态的形式调用而纯虚函数必须在子类中实现该函数才可以使用因为纯虚函数在基类有声明而没有定义。3、两者都是可以被重载体现多态虚函数和纯虚函数都可以在子类中被重载虚函数可以不被重载纯虚函数必须在子类实现以多态的形式被调用。4、形式上virtual{};纯虚函数的定义形式virtual { } 0;在虚函数和纯虚函数的定义中不能有static标识符原因很简单被static修饰的函数在编译时要求前期绑定,然而虚函数却是动态绑定而且被两者修饰的函数生命周期也不一样。vitrual 和 static 就是死对头5、意义上定义纯虚函数就是为了让基类不可实例化没意义因为实例化这样的抽象数据结构本身并没有意义或者给出实现也没有意义。个人感觉很多时候基类它能够实例化对象就是个不合理的存在比如动物你给我实例化一个动物看看6.引用ref1、什么是“引用”声明和使用“引用”要注意哪些问题引用就是某个目标变量的“别名”(alias)对应用的操作与对变量直接操作效果完全相同。声明一个引用的时候切记要对其进行初始化。引用声明完毕后相当于目标变量名有两个名称即该目标原名称和引用名不能再把该引用名作为其他变量名的别名。声明一个引用不是新定义了一个变量它只表示该引用名是目标变量名的一个别名它本身不是一种数据类型因此引用本身不占存储单元系统也不给引用分配存储单元。不能建立数组的引用。2、将“引用”作为函数参数有哪些特点1传递引用给函数与传递指针的效果是一样的。这时被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用所以在被调函数中对形参变量的操作就是对其相应的目标对象在主调函数中的操作。2使用引用传递函数的参数在内存中并没有产生实参的副本它是直接对实参操作无需副本而使用一般变量传递函数的参数当发生函数调用时需要给形参分配存储单元形参变量是实参变量的副本如果传递的是对象还将调用拷贝构造函数。因此当参数传递的数据较大时用引用比用一般变量传递参数的效率和所占空间都好。3使用指针作为函数的参数虽然也能达到与使用引用的效果但是在被调函数中同样要给形参分配存储单元且需要重复使用*指针变量名的形式进行运算这很容易产生错误且程序的阅读性较差另一方面在主调函数的调用点处必须用变量的地址作为实参。而引用更容易使用更清晰。阅读障碍啊3、什么时候用“常引用const 类型标识符 引用名目标变量名[cpp] view plain copyint a ; const int raa; ra1; //错误 a1; //正确 [cpp] view plain copy int a ; const int raa; ra1; //错误 a1; //正确 既要保证效率又要保证传递的数据不能被改变。[cpp] view plain copystring foo( ); void bar(string s); bar(foo( )); //错误 bar(hello world);//错误 [cpp] view plain copy string foo( ); void bar(string s); bar(foo( )); //错误 bar(hello world);//错误 因为bar中参数是非const类型而string的临时对象都是const类型所以把const类型对象赋值给非const类型对象是编译不过的。4、将“引用”作为函数返回值类型的格式、好处和需要遵守的规则?1不能返回局部变量的引用。主要原因是局部变量会在函数返回后被销毁因此被返回的引用就成为了无所指的引用程序会进入未知状态。 2不能返回函数内部new分配的内存的引用。虽然不存在局部变量的被动销毁问题可是有出现了其他问题被函数返回的引用只是作为一个临时变量出现而没有被赋予一个实际的变量那么这个引用所指向的空间由new分配就无法释放造成memory leak。3流操作符重载返回值声明为引用 【必须用引用】估计就是C引入”引用“这个概念主要原因吧。流操作符和这两个操作符常常希望被连续使用例如cout hello endl; 因此这两个操作符的返回值应该是一个仍然支持这两个操作符的流引用。为什么不是流对象呢?每次返回值都是一个流对象的话都要一次拷贝构造函数每次的 都是不同的流对象从开销和效率上看这显然不可取。为什么不是流对象指针呢指针能连续两次吗如果针对流对象能的话要修改整个指针机制这显然不可取。所以流对象引用才是唯一可取的方法4赋值操作符 返回值也是声明为引用【必须用引用】因为它和流操作符一样都可以连续操作 x y 1。用引用很科学。理由如3。5四则运算符 - * / 的返回值不能使引用【必须不能用引用】主要原因是这四个操作符没有side effect因此它们必须构造一个对象作为返回值可选的方案包括返回一个对象、返回一个局部变量的引用、返回一个new分配的对象的引用、返回一个静态对象引用。根据前面提到的引用作为返回值的三个规则第2、3两个方案都被否决了。静态对象的引用又因为((ab) (cd))会永远为true而导致错误。所以可选的只剩下返回一个对象了。5、引用与多态的关系引用是除指针外另一个可以产生多态效果的手段。这意味着一个基类的引用可以指向它的派生类实例。[cpp] view plain copyClass A; Class B:Class A{...}; B b; A ref b; [cpp] view plain copy Class A; Class B:Class A{...}; B b; A ref b; 6、引用与指针的区别
指针通过某个指针变量指向一个对象后对它所指向的变量间接操作。程序中使用指针程序的可读性差而引用本身就是目标变量的别名对引用的操作就是对目标变量的操作。此外就是上面提到的对函数传ref和pointer的区别。参考资料http://blog.csdn.net/wfwd/archive/2006/05/30/763551.aspx7.实现strcpy[cpp] view plain copychar *strcpy(char *strDest, const char *strSrc) { if ( strDest NULL || strSrc NULL) return NULL ; if ( strDest strSrc) return strDest ; char *tempptr strDest ; while( (*strDest *strSrC) ! ‘\0’) ; return tempptr ; } [cpp] view plain copy char *strcpy(char *strDest, const char *strSrc) { if ( strDest NULL || strSrc NULL) return NULL ; if ( strDest strSrc) return strDest ; char *tempptr strDest ; while( (*strDest *strSrC) ! ‘\0’) ; return tempptr ; } 8.实现String类一直都有这么一个印象拷贝构造函数和赋值函数是一对好基友。[cpp] view plain copyclass String { public: String(const char *str NULL); // 通用构造函数 String(const String another); // 拷贝构造函数 ~ String(); // 析构函数 String operater (const String rhs); // 赋值函数 private: char *m_data; // 用于保存字符串 }; String::String(const char *str) { if ( str NULL ) //strlen在参数为NULL时会抛异常才会有这步判断 { m_data new char[1] ; m_data[0] \0 ; } else { m_data new char[strlen(str) 1]; strcpy(m_data,str); } } String::String(const String another) //拷贝构造函数 { m_data new char[strlen(another.m_data) 1]; strcpy(m_data,other.m_data); } String String::operator (const String rhs) //赋值函数 { if ( this rhs) return *this ; delete []m_data; //删除原来的数据新开一块内存 m_data new char[strlen(rhs.m_data) 1]; strcpy(m_data,rhs.m_data); return *this ; } String::~String() { delete []m_data ; } [cpp] view plain copy class String { public: String(const char *str NULL); // 通用构造函数 String(const String another); // 拷贝构造函数 ~ String(); // 析构函数 String operater (const String rhs); // 赋值函数 private: char *m_data; // 用于保存字符串 }; String::String(const char *str) { if ( str NULL ) //strlen在参数为NULL时会抛异常才会有这步判断 { m_data new char[1] ; m_data[0] \0 ; } else { m_data new char[strlen(str) 1]; strcpy(m_data,str); } } String::String(const String another) //拷贝构造函数 { m_data new char[strlen(another.m_data) 1]; strcpy(m_data,other.m_data); } String String::operator (const String rhs) //赋值函数 { if ( this rhs) return *this ; delete []m_data; //删除原来的数据新开一块内存 m_data new char[strlen(rhs.m_data) 1]; strcpy(m_data,rhs.m_data); return *this ; } String::~String() { delete []m_data ; } 9.#includefile.h 与 #include file.h的区别答前者是从Standard Library的路径寻找和引用file.h而后者是从当前工作路径搜寻并引用file.h。10.C用C编译器编译后的函数为什么要加入extend C?两者编译机制不一样为了能够和谐工作extend c告诉C编译器这里按照C编译器的处理方式编译链接就行了。一句话实现C与C及其它语言的混合编程。我在JNI的时候用到这玩意~为什么作为一种面向对象的语言C支持函数重载而过程式语言C则不支持。函数被C编译后在符号库中的名字与C语言的不同。例如假设某个函数的原型为 void foo( int x, int y ); 该函数被C编译器编译后在符号库中的名字为_foo而C编译器则会产生像_foo_int_int之类的名字。现在假设模块A是C语言编译后的模块B是C模块。B中调用A的函数。这样模块B中调用模块A中的函数时在编译阶段模块B虽然找不到该函数但是并不会报错它会在连接阶段中从模块A编译生成的目标代码中找到此函数。11.面向对象的三个基本特征并简单叙述之1. 封装将客观事物抽象成类每个类对自身的数据和方法实行protection(private, protected,public)2. 继承广义的继承有三种实现形式实现继承指使用基类的属性和方法而无需额外编码的能力、可视继承子窗体使用父窗体的外观和实现代码、接口继承仅使用属性和方法实现滞后到子类实现。前两种类继承和后一种对象组合接口继承以及纯虚函数构成了功能复用的两种方式。3. 多态是将父对象设置成为和一个或更多的他的子对象相等的技术赋值之后父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说就是一句话允许将子类类型的指针赋值给父类类型的指针。12. 重载overload)和重写overried的区别从定义上来说重载是指允许存在多个同名函数而这些函数的参数表不同或许参数个数不同或许参数类型不同或许两者都不同。重写是指子类重新定义父类虚函数的方法。从实现原理上来说重载编译器根据函数不同的参数表对同名函数的名称做修饰然后这些同名函数就成了不同的函数至少对于编译器来说是这样的。如有两个同名函数function func(p:integer):integer;和function func(p:string):integer;。那么编译器做过修饰后的函数名称可能是这样的int_func、str_func。对于这两个函数的调用在编译器间就已经确定了是静态的。也就是说它们的地址在编译期就绑定了早绑定因此重载和多态无关重写和多态真正相关。当子类重新定义了父类的虚函数后父类指针根据赋给它的不同的子类指针动态的调用属于子类的该函数这样的函数调用在编译期间是无法确定的调用的子类的虚函数的地址无法给出。因此这样的函数地址是在运行期绑定的晚绑定。13. 如何判断一段程序是由C 编译程序还是由C编译程序编译的[cpp] view plain copy#ifdef __cplusplus coutC; #else coutc; #endif [cpp] view plain copy #ifdef __cplusplus coutC; #else coutc; #endif