zoukankan      html  css  js  c++  java
  • [找工作]程序员面试宝典【笔记】(part 1)

    v  题型:

    数据结构(链表,数,图)、C++编程基础、数组、递归、矩阵、内存管理、时间复杂度、TCP/IP、操作系统、计算机网络、UML、OOA&OOP、自己的项目

    v  swap的几种写法:

    void swap(int* a, int* b)

    {  int temp;

      temp = *a;

      *a = *b;

      *b = temp; }

    {  *a = *a + *b;

       *b = *a - *b;

       *a = *a - *b; }

    {  *a = *a ^ *b;

       *b = *b ^ *a;

       *a = *a ^ *b; }

    v  如何在C++中调用C程序:

    C++和C是两种完全不同的编译链接处理方式,如果直接在C++里面调用C函数,会找不到函数体,报链接错误。要解决这个问题,就要在 C++文件里面显示声明一下哪些函数是C写的,要用C的方式来处理。
    1.引用头文件前需要加上 extern “C”,如果引用多个,那么就如下所示
    extern “C”
    {
    #include “ s.h”
    #include “t.h”
    #include “g.h”
    #include “j.h”
    };
    然后在调用这些函数之前,需要将函数也全部声明一遍。
    2.C++调用C函数的方法,将用到的函数全部重新声明一遍
    extern “C”
    {
    extern void A_app(int);
    extern void B_app(int);
    extern void C_app(int);
    extern void D_app(int);

    }

    C++程序中调用被c编译器编译后的函数,为什么要加extern "C"?

    C++语言支持函数重载,C语言不支持函数重载。函数被C++编译后在库中的名字与C语言的不同。假设某个C 函数的声明如下:
    void foo(int x, int y);
    该函数被C 编译器编译后在库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字用来支持函数重载和类型安全连接。由于编译后的名字不同,C++程序不能直接调用C 函数。C++提供了一个C 连接交换指定符号extern“C”来解决这个问题。例如:
    extern “C”
    {
    void foo(int x, int y);
    // 其它函数
    }
    或者写成
    extern “C”
    {
    #include “myheader.h”
    // 其它C 头文件
    }
    这就告诉C++编译译器,函数 foo 是个C 连接,应该到库中找名字_foo 而不是找_foo_int_int。C++编译器开发商已经对C 标准库的头文件作了extern“C”处理,所以我们可以用#include直接引用这些头文件。

    v  操作符优先级

    优先级

    运算符

    名称或含义

    使用形式

    结合方向

    说明

    1

    []

    数组下标

    数组名[常量表达式]

    左到右

     

    ()

    圆括号

    (表达式)/函数名(形参表)

     

    .

    成员选择(对象)

    对象.成员名

     

    ->

    成员选择(指针)

    对象指针->成员名

     

    2

    -

    负号运算符

    -表达式

    右到左

    单目

    (类型)

    强制类型转换

    (数据类型)表达式

     

    ++

    自增运算符

    ++变量名/变量名++

    单目

    --

    自减运算符

    --变量名/变量名--

    单目

    *

    取值运算符

    *指针变量

    单目

    &

    取地址运算符

    &变量名

    单目

    !

    逻辑非运算符

    !表达式

    单目

    ~

    按位取反运算符

    ~表达式

    单目

    sizeof

    长度运算符

    sizeof(表达式)

     

    3

    /

    表达式/表达式

    左到右

    双目

    *

    表达式*表达式

    双目

    %

    余数(取模)

    整型表达式/整型表达式

    双目

    4

    +

    表达式+表达式

    左到右

    双目

    -

    表达式-表达式

    双目

    5

    << 

    左移

    变量<<表达式

    左到右

    双目

    >> 

    右移

    变量>>表达式

    双目

    6

    大于

    表达式>表达式

    左到右

    双目

    >=

    大于等于

    表达式>=表达式

    双目

    小于

    表达式<表达式

    双目

    <=

    小于等于

    表达式<=表达式

    双目

    7

    ==

    等于

    表达式==表达式

    左到右

    双目

    !=

    不等于

    表达式!= 表达式

    双目

    8

    &

    按位与

    表达式&表达式

    左到右

    双目

    9

    ^

    按位异或

    表达式^表达式

    左到右

    双目

    10

    |

    按位或

    表达式|表达式

    左到右

    双目

    11

    &&

    逻辑与

    表达式&&表达式

    左到右

    双目

    12

    ||

    逻辑或

    表达式||表达式

    左到右

    双目

    13

    ?:

    条件运算符

    表达式1? 表达式2: 表达式3

    右到左

    三目

    14

    =

    赋值运算符

    变量=表达式

    右到左

     

    /=

    除后赋值

    变量/=表达式

     

    *=

    乘后赋值

    变量*=表达式

     

    %=

    取模后赋值

    变量%=表达式

     

    +=

    加后赋值

    变量+=表达式

     

    -=

    减后赋值

    变量-=表达式

     

    <<=

    左移后赋值

    变量<<=表达式

     

    >>=

    右移后赋值

    变量>>=表达式

     

    &=

    按位与后赋值

    变量&=表达式

     

    ^=

    按位异或后赋值

    变量^=表达式

     

    |=

    按位或后赋值

    变量|=表达式

     

    15

    ,

    逗号运算符

    表达式,表达式,…

    左到右

    从左向右顺序运算

    v  ::在C++中是什么意思 

    ::是运算符中等级最高的,它分为三种:
    1)global scope(全局作用域符),用法(::name)
    2)class scope(类作用域符),用法(class::name)
    3)namespace scope(命名空间作用域符),用法(namespace::name)
    他们都是左关联(left-associativity)
    他们的作用都是为了更明确的调用你想要的变量,如在程序中的某一处你想调用全局变量a,那么就写成::a,如果想调用class A中的成员变量a,那么就写成A::a,另外一个如果想调用namespace std中的cout成员,你就写成std::cout(相当于using namespace std;cout)意思是在这里我想用cout对象是命名空间std中的cout(即就是标准库里边的cout)

    v  i++

    !x++;     !与++优先级相同,先计算!x;然后计算x++; (原题:P35)

    v  指针+1的问题

    指针变量加1,即向后移动1 个位置表示指针变量指向下一个数据元素的首地址。而不是在原地址基础上加1。至于真实的地址加了多少,要看原来指针指向的数据类型是什么。

    p指向的是一个字符,p+1就是移动一个字符大小,一个字符就是一个字节,所以p +1 代表的地址就比 p 代表的地址大1。

    p指向的是一个整型,p+1就是移动一个整型大小,即移动4个字节,所以p+1代表的地址比p代表的地址大4.

    v  x++与++x

    #include <stdio.h>

    void main()

    {

        int arr[] = { 6, 7, 8, 9, 10 };

        int *ptr = arr;

        *(ptr++) += 123;//equal:*ptr=*ptr+123;ptr++;

        printf("%d,%d ",*ptr,*(++ptr));//prinft计算参数时从右向左入栈,先执行++ptr;

    }

    RESULT:8,8

    X++先执行运算最后++;++X则先++然后执行其他运算;

    v  隐式类型转换发生在下列这些典型的情况下:

    1. 在混合类型的算术表达式中(最宽的数据类型成为目标类型)。
    2. 用一种类型的表达式赋值给另一种类型的对象。
    3. 把一个表达式传递给一个函数,调用表达式的类型与形式参数的类型不同。
    4. 从一个函数返回一个表达式的类型与返回类型不相同。

    v  判断一个数X是否是2N次方,不可用循环语句:

    !(X&(X-1))==0

    v  Const

    在C程序中,const的用法主要是定义常量、修饰函数参数、修饰函数返回值,在C++中,它还可以修饰函数的定义体,定义类中某个成员函数为恒态函数,即不改变类中的数据成员

    被Const修饰的东西可以受到强制保护,预防意外的变动,能提高程序的健壮性

    Const与#define相比有什么不同?

    Const常量有数据类型,而宏常量没有数据类型,编译器可以对前者进行类型安全检查,而对后者只能进行字符替换,没有类型安全检查,并且在字符替换中可能产生意料不到的错误

    调试工具能对const常量进行调试,但是无法对define常量调试

    No.

    作用

    说明

    参考代码

    1

    可以定义const常量

     

    const int Max = 100; 

    2

    便于进行类型检查

    const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查,而对后者只进行字符替换,没有类型安全检查,并且在字符替换时可能会产生意料不到的错误

    void f(const int i) { .........}
          //对传入的参数进行类型检查,不匹配进行提示

    3

    可以保护被修饰的东西

    防止意外的修改,增强程序的健壮性。

    void f(const int i) { i=10;//error! }
          //如果在函数体内修改了i,编译器就会报错

    4

    可以很方便地进行参数的调整和修改

    同宏定义一样,可以做到不变则已,一变都变

     

    5

    为函数重载提供了一个参考

     

    class A
    {
               ......
      void f(int i)       {......} //一个函数
      void f(int i) const {......} //上一个函数的重载
               ......
    };

    6

    可以节省空间,避免不必要的内存分配

    const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是象#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干个拷贝

    #define PI 3.14159         //常量宏
    const doulbe  Pi=3.14159;  //此时并未将Pi放入ROM中
                  ......
    double i=Pi;   //此时为Pi分配内存,以后不再分配!
    double I=PI;  //编译期间进行宏替换,分配内存
    double j=Pi;  //没有内存分配
    double J=PI;  //再进行宏替换,又一次分配内存!

    7

     提高了效率

    编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高

     

    const int *A; 或 int const *A;  //const修饰指向的对象,A可变,A指向的对象不可变
    int *const A;               //const修饰指针A, A不可变,A指向的对象可变
    const int *const A;           //指针A和A指向的对象都不可变

    v  sizeof与Strlen()

    sizeof    单位:字节

    sizeof(...)是运算符,而不是一个函数。一个简单的例子:
    int a;
    cout<<sizeof a<<endl;

    在头文件中typedef为unsigned int,其值在编译时即计算好了,参数可以是数组、指针、类型、对象、函数等。

    它的功能是:获得保证能容纳实现所建立的最大对象的字节大小

    由于在编译时计算,因此sizeof不能用来返回动态分配的内存空间的大小。
    实际上,用sizeof来返回类型以及静态分配的对象、结构或数组所占的空间,返回值跟对象、结构、数组所存储的内容没有关系。
    具体而言,当参数分别如下时,sizeof返回的值表示的含义如下:数组——编译时分配的数组空间大小;
    指针——存储该指针所用的空间大小(存储该指针的地址的长度,是长整型,应该为4);类型——该类型所占的空间大小;

    对象——对象的实际占用空间大小;
    函数——函数的返回类型所占的空间大小。函数的返回类型不能是void。
    ********************************************************************

    strlen
    strlen(...)是函数,要在运行时才能计算。

    参数必须是字符型指针(char*, 且必须是以''结尾的。当数组名作为参数传入时,实际上数组就退化成指针了。

    int ac[10];

    cout<<sizeof(ac)<<endl;
    cout<<strlen(ac)<<endl;     (ac相当于一个指针,但是strlen只能接受char*类型,所以编译时出错)

    它的功能是:返回字符串的长度。该字符串可能是自己定义的,也可能是内存中随机的,该函数实际完成的功能是从代表该字符串的第一个地址开始遍历,直到遇到结束符''。返回的长度大小不包括''。

    v  结构体长度对齐

    在默认情况下,为了方便对结构体内元素的访问和管理,当结构体内的元素长度都小于处理器的位数的时候,便以结构体里面最长的数据元素为对其单位,也就是说,结构体的长度一定是最长的数据元素的整数倍;如果结构体内存在长度大于处理器位数的元素,那么就以处理器的位数为对齐单位。但是结构体内类型相同的连续元素将在连续的空间内,和数组一样。

    CPU的优化规则大致是这样:对于n字节的元素(n=2,4,8,…),他的首地址能被n整除,才能获得最好的性能。(个人感觉是如果不是这样,那么一个元素很有可能会跨页存储,存在两个不同的页面上,导致访问速度慢)

    v  对其是一种以空间换时间的方法,在访问内存时,如果地址按4字节对齐,则访问效率会高很多,这种现象的原因在于访问内存的硬件电路。一般情况下,地址总线总是按照对齐后的地址来访问的。例如你想得到0x00000001开始的4字节内容,系统需先以0x00000000读4个字节,从中取3字节,然后再用0X00000004最为开始地址,获得下一个四字节,再从中得到第一个字节,两次组合出你想要的内容。但是如果地址一开始就是对齐到0x00000000,则系统只需一次内存读写即可。

    v  对其的自定义设置

    #pragma pack(1)

    struct aStruct…{…};

    #pragma pack()

    v  字节对齐是在编译时决定的,不会再发生变化,在运行时不会再发生变化

    v  静态变量存放在全局数据区,而sizeof计算栈中分配的大小,是不会计算static变量的。

    v  数据类型的长度:

    short  -------------- 2

    int,long,float,指针---4

    double -------------- 8

    v  sizeof vs strlen

    sizeof是算符,strlen是函数

    sizeof可以用类型做参数,strlen只能用char*做参数,且必须是以“”结尾的。

    数组做sizeof的参数不退化,传递给strlen就退化为了指针

    sizeof在编译时计算,strlen在运算时计算

    对函数使用sizeof,在编译阶段会被函数返回值类型取代。

    v  sizeof不是函数,也不是一元运算符,他是个类似宏定义的特殊关键字,sizeof()括号内的内容是不被编译的,只是替换,所以a=8;sizeof(a=6);之后a的值仍然为8。

    v  unsigned影响的仅仅是最高位bit的意义(正/负),而不会影响数据长度。

    v  空类所占空间为1,单一继承的空类空间也为1,多重继承的空类空间还是1,但是虚继承涉及到虚表(虚指针),所以空间大小为4

    v  内联函数和宏定义

    内联函数和普通函数相比可以加快程序运行的速度,因为不需要中断调用,在编译的时候内联函数可以直接被镶嵌到目标代码中,而宏只是一个简单的替换;内联函数要做参数类型检查,这是inline和宏相比的优势;

    inline一般只用于如下情况:

    1. 一个函数不断被重复调用;
    2. 函数只有简单的几行,且函数内不包括for/while/switch语句;

    —————————————————————————————————————————————————————————————————————————————————————————

    v  指针问题:包括常量指针、数组指针、函数指针、this指针、指针传值、指向指针的指针等问题

    v  指针和引用的区别

    非空区别:在任何情况下都不能使用指向空值的引用;

    合法性区别:在使用引用之前不需要测试它的合法性;

    可修改区别:指针可以被重新赋值以指向另一个不同的对象,但是引用则总是指向初始化时被指定的对象,以后不能改变,但是指定的内容可以改变;

    应用区别:使用指针:存在不指向任何对象的可能;需要 在不同的时刻指向不同的对象;使用引用:总是指向一个对象并且一旦指向一个对象后就不会改变指向。

    v  引用的初始化:

    声明一个引用时,必须同时初始化,如同const常量必须声明的同时初始化。

    v  char c[] vs char *c

    char c[]分配的是局部数组,而char *c分配的是全局数组

    v  函数指针

    函数指针

    void (*f)()

    函数返回指针

    void* f()

    const指针

    cons int *

    指向const的指针

    int * const

    指向const的const指针

    cons int* const

    v  malloc和free是C/C++的标准库函数,new/delete是C/C++的运算符。他们都可以用于动态申请内存和释放内存。对于非内部数据类型的对象而言,光用malloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限范围内,不能把执行构造函数和析构函数的任务强加于malloc/free

    v  数组指针和指针数组:

    数组指针

    指针数组

    int (*a)[]

    int *a[]

    —————————————————————————————————————————————————————————————————————————————————————————

    v  STL模版与容器

    _________________

    v  STL的优点:

    • 方便容易的实现搜索数据或对数据排序等一系列的算法
    • 调试程序是更安全和方便
    • 即使是人们用STL在UNIX平台下写的代码,你也可以很容易理解

    v  STL中的一些基本概念

    • 模版(Template)类的宏(macro),正规名:泛型,类的模版-泛型类,函数模版-泛型函数
    • STL标准模版库,一些聪明人自己写的一些模版
    • 容器(Container):可容纳一些数据的模版类,STL中有vector, set, map, multimap 和 deque等容器
    • 向量(Vector):基本数组模版,是一个容器
    • 游标(Iterator):它是一个指针,用来指向STL容器中的元素,也可以指向其他元素

    v  容器向量:标准模板库是一个基于模版的容器类库,包括链表、列表、队列和堆栈;标准模板库还包括许多常用的算法,包括排序和查找;可重用;容器是包括其他对象的对象;标准模版库类有两种类型:顺序和关联;不同OS间可移植;

    v  vector的操作:

    标准库vector类型使用需要的头文件:#include <vector>。vector 是一个类模板。不是一种数据类型,vector<int>是一种数据类型。Vector的存储空间是连续的,list不是连续存储的。

    • 定义和初始化
      vector< typeName > v1;       //默认v1为空,故下面的赋值是错误的v1[0]=5;
      vector<typeName>v2(v1);  或v2=v1 ; 或vector<typeName> v2(v1.begin(), v1.end());//v2是v1的一个副本,若v1.size()>v2.size()则赋值后v2.size()被扩充为 v1.size()。
      vector< typeName > v3(n,i);//v3包含n个值为i的typeName类型元素
      vector< typeName > v4(n); //v4含有n个值为0的元素
      int a[4]={0,1,2,3,3}; vector<int> v5(a,a+5);//v5的size为5,v5被初始化为a的5个值。后一个指针要指向将被拷贝的末元素的下一位置。
      vector<int> v6(v5);//v6是v5的拷贝。
      vector< 类型 > 标识符(最大容量,初始所有值)。
    • 值初始化
      1>     如果没有指定元素初始化式,标准库自行提供一个初始化值进行值初始化。
      2>     如果保存的是含有构造函数的类类型的元素,标准库使用该类型的构造函数初始化。
      3>     如果保存的是没有构造函数的类类型的元素,标准库产生一个带初始值的对象,使用这个对象进行值初始化。
    • vector对象最重要的几种操作
      1. v.push_back(t)    在容器的最后添加一个值为t的数据,容器的size变大。另外list有push_front()函数,在前端插入,后面的元素下标依次增大。
      2. v.size()    返回容器中数据的个数,size返回相应vector类定义的size_type的值。v.resize(2*v.size)或v.resize(2*v.size, 99) 将v的容量翻倍(并把新元素的值初始化为99)
      3. v.empty()     判断vector是否为空
      4. v[n]           返回v中位置为n的元素
      5. v.insert(pointer,number, content)    向v中pointer指向的位置插入number个content的内容。
       还有v. insert(pointer, content),v.insert(pointer,a[2],a[4])将a[2]到a[4]三个元素插入。
      6. v.pop_back()    删除容器的末元素,并不返回该元素。
      7.v.erase(pointer1,pointer2) 删除pointer1到pointer2中间(包括pointer1所指)的元素。vector中删除一个元素后,此位置以后的元素都需要往前移动一个位置,虽然当前迭代器位置没有自动加1,但是由于后续元素的顺次前移,也就相当于迭代器的自动指向下一个位置一样。
      8. v1==v2          判断v1与v2是否相等。
      9. !=、<、<=、>、>=      保持这些操作符惯有含义。
      10. vector<typeName>::iterator p=v1.begin( ); p初始值指向v1的第一个元素。*p取所指向元素的值。对于const vector<typeName>只能用vector<typeName>::const_iterator类型的指针访问。
      11.   p=v1.end( ); p指向v1的最后一个元素的下一位置。
      12.v.clear()    删除容器中的所有元素。
    • #include<algorithm>中的泛函算法
      搜索算法:find() 、search() 、count() 、find_if() 、search_if() 、count_if()
      分类排序:sort() 、merge()
      删除算法:unique() 、remove()
      生成和变异:generate() 、fill() 、transformation() 、copy()
      关系算法:equal() 、min() 、max()
      sort(v1.begin(),vi.begin()+v1.size/2); 对v1的前半段元素排序
      list<char>::iterator pMiddle =find(cList.begin(),cList.end(),'A');找到则返回被查内容第一次出现处指针,否则返回end()。
      vector< typeName >::size_type x ; vector< typeName >类型的计数,可用于循环如同for(int i)

    初学C++的程序员可能会认为vector的下标操作可以添加元素,其实不然:

    vector<int> ivec;   // empty vector

    for (vector<int>::size_type ix = 0; ix != 10; ++ix)

         ivec[ix] = ix; // disaster: ivec has no elements

    上述程序试图在ivec中插入10个新元素,元素值依次为0到9的整数。但是,这里ivec是空的vector对象,而且下标只能用于获取已存在的元素。

    这个循环的正确写法应该是:

    for (vector<int>::size_type ix = 0; ix != 10; ++ix)

         ivec.push_back(ix); // ok: adds new element with value ix
    警告:必须是已存在的元素才能用下标操作符进行索引。通过下标操作进行赋值时,不会添加任何元素,仅能对确知已存在的元素进行下标操作  。

    v  泛型编程

    何谓泛型编程:STL代表用一致的方式编程是可能的;泛型编程是一种基于发现高效算法的最抽象表示的编程方法;STL是一个泛型编程的例子,C++是我们可以实现令人信服的例子的语言。

    v  模版

    一个函数在编译时被分配给一个入口地址,这个入口地址就称为函数的指针,正如指针是一个变量的地址一样。(经测试:函数名和对函数名取地址得到的是同一个值,都可以作为该函数的指针)。有些地方必须使用函数指针才能完成给定的任务,特别是异步操作的回调和其他需要匿名回调的结构。另外,想线程的执行和时间的处理,如果缺少了函数的支持也是很难完成的。

    —————————————————————————————————————————————————————————————————————————————————————————————

    v  面向对象(Object-Oriented)

    编程是在计算机中反应世界

    面向对象的优点:良好的可复用性、易维护、良好的可扩充性

    面向对象技术的基本概念是:对象、类和继承

    C++中的空类默认产生的成员函数:

    默认构造函数、析构函数、拷贝构造函数、赋值函数

    C++中struct也可以有constructor/destructor及成员函数,它和class的区别是:class的默认访问控制是private,struct中默认访问控制是public。

    在一个类中,初始化列表的初始化变量顺序是根据成员变量的声明顺序来执行的。

    MFC库中,为什么CObject的析构函数是虚函数?

    保证在任何情况下,不会出现由于析构函数未被调用而导致内存泄漏

    析构函数可以是内联函数,但内联函数不能为virtual

    多态:允许将子类类型的指针赋值给父类类型的指针,多态在Object Pascal和C++中都是通过虚函数(Virtual Function)实现的。

    • overload vs override:
      • overload: 范围相同,参数不同,静态,函数调用在编译期间确定,早绑定,与面向对象无关,与多态无关。
      • override:范围不同,参数相同,子类重新定义父类的虚函数,函数动态调用、运行期绑定。

    实现代码重用

    封装:隐藏实现细节,使得代码模块化

    继承:扩展已存在的代码模块(类)

    多态:同一操作作用于不同对象上,产生不同的解释和执行结果—实现接口重用

    友元函数:定义在类外部的普通函数,但他需要在类体内说明,为了与该类的成员函数区别,说明时需要在前面加上friend来区别;他虽然不是成员函数,但却可以访问类的私有变量。

    友元还可以是类,称为友元类

    继承:可以使一个新类获得其父类的操作和数据结构,程序员只需在新类中增加原有类中没有的成分。

    为了不破坏类的封装性,所有操作应该通过接口进行沟通。

    类中的static变量存在数据段,不在类的实例空间中。

  • 相关阅读:
    KVM快速构建虚拟机
    工程师测试
    配置SMB,NFS
    Shell脚本基础应用
    Web网页部署
    基础邮件,mariadb数据库
    SElinux,firewalld配置
    Linux管理员测试
    磁盘分区
    配置权限,LDAP
  • 原文地址:https://www.cnblogs.com/sevensd/p/5502924.html
Copyright © 2011-2022 走看看