目录
8.map,set的区别,用什么实现的,然后追问二叉树,平衡二叉树。。。
16.数据结构中的二叉树,深度优先和广度优先遍历分别是什么样的?分别用什么数据结构可以实现?
25.时间复杂度,如果一个算法的时间复杂度是O(1)代表的含义是什么
26.类里有一个int有一个virtual,占多少字节。各在什么位置。
1.数组和指针的区别
(1)数组是用于存储多个相同数据类型的集合;指针相当于一个变量,存放的是其他变量在内存中的地址
(2)同类型指针变量可以相互赋值;数组不行
(3)数组在内存中连续存放;指针很灵活,可以指向任意类型的数据
(4)sizeof(数组名)=数组的大小;sizeof(指针),32位系统下=4
(5)数组传参时,会退化为指针
2.指针和引用的区别
(1)指针存储变量的地址,引用跟原来的变量实质上是同一个东西,只不过是原变量的一个别名
(2)指针可以为空;引用不可以为空,使用之前必须初始化
(3)指针的值在初始化后可以改变;引用不可以
(4)sizeof指针得到的是指针本身的大小;sizeof引用得到的是变量的大小
(5)指针可以多级;引用只有一级
3.C++和C的区别
(1)c++面向对象;c面向过程
(2)动态管理内存方法不一样:c++是new/delete;c是malloc/free
(3)c++中的类c中没有
(4)c++中的引用c中没有
(5)c++支持函数重载;c不支持
4.new和malloc的区别
(1)属性:new是C++关键字,需要编译器支持;malloc是库函数,需要头文件支持
(2)参数:new不需要指定内存块大小;malloc需要
(3)返回类型:new返回对象类型的指针;malloc返回void*
(4)分配失败:new分配失败抛出异常;malloc返回null
(5)重载:new允许重载,malloc不可以
5. delete和free有什么区别?
(1)delete是C++的关键字,不需要头文件支持;而free是C中的库函数,需要头文件支持。
(2)delete会调用类的析构函数和释放内存; free只是释放内存。
6.虚函数和纯虚函数的区别
(1)纯虚函数在基类只有定义,没有实现;而虚函数既有定义,也有实现的代码。
(2)虚函数在子类中也可以不重写;纯虚函数必须在子类中去实现
(2)包含纯虚函数的类不能定义其对象,而包含虚函数的则可以。
7.构造函数和析构函数
构造函数用来初始化类对象的成员的,所以当创建类对象就会调用构造函数。特点:
(1)函数名和类名必须一样,没有返回值。
(2)当没有显式的定义构造函数时,系统会自己生成默认的构造函数。
(3)可以重载,不可以为虚函数。
析构函数用来释放对象使用的资源,并销毁非static成员。特点:
(1)函数名是在类名前加上~,无参数且无返回值。
(2)一个类只能有且有一个析构函数,如果没有显式的定义,系统会生成一个缺省的析构函数。
(3)析构函数不能重载。
8.map,set的区别,用什么实现的,然后追问二叉树,平衡二叉树。。。
map和set区别:
(1)map中的元素是key-value(关键字--值)对:关键字起到索引的作用,值则表示与索引相关联的数据;Set就是关键字的简单集合。
(2)set的迭代器是const的,不允许修改元素的值;map允许修改value,但不允许修改key。
(3)map支持下标操作,可以用key做下标,不建议使用;set不支持下标操作。
底层实现都是红黑树。
二叉树:每个节点最多含有两个子树的树结构。
平衡二叉树:任何节点的两个子树的高度最大差为1
红黑树:节点是红色或黑色;根节点、叶子节点是黑色;每个红色节点的两个子节点都是黑色
9.循环和递归的区别,优缺点
循环:通过设置初始值和终止条件,在一个范围内重复运算
递归:函数内部调用这个函数本身。
(2)优缺点
递归
优点:可读性好;
缺点:浪费空间,而且递归太深容易造成堆栈的溢出。
循环
优点:代码运行效率好,没有额外的空间开销;
缺点:代码不如递归简洁
10.class和struct的区别
(1)默认访问权限
struct是public,class是private
(2)默认继承权限
struct是public,class是private
(3)若没有定义构造函数,struct可以使用{ }进行初始化,而只有当class的所有数据成员及函数为public时,可以使用{ }进行初始化
(4)class这个关键字可用于定义模板参数。strcut不用于定义模板参数
class和struct可以相互继承,默认继承方式取决于子类
11指针数组和数组指针的区别
数组指针(也称行指针)
定义 int (*p)[n];指向一维数组的指针,亦称行指针
指针数组
定义 int *p[n];
区别:数组指针只是一个指针变量,C语言里专门用来指向二维数组的,它占有内存中一个指针的存储空间。指针数组是多个指针变量,以数组形式存在内存当中,占有多个指针的存储空间。
12.list和vector区别
(1)vector底层实现是数组;list是双向链表。
(2)vector支持随机访问,list不支持。
(3)vector是顺序内存,list不是。
(4)vector在中间节点进行插入删除会导致内存拷贝,list不会。
(5)vector一次性分配好内存,不够时才进行2倍扩容;list每次插入新节点都会进行内存申请。
(6)vector随机访问性能好,插入删除性能差;list随机访问性能差,插入删除性能好。
13.内存溢出和内存泄漏?怎么实现检测到内存泄露?
内存溢出:系统已经不能再分配出你所需要的空间
内存泄漏: 意思就是你用资源的时候为他开辟了一段空间,当你用完时忘记释放资源了,这时内存还被占用着,一次没关系,但是内存泄漏次数多了就会导致内存溢出
第一:良好的编码习惯,尽量在涉及内存的程序段,检测出内存泄露。
第二:将分配的内存的指针以链表的形式自行管理,使用完毕之后从链表中删除,程序结束时可检查链表。
第三:Boost 中的smart pointer。
第四:一些常见的工具插件,如ccmalloc、Dmalloc、Leaky等等。
14.空指针和野指针的区别
空指针是指一个指针的值为null
野指针会指向一段实际的内存,只是它指向哪里我们并不知情,或者是它所指向的内存空间已经被释放。
15.深拷贝和浅拷贝
浅拷贝和深拷贝主要区别就是复制指针时是否重新创建内存空间。如果没有创建内存只赋值地址为浅拷贝,创建新内存并把值全部拷贝一份就是深拷贝。
16.数据结构中的二叉树,深度优先和广度优先遍历分别是什么样的?分别用什么数据结构可以实现?
DFS深度优先遍历:沿着树的深度遍历树的节点,先访问根结点,然后是左子树,右子树
BFS广度优先遍历:横向遍历,沿着树的宽度,先访问根结点,然后左右子节点。然后是上述左右子节点的子节点,一层一层往下。
(前者用栈,后者用队列)
17.深度优先遍历有哪几种?(先序、中序、后序)
18. 一个空类中包括什么
默认构造函数
析构函数
拷贝构造函数
赋值运算符(operator=)
取址运算符
取址运算符const
20.虚函数,多态实现
拥有virtual关键字的函数称为虚函数
虚函数的作用是实现多态。要成为虚函数满足两点:依赖函数调用;可以取地址
在父类函数前加上virtual关键字,在子类中重写,程序运行时通过对象的实际类型调用相应的函数
21.动态内存分配
系统根据程序的需要即时分配内存空间,分配的大小就是程序要求的大小
22. C++的三个基本特征以及每个的具体解释
封装:将对象的属性和行为封装起来,也就是将数据和基于数据的操作封装在一起,保护数据并隐蔽具体的细节,只保留有限的接口与外界联系
继承:是指可以让某个类型的对象获得另一个类型的对象的属性的方法。
多态:在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。
底层原理
第一:编译器在发现基类中有虚函数时,会自动为每个含有虚函数的类生成一份虚函数表,该表是一个一维数组,虚表里保存了虚函数的入口地址。
第二:编译器会在每个对象的前四个字节中保存一个虚表指针vptr,指向对象所属类的虚函数表。在程序运行时,根据对象的类型去初始化vptr,从而让vptr指向正确的虚函数表。
23Static的作用
(1)隐藏
(2)保持变量内容的持久
(3)默认初始化为0
24.说一下宏
C/C++中的宏定义是进行符号常量定义,宏定义会在预处理阶段将用define定义的内容对代码中相应的标识符进行替换。
使源代码更具有可读性,能在一定程度上提高程序的运行效率。
25.时间复杂度,如果一个算法的时间复杂度是O(1)代表的含义是什么
时间复杂度是指执行算法所需要的计算工作量
O(1) 表示耗时/耗空间与输入数据大小无关,无论输入数据增大多少倍,耗时/耗空间都不变。
26.类里有一个int有一个virtual,占多少字节。各在什么位置。
8字节(4+4)
Vptr前4个
Int后4个
27.类的生命周期
(1)对于全局对象,程序一开始,其构造函数就先被执行(比程序进入点更早);程序即将结束前其析构函数将被执行。
(2)对于局部对象,当对象诞生时,其构造函数被执行;当程序流程将离开该对象的声明周期时,其析构函数被执行。
(3)对于静态(static)对象,当对象诞生时其构造函数被执行;当程序将结束时其析构函数才被执行,但比全局对象的析构函数早一步执行。
(4)对于以new方式产生出来的局部对象,当对象诞生时其构造函数被执行,析构函数则在对象被delete时执行。
28.引用传递
当使用引用变量作为形参时,它将变为实参列表中相应变量的别名,对形参进行的任何更改都将真正更改正在调用它的函数中的变量。当以这种方式将数据传递给形参时,该实参被称为按引用传递。
29. const和#define的区别
(1)起作用阶段
define:编译预处理阶段起作用;const:程序运行时起作用
(2)起作用方式
define:简单字符替换,没有类型检查,存在边界错误;const:有类型检查
(3)存储方式:
define:宏定义是直接替换,不会分配内存,存储于程序的代码段中; const:需要进行内存分配,占用数据段空间
(4)define:不能调试;const:可以调试
(5)define:可以再定义;const:不可以
(6) define可以用来防止头文件重复引用,而const不能;
(7)const采用一个普通的常量名称,define可以采用表达式作为名称;
(8)define不能作为参数传递给函数;const常量可以在函数的参数列表中出现
30.函数和宏的区别
(1)宏做的是简单的字符串替换,不受类型限制;而函数是参数的传递,受到参数类型的限制。
(2)宏在预处理阶段起作用,函数参数的调用是在函数执行时起作用。
(3)函数可以进行调试;宏不可以进行调试。
(4)函数支持递归,宏不支持。
(5)函数在调用时会产生时间和空间上的开销;宏在调用时则没有,因为宏进行的只是简单的字符串替换。
31.虚析构函数的作用
总的来说虚析构函数是为了避免内存泄露,而且是当子类中会有指针成员变量时才会使用得到的。也就说虚析构函数使得在删除指向子类对象的基类指针时可以调用子类的析构函数达到释放子类中堆内存的目的,而防止内存泄露的。
32.面向对象六大基本原则
(1)单一职责原则
(2)开闭原则
对扩展开放,对修改关闭
(3)里氏替换原则
(4)依赖倒置原则
(5)接口隔离原则
(6)迪米特原则
33.手撕一个String类
#include<bits/stdc++.h>
using namespace std;
class String{
public:
// 默认构造函数
String(const char* str = nullptr);
// 拷贝构造函数
String(const String& str);
// 析构函数
~String();
// 字符串赋值函数
String& operator= (const String& str);
char* getStr()
{
return this->data;
}
private:
char* data;
int size;
} ;
// 构造函数
String::String(const char* str)
{
if(str == nullptr) // 加分点
{
data = new char[1];
data[0] = '\0';
size = 0;
}
else
{
size = sizeof(str);
data = new char[size+1];
strcpy(data, str);
}
}
// 拷贝构造函数
String::String(const String& str)
{
size = str.size;
data = new char[size+1];
strcpy(data, str.data);
}
// 析构函数
String::~String()
{
delete[] data;
}
String& String::operator=(const String& str)
{
if(this == &str) return *this; // 得分点:检查自赋值
delete[] data; // 得分点:释放原有空间
size = str.size;
data = new char[size+1];
strcpy(data, str.data);
return *this;
}
int main()
{
char* s = "hello";
String* testStr = new String("12345");
String* testStr2 = new String(s);
String testStr3(*testStr2);
String testStr4 = testStr3;
String testStr5 = std::move(testStr4); // move()可以将值转化成右值
cout << testStr->getStr() << endl;
cout << testStr2->getStr() << endl;
cout << testStr3.getStr() << endl;
cout << testStr4.getStr() << endl;
cout << testStr5.getStr() << endl;
}