C++面试基础知识点

C++开发面试基础知识点

 

1. 语言基础

1.1 const的用法

1)在定义的时候必须进行初始化

2)指针可以是const  指针,也可以是指向const对象的指针

3)定义为const的形参,即在函数内部是不能被修改的

4)类的成员函数可以被声明为常成员函数,不能修改类的成员变量

5)类的成员函数可以返回的是常对象,即被const声明的对象

6)类的成员变量是常成员变量不能在声明时初始化,必须在构造函数的列表里进行初始化

note: const如何做到只读?

这些在编译期间完成,对于内置类型,如int, 编译器可能使用常数直接替换掉对此变量的引用。而对于结构体不一定。

 1.2 static的用法

1)在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
2)在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。
3)在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用

4)类内的static成员变量属于整个类所拥有,不能在类内进行定义,只能在类的作用域内进行定义

5)类内的static成员函数属于整个类所拥有,不能包含this指针,只能调用static成员函数

static全局变量与普通的全局变量有什么区别:

static全局变量与普通的全局变量有什么区别:static全局变量只初使化一次,防止在其他文件单元中被引用;
static局部变量和普通局部变量有什么区别:static局部变量只被初始化一次,下一次依据上一次结果值;
static函数与普通函数有什么区别:static函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝

1.3 extern c 作用

告诉编译器该段代码以C语言进行编译

1.4 指针和引用的区别

1)引用是直接访问,指针是间接访问

2)引用是变量的别名,本身不单独分配自己的内存空间,而指针有自己的独立内存空间

3)引用绑定内存空间(必须赋初值),是一个变量别名不能更改绑定,可以改变对象的值。

总的来说:引用既具有指针的效率,又具有变量使用的方便性和直观性

1.4 关于静态内存分配和动态内存分配的区别及过程

1) 静态内存分配是在编译时完成的,不占用CPU资源;动态分配内存运行时完成,分配与释放需要占用CPU资源;

2)静态内存分配是在栈上分配的,动态内存是堆上分配的;

3)动态内存分配需要指针或引用数据类型的支持,而静态内存分配不需要;

4)静态内存分配是按计划分配,在编译前确定内存块的大小,动态内存分配运行时按需分配。

5)静态分配内存是把内存的控制权交给了编译器,动态内存把内存的控制权交给了程序员;

6)静态分配内存的运行效率要比动态分配内存的效率要高,因为动态内存分配与释放需要额外的开销;动态内存管理水平严重依赖于程序员的水平,处理不当容易造成内存泄漏。

volatile(必须将cpu的寄存器缓存机制回答的很透彻)

1访问寄存器比访问内存单元要快,编译器会优化减少内存的读取,可能会读脏数据。声明变量为volatile,编译器不再对访问该变量的代码优化,仍然从内存读取,使访问稳定。

总结:volatile关键词影响编译器编译的结果,用volatile声明的变量表示该变量随时可能发生变化,与该变量有关的运算,不再编译优化,以免出错。

2)使用实例如下(区分C程序员和嵌入式系统程序员的最基本的问题。)

并行设备的硬件寄存器(如:状态寄存器)
一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
多线程应用中被几个任务共享的变量
3)一个参数既可以是const还可以是volatile吗?解释为什么。

可以。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
4)一个指针可以是volatile 吗?解释为什么。
可以。尽管这并不很常见。一个例子当中断服务子程序修该一个指向一个buffer的指针时。

下面的函数有什么错误:

int square(volatile int *ptr) {
return *ptr * *ptr;
}

下面是答案:
这段代码有点变态。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:

int square(volatile int *ptr){
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}


由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:

long square(volatile int *ptr){
int a;
a = *ptr;
return a * a;
}

1.5 string实现

class String{

    public:

        //普通构造函数
        String(const char *str = NULL);
        //拷贝构造函数
        String(const String &other);
        //赋值函数
        String & operator=(String &other) ;
        //析构函数
        ~String(void);
    private:
        char* m_str;
};

分别实现以上四个函数

//普通构造函数

String::String(const char* str){
    if(str==NULL) //如果str为NULL,存空字符串{
        m_str = new char[1]; //分配一个字节
        *m_str = ‘\0′; //赋一个’\0′
}else{

       str = new char[strlen(str) + 1];//分配空间容纳str内容
        strcpy(m_str, str); //复制str到私有成员m_str中
    }

}

//析构函数
String::~String(){
    if(m_str!=NULL) //如果m_str不为NULL,释放堆内存{
        delete [] m_str;
        m_str = NULL;
}
}


//拷贝构造函数
String::String(const String &other){
    m_str = new char[strlen(other.m_str)+1]; //分配空间容纳str内容
    strcpy(m_str, other.m_str); //复制other.m_str到私有成员m_str中 
}

