C/C++面试题汇总

一、基础语言知识

1.使用struct关键字和class关键字定义类以及在类的继承方面有啥区别?

  1. 定义类的差别:C语言中的struct 关键字也可以实现类,用class 关键字和struct 关键字定义类的唯一差别就在于默认访问级别不同。默认情况下,struct 成员的访问级别为public,而class 成员的访问级别是private 。
  2. 类的继承的差别:使用class 关键字定义的类,它的派生类默认具有private 继承,而使用struct 关键字定义的类,它的派生类默认具有public 继承。

2.派生类和虚函数的概述?

  1. 基类中的虚函数被派生类继承过去之后,是希望派生类根据自己的实际需求进行重新定义,以实现特定的功能。如果派生类没有重新定义基类中的某个虚函数,则在调用的时候会使用基类中定义的版本。
  2. 派生类中函数的声明必须和基类中定义的方式完全匹配。
  3. 基类中被声明为虚函数,在派生类中也依然是虚函数。

3.虚函数和纯虚函数的区别?

  1. 带有纯虚函数的类被称之为虚基类,也叫做抽象基类,这种类型的类是不能直接生成对象的,只能被继承。
  2. 继承之后,在派生类中对纯虚函数进行重新定义,然后这个派生类才是我们常见的正常的类。如果在派生类中没有对这个纯虚函数重新定义,那么这个派生类也将成为虚基类。
  3. 虚函数在派生类中是可以不重新被定义的,但是纯虚函数在派生类中必须得被重新定义。

4.深拷贝和浅拷贝的区别?

  1. 浅拷贝只是对指针的拷贝,拷贝之后,两个指针同时指向同一个内存;深拷贝不但对指针进行拷贝,还对指针所指向的内容进行拷贝,源指针和经过深拷贝之后的指针是指向两个不同的地址的。
  2. 可能存在的问题:浅拷贝只是拷贝了指针而已,使得两个指针同时指向同一个内存地址,这样在对象结束调用析构函数时,会造成同一个资源被释放两次,造成程序崩溃;浅拷贝使得两个指针都指向同一块内存,任何一方的变动都会对另一方造成影响。

5.C++的特点是什么?多态实现的机制是什么?多态作用是什么?

  1. C++ 中的多态机制主要体现在两个方面:一个是函数的重载,一个是接口的重写。
  2. 函数重载,体现的是静态多态;接口多态,指的是“一个接口多种形态”的意思。每一个对象内部都有一个虚函数表指针,该虚函数表指针指向该类的虚函数表,所以在程序中,不管你的对象类型如何转换,但是该对象内部的虚函数表指针的指向始终是固定的,所以,才能实现动态的对象函数调用,这就是C++ 多态实现的原理。
  3. 多态的基础是继承,需要类中虚函数的支持。派生类继承基类的大部分资源,但是不能继承基类的构造函数、析构函数、拷贝构造函数、operator=函数、友元函数等。
  4. 多态作用:a、隐藏了函数的实现细节,代码能够模块化。b、函数接口重用:为了类在继承和派生的时候正确使用。

6.类的多重继承有什么问题?怎么样才能消除多重继承中的二义性?

  1. 增加了程序的复杂度,使得程序的编写和维护比较困难。
  2. 多重继承,使得派生类和基类中的同名函数产生二义性问题,对于同名函数的调用,不知道调用的是派生类自己的还是基类的,要是基类的,是哪一个基类的,这是引发的问题。C++ 中使用虚继承来消除这个二义性问题。或者使用成员限定符“作用域运算符::来避免这个问题。

7.什么叫做静态关联,什么叫做动态关联?

  1. 静态关联:程序在编译阶段,就能确定实际执行的动作,比如你是用类的对象来调用类的函数成员;
  2. 动态关联:程序运行阶段才能确定执行的动作,比如多态的使用。

8.析构函数可以抛出异常么?为什么不能抛出异常?除了资源泄漏,还有其他需要考虑的么?

  1. C++ 标准明确规定,类的析构函数不能抛出异常、也不应该抛出异常。
  2. 如果对象在运行期间出现了异常,C++ 异常处理机制则有责任去清除那些由于出现异常而导致已经失效了的对象,并释放对象原来所分配的资源,这其实就是调用对象的析构函数来完成资源的释放任务,所以从这个意义上来讲,析构函数已经变成了异常处理机制中的一部分。
  3. 如果析构函数抛出了异常,则异常点之后的语句都不会被执行,如果析构函数在异常点之后执行了某些必要的动作比如是释放内存资源的动作,那么则会导致这些释放资源的动作不会被执行,那么便会造成资源泄露的现象发生。
  4. 通常异常发生时,C++ 机制会调用已经构造的对象的析构函数来释放资源,此时,若析构函数本身抛出异常,则前一个异常在尚未处理完全的情况下又出现了新的异常,会造成程序的崩溃。

