C++面试题目汇总

C++ 面试题目汇总

因为最近开始面试了,虽然C++平时用的还算多,但是很多理论部分的东西总会忘,但那些又是面试的重点,所以在这里自己总结一下,顺便放上自己撸的代码。

C++基础

这里只放上和C++本身有关的试题


#include <> 和 #include ""的区别

#include用来指明引用的头文件,但是一般只有相对路径,不会引用完整路径。

#include <> 表明从预定的缺省路径下找头文件,而#include ""表明先从本文件的当前目录下查找,如果没有,再按照#include <>预定的缺省路径下查找。


#ifdef x #define x … #endif 作用

防止头文件在被多次引用的时候重复定义,这是C和C++的所有编译器通用的。

但是微软的编译器还提供了另外一种方式#pragma once

因为#ifdef ...这种方法是利用宏定义特性来保证不会被重复引用的,但是#ifdef 这个宏定义可以在文件的任何地方使用,所以编译器必须将文件读完才能完成工作。但是#pragma once是单独的一个宏定义,编译器只要可以立即标记,所以第二种更快,但无法跨平台。


extern “C”

extern "C"由两部分组成。

extern 表示可以被外部调用,"C"告诉编译器作用域中的代码要以C语言的方式编译。

首先明确一个概念,C语言和C++是两个独立的语言,只不过C++看起来像C语言而已,他们的标准不一样,也就导致编译器编译的时候是不一样的。就好比同样是“元宵”,南方人和北方人所说的不是一个东西,反过来说,可能因为地域或其他原因,双方想要表达同一样东西的时候可能说的东西也不一样。

同样的,以void fun();这个函数声明为例,同样的一个函数声明,用gcc编译器编译的时候可能是用_fun来标记,而g++编译器编译的时候可能又是用_fun_来标记。导致的问题就是,在同一个工程下编译时,同一个函数在不同源文件编译的时候标记不同,到链接的时候自然在C++的源文件中无法找到那个声明了但没有被实现的函数。

下面用两个例子来说明

先在test.c文件中定义print()函数,然后在main.cpp中声明这个函数为外部引用,而已要以C语言的方式编译!
// test.c
#include <stdio.h>

void print(){
    printf("ok\n");
}

// main.cpp
#include <iostream>
using namespace std;

extern "C" void print();

int main()
{
    print();
    return 0;
}
但通常情况下是直接调用api,所以相当于C语言源文件和C++源文件会共享这个头文件,我们只需要让这个头文件在两个源文件调用的时候分别以各自的规则编译就好了。
//test.h
#ifndef TEST_H
#define TEST_H

#ifdef __cplusplus
extern "C" {
#endif // __cplusplus

void print();

#ifdef __cplusplus
}
#endif // __cplusplus


#endif // TEST_H
头文件里利用宏定义__cplusplus(注意是两个下划线)来区别编译,这个宏定义是编译器内置的,还有更多可以自行查询
//test.c
#include <stdio.h>
#include "test.h"

void print(){
    printf("ok\n");
}
//main.cpp
#include <iostream>
#include "test.h"

using namespace std;

int main()
{
    print();
    return 0;
}

C/C++的各自优缺点
C语言
  • 面向过程,因为语言自身的原因不会也无法轻松的开发过于庞大的工程,所以更注重通过过程利用数据结构等更快的得到结果。
  • 语法相对简单一点,可以进行底层操作是他的优势。
C++
  • 面向对象,但同时也可以进行底层操作,但是modern C++建议尽量用对象而不是指针等。因为面对对象,所以解决问题更贴近生活实际。
  • 模板编程更灵活

语言本身没有好坏,只是适用场景不同罢了。


const关键字

const作用和#define很像,但区别在于const会真实的声明一个变量,只不过无法直接修改值,但可以通过const_cast去除变量指针的const属性,然后通过更改指针对应的值来间接更改变量的值。

同时const还可以用在函数声明中,可以给需要保护的参数加上const修饰符,防止调用时被修改。

虽然const放在只读数据段,但因为const是变量,编译器可以对其进行类型安全检查,有些IDE还可以对他进行调试。

而宏定义是直接替换,无法进行检查,很容易出错,而且不容易定位错的位置。


static,const,局部,全局变量存放位置

static变量全局变量存放在全局/静态区域编译期分配内存,程序结束时释放。

const变量储存在只读数据段,编译时存在符号表中,第一次使用时分配空间,程序结束时释放。

局部变量储存在栈内,作用域结束时被释放。


数组和指针

编译期中数组可以利用sizeof等运算符参与计算,有数组的性质。也就是说,数组这个概念是面对编译器的,不是面对应用程序的。

运行期中数组就是指针的另外一种表现形式而已,只不过静态数组定长,动态数组不定长等等。


sizeof

sizeof不是一个函数,是一个运算符

sizeof只能用于计算占用栈中内存的大小。

