常见面试题(总结)- C++基础

常见面试题(总结)- C++基础

1.C和C++的区别和联系?

  • C是一个结构化语言,它的重点在于算法和数据结构。对语言本身而言,C是C++的子集。而在C++相对于原来的C还有所加强,引入了对象和类的概念
  • C程序的设计首要考虑的是如何通过一个过程,对输入进行运算处理得到输出。对于C++,首要考虑的是如何构造一个对象模型,让这个模型能够配合对应的问题,这样就可以通过获取对象的状态信息得到输出或实现过程控制。
  • 因此C与C++的最大区别在于它们解决问题的思想方法不一样。

2.面向对象三大特性?

  • 封装:隐藏对象的属性和实现细节,对外提供公共的访问方式,以防止数据的随意访问和修改。
  • 继承:通过扩展一个已有的类,并继承该类的属性和行为,来创建一个新的类。
  • 多态:对于不同对象接收相同消息时产生不同的动作。程序运行时的多态性通过继承和虚函数来体现。

3.new/delete和malloc/free的区别与联系?

  • malloc与free是C++/C语言的标准库函数new/delete是C++的运算符。其实new/delete内部实现也调用了malloc/free,它们都用于在 上进行动态的内存操作(申请动态内存和释放内存)。
  • 用malloc函数需要指定内存分配的字节数并且不能初始化对象,new 会自动调用对象的构造函数。delete 会调用对象的析构函数,而free 不会调用对象的析构函数。new是强制类型的,不需要考虑类型,而malloc不是,它返回的指针是void*型,必须要强转成需要的类型。

4.引用和指针的区别?

  • 初始化要求不同。引用在创建的同时必须初始化,而指针在定义的时候不必初始化,可以在定义后面的任何地方重新赋值。
  • 可修改性不同。引用一旦被初始化,它就不能被另一个对象引用:而指针在没有const修饰的时候是都可以指向另一个对象。
  • 作为函数参数传递时的不同。使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作; 而使用指针传递函数的参数,当发生函数调用时,会创建一个临时的局部指针变量的形参,指向实参的地址,本质上两者上都是可以改变实参的值。如果改变形参指针的指向,在新地址上的修改并不能同步到实参的地址上。由于形参指针终归是个局部的临时变量,总归还存在一些不安全因素,如下所示:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void GetMemory(char *p,int num)
{
	p=(char*)malloc(num);
}
int main()
{
	char *str=NULL;
	GetMemory(str,100);
	strcpy(str,"hello");
	printf(str);
    return 0;
}
  • 结果是程序不能正常运行,原因是GetMemory函数中的char *p指针是个临时变量,函数中开辟的空间给形参p是没有用的,main函数中的str还是没有开辟空间
  • 实际上,在二进制层面,引用·般都是通过指针来实现的,只不过编译器帮我们完成了转换。总的来说,引用既具有指针的效率,又具有变量使用的方便性和直观性,而且安全性也比指针来得高,但是却没有指针那么灵活。

5.什么是内存对齐?为什么需要内存对齐?

内存对齐规则是按照结构体成员的声明顺序,依次安排内存,主要遵循以下两个原则:

  • 1.每个成员的起始地址的先对于结构体的首地址的偏移量必须为该成员所占内存大小的整数倍
  • 2.结构体占内存的总长度最大成员所占内存大小的整数倍

为什么要内存对齐?

  • 可以使程序的执行效率提高,每个成员的起始地址的先对于结构体的首地址的偏移量必须为该成员所占内存大小的整数倍,可以在访问成员变量的时候根据迅速,所以如果数据没有内存对齐,CPU访问这些数据时,可能就需要执行更多次的读取操作才行。

6.什么是字节序?

  • 字节序是指多字节数据在计算机内存中存储或者网络传输时各字节的存储顺序。 字节序有大端小端之分:
  • 1. 小端:将低序字节存储在起始地址
  • 2. 大端:将高序字节存储在起始地址

0x1234567的大端字节序和小端字节序的写法如下:

  • 计算机内存中存储的数据通常是使用小端存储,而TCP/IP协议规定网络数据流应采用大端字节序,所以在网络通信在发送和接受数据的时候往往也涉及到数据大小端的转化。

7.class与struct的区别

  • C++中的struct对C中的struct进行了扩充,它已经不再只是一个包含不同数据类型的数据结构了,它已经获取了太多的功能:

    ①struct能包含成员函数吗? 能!
    ②struct能继承吗? 能!!
    ③struct能实现多态吗? 能!!!

既然这些它都能实现,那它和class还能有什么区别?

最本质的一个区别就是成员默认属性和默认继承权限的不同:

①若不指明,struct成员的默认属性是public的,class成员的默认属性是private的;

②若不指明,struct成员的默认继承权限是public的,class成员的默认继承权限是private的;

8.析构函数能否作为虚函数?这样有什么好处?

析构函数可以作为虚函数,也可以不作为虚函数。通常一个类被用来作为基类的时候,才把析构函数写成虚函数。一般情况下类的析构函数里面都是释放内存资源,而析构函数不被调用的话往往就会造成内存泄漏。析构函数作为虚函数为了当用一个基类的指针删除一个派生类的对象时,利用到了多态的特性,派生类的析构函数会被调用。

