剑指 offer 笔记(二)

12 篇文章 0 订阅

第二章 面试需要的基础知识

面试官谈基础知识


  • C++ 的基础知识,如面向对象的特性构造函数析构函数动态绑定等,能够反映出应聘者是否善于把握问题本质,有没有耐心深入一个问题。另外还有常用的设计模式UML 图等,这些都能体现应聘者是否有软件工程方面的经验。

  • 对基础知识的考查需要特别重视 C++ 中对内存的使用管理。内存管理是 C++ 程序员特别注意的,因为内存的使用和管理会影响程序的效率和稳定性

  • 基础知识反映了一个人的基本能力和基础素质,是以后工作中最核心的能力要求。一般考查:(1)数据结构和算法;(2)编程能力;(3)部分数学知识,如概率;(4)问题的分析和推理能力

  • 比较重要的四块基础知识:(1)编程基本功,尤其是字符串处理这一类的问题;(2)并发控制;(3)算法、复杂度;(4)语言的基本概念

  • 一般会考查编程基础计算机系统基础知识算法及设计能力。这些是成为一个软件工程师的最基本的要求,这些方面表现出色的人,一般认为是有发展潜力的。

  • 最重要的基础知识包括三大块:(1)对 OS 的理解程度。这些知识对于工作中常遇到的内存管理、文件操作、程序性能、多线程、程序安全等有重要帮助。对于 OS 理解比较深入的人对于偏底层的工作上手一般比较快。(2)对于一门编程语言的掌握程度。一个热爱编程的人应该会对某种语言有比较深入的了解。通常这样的人对于新的编程语言上手也比较快,而且理解比较深入。(3)常用的算法和数据结构

编程语言


  • 在面试过程中,面试官要么直接问语言的语法,要么让应聘者用一种编程语言写代码解决一个问题,通过写出的代码来判断应聘者对他使用的语言的掌握程度。

  • 做底层开发比如经常写驱动的人更习惯用 C,Linux 下有很多程序员用 C++ 开发应用程序,基于 Windows 的 C# 项目已经越来越多,跨平台开发的程序员则可能更喜欢 Java,随着苹果 ipad、iphone 的热销已经有很多程序员投向了 Objective C 的阵营,同时还有很多人喜欢用脚本语言如 Perl、Python 开发短小精致的小应用软件。