9.拷贝构造函数的作用及其用途(拷贝构造函数什么时候会被调用?)?什么时候需要自定义拷贝构造函数?

  1. 拷贝构造函数原型的一般形式:类名(const 类名 &形参对象名) { };构造函数的形参是本类对象的引用而不是本类的对象,是为了防止引起拷贝构造函数无休止地递归调用。
  2. 拷贝构造函数在如下三种情况下会被调用:
    (1)程序新建立一个对象,并且用另外一个已存在同类的对象对它进行初始化时;
    (2)当函数的形式参数是类的对象时;
    (3)当函数的返回值是类的对象时.
  3. 如果在类的构造函数中存在动态内存分配,那么则必须定义“拷贝构造函数”,否则会导致两个对象成员指向同一个地址,出现“指针悬挂”问题。

10.静态函数成员和静态数据成员有什么意义?

  1. 非静态数据成员,每个对象都有自己的一份拷贝;
    而静态数据成员,则被当做是类的成员,在程序中只被分配一份内存,是该类的所有对象所共同享有的,它的值一旦被更新,所有对象都可见这个更新;
    静态数据成员存储在全局静态存储区,所以不能在类中声明定义,应该在类外定义。由于它不属于某一个特定类的对象,在没有产生类对象时作用域就可见,所以在没有产生类对象的时候也可以使用类名+作用域运算符::来调用静态数据成员。
  2. 静态函数成员,与静态数据成员是一样的,都是属于整个类的,而不是属于某一个具体的类的对象的。
    对于每一个普通函数成员,它都是属于某一个具体的类对象的,都有自己的 this 指针指向这个对象。而静态函数成员没有 this 指针,它无法调用属于类的非静态数据成员和非静态函数成员。
    静态成员之间可以互相访问,包括静态成员函数访问静态数据成员和访问静态函数成员;非静态函数成员可以任意地访问静态数据成员和静态函数成员。
  3. 没有了 this 指针的额外开销,静态函数成员与类的普通函数相比,执行速度上会有少许的增长。

11.内存的静态分配和动态分配有什么区别?

  1. 时间不同:静态分配是发生在程序编译期间的,动态分配则是发生在程序执行期间的。
  2. 空间不同:堆区域是动态分配的,没有静态分配的堆区域。栈区域有 2种 分配方式:静态分配栈和动态分配栈。
    静态分配栈,是由编译器完成的,比如函数局部变量的分配、比如函数形式参数的分配。
    allocate类 可以从栈里分配动态内存,不用担心内存泄漏问题,当函数返回时,通过allocate 申请的内存就会被自动释放。

12.深入谈谈堆和栈?

  1. 分配和管理方式不同:
    (1)堆内存是动态分配的,其空间的分配和释放都是由程序员手动完成的。
    (2)栈内存是由编译器管理的。栈有 2 种分配方式:静态分配和动态分配。
    静态分配是由编译器完成,比如函数局部变量的分配、比如函数形式参数的分配。 allocate类 可以从栈里分配动态内存,不用担心内存泄漏问题,当函数返回时,通过allocate 申请的内存就会被自动释放。但是,这里要注意:栈的动态分配和堆内存的动态分配是不同的,它的动态分配是由编译器释放的,无需手工控制。
  2. 产生内存碎片不同:
    对于堆内存,频繁的new/delete 运算符和malloc/free 函数调用,势必会造成内存空间的不连续,造成大量的内存碎片,使程序效率降低;
    对于栈内存,则不存在内存碎片问题,这是因为栈这种数据结构是先进后出的,不可能有一个内存块从栈的中间位置弹出。
  3. 生长方向不同:
    (1)堆内存,是向着内存地址增加的方向增长的,从内存空间的低地址向高地址的方向依次增长。
    (2)栈内存,是向着内存地址减小的方向增长的,从内存空间的高地址向低地址增长。
    (3)在内存中,“堆”和“栈”共用全部的自由空间,只不过各自的起始地址和增长方向不同,它们之间并没有一个固定的界限,如果在运行时,“堆”和 “栈”增长到发生了相互覆盖时,称为“栈堆冲突”,系统肯定垮台。

13.explicit 关键字是干什么用的?

  1. 首先,C++ 中的 explicit 关键字只能用于修饰只有一个参数的类构造函数,它的作用是表明该构造函数是显示的,而非隐式的。 跟它相对应的另一个关键字是 implicit,意思是该构造函数是隐藏的。类构造函数默认情况下即声明为implicit( 隐式 )。
  2. explicit 关键字的作用就是防止 类构造函数的隐式自动转换。
    上面也已经说过了,explicit 关键字只对有一个参数的类构造函数有效,如果类构造函数参数大于或等于两个时, 是不会产生隐式转换的,所以explicit关键字也就无效了。
    但是,也有一个例外,就是当除了第一个参数以外的其他参数都有默认值的时候,explicit关键字依然有效,此时,当调用构造函数时只传入一个参数,等效于只有一个参数的类构造函数。

