C/C++ Tips(1)

1、关于vector的内部实现

         对于vector,任何插入删除的操作都会使迭代器失效,所以要小心

        vector内部实现其实就是一块连续的内存,它和传统的array不同的它可以扩容,不用考虑越界。

        vector的迭代器就是最简单的指向容器内类型的指针。其内部有三个指针,start(指向数据存储区的首地址),finish(已使用数据区的尾端),end_of_storage(指向容量尾端,容量一般富余)。当finish指针和end_of_storage指针相等的时候,容量满载,如果继续插入元素,就得扩容。

        需要扩容的时候,空间适配器会重新寻找一块更大的内存空间,然后start指向新内存首地址,原始数据复制,新数据插入,同时更新finish和end_of_storage即可。扩容的规模一般是原始容量的两倍。

2、Debug和Release

         Debug通常称为调试版本,它包含调试信息,并且不对代码进行任何优化,便于程序员调试程序。

         Release称为发布版本,它往往是将代码进行了各种优化,使程序在代码大小和运行速度上是最优的,以便用户很好的使用。

         只有debug版的程序才能设置断点,单步执行。

        实际上debug和release没有本质上的界限,它们只是一组编译选项的集合,编译器只是按照预定的选项行动,事实上,我们甚至可以修改这些选项,得到优化的可调试版本或是带跟踪语句的发布版本。

3、引用和指针的区别

        从概念上讲,指针本质上就是存放地址的一个变量,在逻辑上是独立的,它可以被改变,包括其所指向地址的改变和其指向地址中所存放数据的改变。

        而引用是一个别名,在逻辑上是不独立的,它的存在具有依附性,所以引用必须在一开始就初始化,而且其引用的对象在整个生命期中是不可能改变的。

        在C++中,指针和引用经常用于函数的参数传递,然而指针传递参数和引用传递参数在本质上是不同的。

        指针传递参数本质上是值传递,它传递的是一个地址值。传递过程中,被调函数的形参作为被调函数的局部变量处理,即在栈中开辟了内存空间以存放主调函数放进来的实参的值,从而形成了实参的一个副本。值传递的特点是被调函数对形式参数的操作都是作为局部变量进行,不会影响主调函数的实参的值。

       总结一下

          相同点,都是地址的概念

          指针指向一块内存,它的内容是指向内存的地址,而引用是某块内存的别名。

          不同点:

          1)指针是一个实体,引用仅是个别名

          2)引用只能在定义时被初始化一次,之后不可变,指针可变。

          3)引用没有const,指针有const,const的指针不可变。

          4)引用不能为空,指针可以为空

          5)sizeof(引用)得到的是所指向变量的大小,而sizeof指针得到的是指针本身的大小。

          6)指针和引用的自增操作运算结果不一样

          7)引用是类型安全的,而指针不是。

4、复制构造机制

        拷贝构造函数,是一种特殊的构造函数,它由编译器调用来完成一些基于同一类的其它对象的构建及初始化。唯一的参数(对象的引用)是不可变的(const)类型。

        拷贝构造函数调用的三种形式。

            1)一个对象作为参数,以值传递的方式传入函数体

            2)一个对象作为函数返回值,以值传递的方式从函数返回

            3)一个对象给另一个对象进行初始化(常称为复制初始化)

       总结:当某对象是按值传递时(无论是作为函数参数,还是作为函数返回值),编译器都会先建立一个此对象上的临时拷贝,而建立临时拷贝时就会调用拷贝构造函数。

       如果在类中没有显式的声明一个拷贝构造函数,那么编译器就会自动生成一个默认的拷贝构造函数,该构造函数完成对象之间的浅拷贝。

       在某些状况下,类成员变量需要动态开辟内存,如果实行浅拷贝,就是把一个对象里的值完全复制给另一个对象,如A=B。这时如果B中有一个成员变量指针已经申请了内存,那么A中的成员变量也指向那块内存。这就出现了问题:当B把内存释放了,这时A的指针就是野指针了,出现运行错误。这时候就要使用深拷贝了,要自定义拷贝构造函数。

      深拷贝和浅拷贝可以简单理解为:如果一个类拥有资源,当这个类发生复制过程的时候,如果重新分配了资源,这个过程就是深拷贝。反之,没有重新分配资源,就是浅拷贝。

       对于凡是包含动态分配成员或包含指针成员的类都应该提供拷贝构造函数。

       在提供拷贝构造函数的同时,还应该考虑重载“=”赋值操作符号

