【面试】Ready GO

来源:互联网 发布:php网页源代码加密 编辑:程序博客网 时间:2024/05/18 07:38

一. C++基本知识:面向对象的特性、构造函数、析构函数、动态绑定; C++中对内存的使用管理。

      基础知识: (数据结构和算法) (编程能力) (部分数学知识) (问题的分析和推理能力) (并发控制)


二. C++基础知识


1. 定义一个空的类型,里面没有任何成员变量和成员函数,该类型sizeof,得到结果是多少?                                  

    当我们声明该类型的示例的时候,必须在内存中占有一定空间,否则无法使用这些示例。调用构造函数和析构 函数只需要知道函数的地址即可,这些函数的地址只与类型相关,而与函数的示例无关。C++编译器一旦发现一个类型中有虚函数,就为该函数生成虚函数表,并在该类型的每一个实例中添加一个指向虚函数表的指针

如果传入的参数不是引用而是实例,那么从形参到实参会调用一次复制构造函数,把参数声明为引用可以避免这样无谓的消耗。


2. 标准库string类型                                                              

    字符串字面值与标准库string类型不是同一种类型         

     for (string::size_type i=0; i<str.size(); i++)

3. 标准库 vector类型

    vector<int>::size_type 

    for(vector<int>::size_type i=0; i!=vec.size(); i++)

4. 迭代器(一种检查容器内元素并遍历元素的数据类型)

    vector<int>::iterator iter = ivec.begin(); (由于end操作返回的迭代器不指向任何元素,因此不能对它进行解引用或自增操作)。

    for (vector<int>::iterator iter=ivec.begin(); iter != ivec.end(); iter++)

          *iter = 0;

5. 成员函数 const不能改变其所操作的数据成员,const必须同时出现在声明和定义中,若只出现在一处将出现编译错误。double avg_price() const


6. 类对象

class Sales_item {

public:

private:

        std::string isbn;

}

Sales_item item;  定义一个一个新的类型,但没有进行存储分配

7. 从const成员函数返回*this (在普通的非const成员函数,this的类型是一个指向类类型的const指针,可以改变this所指向的值,但不能改变this所保存的地址)


8. 构造函数初始化式 (可以初始化const对象或引用类型的对象,但不能对他们赋值。 初始化const或引用类型数据的唯一机会是在构造函数初始化列表中)

class ConstRef {

public:

       ConstRef(int li);

private:

       int i;

       const int ci;

       int &ri;

};

9. static类成员 (static成员函数没有this形参,他可以直接访问所属类的static成员,但不能直接使用非static成员。static成员是与特定类相关的) static成员是类的组成部分但不是任何对象的组成部分,所以static成员函数不能被声明为const,static成员函数也不能被声明为虚函数


三. 重载操作符与转换

1. 保留字operator后接需定义的操作符符号。重载操作符必须具有一个类类型的操作数(用于内置类型的操作符,其含义不能改变)。  至少有一个类类型的或枚举类型的操作数

Sales_item operator+(const Sales_item &, const Sales_item &);


2. 类成员与非成员(大多数重载操作符可以作为普通非成员函数或类的成员函数)

ConstRef::ConstRef(int li): i(li), ci(i), ri(li) {}


3. 输出操作符<<的重载

ostream& operator <<(ostream& os, const Sales_item &object) {      //第二个参数是一个引用以避免复制实参

        out << s.isbn << "\t" << s.avg_price();

        return out;

}


4. 输入操作符>>的重载

instream& operator >>(instream &in, Sales_item &s) {

      double price;

      in >> s.isbn >> s.units_sold >> price;

      if (in)        //只在使用读入数据之前检查一次即可

           s.revenue = s.units_sold * price;

      else

           s = Sales_item();     //reset 错误恢复

}


5. 算术操作符和关系操作符

Sales_item operator(const Sales_item &lhs, const Sales_items &rhs) { //加法返回一个值,而不是一个引用

       Sales_item ret(lhs);

       ret += rhs;

       return ret;

}

inline bool operator ==(const Sales_item &lhs, const Sales_item &rhs) {

       return lhs.a == rhs.b && lhs.b == rhs.b;

}

inline bool operator !=(const Sales_item &lhs, const Sales_item &rhs) {

           return !(lhs == rhs);

}

Sales_items &operator +=(const Sales_items &rhs) {

         units_sold += rhs.units_sold;

          return *this;

}


四. 面向对象编程

在c++中,通过基类的引用或指针调用虚函数时,发生动态绑定。在运行时确定,所指向对象的实际类型所定义。如果派生类没有重新定义某个虚函数,则使用基类中定义的版本。已定义的类才可以用作基类,如果声明但没有定义,则不能用作基类。

1. virtual 与其他成员函数

    触发动态绑定: (1) 只有指定为虚函数的成员函数才能进行动态绑定 (2)必须通过基类类型的引用或指针进行函数调用

    非虚函数总是在编译时根据调用函数的对象、引用或指针的类型二确定。 