C++

  • C++ 是各大公司面试的首选编程语言,应聘者不管去什么公司求职,都应该在一定程度上掌握 C++。

  • 通常语言面试有三种类型

    • 第一种题型是面试官直接询问应聘者对 C++ 概念的理解
      • 这种类型的问题,面试官特别喜欢了解应聘者对 C++ 关键字的理解程度
      • 例题:
        • 定义一个空的类型,里面没有任何成员变量和成员函数。对该类型求 sizeof,得到的结果是多少?
        • 如果在该类型中添加一个构造函数和析构函数,再对该类型求 sizeof,得到的结果又是多少?
        • 如果把析构函数标记为虚函数呢?
      • 解析:
        • 答案是1。空类型的实例中不包含任何信息,本来求 sizeof 应该是0,但是当我们声明该类型的实例时,它必须在内存中占有一定的空间,否则无法使用这些实例。至于占用多少内存,由编译器决定。一般每个空类型的实例占用1字节的空间。
        • 答案是1。调用构造函数和析构函数只需要知道函数的地址即可,而这些函数的地址只与类型有关,而与类型的实例无关,编译器也不会因为这两个函数而在实例内添加任何额外的信息。
        • C++ 的编译器一旦发现一个类型中有虚函数,就会为该类型生成虚函数表,并在该类型的每一个实例中添加一个指向虚函数表的指针。在32位的机器上,一个指针占4字节的空间,因此求 sizeof 得到4;如果是64位的机器,则一个指针占8字节的空间,因此求 sizeof 得到8。
    • 第二种题型就是面试官拿出事先准备好的代码,让应聘者分析代码的运行结果
      • 这种题型选择的代码通常包含比较复杂微妙的语言特性,这要求应聘者对 C++ 考点有着透彻的理解。即使应聘者对考点有一点点模糊,那么最终他得到的结果和实际运行的结果可能就会差距甚远。

      • 例题:

        • 要求分析下列代码编译运行的结果,并选择:A、编译错误;B、编译成功,运行时程序崩溃;C、编译运行正常,输出10。
          class A{
              private:
                  int value;
              public:
                  A(int n){
                      value = n;
                  }
                  A(A other){
                      value = other.value;
                  }
                  void Print(){
                      std::cout << value << std::endl;
                  }
          };
          
          int _tmain(int argc, _TCHAR* argv[]){
              A a = 10;
              A b = a;
              b.Print();
              return 0;
          }
          
      • 解析:

        • 在上述代码中,复制构造函数 A(A other) 传入的参数是 A 的一个实例;
        • 由于是传值参数,我们把形参复制到实参会调用复制构造函数;
        • 因此,如果允许复制构造函数传值,就会在复制构造函数内调用复制构造函数,就会形成永无止境的递归调用从而导致栈溢出;
        • 因此,C++ 的标准不允许复制构造函数传值参数,编译将会出错;
        • 要解决这个问题,我们可以把构造函数修改为 A(const A& other),也就是把传值参数改成常量引用。
    • 第三种题型就是要求应聘者写代码定义一个类型或者实现类型中的成员函数
      • 让应聘者写代码的难度自然比让应聘者分析代码要高不少,因为能想明白的未必就能写得清楚;
      • 很多考查 C++ 语法的代码题重点考查构造函数、析构函数及运算符重载。
  • 推荐阅读的 C++ 书籍:

    • 《Effective C++》:适合面试之前突击 C++,列举了使用 C++ 经常出现的问题及解决这些问题的技巧;
    • 《C++ Primer》:全面了解 C++ 的语法;
    • 《深度探索 C++ 对象模型》:有助于深入了解 C++ 对象的内部。
    • 《The C++ Programming Language》:全面深入掌握 C++。