9.static的作用

1、修饰函数的局部变量:

  • 并不会改变其作用域,变量离开作用域之后并没有销毁,而是放在内存中的全局静态区,并且只会被初始化一次。

2、修饰全局函数和全局变量:

  • 作用域发生变化。只能在本源文件使用,源程序中的其他文件不能使用它,这样的好处是不会和其他文件中定义相同名字的函数或变量发生冲突

3、修饰类里面的成员变量:

  • 和1差不多,定义多个static y,但只有一个y,不进入类的大小计算,不依赖于类对象的存在而存在(调用的时候不用创建对象,直接通过类名调用)

4、修饰类的成员函数:

  • 用static修饰过的不依赖于类对象的存在而存在(调用的时候不用创建对象,直接通过类名调用)

10.C++ 中的4种转化

C++中四种类型转换是: static_ cast, dynamic. cast, const. cast, re interpret cast

1、const_ cast
用于将const变量转为非const

2、static_ _cast
用于各种隐式转换,比如非const转const, void*转指针等,static_ cast 能用于多态向上
转化,如果向下转能成功但是不安全,结果未知;

3、dynamic_ cast
用于动态类型转换。只能用于含有虚函数的类,用于类层次间的向上和向下转化。只能转指
针或引用。向下转化时,如果是非法的对于指针返回NULL,对于引用抛异常。要深入了解内部
转换的原理。

  • 向上转换:指的是子类向基类的转换
  • 向下转换:指的是基类向子类的转换

它通过判断在执行到该语句的时候变量的运行时类型和要转换的类型是否相同来判断是否
能够进行向下转换。

4、reinterpret_ cast
几乎什么都可以转,比如将int转指针,可能会出问题,尽量少用:

5、为什么不使用C的强制转换?
C的强制转换表面上看起来功能强大什么都能转,但是转化不够明确,不能进行错误检查,
容易出错。

11.C++中的智能指针

智能指针主要用于管理在上分配的内存,本质上是个,它将普通的指针封装为一个栈对象。当栈对象的生存周期结束后,会在析构函数中释放掉申请的内存,从而防止内存泄漏。
常见智能指针:

  • unique_ _ptr

unique_ptr实现独占式拥有或严格拥有概念,保证同一时间内只有一个智能指针可以指向该对象。它对于避免资源泄露(例如“以new创建对象后因为发生异常而忘记调用delete")特别有用。

  • shared_ ptr

shared_ptr实现共享式拥有概念。多个智能指针可以指向相同对象,当新增一个对象时引用计数加1,当过期时引用计数减一。只有引用计数为0时,智能指针才会自动释放引用的内存资源。从名字share就可以看出了资源可以被多个指针共享,它使用计数机制来表明资源被几个指针共享。可以通过成员函数use_ count ()来查看资源的所有
者个数。除了可以通过new来构造,还可以通过传入auto
ptr, unique_ ptr, weak_ ptr来构造。当我们调用release()时,当前指针会释放资源所有权,计数减一。

  • weak_ptr

当两个对象相互使用一个shared_ ptr 成员变量指向对方,会造成循环引用,使引用计数失效,从而导致内存泄漏。为了解决循环引用导致的内存泄漏,引入了weak_ ptr 弱指针,weak_ _ptr的构造函数不会修改引用计数的值,从而不会对对象的内存进行管理,其类似一个普通指针,但不指向引用计数的共享内存,但是其可以检测到所管理的对象是否已经被释放,从而避免非法访问。

12.请你说说你理解的虚函数和多态

多态的实现主要分为静态多态动态多态,静态多态主要是重载,在编译的时候就已经确定;动态多态是用虚函数机制实现的,在运行期间动态绑定。举个例子:一个父类类型的指针指向一个子类对象时候,使用父类的指针去调用子类中重写了的父类中的虚函数的时候,会调用子类重写过后的函数。如果一个父类有多个子类重写父类中的虚函数,就可以通过父类指针实现“不变的代码来实现可变的算法”,这也是多态所体现的。

13.虚函数的实现

虚函数的实现:在有虚函数的类中,类的最开始部分是一个虚函数表的指针,这个指针指向一个
虚函数表,表中依次放了类中所以虚函数的地址(调用的时候相当于一个个函数指针),实际的虚函数在代码段(. text)中。当子类继承了父类的时候也会继承其虚函数表,当子类重写父类中虚函数时候,会将其继承到的虚函数表中的地址替换为重新写的函数地址。使用了虚函数,会增加访问内存开销,降低效率。

举例说明:

父类Base(创建的对象为b)中有虚函数f(), g(),h(),他们的函数的地址依次在虚函数表中
在这里插入图片描述

子类Derive(创建的对象为d)继承父类Base,新增虚函数g1(),h1(), 并且重写父类函数f()
在这里插入图片描述
可以看到当子类重写父类中虚函数时候,会将其继承到的虚函数表中的地址替换为重新写的函数地址

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值