2. 公用、私有、和受保护的继承 

    去除个别成员

     class Base{

 public: 

             std::size_t size() const {return n;}

     protected:

             std::size_t n;

 }

    class Derived : private Base {

     public:

             using Base::size;

     protected:

            using Base::n;

    }

3. 友元关心和继承

     友元关系不能继承


4.   构造函数为什么不能是虚函数

本文的主题是构造函数不能是虚函数,首先这不需要你用脑子去记,因为当你写出来虚构造函数时,编译器是能检查出来的。本文的目的是为什么构造函数不能是虚函数。

首先,先看一段错误的代码,下面的代码是通不过编译阶段的。

复制代码
1 class A{2 public:3     virtual A(){4         this->value = 0;5     }6 private:7     int value;8 };
复制代码

为什么构造函数不能是虚函数呢?这里你需要知道一个概念,那就是虚函数表vtbl,每一个拥有虚成员函数的类都有一个指向虚函数表的指针。对象通过虚函数表里存储的虚函数地址来调用虚函数。

那虚函数表指针是什么时候初始化的呢?当然是构造函数。当我们通过new来创建一个对象的时候,第一步是申请需要的内存,第二步就是调用构造函数。试想,如果构造函数是虚函数,那必然需要通过vtbl来找到虚构造函数的入口地址,显然,我们申请的内存还没有做任何初始化,不可能有vtbl的。因此,构造函数不能是虚函数。


5. 如何限制一个类对象只在堆上分配或者只在栈上分配?

第一种说法
//只在栈上class stackonly{private: void * operator new(size_t Size) { }};//只在堆上class heaponly{private: heaponly(){} ~heaponly(){}};

前者利用了c++的重载机制+访问控制机制。后者利用了c++的访问控制机制。
前者重载了new运算符,并设为私有,因此,当用 new stackonly;时编译器就会报错。
后者则将构造函数设为私有,因此,当你 heaponly h;时会自动调用构造函数,这时编译器也会报错。

这种技巧在c++中是很常用的比如设计模式中的单件模式。


6. 

trdest.resize(strsrc.size());      // !!!必须预先设置一个大小与strsrc相同          transform(strsrc.begin(), strsrc.end(), strdest.begin(), to_upper); // 转换为大写 


7. static 

类的static成员变量属于该抽象类,需要在类定义时初始化,不可以在对象的构造函数中初始化。static成员函数不可以声明为const和virtual

1.静态成员变量从属于一个类而非某个具体的对象,它的值被该类的所有对象所共享。 
2.public的静态成员,可以由类名(或对象名)直接通过 “.” 操作符引用。

先初始化 静态变量 然后运行静态构造函数

次序是static变量,static构造函数,最后是非static构造函数;


可以看出,静态数据成员有以下特点:

  • 对于非静态数据成员,每个类对象都有自己的拷贝。而静态数据成员被当作是类的成员。无论这个类的对象被定义了多少个,静态数据成员在程序中也只有一份拷贝,由该类型的所有对象共享访问。也就是说,静态数据成员是该类的所有对象所共有的。对该类的多个对象来说,静态数据成员只分配一次内存,供所有对象共用。所以,静态数据成员的值对每个对象都是一样的,它的值可以更新;
  • 静态数据成员存储在全局数据区。静态数据成员定义时要分配空间,所以不能在类声明中定义。在Example 5中,语句int Myclass::Sum=0;是定义静态数据成员;
  • 静态数据成员和普通数据成员一样遵从public,protected,private访问规则;
  • 因为静态数据成员在全局数据区分配内存,属于本类的所有对象共享,所以,它不属于特定的类对象,在没有产生类对象时其作用域就可见,即在没有产生类的实例时,我们就可以操作它;
  • 静态数据成员初始化与一般数据成员初始化不同。静态数据成员初始化的格式为:
    <数据类型><类名>::<静态数据成员名>=<值>
  • 类的静态数据成员有两种访问形式:
    <类对象名>.<静态数据成员名> 或 <类类型名>::<静态数据成员名>
    如果静态数据成员的访问权限允许的话(即public的成员),可在程序中,按上述格式来引用静态数据成员 ;
  • 静态数据成员主要用在各个对象都有相同的某项属性的时候。比如对于一个存款类,每个实例的利息都是相同的。所以,应该把利息设为存款类的静态数据成员。这有两个好处,第一,不管定义多少个存款类对象,利息数据成员都共享分配在全局数据区的内存,所以节省存储空间。第二,一旦利息需要改变时,只要改变一次,则所有存款类对象的利息全改变过来了;
  • 同全局变量相比,使用静态数据成员有两个优势:
  1. 静态数据成员没有进入程序的全局名字空间,因此不存在与程序中其它全局名字冲突的可能性;
  2. 可以实现信息隐藏。静态数据成员可以是private成员,而全局变量不能;
