C/C++最新2022年面试题总结(持续更新)

初级

1. C和C++的区别

  • C++增加new和delete进行内存分配,取代了C中的malloc和free。
  • C++添加了字符串类,取代C中的字符数组处理函数。
  • C++中使用iostream类库来做控制态的输入输出,取代了C中的stdio函数库。
  • C++中用try/catch/throw进行异常处理,取代了C中的setjmp()和longjmp()函数。
  • C++允许函数重载,参数类型不完全相同即可,而C不允许。
  • C++允许变量定义语句在程序中的任何地方,只要在是使用它之前就可以;而C必须要在函数开头部分定义。
  • C++中新增了引用。

2. C++指针和引用的区别

  1. 指针是一个新的变量,存储了另一个变量的地址,我们可以通过访问这个地址来修改另一个变量;
    引用只是一个别名,还是变量本身,对引用的任何操作就是对变量本身进行操作。
  2. 引用只有一级,而指针可以有多级。
  3. 指针传参的时候,还是值传递,指针本身的值不可以修改,需要通过解引用才能对指向的对象进行操作;
    引用传参的时候,传进来的就是变量本身,因此变量可以被修改。
  4. 引用必须在声明时初始化,且不能更换绑定的变量;
    指针可以在任何时候初始化,且非const的指针可重新赋值。
  5. 指针传参时,有可能传入空指针,因而需要进行空指针判断;
    引用则不存在空引用一说,因而能提高一些效率。