5、虚机制是怎么实现的

        虚函数在C++中的实现机制就是用虚表和虚指针,每个类定义一个虚表,每个对象定义一个虚指针。

6、虚析构函数的作用

        如果一个基类的析构函数被说明为虚析构函数,则它派生类中的析构函数也是虚析构函数。

        说明虚析构函数的目的在于使用delete运算符删除一个对象时,能保证析构函数被正确的执行。因为设置析构函数后,可以采用动态联编方式选择析构函数。

        先调用子类析构函数,再调用子类的析构函数,防止析构不完全。

7、数组和链表区别

        C++中可以用数组处理一组数据类型相同的数据,但不允许动态定义数组的大小,即在使用数组之前必须确定数组的大小。而在实际应用中,有时无法确定数组的大小,只能将数组定义为足够大。这样数组中有些空间可能不被使用,从而造成内存空间浪费。

         链表是一种常用的数据分配方式,它采用动态分配内存的形式实现。需要时可以用new分配内存空间,不需要时用delete将内存释放,不会造成内存空间的浪费。

         数组中数据是连续存储的,而链表是随机存储的。

         数组随机访问,插入删除效率较低。链表访问某个元素需要从头遍历,较数组慢,但插入删除不需要移动元素。

8、面向过程和面向对象

        面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个个依次调用就可以了。

        面向对象是把构成问题事物分解成一个个对象,建立对象的目的不是为了完成一个步骤,而是为了描述某个事物在解决整个问题的步骤中的行为。

        面向过程和面向对象的区别并没有人们想象中的大。面向对象的大部分思想在面向过程中也能体现,但面向过程最大的问题在随着系统的膨胀,面向过程将无法应付,最终导致系统的崩溃。面向对象的提出就是为了解决这一软件危机。

9、虚函数和纯虚函数

        1)虚函数和纯虚函数可以定义在同一个类中,含有纯虚函数的类被称为抽象类。而只含有虚函数的类不能称为抽象类。

        2)虚函数可以被直接使用,也可以被子类重载以后以多态的形式调用,而纯虚函数必须在子类中实现该函数才可以被调用,因为纯虚函数在基类只有声明而没有定义。

        3)虚函数和纯虚函数都可以在子类中重载,以多态的方式被调用

        4)虚函数和纯虚函数通常存在于抽象基类之中,被继承的子类重载,目地是提供一个统一的接口

        5)virtual {method body}

              virtual {}=0;

        在虚函数和纯虚函数的定义中不能含有static标识符,被static修饰的函数在编译时要求前期绑定,然而虚函数却是动态绑定,被两者修饰的函数的生命周期也不一样。

        6)如果一个类中含有纯虚函数,那么任何对该类实例化的语句都会有错误的产生。因为抽象基类是不能直接被调用的,必须被子类继承重载之后,根据要求调用子类的方法。

10、C++静态全局变量、静态局部变量、全局变量、局部变量

          按存储区域分:全局变量、静态全局变量和静态局部变量都存放在内存的全局数据区,局部变量放在内存的栈区。

          按作用域分:全局变量在整个工程文件内都有效;静态全局变量只在定义它的文件内有效;静态局部变量只在定义它的函数内有效,只是程序仅分配一次内存,函数返回后,该变量不会消失。局部变量在定义它的函数内有效,函数返回后消失。

         全局变量和静态变量如果没有手动初始化,则由编译器初始化为0,局部变量的值不可知。