2、静态成员函数

  与静态数据成员一样,我们也可以创建一个静态成员函数,它为类的全部服务而不是为某一个类的具体对象服务。静态成员函数与静态数据成员一样,都是类的内部实现,属于类定义的一部分。普通的成员函数一般都隐含了一个this指针,this指针指向类的对象本身,因为普通成员函数总是具体的属于某个类的具体对象的。通常情况下,this是缺省的。如函数fn()实际上是this->fn()。但是与普通函数相比,静态成员函数由于不是与任何的对象相联系,因此它不具有this指针。从这个意义上讲,它无法访问属于类对象的非静态数据成员,也无法访问非静态成员函数,它只能调用其余的静态成员函数

关于静态成员函数,可以总结为以下几点:

  • 出现在类体外的函数定义不能指定关键字static;
  • 静态成员之间可以相互访问,包括静态成员函数访问静态数据成员和访问静态成员函数;
  • 非静态成员函数可以任意地访问静态成员函数和静态数据成员;
  • 静态成员函数不能访问非静态成员函数和非静态数据成员;
  • 由于没有this指针的额外开销,因此静态成员函数与类的全局函数相比速度上会有少许的增长;
  • 调用静态成员函数,可以用成员访问操作符(.)和(->)为一个类的对象或指向类对象的指针调用静态成员函数,也可以直接使用如下格式:
    <类名>::<静态成员函数名>(<参数表>)
    调用类的静态成员函数。

内联函数的定义对编译器而言必须是可见的,以便编译器能够在调用点内联展开该函数的代码。此时,仅有函数原型是不够的。

内联函数一般与宏相比较,宏采用的是直接代入的方式,不进行类型检查。



指针和数组的区别

1. 数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。数组名对应着(而不是指向)一块内存,其地址与容量在生命期内保持不变,只有数组的内容可以改变。

指针可以随时指向任意类型的内存块,它的特征是“可变”,所以我们常用指针来操作动态内存。指针远比数组灵活,但也更危险


2. 为了节省内存,C/c++把常量字符串放到单独的一个内存区域,当几个指针赋值给相同的常量字符串时吗,它们实际上会指向相同的内存地址。但用常量内存初始化数组,情况有所不同。

char a[] = "abcdefg";---------------数组内容能修改(字符数组)

char *p = "abcdefg";-----------------内容不能修改(字符串常量

3. 计算内存容量

    用运算符sizeof可以计算出数组的容量(字节数)

这是因为sizeof(p)得到的是一个指针变量的字节数,相当于sizeof(char*),而不是p所指的内存容量。C++/C语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。

指针引用区别: 声明一个引用必须同时初始化

1. 非空区别: 一个引用必须总是指向某些对象

2. 合法性; 使用引用不需要测试合法性

3. 可修改区别:


第七章 指针与引用

【问题一】///////////////////////////////////////////////////////

void swap(int *p, int *q)

{

     int *temp;

     *temp = *p; //不是指向而是拷贝

     *p = *q;

     *q = *temp;

}    //int *temp 不分配内存,于是系统在拷贝时临时给了一个随机地址,且函数结束后不收回,造成内存泄露

【问题二】///////////////////////////////////////////////////////

voidGetMemory(char *p, int num) {

    p = (char *)malloc(sizeof(char) * num);

}

int main() {

   char *str = NULL;

   GetMemory(str, 100);   

}

*p实际上是主函数str的一个副本,编译器总是要为每个参数制作临时副本。于是P所指内存改变了,但是str没变。

每执行一次就会申请一块内存,申请不能释放造成内存泄露

void GetMemory(char **p, int num) {  //right

    *p = (char *)malloc(sizeof(char) *num);

}

char *GetMemory(char *p, int num) { //right

     p = (char *)malloc(sizeof(char) *num);

     return p;

}

[char *str char str[]]+++++++++++++++++++++++++++

char c[] = "aaaa"; //分配一个局部数组

char *c = "bbbb";  //分配一个全局数组


如果const位于*的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;
如果const位于*的右侧,const就是修饰指针本身,即指针本身是常量。


C++ new能完成动态内存分配和初始化工作


BSS段:BSS段(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域。BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。

数据段:数据段(data segment)通常是指用来存放程序中已初始化的全局变量的一块内存区域。数据段属于静态内存分配。

代码段:代码段(code segment/text segment)通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读, 某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。

堆(heap):堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)

栈(stack):栈又称堆栈, 是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进先出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。



创建型模式(CREATIONAL PATTERN)

在软件工程中,创建型模式是处理对象创建的设计模式,试图根据实际情况使用合适的方式
创建对象。基本的对象创建方式可能会导致设计上的问题,或增加设计的复杂度。创建型模式通
过以某种方式控制对象的创建来解决问题。
创建型模式由两个主导思想构成。一是将系统使用的具体类封装起来,二是隐藏这些具体类
的实例创建和结合的方式。包含如下pattern:
抽象工厂Abstract Factory 模式,提供一个创建相关或依赖对象的接口,而不指定对象的具
体类。
工厂方法Factory Method 模式,允许一个类的实例化推迟到子类中进行。
生成器Builder 模式,将一个复杂对象的创建与它的表示分离,使同样的创建过程可以创建
不同的表示。
延迟初始化模式,将对象的创建,某个值的计算,或者其他代价较高的过程推迟到它第一次

0 0