//赋值函数
String & String::operator=(String &other){
    if(this == &other) //若对象与other是同一个对象,直接返回本{
        return *this
}
    delete [] m_str; //否则,先释放当前对象堆内存
    m_str = new char[strlen(other.m_str)+1]; //分配空间容纳str内容
    strcpy(m_str, other.m_str); //复制other.m_str到私有成员m_str中
    return *this;
}

1.6 用struct关键字与class关键定义类以及继承的区别

(1)定义类差别

(2)继承差别

note: 主要点就两个:默认的访问级别和默认的继承级别 class都是private

1.7 C++多态性与虚函数表

多态分为静态多态和动态多态。静态多态是通过重载模板技术实现,在编译的时候确定。动态多态通过虚函数继承关系来实现,执行动态绑定,在运行的时候确定。
动态多态实现有几个条件:(1) 虚函数; (2) 一个基类的指针或引用指向派生类的对象

基类指针在调用成员函数(虚函数)时,就会去查找该对象的虚函数表。虚函数表的地址在每个对象的首地址。查找该虚函数表中该函数的指针进行调用。每个对象中保存的只是一个虚函数表的指针,C++内部为每一个类维持一个虚函数表,该类的对象的都指向这同一个虚函数表。虚函数表中为什么就能准确查找相应的函数指针呢?因为在类设计的时候,虚函数表直接从基类也继承过来,如果覆盖了其中的某个虚函数,那么虚函数表的指针就会被替换,因此可以根据指针准确找到该调用哪个函数。

编译器为每一个类维护一个虚函数表,每个对象的首地址保存着该虚函数表的指针,同一个类的不同对象实际上指向同一张虚函数表。

静态多态是指通过模板技术或者函数重载技术实现的多态,其在编译器确定行为。动态多态是指通过虚函数技术实现在运行期动态绑定的技术。

纯虚函数如何定义,为什么对于存在虚函数的类中析构函数要定义成虚函数?

为了实现多态进行动态绑定,将派生类对象指针绑定到基类指针上,对象销毁时,如果析构函数没有定义为析构函数,则会调用基类的析构函数,显然只能销毁部分数据。如果要调用对象的析构函数,就需要将该对象的析构函数定义为虚函数,销毁时通过虚函数表找到对应的析构函数。

析构函数能抛出异常吗?

(1) 如果析构函数抛出异常,则异常点之后的程序不会执行,如果析构函数在异常点之后执行了某些必要的动作比如释放某些资源,则这些动作不会执行,会造成诸如资源泄漏的问题

(2) 通常异常发生时,c++的机制会调用已经构造对象的析构函数来释放资源,此时若析构函数本身也抛出异常,则前一个异常尚未处理,又有新的异常,会造成程序崩溃的问题

面向对象的三个基本特征,并简单叙述之?

  • 封装:将客观事物抽象成类,每个类对自身的数据和方法实行protection(private, protected,public)
  • 继承:广义的继承有三种实现形式:实现继承(指使用基类的属性和方法而无需额外编码的能力)、可视继承(子窗体使用父窗体的外观和实现代码)、接口继承(仅使用属性和方法,实现滞后到子类实现)。前两种(类继承)和后一种(对象组合=>接口继承以及纯虚函数)构成了功能复用的两种方式。
  • 多态:系统能够在运行时,能够根据其类型确定调用哪个重载的成员函数的能力,称为多态性。

重载(overload)和重写(overried,有的书也叫做“覆盖”)的区别?

  • 从定义上来说:重载:是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。重写:是指子类重新定义父类虚函数的方法。
  • 从实现原理上来说:重载:编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数(至少对于编译器来说是这样的)。如,有两个同名函数:function func(p:integer):integer;和function func(p:string):integer;。那么编译器做过修饰后的函数名称可能是这样的:int_func、str_func。对于这两个函数的调用,在编译器间就已经确定了,是静态的。也就是说,它们的地址在编译期就绑定了(早绑定),因此,重载和多态无关重写:和多态真正相关。当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态的调用属于子类的该函数,这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。因此,这样的函数地址是在运行期绑定的(晚绑定)。

 多态的作用?

  • 隐藏实现细节,使得代码能够模块化;扩展代码模块,实现代码重用;
  • 接口重用:为了类在继承和派生的时候,保证使用家族中任一类的实例的某一属性时的正确调用

虚函数与纯虚函数区别

  • 虚函数在子类里面也可以不重载的;但纯虚必须在子类去实现
  • 带纯虚函数的类叫虚基类也叫抽象类,这种基类不能直接生成对象,只能被继承,重写虚函数后才能使用,运行时动态动态绑定!

纯虚函数如何定义?含有纯虚函数的类称为什么?为什么析构函数要定义成虚函数?

纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。纯虚函数是虚函数再加上= 0。virtual void fun ()=0。含有纯虚函数的类称为抽象类在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显

你可能感兴趣的:(面试总结,c++)