11、常用设计模式

          单件模式,抽象工厂模式和工厂模式 适配器模式 装饰模式 观察者模式 外观模式

          单件模式,这是用得最多的模式,每一个正式软件都要用到它,全局配置、唯一资源。

          单件模式中构造函数应该设置为protected,这样才可以扩张这个构造函数。

           1)对于一个类,占用的系统资源非常多,而且这些资源可以被全局共享,则可以设置为单件模式,强迫全局只有一个实例。

           2)对于一个类,需要对实例进行计数,可以在createInstanse中进行并可以对实例的个数进行限制。

           3)对于一个类,需要对其实例的具体行为进行控制,例如期望返回的实例实际上是自己子类的实例。这样可以通过单件模式,对用户端代码保持透明。

           如果是多线程的情况下怎么使用单件模式。

           ans:多线程应该也是单件的吧,因为线程之间是共享内存的。

           如果限制实例不超过十个该怎么办

           ans:使用类似线程池的东西

12、关于图形学

            直方图均衡化:改变图像像素的灰度分布

            空域滤波:对空间图像进行加强

            线性锐化滤波,非线性平滑滤波

13、C++中函数体存放在哪儿

       C++程序的内存格局通常为4个区

       1)全局数据区:存放全局变量,静态变量,常量

       2)代码区:存放所有类成员函数和非成员函数代码(想知道这个代码区具体是哪儿)

       3)栈区:存放为运行函数而分配的局部变量,函数参数,返回类型,返回地址

       4)堆区:余下的就是堆(使用new和delete)

14、将引用作为函数返回值类型的好处以及需要遵守的规则

          好处:内存中不产生返回值的副本

         注意:

             1)不能返回局部变量的引用

             2)不能返回函数内部new分配的内存的引用(因为函数返回的引用知识作为一个局部变量出现,而没有赋予给一个实际的变量,那么这个引用指向的空间就无法释放,造成内存泄漏)

             3)可以返回类成员的引用,但最好设为const

             4)流操作符重载返回值申明为引用的作用

         因为操作符通常被希望连续使用,指针需要重复创建副本,不可取,唯一的方法就是使用引用了

15、程序运行的时候突然崩溃,如何查找错误

          貌似这个题俺没有听明白面试官说的什么意思,就说用断点、逐行定位到错误的区域

          后来他解释说是要根据崩溃的点将错误范围一步一步缩小,还说这是一种思想。。~~~俺就开始画圈圈了。。。很多时候我们是这么做的但是说不出来。

16、map的内部实现

         vector封装了数组,list封装了链表,map和set封装了二叉树(完全木有想到啊~~)

        进一步提问为什么map和set插入删除效率比其它序列容器高

         因为map和set之中的所有元素都是以节点形式存储,其结构和链表节点结构差不多。之所以效率高是因为不需要做内存拷贝和内存移动,只要修改节点指针指向就可以了。

17、图形的基本变换包括哪些方面

          放大 缩小 旋转 拉伸 视点变换 坐标映射……

         这以后就问了好多这方面的问题。。~诸如变换矩阵之类、三维图形旋转之类、图形算法之类

18、请说一下extern C的作用

         extern "C"的主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般之包括函数名。
        这个功能十分有用处,因为在C++出现以前,很多代码都是C语言写的,而且很底层的库也是C语言写的,为了更好的支持原来的C代码和已经写好的C语言库,需要在C++中尽可能的支持C,而extern "C"就是其中的一个策略。

19、请说一下#ifdef...的作用

        防止头文件被重复引用。

20、关键字static有什么作用

       静态全局变量不能被其它文件所用
       其它文件可以定义相同名字的变量,不会发生冲突
       静态函数不能被其它文件所用
       其它文件中可以定义相同名字的函数,不会发生冲突
       大家知道函数在栈上分配的空间在此函数执行结束时会释放掉。如果想将函数中变量的值保存到下一次调用,就可以使用静态变量。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值