14.内联函数和宏定义的区别?

  1. 内联函数是指在普通函数的前面加一个关键字 inline 来标识。对于函数调用而言,每一次函数调用都会消耗时间,所以,对于对于语句比较短小的函数,若是被频繁调用,这时所花费的时间会远大于“把该函数直接写进程序执行流中,而不是去调用它”所花费的时间,因此这是很有益的。
  2. 宏定义,不检查函数参数、返回值什么的,只是简单的进行宏展开操作;相对来说,内联函数会检查参数类型,所有更安全。
  3. 宏是由预处理器进行宏展开,函数内联是通过编译器来控制实现。

15.内存对齐的原则是什么?

  1. 结构体的整体空间大小是占用空间最大的成员(的类型)所占用空间字节数的整数倍。
  2. 数据对齐原则:内存按照结构体成员的先后顺序排列,当排列到该成员时,前面已经摆放的空间大小必须是该成员类型大小的整数倍,如果不够则补齐之后然后再摆放该成员。

16.string的实现

#ifndef STRING_H_
#define STRING_H_
#include<iostream>

class String {
private:
	char * str;
	int len;
public:
	//普通构造函数
	String(const char * s);
	//复制构造函数
	String(const String &);	
	//析构函数
	~String();
	//赋值构造函数
	String & operator=(const String &);
};
#endif

String::String(const char * s) {
	len = std::strlen(s);
	str = new char[len + 1];
	std::strcpy(str, s);
}
String::String(const String & st) {
	len = st.len;
	str = new char[len + 1];
	std::strcpy(str, st.str);
}
String::~String() {
	delete[] str;
}
String & String::operator=(const String &st) {
	if (this == &st)  return *this;
	delete[] str;
	len = st.len;
	str = new char[len + 1];
	std::strcpy(str, st.str);
	return *this;
}

17.头文件中的ifndef/define/endif 干什么用的?

预处理模块时使用。可以防止头文件被重复使用。

18.extern c 的作用?

告诉编译器该段代码使用C语言进行编译。

二、C++11和STL

1.STL各个容器的实现原理?

  1. vector,指的是顺序容器,它是一个动态数组,支持元素的随机插入、删除、查找等操作。
    (1)vector 在内存中是一块连续存储的内存空间,当在旧内存空间不够用的情况下,它会自动分配另一个大小是旧内存空间 2倍 的新内存空间,然后把旧内存空间中的所有数据都拷贝进新内存空间中去,之后再在新内存空间中的原数据的后面继续进行构造新元素,并且同时释放旧内存空间,并且,由于vector 空间的重新配置,导致旧vector 的所有迭代器都失效了
    (2)vector 中数据的随机存取效率很高,O(1)的时间的复杂度,但是在vector 中随机插入元素,需要移动的元素数量较多,效率比较低下。
  2. map,指的是关联容器,它是以“键值对”的形式进行存储的,方便根据关键字来迅速查找其对应的值。
    关键字起到索引的作用,值则表示与索引相关联的数据。它的底层实现结构是红黑树,插入元素、删除元素等操作都在O(logN)的时间复杂度。
  3. set,指的是关联容器,set 中存放的是关键字,也即值,也就是说在set 中,关键字就是值,两者融为一体。
    set 的底层实现结构也是红黑树,它同样支持高效的元素插入、元素删除等操作。另外,set支持高校的关键字检查是否在set 中。

2.STL有7 种主要的容器

vector、list、deque、map、multimap、set、multiset

3.什么叫做智能指针?常用的智能指针有哪些?智能指针的实现是怎样的?

  1. C++11新标准中,引入了智能指针的概念。智能指针,是一个存储指向动态分配(堆)对象指针的类,构造函数传入普通指针,析构函数释放指针。栈上分配,函数或程序结束后自动释放,防止内存泄露。
  2. 使用引用计数器,类与指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针。
    创建类的新对象时,初始化指针并将引用计数置为1;
    当对象作为另一对象的副本而创建时,引用计数 +1;
    对一个对象进行赋值时,引用计数 -1(当引用计数减至 0 时,则删除基础对象),并增加右操作数所指对象的引用计数 +1;
    调用析构函数时,构造函数减少引用计数,当引用计数减至 0 时,则删除基础对象。
  3. 智能指针如下:
    (1)std::auto_ptr,不支持复制(拷贝构造函数)和赋值(operator =),编译不会提示出错。
    (2)unique_ptr, 不支持复制和赋值,但比 auto_ptr 好,直接赋值会编译出错。
    (3)shared_ptr,基于引用计数的智能指针。可随意赋值,直到内存的引用计数为 0 的时候这个内存会被释放。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值