目录
18.用代码实现构造函数,析构函数,拷贝构造函数,和拷贝构造运算符
红色字体的都是面试上面实际遇到的问题,本文只供参考
一.语法篇
1.malloc,free和new,free的区别
malloc和free都是C语言的库函数,而new和free就操作符,这样我们就可以对new和free进行重载
下面是之间的区别
1.malloc需要自己手动申请空间,new系统会自动计算申请空间;
2.分配空间异常不同:new分配空间失败的话会返回异常,malloc申请空间失败的话会返回void*
3.返回值不同:new返回对象指针,malloc返回void*,需要我们自己强转
4.new是先调用operator_new,然后申请足够的空间,再调用构造函数,初始化成员变量
malloc是根据要分配空间的大小,<128k,会在内存池或者brk上分配空间 >128k,会在mmap上分配空间
5.delete是先调用析构函数,再调用operator_delete,然后释放空间
delete后面参数是对象指针,free参数是void*
衍胜出来的问题,面试问过
free(void*)怎么知道释放空间的大小
向左偏移16个字节
malloc分配的内存是物理内存还是虚拟内存
2.介绍一下default
default是C++11的新特型,主要用于在类的声明中显式指定编译器生成默认的特殊成员函数实现。
3.C++的几种强制类型转换
1.const_cast
2.static_cast
1.用于基本数据类型之间的转换
2.将指向派生类的指针转换为基类的指针,也就是上行转换,编译器会在编译的时候检查这个派生类是否是基类的子类,所以转换的过程是安全的
3.将指向基类的指针转换为派生类的指针,也就是下行转换,编译器没有进行动态类型检查,所以是不安全的,应该使用dynamic_cast
4.在有关联的类型之间进行转换,比如转换枚举值为整数
5.将其它类型的指针转换为void*指针,比如将int类型的指针转换为void型的指针
3.dynamic_cast
dynamic_cast就做到了动态类型检查,所以就能够保证下行转换的安全性。下行转换又分两种情况,一种是基类指针指向派生类型,然后进行转换,这种转换是安全的,第二种情况是基类指针指向基类类型,这种转换就需要进行动态运行检查,转换失败,会返回NULL
4.reinterpret_cast
1.指针之间类型的转换
2.指针转换成整形,但是要注意转换的时候一定要转换到足够长的整形类型中,指针在16、32、64位操作系统下大小分别为2、4、8字节,所以在转换的时候至少要转换到对应大小的整形
3.整形转换为指针
4.介绍一下虚函数机制
虚函数机制就是为了解决C++的一些动态特性的问题,像函数的动态绑定,运行时候类型识别(RTTI);
1.在多态中,用虚函数机制,就能解决A类型指针指向B类型对象这样的问题;
基类成员函数前面加上virtual,该类函数就是虚函数,原理编译的时候编译器在类中发现virtual关键字之后,不会进行简单的对对象类型进行函数绑定,而是在运行阶段的时候根据对象的类型进行绑定;
在基类包含虚函数的时候,编译器会在对象的第一个数据成员的位置上生成vfptr(构造函数初始化),该指针指向虚函数的地址的数组,这个数组就是虚函数表
子类没有重写基类的虚函数的时候,子类继承基类的虚函数表,vfptr指向的是基类的函数
子类重写积基类的虚函数的时候,子类的虚函数表就会被覆盖,vfptr指向的是子类的函数
然后在运行的时候vfptr指向的虚函数表中找到函数的地址,然后调用函数
2.运行时候的类型识别
typeid在编译和运行的时候都可以进行类型识别,但是在运行时候进行类型识别就需要虚函数机制;
dynamic_cast 父子类的上行转换不会有问题,但是下行也就是父类转换为子类的时候,会存在越界的情况,基类加上虚函数的时候程序就不会报错了;所以保证了dynamic_cast的进行上下行转换的安全;
5.左值引用和右值引用(C++11)
6.指针和引用的区别
1.指针是一个实体,指向变量的地址,而引用是一个别称;
2.指针可以有多级,而引用只有一级;
3.声明指针变量后,可以在程序的任何地方进行初始化,引用声明完必须初始化;
4.引用不能指向空,指针可以指向空;
7.C++参数传递的三种方式
1.值传递
原变量的值不会发生变化
2.引用传递
原变量的值发生变化
3.指针传递
原变量的值发生变化
8.移动语义(C++11)
9.完美转发(C++11)
10.C++的智能指针有哪些,简述一下各自特点(C++11)
1.auto_ptr(C++11标记废弃,C++17废除)
2.unique_str(C++11)
3.shared_ptr(C++11)
4.weak_ptr(C++11)
11.函数模板和类模板
12.#include<>和#include""区别
#include是预编译器编译指令,会在预编译的时候寻找后面的文件名然后把文件的内容的包含到当前文件,预编译生成的是.i文件,我们打开.i之后发现就是替换
#include<>一般包含的是第三方库或者标准库的头文件,系统指定的路径下找头文件
#include“”一般包含的是用户定义的头文件,当前目录下找头文件,找不到的话,会去系统指定的路径下找
衍生出来的面试问题
其他.cpp文件能调用#include <.cpp>文件吗,为什么
会报函数重复定义的错误,怎么解决呢,函数定义为内联函数就解决了
13.const和static的作用
面试问道到的问题:在C++的类中,用const分别修饰成员变量和成员函数的作用是什么?
当const
修饰成员变量时,意味着该变量的值在对象创建后不能被修改。这有助于确保某些属性在对象生命周期内保持不变,从而避免在程序运行过程中意外修改这些关键数据;
当const
修饰成员函数时,表示该函数不会修改对象的数据成员。这种函数被称为常成员函数,它们只能读取对象的数据成员,而不能修改它们。这有助于确保在调用该函数时不会意外地改变对象的状态,从而提高了程序的健壮性;
15.数据库连接池的实现
16.C++11可变参数模板
18.编写类String 的构造函数、析构函数和赋值函数,已知类String 的原型为:
class String
{
public:
String(const char *str=NULL);//普通构造函数
String(const String &str);//拷贝构造函数
String & operator =(const String &str);//赋值函数
~String();//析构函数
private:
char* m_data;//用于保存字符串
};
#include <iostream>
#include <string>
#include <string.h>
using namespace std;
class String
{
public:
String(const char *str=NULL);//普通构造函数
String(const String &str);//拷贝构造函数
String & operator =(const String &str);//赋值函数
~String();//析构函数
private:
char* m_data;//用于保存字符串
};
//普通构造函数
String::String(const char *str)
{
if (str==NULL)
{
m_data=new char[1]; //对空字符串自动申请存放结束标志'\0'的空间
if (m_data==NULL)
{//内存是否申请成功
std::cout<<"申请内存失败!"<<std::endl;
exit(1);
}
m_data[0]='\0';
}
else
{
int length=strlen(str);
m_data=new char[length+1];
if (m_data==NULL)
{//内存是否申请成功
std::cout<<"申请内存失败!"<<std::endl;
exit(1);
}
strcpy(m_data,str);
}
}
//拷贝构造函数
String::String(const String &other)
{ //输入参数为const型
int length=strlen(other.m_data);
m_data=new char[length+1];
if (m_data==NULL)
{//内存是否申请成功
std::cout<<"申请内存失败!"<<std::endl;
exit(1);
}
strcpy(m_data,other.m_data);
}
//赋值函数
String& String::operator =(const String &other)
{//输入参数为const型
if (this == &other) //检查自赋值
{ return *this; }
delete [] m_data;//释放原来的内存资源
int length=strlen(other.m_data);
m_data= new char[length+1];
if (m_data==NULL)
{//内存是否申请成功
std::cout<<"申请内存失败!"<<std::endl;
exit(1);
}
strcpy(m_data,other.m_data);
return *this;//返回本对象的引用
}
//析构函数
String::~String()
{
delete [] m_data;
}
int main()
{
String a;
String b("abc");
a = b;
system("pause");
}
19.内联函数的使用
20.友元函数的使用
21.如何防止一个头文件 include 多次
#pragam once
22.说一说你知道的C++11的新特性
右值引用
移动语义
智能指针
完美转发
可变参数模板
自动类型推导auto和表达式类型推导
lambda表达式,是一个匿名函数,其实就是一个函数对象
函数对象
chrono时间库
23.C++线程池的设计
线程池要素
1.维护一堆线程数组
2.任务队列(函数模板和可变参数列表和函数适配器)
template<class F,class... Args>
void enqueue(F&&f,Args&&...args){
std::function<void()>task = std::blid(std::forward<F>(f),std::forward<Args>((args)...);
{
std::unique
}
}
3.condition_cariable 通知线程数组取任务去完成
没写完
#include<queue>
class ThreadPool{
ThreadPool(int numThreads) :stop(false){
for(int i = 0; i < numThreads;i++)
{
threads.emplace_back([this]){
while(1){
std::unique_lock
}
}
}
}
private:
std::vector<std::thread> threads;//线程数组
std::queue<std::function<void()>> tasks;//任务队列
std::mutex ntx; //互斥锁
std::condition_variable condition;//条件变量
bool stop;
}
24.虚析构函数的作用
如果一个基类的编辑析构函数没有设置为虚函数。此时有一个基类指针指向一个派生类对象,通过基类指针删除派生类对象,只会调用基类的析构函数,编译器并不知道实际删除的是派生类对象。这可能会导致编辑内存泄露问题;
25.C++11 thread库的使用(c++11)
1.创建线程thread thread1(test,"参数值");thread thread1(MyClass::test,&obj);
thread1.join();//主线程会检查子线程有没有结束,用的多,是阻塞的
thread1.detach();//主线程不会等待子线程,子线程后台执行
bool isJoin = thread1.joinable()//比较严谨
{
thread1.join();
}
2.多线程常见错误
std::ref(a); //传递引用型变量
变量局部变量,函数结束后线程再去取a的引用就取不到了
函数为private,使用友元函数;
friend void test();
26.C++线程同步(线程安全就是多线程改成单线程运行结果一样那就是安全的)
1.互斥量(解决数据竞争问题)
std::mutex mtx;
mtx.lock();
mtx.unlock();
互斥量死锁
概念
两个互斥量m1,m2;
两个线程先对m1,m2持有所有权,两个互相等待,就卡住了
怎么解决
两个线程谁先获取m1,就继续获取m2,这样就可以了
2.lock_guard和unique_lock
lock_guard和unique_lock是C++标准库的互斥量封装库,作用同mutex;
lock_guard(根据特性自己实现他的构造函数和析构函数)
构造函数调用时(初始化的时候),互斥量会被自动加锁;(两个构造函数,第一个参数是mutex,第二个参数就是不用加锁了)
析构函数调用时,互斥量会被自动解锁;
lock_guard对象不能复制和移动,只能在局部作用域使用;
unique_lock(更加灵活的管理,自己实现一下更好)
1.构造函数加锁,加上第二个参数写上就需要自己手动加锁try_lock_for(std::chrono::seconds(5)).;//加锁进行延迟操作;
2.trr_lock_until(//指定时间);
3.call_once与其使用场景(单例,日志类,线程池,连接池都可以使用单例模式)
两个线程同时使用单例实例,会存在单例模式中会new两次
call_once能保证多线程情况下只能调用一次;、
4.condition_variable
和mutex一起使用的
任务队列为空: wait(),wait_for(),和wait_until()进行等待
加任务:notify_once或者notify_all()通知等待的线程
5.原子操作atomic
27.异步并发面试的问题
1.async,future
async可以把函数打包成future的对象;
std::future<int>future_result = std::async(std::launch::async,func);//这个函数不阻塞直接运行
future_result.get();
2.packaged_task
3.promise
28.构造函数能声明为虚函数吗,原因是什么
不能,原因shi
二.STL标准模板库
容器
vector
list
单向链表
容器适配器
迭代器
算法
三.设计模式
单例模式(饿汉式 or 懒汉式)
1.构造函数用private限制;
2.接口用static保证唯一性;
3.拷贝构造和拷贝构造运算符delete掉;
工厂模式
观察者模式
至少有两个类,一个观察的类,一个被观察的类,被观察的类的行为发生变化,会通知观察者,然后再进行后续操作;