值传递、指针传递、引用传递的区别和效率
  • 值传递:有一个形参向函数所属栈区拷贝数据的过程,如果值传递的对象是类对象或是大的结构体对象,将耗费时间和空间(传值)
  • 指针传递:同样有一个形参向函数所属栈区拷贝数据的过程,但拷贝的数据是4个字节的地址(传值,但是传地址的值
  • 引用传递:同样有上述过程,但是针对地址的,相当于为该数据所在地址起了一个别名(传地址)
    效率:指针>引用>值传递,但推荐引用传递,代码逻辑上更加紧凑。

3. 结构体struct和共同体union的区别

  1. 结构体将不同类型的数据组合成一个整体,是自定义类型;
    共同体是不同类型的几个变量共同占用一段内存。
  2. 结构体中的每个成员都有自己独立的地址,它们是同时存在的;
    共同体中的所有成员占用同一段内存,它们不能同时存在(会覆盖)。
  3. sizeof(struct)是内存对齐后所有成员长度的总和;
    sizeof(union)是内存对齐后最长数据成员的长度。

4. #define、inline、const的区别

(1) #define

宏定义的常量没有类型,只是简单的替换;在预处理时进行替换;不可以用指针去指向;可以定义简单的函数。

(2) const

const定义的常量有类型名字,存放在静态区域;所定义的变量在编译时确定其值;可以使用指针指向;不可以定义函数。

(3) inline

inline 用于修饰函数,表示在调用该函数的时候直接在调用的地方进行展开,但是这只是向编译器提出这种需求,具体会不会展开取决于编译器本身。内联函数一般为简单函数,这将会减少函数调用时的压栈出栈函数等的开销。

对于普通的常量而言,宏定义的方式缺少类型检查,因而将会导致很多的隐患,而 const 机制可以很好的继承了他的优点,同时也克服了他的缺点。而对于 inline 而言,他是一种很好的替代宏函数的方式,对宏函数进行参数传递的时候并不会产生向宏函数这样的多次求值的错误,而且内联函数同时支持调试。因而我们在使用的过程中应该尽量的使用 const 常量和 inline 函数来替代宏函数的使用。

5. 重载overload、覆盖(重写)override、隐藏(重定义)overwrite的区别

(1) 重载

函数名相同,函数的参数个数、参数类型或参数顺序三者中必须至少有一种不同。函数返回值的类型可以相同,也可以不相同。发生在一个类内部,不能跨作用域。

class Animal
{
   
public:
    void func1(int tmp)
    {
   
        cout << "I'm an animal -" << tmp << endl;
    } 
    void func1(const char *s)//函数的重载
    {
   
        cout << "I'm an animal func1 -" << s << endl;
    }
}
(2) 重定义

也叫做隐藏,子类重新定义父类中有相同名称的非虚函数 ( 参数列表可以不同 ) ,指子类的函数屏蔽了与其同名的父类函数。除非将子类强制转换为父类,才可以重新调用父类的函数。

class Animal
{
   
public:
    void func1(int tmp)
    {
   
        cout << "I'm an animal -" << tmp << endl;
    }
    virtual void func2(int tmp)
    {
   
        cout << "I'm virtual animal func2 -" << tmp << endl;
    }
};
 
class Fish :public Animal
{
   
public:
    void func1()//函数的重定义 会隐藏父类同名方法
    {
   
        cout << "I'm a fish func1" << endl;
    } 
    void func2(int tmp) //函数的重写, 覆盖父类的方法 override
    {
   
        cout << "I'm a fish func2 -" << tmp << endl;
    }
};
(3)重写

也叫做覆盖,一般发生在子类和父类继承关系之间。子类重新定义父类中有相同名称和参数的虚函数,必须要有virtual关键字。这样可以使用父类的对象或指针调用子类的函数。

当子类进行重写时,尽量加上override关键字,这样编译器可以检测是否重写,就不会产生不小心打错的情况。
当子类进行函数重写时,如果加上final关键字,则该函数就不能被它的子类继承。

6. malloc和new的区别

new/delete,malloc/free都是动态分配内存的方式

  1. malloc/free是库函数,new/delete是C++运算符
  2. malloc对开辟的空间大小严格指定,而new只需要对象名
  3. new为对象分配空间时,调用对象的构造函数,delete调用对象的析构函数

7. delete和delete[]的区别

假如使用new[]创建一个对象数组,则delete只会调用第一个对象的析构函数(造成内存泄漏),而delete[]会调用每个成员的析构函数。
用new分配的内存用delete释放,用new[]分配的内存用delete[]释放。

8. 堆heap和栈stack的区别

  1. 大小不同:一般来讲,在32为系统下面,堆内存可达到4G的空间,从这个角度来看堆内存几乎是没有什么限制的。但是对于栈来讲,一般都是有一定空间大小的,例如,在vc6下面,默认的栈大小好像是1M。
  2. 速度不同:栈比较快,堆相对来说慢
  3. 分配方式不同:栈有静态和动态分配,存放函数的参数值、局部变量,由编译器自动分配释放;
    堆只有动态分配,是由new或malloc分配的内存块,由应用程序控制,需要程序员手动利用delete释放,如果没有,程序结束后,操作系统自动回收。
  4. 因为堆的分配需要使用频繁的new/delete,造成内存空间的不连续,会有大量的碎片,使程序效率降低;
    栈则不会存在这个问题。
  5. 堆的生长方向是向上的,也就是向着内存地址增加的方向;
    栈的生长方式是向下的,是向着内存地址减小的方向增长。
C++内存管理

在C++中,内存被分成五个区:栈、堆、全局区/静态存储区、常量存储区、代码区
栈:存放函数的局部变量、函数参数、返回地址等,由编译器自动分配和释放。
堆:动态申请的内存空间,就是由 malloc 分配的内存块,由程序员控制它的分配和释放,如果程序执行结束还没有释放,操作系统会自动回收。
全局区/静态存储区:存放全局变量和静态变量,程序运行结束操作系统自动释放。
常量存储区:存放的是常量,不允许修改,程序运行结束自动释放。
代码区:存放代码,不允许修改,但可以执行。编译后的二进制文件存放在这里。

int g_var = 0; // g_var 在全局区
char *gp_var;  // gp_var 在全局区

int main()
{
   
    int var;                    // var 在栈区
    char *p_var;                // p_var 在栈区
    char arr[] = "abc";         // arr 为数组变量,存储在栈区;"abc"为字符串常量,存储在常量区
    char *p_var1 = "123456";    // p_var1 在栈区;"123456"为字符串常量,存储在常量区
    static int s_var = 0;       // s_var 为静态变量,存在静态存储区
    p_var = (char *)malloc(10); // 分配得来的 10 个字节的区域在堆区
    free(p_var);
    return 0;
}

9. 虚函数和纯虚函数的区别

虚函数和纯虚函数都可以在子类中被重写

  1. 纯虚函数只有定义,没有实现,要用 =0 来结尾,故其在子函数中必须实现,它更像是对子类的约束(接口);
    虚函数既有定义,又有实现的代码,子函数可以重写父类的虚函数来实现子类的特殊化。
  2. 包含纯虚函数的类,被称为“抽象类”,抽象类不能直接new出对象,只有实现了纯虚函数的子类才能new出对象;
    包含虚函数的类可以直接new出对象。

10.大端存储和小端存储的区别

大端模式: 数据的高字节保存在内存的低地址中。
小端模式:数据的高字节保存在内存的高地址中。

// 在小端模式中
char a[4] = 0x11223344; // 则a[0] = 0x44, a[3] = 0x11

为什么要区分大小端:因为大小端有着各自的优点,比如小端存储当进行强制类型转换时不需要进行字节内容的调整,直接按照数据的大小尽心截断即可。而大端储存方式中符号位永远位于第一个字节,很方便判断正负。

11. 指针函数和函数指针的区别

指针函数:

与普通函数一样,只是它返回的是一个对象的指针。

注意:当函数返回指针时,一定要是一个指向全局变量、静态局部变量(两者均分配到全局存储区)或者分配到堆上的变量(使用malloc或者new分配的对象)的指针,因为局部变量的栈空间会在函数调用结束时被销毁,导致指针将访问一个未知的地址。

函数指针:

指向函数入口地址的指针,其声明需要明确的包含函数的返回类型和参数类型。
假如有函数:

void fun(int a, int b);

如果我们要定义一个指向函数fun的函数指针,则:

void (*p)(int, int)
  • 4
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值