数组在编译器过后就只是指针的另外一个形式而已,所以如果是计算外部数组的大小,sizeof无法完成。

sizeof只能计算静态数组的大小,而且是全部大小,不是非空大小。

sizeof如果是类型名需要加括号,变量名不需要,因为他是运算符。


空指针,悬垂指针,野指针,智能指针

空指针是指指向地址为NULL(0)的指针变量

悬垂指针是指指向一个已经不存在的对象的地址的指针

野指针是指因为没有初始化等原因指向一处随机或者无效的地址的指针

智能指针首先是在boost库中实现的,后来被C++标准引用


malloc/free,new/delete,new[]/delete[]三者区别

malloc/free是c语言的标准库函数,用户可以用它进行内存的申请和释放

new/delete是c++的关键字,对于内置数据类型,他们功能是一样的;但是对于对象,malloc/free无法满足需求,因为对象需要自动创建构造函数,销毁时需要自动调用析构函数,这些只能编译器来操作。

new/delete和new[]/delete[]一定要配套使用,因为二者的实现机制不同。


OO的基本概念,三个基本特征?

基本概念:类,对象,继承
三个基本特征:封装,继承,多态


C++空类默认成员函数

构造函数,析构函数,复制构造函数,赋值函数


静态变量可以在不同实例中共享
#include <iostream>

using namespace std;

class A{
public:
    A(){}
    virtual ~A(){}
    // 这里只是声明,无法直接定义
    static int i;
};
// 静态变量需要在外部定义
int A::i = 0;

int main()
{
    A a,b;
    a.i = 0;
    cout << b.i <<endl;
    return 0;
}

类和实例的关系

类和实例的关系相当于课程和物理课的关系,相当于学生和我的关系,相当于父亲和我的爸爸的关系,等等等…

这个问题很重要,因为很多时候我们会忘记。


类内静态变量只可声明,不能定义?

上个问题说清楚了类和实例的关系,那么类中定义的变量都会在创建实例的时候全部都声明并定义一遍,静态变量也是,如果允许类内定义静态变量也就是如果允许在类内存在static int a = 0;这样的语句,由因为前面说了,类内静态变量是可以在不同的实例之间共用的,那么就会让这个公共变量每次在创建新的实例的时候赋值为0。


虚函数

在类的继承中,是允许子类中存在和父类相同的方法的,这种情况下,如果子类的实例调用这个方法,只会运行子类的方法,而不是父类的;如果子类没有同名方法的定义,调用的时候会自动调用父类的,有的时候类之间的关系很复杂,需要找半天才能找到对应的方法所在的类,所以虚函数可以解决这个问题。

但有例外,构造函数和析构函数,这两个方法是创建实例时就会调用的

#include <iostream>

using namespace std;

class A{
public:
    A(){cout << "a1\n";}
    virtual ~A(){cout << "a2\n";}
    static int i;
    int d = 0;
    void fun(){
        cout << "ok" << endl;
    }
};

class B : public A{
public:
    B(){cout << "b1\n";}
    virtual ~B(){
        cout << "b2\n";
    }
};

int A::i = 0;

int main()
{
    B b;
    return 0;
}

父子类调用顺序
很清楚的看到,调用顺序是,父类构造函数->子类构造函数->子类析构函数->父类析构函数,那么有一个问题,如果我们不想调用父类的析构函数呢?这时就是虚函数的用处了。

但这里要说一点,这二者中只有析构函数可以被声明为虚函数,原因自不用说,为什么构造函数不可以呢?因为虚函数本身是一种欠缺信息的处理方式,对象被创建时相对较为复杂,必须要明确的知道他的类型等信息,所以构造函数不可以。

总而言之,虚函数就是把修饰的对象限定在本类及子类中(不向上追溯),可以显式表明这个函数/变量没有继承父类,也可以防止意外调用父类的函数/使用父类的成员变量。


纯虚函数

纯虚函数只有接口,没有定义,可以基类声明接口,子类完善定义。


为什么不能滥用虚函数

首先虚函数的功能不是每个函数都适用或者必须的,其次,虚函数是有代价的,每个虚函数都要维护一个虚函数表,因为使用虚函数的时候会带来一定的系统开销。


构造函数可以是内联函数

什么是多态?多态有什么用?

多态是将父类的指针/引用指向子类的对象。
多态是由虚函数机制实现的,多态的作用是接口重用。


重载和覆盖

子类重新定义基类的虚函数叫做覆盖。
重载对于编译器而言,只是取了一个另外的名字而已,所以编译期就可以确定,但是虚函数是运行期才能确定。


public,protected,private区别

public的变量/函数可以被对象任意访问和修改
private只能被类内的成员访问/修改
在没有继承的情况下,protected和private一样
继承的情况下,派生类对象无法访问基类中的protected变量/函数,只有派生类可以访问。


  • 6
    点赞
  • 94
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值