易错题总结2

来源:互联网 发布:朱生豪 莎士比亚 知乎 编辑:程序博客网 时间:2024/06/02 03:24
1.构造函数生成的顺序建立派生类对象时,3种构造函数分别是a(基类的构造函数)、b(成员对象的构造函数)、c(派生类的构造函数)这3种构造函数的调用顺序为: A.abcB.acbC.cabD.cba答案是A,b的意思应该是父类在子类中还有一个对象作为子类的成员然后就是这样的构造函数的顺序2.运算符重载如果友元函数重载一个运算符时,其参数表中没有任何参数则说明该运算符是:A.一元运算符B.二元运算符C.选项A)和选项B)都可能D.重载错误答案是D,友元函数重载时,参数列表为1,说明是1元,为2说明是2元成员函数重载时,参数列表为空,是一元,参数列表是1,为23.为什么不使用#define而使用const和inline一.#define是预处理,比如#define AS 1.653 ,可能AS不会进入记号表,如果出错,可能出现的错误提示是1.653而不是AS二.define不会做类型检查(容易出错),const拥有类型,会执行相应的类型检查    define仅仅是宏替换,不占用内存,而const会占用内存    const内存效率更高,编译器可能将const变量保存在符号表中,而不会分配存储空间,这使得它成   为一个编译期间的常量,没有存储和读取的操作  当使用#define定义一个简单的函数时,强烈建议使用内联函数替换!    内联函数和宏的区别在于,宏是由预处理器对宏进行替代,而内联函数是通过编译器控制来实现的。而且内联函数是真正的函数,只是在需要用到的时候,内联函数像宏一样的展开,所以取消了函数的参数压栈,减少了调用的开销。4.explicit:阻止执行隐式类型转换(以下借用百度百科)explicit构造函数是用来防止隐式转换的。请看下面的代码:class Test1{public:    Test1(int n)    {        num=n;    }//普通构造函数private:    int num;};class Test2{public:    explicit Test2(int n)    {        num=n;    }//explicit(显式)构造函数private:    int num;};int main(){    Test1 t1=12;//隐式调用其构造函数,成功    Test2 t2=12;//编译错误,不能隐式调用其构造函数    Test2 t2(12);//显式调用成功    return 0;}Test1的构造函数带一个int型的参数,代码23行会隐式转换成调用Test1的这个构造函数。而Test2的构造函数被声明为explicit(显式),这表示不能通过隐式转换来调用这个构造函数,因此代码24行会出现编译错误。普通构造函数能够被隐式调用。而explicit构造函数只能被显式调用。5.拷贝构造函数的使用:(只有一个参数,而且要为引用,不然会造成无限的复制构造)class Widget{public:  Widget();                            //default构造函数  Widget(const Widget& rhs);           //拷贝构造函数  Widget& operator=(const Widget& rhs);//拷贝赋值符}; Widget w1;                            //调用default构造函数 Widget w2(w1);                        //调用拷贝构造函数 w1=w2;                                //调用拷贝赋值符 Widget w3=w2;                         //调用拷贝构造函数6.类型转换:公有派生类对象可以被当作基类的对象使用,反之则不可.一.派生类的对象可以隐含转换为基类对象;二.派生类的对象可以初始化基本的引用三.派生类的指针可以隐含转换为基类的指针.通过基类对象名,指针只能使用从基类继承的成员7.#include <iostream>using namespace std;class A{public:    int m;    int* p;};int main(){    A s;    s.m = 10;    cout << s.m << endl; //10    s.p = &s.m;    *s.p = 5;//!!!    cout << s.m << endl; //5    return 0;}8.若函数参数为引用,则函数将不再为传入的实参建立拷贝,结果直接作用在实参上,是c++提供的一种跨函数传值的手段。这里同时加了const防止更改实参,则只是省去了函数建立拷贝的步骤,应该是为了提升运行效率,去掉&不会影响程序结构。9.std::string str,str可以被修改,而且会调用拷贝构造函数。std::sring& str,str可以被修改,但不会调用拷贝构造函数。const::string str ,str不能被修改,但会调用拷贝构造函数。const::string& str,str不能被修改,而且也不会调用拷贝构造函数。10.深拷贝与浅拷贝简单的来说就是,在有指针的情况下,浅拷贝只是增加了一个指针指向已经存在的内存,而深拷贝就是增加一个指针并且申请一个新的内存,使这个增加的指针指向这个新的内存.采用深拷贝的情况下,释放内存的时候就不会出现在浅拷贝时重复释放同一内存的错误!11.this指针隐含于每一个非静态成员函数中每个对象调用函数时,会先把对象的地址传给this指针,然后通过this指针来调用12.1、在类的定义中进行的,只有conststatic 且 integral 的变量。2、在类的构造函数初始化列表中, 包括const对象和Reference对象。3、在类的定义之外初始化的,包括static变量。因为它是属于类的唯一变量。4、普通的变量可以在构造函数的内部,通过赋值方式进行。当然这样效率不高。13.static:1.当一个进程的全局变量被声明为static之后,它的中文名叫静态全局变量。静态全局变量和其他的全局变量的存储地点并没有区别,都是在.data段(已初始化)或者.bss段(未初始化)内,但是它只在定义它的源文件内有效,其他源文件无法访问它。所以,普通全局变量穿上static外衣后,它就变成了新娘,已心有所属,只能被定义它的源文件(新郎)中的变量或函数访问。14.重载为成员函数:前置单目运算符,重载函数没有形参后置++运算符,重载函数需要有一个int形参//前置单目运算符重载    Clock& operator ++ ();//后置单目运算符重载    Clock operator ++ (int); 15.虚函数不能为内联函数,而且并且是非静态的函数,因为静态的是属于类的,不是属于对象的,而虚函数就是要被对象操作,在类里声明,类外定义,因为虚函数是要动态绑定,就是要在运行时候处理,而内联函数是在编译时候就处理构造函数不能是虚函数析构函数可以是虚函数虚函数一般不声明为内联函数,因为对虚函数的调用需要动态绑定,而对内联函数的处理是静态的如果派生类和基类函数都一样,那么派生类就算不加virtual也会被视为虚函数16.为什么需要虚析构函数:假设基类对象指针指向派生类成员对象,那么如果不把基类和派生类的析构函数加virtual那么就会只析构基类的所分配的空间,不会析构派生类的所分配的空间17.cout << sizeof("\x0012") << endl;    cout << sizeof("hello") << endl;输出 26cout << sizeof("\48") << endl;  3  "'\"后面是跟8进制,但是有个8,超过了8,所以"\4"是一个字符,8是一个字符cout << sizeof("\048") << endl; 3 cout << sizeof("\048\48") << endl; 5cout<<sizeof("??=")<<endl; 旧版本编译器为2,新版本为4,因为历史原因,以前??=相当于#号18.归并排序时间复杂度的递推公式T(n)=2T(n2)+O(n)的解是什么?其中O(n)项代表什么?nlogn,归并两个已排序子向量的时间19. 重写Overriding是父类与子类之间多态性的一种表现,重载Overloading是一个类中多态性的一种表现。20.论:1. 构造函数不能为 virtual, 构造函数不能继承;2. 如果子类不显式调用父类的构造函数,编译器会自动调用父类的【无参构造函数】;3. 继承构造函数(Inheriting constructors)(1) C++11 才支持;(2) 实质是编译器自动生成代码,通过调用父类构造函数来实现,不是真正意义上的【继承】,仅仅是为了减少代码书写量21.C++中析构函数的执行不应该抛出异常;(2) 假如析构函数中抛出了异常,那么系统将变得非常危险,也许很长时间什么错误也不会发生;但也许系统有时就会莫名奇妙地崩溃而退出了,而且什么迹象也没有; 3) 当在某一个析构函数中会有一些可能(哪怕是一点点可能)发生异常时,那么就必须要把这种可能发生的异常完全封装在析构函数内部,决不能让它抛出函数之外,即在析构函数内部写出完整的throw...catch()块。22.进程与线程的区别:进程是一个具有独立功能的程序关于某个数据集合的一次运行活动。它可以申请和拥有系统资源,是一个动态的概念,是一个活动的实体在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位。由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统内多个程序间并发执行的程度。23.C++中什么类型的成员变量只能在构造函数的初始化列表中进行1.常量成员(const),因为常量只能初始化不能赋值,所以必须放在初始化列表里面2.引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面3.没有默认构造函数的类类型,因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化(需要初始化的数据成员是对象)24.构造函数不能被声明为const类型,因此在创建一个const类型的对象时,只有在构造完成后,才获得const属性。25.c++的编译器会对所有的函数重新命名,但是C的编译器不会改变函数名称,所以如果在c++中直接调用C编写的函数就会找不到调用的函数。因此,用extern “C”声明以后,C++的编译器就不会再改变C函数的名字了。C++语言在编译的时候为了解决函数的多态问题,会将函数名和参数联合起来生成一个中间的函数名称,而C语言则不会,因此会造成链接时找不到对应函数的情况,此时C函数就需要用extern “C”进行链接指定,这告诉编译器,请保持我的名称,不要给我生成用于链接的中间函数名。C++支持函数重载,而过程式语言C则不支持26.volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。当要求使用volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。volatile 指出 i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的汇编代码会重新从i的地址读取数据放在b中。而优化做法是,由于编译器发现两次从i读数据的代码之间的代码没有对i进行过操作,它会自动把上次读的数据放在b中。而不是重新从i里面读。这样一来,如果i是一个寄存器变量或者表示一个端口数据就容易出错,所以说volatile可以保证对特殊地址的稳定访问。27. C++的四种强制转型形式每一种适用于特定的目的:   ·dynamic_cast 主要用于执行“安全的向下转型(safe downcasting)”,也就是说,要确定一个对象是否是一个继承体系中的一个特定类型。它是唯一不能用旧风格语法执行的强制转型,也是唯一可能有重大运行时代价的强制转型。    ·static_cast 可以被用于强制隐型转换(例如,non-const 对象转型为 const 对象,int 转型为 double,等等),它还可以用于很多这样的转换的反向转换(例如,void* 指针转型为有类型指针,基类指针转型为派生类指针),但是它不能将一个 const 对象转型为 non-const 对象(只有 const_cast 能做到),它最接近于C-style的转换。  ·const_cast 一般用于强制消除对象的常量性。它是唯一能做到这一点的 C++ 风格的强制转型。   ·reinterpret_cast 是特意用于底层的强制转型,导致实现依赖(implementation-dependent)(就是说,不可移植)的结果,例如,将一个指针转型为一个整数。这样的强制转型在底层代码以外应该极为罕见。28.  char dest[] = "helloworld";//注意     char* src = "hello";     strcpy(dest, src);     这个是正确的,但是如果改成char *dest="helloworld";就是错误的30.strcpy的实现://为了实现链式操作,将目的地址返回,加3分!char * strcpy( char *strDest, const char *strSrc ) { assert( (strDest != NULL) && (strSrc != NULL) ); char *address = strDest;  while( (*strDest++ = * strSrc++) != '\0’ ); //++的优先级比*高  return address;}31.C的struct与C++的struct的区别:C中struct只是作为一种复杂数据类型定义,不能用于面向对象编程,struct中只能定义成员变量,不能定义成员函数。C++中的struct和class的区别:对于成员访问权限以及继承方式,class中默认的是private的,而struct中则是public的。class还可以用于表示模板类型,struct则不行32.高度为h的AVL树,至少包含S(H)=fib(h+3)-1节点//fib是斐波那契数列33.数据库索引采用B+树的主要原因是 B树在提高了磁盘IO性能的同时并没有解决元素遍历的效率低下的问题。正是为了解决这个问题,B+树应运而生。B+树只要遍历叶子节点就可以实现整棵树的遍历。而且在数据库中基于范围的查询是非常频繁的,而B树不支持这样的操作(或者说效率太低)。为表设置索引要付出代价的:一是增加了数据库的存储空间,二是在插入和修改数据时要花费较多的时间(因为索引也要随之变动)B+树只有达到叶子结点才命中(B-树可以在非叶子结点命中),其性能也等价于在关键字全集做一次二分查找;34. 内联函数和宏的区别在于,宏是由预处理器对宏进行替代,而内联函数是通过编译器控制来实现的。而且内联函数是真正的函数,只是在需要用到的时候,内联函数像宏一样的展开,所以取消了函数的参数压栈,减少了调用的开销.#define没有类型检查!不占内存空间35.C++程序经常要调用C函数,而C程序只能用malloc/free管理动态内存。如果用free释放“new创建的动态对象”,   那么该对象因无法执行析构函数而可能导致程序出错。  如果用delete释放“malloc申请的动态内存”,理论上讲程序不会出错,但是该程序的可读性很差。所以new/delete,malloc/free必须配对使用。  1.malloc/free是C/C++语言的标准库函数,new/delete是C++的运算符  2.new能够自动分配空间大小  3.对于用户自定义的对象而言,用maloc/free无法满足动态管理对象的要求。36.为什么要用static_cast转换而不用c语言中的转换?  A* p1 = (A*) &b; // 这句是c风格的强制类型转换,编译不会报错,留下了隐患    A* p2 = static_cast<A*>(&b); // static_cast在编译时进行了类型检查,直接报错应使用static_cast取代c风格的强制类型转换,较安全。37.什么是虚拟内存?虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换38.get和post的区别答:从逻辑上get是安全的,post是不安全的。get一般用于获取数据,post可以用来提交数据,如表单。追问:get不能提交数据么,可以,不过需要跟在url后面,这样安全性会降低,如果使用url传递用户名,密码的话会非常不安全,因为全部为明文的。而且get有最大长度限制,因为url每种浏览器都有一个最大长度。39. const在c/c++中的区别1.C++中的const正常情况下是看成编译期的常量,编译器并不为const分配空间,只是在编译的时候将期值保存在名字表中,并在适当的时候折合在代码中,C中的const会给分配空间,所以在C语言里面const命名的变量,不能直接成为数组的参数2.C++中,const默认使用内部连接.而C中使用外部连接.内连接:编译器只对正被编译的文件创建存储空间,别的文件可以使用相同的表示符      或全局变量.C/C++中内连接使用static关键字指定.因此在不同的编译单元中可以有同名的const 变量定义。C语言中:外连接:所有被编译过的文件创建一片单独存储空间.一旦空间被创建,连接器必须解决对这片存储空间的引用.全局变量和函数使用外部连接,只能有一个同名变量.通过extern关键字声明,可以从其他文件访问相应的变量和函数.C中的const,功能比较单一,较容易理解:作用:被修饰的内容不可更改。使用场合: 修饰变量,函数参数,返回值等。(c++中应用场合要丰富的多)特点: 是运行时const,因此不能取代#define用于成为数组长度等需要编译时常量的情况。同时因为是运行时const,可以只定义而不初始化,而在运行时初始化。如 const int iConst;。 另外,在c中,const变量默认是外部链接,因此在不同的编译单元中如果有同名const变量,会引发命名冲突,编译时报错。c++中的const:跟c中比较,内容要丰富很多,当然,作用也更大了1:非类成员const*在c++中,const变量(在这里涉及的const都不是类中的const,类中的const专门提出来记录)默认是内部连接的,因此在不同的编译单元中可以有同名的const 变量定义。*是编译时常量,因此可以像#define一样使用,而且因为上面一点,可以在头文件中定义const变量,包含的不同的cpp文件(编译单元)中使用而不引起命名冲突。*编译器默认不为const变量分配内存,除非:1. 使用 extern 申明, 2:程序中有引用const 变量的地址。* 可以使用下面的类型转换(不安全的): 1: int * = (int *)pConst 2: int * = const_cast<int*>pConst(c++解const属性cast)* 函数参数或者返回值能使用 const & or const * 时,尽量使用const属性,增强程序健全性。*c++中临时对象/内置变量默认具有const属性2:类中的const*类中的const与c语言中的const一样,只是运行时常量,不能作为数组维数使用,即不能取代#define。在类中使用下面两种方式取代#define: 1:static const... 2: enum{....}//enum 不占存储空间*类中的const 变量占用存储空间*类中的const成员变量需要在构造函数初始化列表中初始化*const 对象:在该对象生命周期内,必须保证没有任何成员变量被改变。const对象只能调用const成员函数。*const成员函数: void fun() const ... 不仅能被const对象调用,也能被非const对象调用,因此,如果确认一个任何成员函数不改变任何成员变量,应该习惯性将该函数定义成const类型。 如果const成员函数需要改变成员变量,有两种实现方式: 1 ,const_cast<class*> this强制取消this指针的const属性。 2:将被改变的成员变量定义成mutable:mutable int i; //应永远只使用第二种方法,让任何阅读程序的人都知道该变量可能被const函数改变。*如果一个对象被定义成const,那么该const对象“可能”会被放入到ROM当中,这在嵌入式开发当中有时非常重要。。。。(不能有任何自定义的constructor 和destructor。它的基类或者成员对象不能有自定义的constructor和destructor,不能有任何mutable成员变量)40.C++的接口和抽象类有什么区别?1.抽象类是只要有1个成员函数是纯虚函数即可,虚基类(接口)则是所有的成员全部是纯虚函数2.另外,在Java中,所有的类(包括抽象类)只能单根继承,即一个派生类只能继承自一个基类(即使基类是抽象类也是如此),这是跟C++最大的不同。但接口可以多继承,即任何一个类都可以继承多个接口41.C++模板的缺点:1.首先,由于C++没有二进制实时扩展性,所以模板不能像库那样被广泛使用。模板的数据类型只能在编译时才能被确定。因此,所有用基于模板算法的实现必须包含在整个设计的头文件中2.可能会造成代码体积膨胀3.,由于模板只是最近加入C++标准中,所以有些C++编译器还不支持模板,当使用这些编译器时编译含有模板的代码时就会发生不兼容问题优点:1.比#define好,能做型式检查,比较安全 2.通用型3.编译时而不是运行时检查数据类型,保证数据安全42.模板代码一般放在头文件中,编译器需要看到模板源代码43.就像大家更熟悉的const一样,volatile是一个类型修饰符(type specifier)。它是被设计用来修饰被不同线程访问和修改的变量。如果不加入volatile,基本上会导致这样的结果:要么无法编写多线程程序,要么编译器失去大量优化的机会。volatile的作用是: 作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值44.分页和分段系统有许多相似之处,但在概念上两者完全不同,主要表现在: 1、页是信息的物理单位,分页是为实现离散分配方式,以消减内存的外零头,提高内存的利用率;或者说,分页仅仅是由于系统管理的需要,而不是用户的需要。 段是信息的逻辑单位,它含有一组其意义相对完整的信息。分段的目的是为了能更好的满足用户的需要。 2、页的大小固定且由系统确定,把逻辑地址划分为页号和页内地址两部分,是由机器硬件实现的,因而一个系统只能有一种大小的页面。 段的长度却不固定,决定于用户所编写的程序,通常由编辑程序在对源程序进行编辑时,根据信息的性质来划分。 3、分页的作业地址空间是维一的,即单一的线性空间,程序员只须利用一个记忆符,即可表示一地址。 分段的作业地址空间是二维的,程序员在标识一个地址时,既需给出段名,又需给出段内地址。45.怎么检测死循环(流程不知道哪里陷入了死循环(提示,可以通过计数打日志的方式来查看哪一行出现了死循环))46.一旦类成员变量出现了指针,就要注意在拷贝构造函数复制内存,不然会发生浅拷贝47.全局对象的构造函数会在main函数之前执行48.预处理(Preprocessing):宏定义,文件包含,条件编译编译(Compilation):扫描(词法分析)、语法分析、语义分析、源代码优化、代码生成、目标代码优化。汇编(Assembly)链接(Linking):静态链接和动态链接。49.Linux创建进程的几种方式系统调用fork()和vfork()是无参数的,而clone()则带有参数。fork()是全部复制,vfork()是共享内存,而clone() 是则可以将父进程资源有选择地复制给子进程,而没有复制的数据结构则通过指针的复制让子进程共享,具体要复制哪些资源给子进程,由参数列表中的 clone_flags来决定。另外,clone()返回的是子进程的pid50.为什么要3次握手防止已过期的连接请求报文突然又传送到服务器,因而产生错误。Client发生一个请求连接报文可能因为网络延迟等原因,没有送达到server中。但是当这个client的请求报文送达到server时,如果没有三次握手的话,server就会直接发数据可client,这样会导致server资源的浪费。51.为什么要4次挥手?确保数据能够完成传输。但关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;但未必你所有的数据都全部发送给对方了,所以你可以未必会马上会关闭SOCKET,也即你可能还需要发送一些数据给对方之后,再发送FIN报文给对方来表示你同意现在可以关闭连接了,所以它这里的ACK报文和FIN报文多数情况下都是分开发送的。52.构造函数为什么不能是虚函数?  虚函数对应一个vtable,这大家都知道,可是这个vtable其实是存储在对象的内存空间的。问题出来了,如果构造函数是虚的,就需要通过 vtable来调用,可是对象还没有实例化,也就是内存空间还没有,无法找到vtable,所以构造函数不能是虚函数。53.new一个对象涉及几个步骤?其中哪个步骤可以通过重载new操作符来修改。 2个步骤,申请内存和调用构造函数,第一个步骤可以通过重载new实现54.int a[100]; delete a; 上述代码是否有问题?int a[100]是在栈上申请了空间,栈是系统自动分配释放的,delete a会破坏栈,所以delete一般是和new一起搭配用的55.private,public,protected,三个权限只是语法糖,编译时候编译器检查,编译器没有权限的概念不信可以试试#define private public;#define protected public;56.数组为什么不能用变量做长度?因为定义数组时,分配空间是需要一个固定的值,来确定你所申请的空间的大小。若int n = 9;int a[n];这样使用,根据编译器的不同,不知道你是用的是什么编译器,编译可能会通过,但是那个n始终是个变量,若n的值改变,则数组大小也会改变,在对其使用的时候就会使用到数组以外的内容,对程序会造成隐患,基本是不会让你编译通过的。57.快排的优化? 尾递归58.union的好处? 省空间,可以测大小端59.void *memset(void *s, int ch, size_t n); //将s中前n个字节用ch替换并返回s假如指针s指向的类对象包含指针成员变量,那么在清零的过程中,就会将该指针的值置为0,不再指向原内存空间,原内存空间得不到释放导致内存泄漏。string类的构造函数在构造string对象时,str_data指针会指向堆中字符串长度加1大小的内存空间,而使用memset函数对string类型对象清零后str_data的值变成了0,指向的原内存空间在析构函数中不会被释放,导致内存泄漏。因此我们可以得出结论:不要轻易零初始化string、vector等STL标准容器及具有动态内存管理的类。60.虚函数(virtual)为啥不能是static静态成员函数,可以不通过对象来调用,即没有隐藏的this指针。virtual函数一定要通过对象来调用,即有隐藏的this指针。static成员没有this指针是关键!static function都是静态决议的(编译的时候就绑定了)而virtual function 是动态决议的(运行时候才绑定)61.static成员函数为什么不能为const这是C++的规则const修饰符用于表示函数不能修改成员变量的值,是针对对象的,该函数必须是含有this指针的类成员函数,函数调用方式为thiscall而类中的static函数本质上是全局函数,调用规约是__cdecl或__stdcall,不能用const来修饰它62.引用从底层上可以看成是受限制的指针,看成int *const pa只要const位于指针声明操作符右侧,就表明声明的对象是一个常量,且它的内容是一个指针,也就是一个地址,并且在声明的时候一定要给它赋初始值。一旦赋值,以后这个常量再也不能指向别的地址。所以这*const pa就是引用的语义pa不能变,就是不能引用其他变量了,只能自始至终引用一个C++中堆引用的说明可以说是一个变量的别名,即这个变量和它的引用共享数据的存储单元,而实际上的操作是传递了指针但是只能说一般是用指针实现,并非必须,因为比如重载操作符返回值为引用的时候不能为指针63.蓄水池抽样算法:假设当前正要读取第n个数据,则我们以1/n的概率留下该数据,否则留下前n-1个数据中的一个。以这种方法选择,所有数据流中数据被选择的概率一样。简短的证明:假设n-1时候成立,即前n-1个数据被返回的概率都是1/n-1,当前正在读取第n个数据,以1/n的概率返回它。那么前n-1个数据中数据被返回的概率为:(1/(n-1))*((n-1)/n)= 1/n,假设成立。例题:给你一个长度为N的链表。N很大,但你不知道N有多大。你的任务是从这N个元素中随机取出k个元素。你只能遍历这个链表一次。你的算法必须保证取出的元素恰好有k个,且它们是完全随机的(出现概率均等)。该算法是针对从一个序列中随机抽取不重复的k个数,保证每个数被抽取到的概率为k/n这个问题而构建的。做法是: -首先构建一个可放k个元素的蓄水池,将序列的前k个元素放入蓄水池中。然后从第k+1个元素开始,以k/n的概率来决定该元素是否被替换到池子中。 当遍历完所有元素之后,就可以得到随机挑选出的k个元素。复杂度为O(n).64.下面定义节点typedef struct ListNode{      int value;      ListNode* next;  }ListNode;  在递归算法中的做法是:1找到最后一个节点和倒数第二个节点,把最后一个节点设为头节点的后继2反转这两个节点3倒数第三个和第四个节点重复执行步骤2其中注意,链表是以节点后继为NULL结束的,在更改指针的过程中要把改后的节点后继改为NULL代码如下:void Inversion_Recursion(ListNode* p,ListNode* Head)  {      if(p->next==NULL)      {          Head->next=p;          return;//找到最后一个节点      }      Inversion_Recursion(p->next,Head);      p->next->next=p;//反转节点      p->next=NULL;//第一个节点反转后其后继应该为NULL  }  64.C语言泛型实现swap()void Swap(void* vp1, void* vp2, int size)  {      void* p = (void*)malloc(size);      assert(p != NULL);      memcpy(p, vp1, size);      memcpy(vp1, vp2, size);      memcpy(vp2, p, size);      free(p);  }  65.快速排序的优化:1.三数取中(median-of-three)引入的原因:虽然随机选取枢轴时,减少出现不好分割的几率,但是还是最坏情况下还是O(n^2),要缓解这种情况,就引入了三数取中选取枢轴举例:待排序序列为:8 1 4 9 6 3 5 2 7 0左边为:8,右边为0,中间为6.我们这里取三个数排序后,中间那个数作为枢轴,则枢轴为62.当待排序序列的长度分割到一定大小后,使用插入排序。3.在一次分割结束后,可以把与Key相等的元素聚在一起,继续下次分割时,不用再对与key相等元素分割4.优化递归操作,快排函数在函数尾部有两次递归操作,我们可以对其使用尾递归优化66.ISO C++ 11 标准的一大亮点是引入Lambda表达式。基本语法如下:[capture list] (parameter list) ->return type { function body }[1] 其中除了“[ ]”(其中捕获列表可以为空)和“复合语句”(相当于具名函数定义的函数体),其它都是可选的。它的类型是唯一的具有成员operator()的非联合的类类型,称为闭包类型(closure type)。C++中,一个lambda表达式表示一个可调用的代码单元。我们可以将其理解为一个未命名的内联函数。它与普通函数不同的是,lambda必须使用尾置返回来指定返回类型。例如调用<algorithm>中的std::sort,ISO C++ 98 的写法是要先写一个compare函数:bool compare(int&a,int&b){return a>b;//降序排序}然后,再这样调用:sort(a,a+n,compare);然而,用ISO C++ 11 标准新增的Lambda表达式,可以这么写:sort(a,a+n,[](int a,int b){return a>b;});//降序排序这样一来,代码明显简洁多了。由于Lambda的类型是唯一的,不能通过类型名来显式声明对应的对象,但可以利用auto关键字和类型推导:auto f=[](int a,int b){return a>b;};和其它语言的一个较明显的区别是Lambda和C++的类型系统结合使用,如:auto f=[x](int a,int b){return a>x;});//x被捕获复制int x=0,y=1;auto g=[&](int x){return ++y;});//y被捕获引用,调用g后会修改y,需要注意y的生存期bool(*fp)(int,int)=[](int a,int b){return a>b;});//不捕获时才可转换为函数指针Lambda表达式可以嵌套使用。即将出版的ISO C++14支持基于类型推断的泛型lambda表达式。上面的排序代码可以这样写:sort(a,a+n,[](const auto&a,const auto&b){return a>b;});//降序排序:不依赖a和b的具体类型因为参数类型和函数模板参数一样可以被推导而无需和具体参数类型耦合,有利于重构代码;和使用auto声明变量的作用类似,它也允许避免书写过于复杂的参数类型。特别地,不需要显式指出参数类型使使用高阶函数变得更加容易。67.四次挥手过程,如果主动关闭方没有等待2MSL,会如何?被动方收到ACK,关闭连接。但是主动方无法知道ACK是否已经到达B,于是开始等待?等待什么呢?假如ACK没有到达被动方,主动方会为FIN这个消息超时重传 timeout retransmit ,那如果主动方等待时间足够,又收到FIN消息,说明ACK没有到达被动方,于是再发送ACK,直到在足够的时间内没有收到FIN,说明ACK成功到达。这个等待时间至少是:被动方的timeout + FIN的传输时间,为了保证可靠,采用更加保守的等待时间2MSL。68.服务器与服务器之间传输文件夹下的文件,一个文件夹下有10个文件,另一个文件夹下有100个文件,两个文件夹大小相等,问,哪个传输更快?(我答的10个文件更快,因为建立连接数更少,建立连接的开销比传输文件的开销大。事后讨论下,还有另一个,文件写入磁盘,要计算文件的起始位置,文件数目少的话,这个开销就小了。)69.HTTP状态码:1消息(1字头) 成功(2字头) 重定向(3字头)  请求错误(4字头) 服务器错误(5、6字头)70.快排优化:1.当待排序序列的长度分割到一定大小后,使用插入排序2.在一次分割结束后,可以把与Key相等的元素聚在一起,继续下次分割时,不用再对与key相等元素分割3.三数取中4.优化递归操作71.分组数据包在网络中怎么传输的(这个大部分是默认网关,我是没有答上来)72. 如果一个变量是局部变量,那么每个线程都会有一个该局部变量的拷贝,一个线程对该局部变量的改变不会影响到其他的线程。 也就是说,多线程会影响全局变量和静态变量(局部和非局部),但是不会影响普通的局部变量
0 0
原创粉丝点击