C#

  • C# 可以看成一门以 C++ 为基础发展起来的托管语言,因此它的很多关键字甚至语法都和 C++ 类似。

  • 虽然学习 C# 与 C++ 相同或者类似的部分很容易,但要掌握并区分两者不同的地方却不是一件很容易的事情。

  • 面试官总是喜欢深究模棱两可的地方,因此要着重注意 C# 与 C++ 不同的语法特点。

    • 例题:
      • 在 C++ 中可以用 struct 和 class 来定义类型。这两种类型有什么区别?
      • 如果在 C# 中呢?
    • 解析:
      • 如果没有标明成员函数或者成员变量的访问权限级别,那么在 struct 中默认的是 public,而在 class 中默认的是 private。
      • C# 与 C++ 不一样。在 C# 中如果没有标明成员函数或者成员变量的访问权限级别,则在 struct 和 class 中都是 private 的。struct 和 class 的区别是 struct 定义的是值类型,值类型的实例在栈上分配内存;而 class 定义是引用类型,引用类型的实例在堆上分配内存。
  • 和 C++ 一样,在 C# 中,每个类型都有构造函数。但和 C++ 不同的是,C# 中可以为类型定义一个 Finalizer 和 Dispose 方法以释放资源。

    • Finalizer 方法虽然写法与 C++ 的析构函数看起来一样,都是 ~ 后面跟类型名,但与析构函数的调用时机却不同,C# 的 Finalizer 是在运行时(CLR)进行垃圾回收时才会被调用的,它的调用时机是由运行时决定的,因此对程序员来说是不确定的;
    • 在 C# 中可以为类型定义一个特殊的构造函数:静态构造函数。这个函数的特点是在类型第一次被使用之前由运行自动调用,而且保证只调用一次。
    • 例题:
      • 运行下面的 C# 代码,输出的结果是什么?
        class A{
            public A(string text){
                Console.WriteLine(text);
            }
        }
        
        class B{
            static A a1 = new A("a1");
            A a2 = new A("a2");
        
            static B(){
                a1 = new A("a3");
            }
        
            public B(){
                a2 = new A("a4");
            }
        }
        
        class Program{
            static void Main(String[] args){
                B b = new B();
            }
        }
        
    • 解析:
      • 在调用类型 B 的代码之前先执行 B 的静态构造函数;
      • 静态构造函数先初始化类型的静态变量,再执行函数体内的语句;
      • 因此,先打印 a1 再打印 a3;
      • 接下来执行 B b = new B(),即调用 B 的普通构造函数;
      • 构造函数先初始化成员变量,再执行函数体内的语句;
      • 因此,得到的结果将是打印出4行,分别是 a1、a3、a2、a4。
  • 我们除了要关注 C# 和 C++ 不同的知识点,还要格外关注 C# 一些特有的功能,比如反射、应用程序域等。

    • 例题:

      • 下面是一段关于反射和应用程序域的代码,运行它得到的结果是什么?
        [Serializable]
        internal class A: MarshalByRefObject{
            public static int Number;
            public void SetNumber(int value){
                Number = value;
            }
        }
        
        [Serializable]
        internal class B{
            public static int Number;
            public void SetNumber(int value){
                Number = value;
            }
        }
        
        class Program{
            static void Main(string[] args){
                String assambly = Assembly.GetEntryAssembly().FullName;
                AppDomain domain = AppDomain.CreateDomain("NewDomain");
                A.Number = 10;
                String nameOfA = typeof(A).FullName;
                A a = domain.CreateInstanceAndUnwrap(assembly, nameOfA) as A;
                a.SetNumber(20);
                Console.WriteLine("Number in class A is {0}", A.Number);
                B.Number = 10;
                String nameOfB = typeof(B).FullName;
                B b = domain.CreateInstanceAndUnwrap(assambly, nameOfB) as B;
                b.SetNumber(20);
                Console.WriteLine("Number in class B is {0}", B.Number);
            }
        }
        
    • 解析:

      • 先创建一个名为 NewDomain 的应用程序域,并在该域中利用反射机制创建类型 A 的一个实例和类型 B 的一个实例;
      • 类型 A 继承自 MarshalByRefObject,而 B 不是;
      • 虽然这两个类型的结构一样,但由于基类不同而导致在跨越应用程序域的边界时表现出来的行为将大为不同;
      • 由于 A 继承自 MarshalByRefObject,那么 a 实际上只是在默认的域中的一个代理实例(Proxy),它指向位于 NewDomain 域中的 A 的一个实例;
      • 当调用 a 的方法 SetNumber 时,是在 NewDomain 域中调用该方法的,它将修改 NewDomain 域中静态变量 A.Number 的值并设为20;
      • 由于静态变量在每个应用程序域中都有一份独立的拷贝,修改 NewDomain 域中的静态变量 A.Number 对默认域中的静态变量 A.Number 没有任何影响;
      • 由于 Console.WriteLine 是在默认域中的应用程序域中输出 A.Number,因此输出仍然是 10;
      • 由于 B 只是从 Object 继承而来的类型,它的实例穿越应用程序域的边界时,将会完整地复制实例;
      • 因此,我们尽管试图在 NewDomain 域中生成 B 的实例,但会把实例 b 复制到默认的应用程序域上进行,它将修改默认域上的 B.Number 并设为20,再在默认域上调用 Console.WriteLine时,它将输出20。
  • 推荐阅读的 C# 书籍

    • Professional C#》:详细讲述了 C# 和其他语言的区别;
    • CLR Via C#》:不仅深入地介绍了 C# 语言,同时对 CLR,.NET 进行了全面的剖析。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值