为什么基类析构函数必须使虚函数,为什么c++默认的析构函数不是虚函数
当某个类被继承的时候,根据替换原则,可以使用派生类替换基类对象,当释放的时候如果不将基类的析构函数声明为虚函数的话,编译器只会释放基类的对象资源,但是实际上我们建立的是子类对象,想要释放的也是子类对象,所以应该把基类的析构函数声明成虚函数,子类去重写,这样就可以动态的检测到底是什么对象需要被析构,从而可以正确的析构我们想要析构的对象;
如果某个类并不需要被继承,那么其析构函数不必要声明成虚函数,为什么声明成虚构函数在上面已经解释,但是需要注意的一点是,当某个类中存在虚函数的时候,会需要创建虚函数表,和虚表指针。这些都会占有额外的内存,如果某个类没有被继承,那么也就没有必要来浪费这些内存;(虚函数表保存的是虚函数的地址,虚表指针保存的是指向各自虚函数表的地址,当调用某个类中的虚函数的时候,通过虚表指针先找到虚函数表,然后再通过虚函数表内中的虚函数地址,进行对应的虚函数调用)
函数指针
函数指针指向的是函数,有了函数指针之后,就可以通过这个指针进行函数的调用;
fork函数
fork函数的作用就是创建一个和当前进程一样的进程;
如果在父进程中调用fork,返回的是子进程的pid,如果在子进程中调用fork,返回的是0(如果成功的话) 失败的话返回一个负值;
fork是将主进程的资源全部拷贝了一份给子进程,两个进程的资源是独立的,互不影响。
请你来说一下析构函数的作用
- 声明方式,在与类名相同的函数前面添加~
- 与构造函数的部分作用相反,构造函数的作用是,在创建对象的时候,分配资源,初始化成员,而析构函数的作用就是,释放资源
- 当对象出了作用与之后,系统自动调用析构函数
- 如果用户没有编写析构函数,系统自动生成一个默认析构函数
请你来说一下静态函数和虚函数的区别
静态函数在编译的阶段就已经确定,而虚函数则是在运行时进行动态绑定
虚函数会用到虚函数表机制,需要额外的内存开销
扩展: 静态类型和动态类型
静态类型是变量申明时的类型或者表达式生成的类型,在编译的时候已知
动态类型则是变量或者表达hi表示的内存中的对象类型,知道运行时才可知
重载和覆盖
重载是指相同的函数名,不同的函数参数个数、类型,时编译时的多态
覆盖一般是指重写,他是作用在子类与父类中的,如在父类中定义了一个虚函数,在派生类中重新定义了相同的函数,这叫覆盖
(重写)
static
全局变量:
对于全局变量声明成static之后,这个变量只能在当前的文件中使用;
局部变量
如果函数中的局部变量声明成static之后,当出了函数的作用域,这个变量不能被使用,但是也没有被销毁,而是一直存在于内存栈中,一直到下一次函数调用的时候才能被使用,并且一直保持着相同的值;
类中的成员变量/成员函数
如果类中的成员变量或者成员函数被声明成static,那么这个成员变量和成员函数没有了this指针,只能通过类名::变量/函数名来调用
你是怎样理解虚函数和多态的
多态分为静态多态和动态多态,静态多态指的是重载,重载在编译期间就已经确定,而动态多态是通过虚函数实现的,他是在运行期间才能进行确定;
虚函数的声明方式是通过在函数的前面添加virtual ,如果基类中声明了一个虚函数,在派生类中重写了这个函数,那么在通过派生类当作参数传递到基类的引用上时,编译器会自动的识别我们到底是想要调用的谁的函数;
虚函数的工作原理是通过虚函数表和虚表指针来进行工作的,所谓的虚函数表就是一个包含了类中所有虚函数的函数地址的指针,如果在基类中有一到多个虚函数,那么就会创建一个隐藏的虚函数表,表中存储的是类中的所有虚函数的函数地址,当派生类继承基类的时候,也会把这个虚函数表继承下来,如果在派生类中重写了基类的虚函数,那么派生类的虚函数表中的地址会更新成新的函数地址,而每个具有虚函数表的类中,在所有成员的前面,都会隐藏一个虚表指针,这个指针指向的就是本类的虚函数表,在进行虚函数调用的时候,就是通过对象找到虚表指针然后再找到虚函数表中对应的函数地址进行对应函数的调用;
++i与i++
先递增1,然后返回递增之后的值,先返回值,再自增1
智能指针shared_ptr的实现
他的实现是通过构造函数、拷贝构造函数、赋值、析构函数组成的
首先他是一个模板类,类里面有一个模板指针,
默认构造函数:形参的默认值是nullptr, 构造的时候,如果参数的值为空,计数器=0,反之=1
拷贝构造函数,先判断传进来的参数与当前的对象是否是同一个对象,如果不是同一个对象就将传进来的参数的指针与计数器复制一份传递给当前对象的对应参数
=运算符的重载 将=右边的对象计数器-1,=左边的对象计数器+1
如果-1之后,计数器=0 删除右边的对象
析构函数:将当前的对象计数器-1,并且判断-1之后的计数器是否为=,如果为= 删除当前的对象
代码如下
#include <memory>
#include <iostream>
using namespace std;
template<typename T>
class smart{
private:
T* _ptr;
int* _count; //reference counting
public:
//构造函数
smart(T* ptr = nullptr):_ptr(ptr){
if (_ptr){
_count = new int(1);
}
else{
_count = new int(0);
}
}
//拷贝构造
smart(const smart& ptr){
if (this != &ptr){
this->_ptr = ptr._ptr;
this->_count = ptr._count;
(*this->_count)++;
}
}
//重载operator=
smart operator=(const smart& ptr){
if (this->_ptr == ptr._ptr){
return *this;
}
if (this->_ptr){
(*this->_count)--;
if (this->_count == 0){
delete this->_ptr;
delete this->_count;
}
}
this->_ptr = ptr._ptr;
this->_count = ptr._count;
(*this->_count)++;
return *this;
}
//operator*重载
T& operator*(){
if (this->_ptr){
return *(this->_ptr);
}
}
//operator->重载
T& operator->(){
if (this->_ptr){
return this->_ptr;
}
}
//析构函数
~smart(){
(*this->_count)--;
if (*this->_count == 0){
delete this->_ptr;
delete this->_count;
}
}
//return reference counting
int use_count(){
return *this->_count;
}
};
以下四行代码的区别是什么?
const char * arr = "123";
char * brr = "123";
const char crr[] = "123";
char drr[] = "123";
- 从内向外看,首先声明了一个指针,指针是char,因此首先是声明了一个char类型的指针,再向外看是一个const,因此解释就是指向一个字符常量或者字符串常量的指针,arr的值不能被修改,因为他是指向常量的,“123” 是一个字符串常量,保存在常量区;
- 声明了一个指针,这个指针指向字符或者字符串 ,由于初始化的时候,brr指向的是一个字符串常量,因此同样不能通过brr去修改“123”;
- 声明了一个数组,数组容纳的是一个常量(“123”),由于使用const进行修饰,因此不能通过crr修改“123”的值;
- 声明了一个字符数组,容纳的是“123”,由于数组是值类型,保存在栈上,所以可以通过drr修改“123”;
c++中是怎样定义常量额,常量存放在内存的那个地方
- c++定义常量的方式就是在变量前添加上const;
- 对于全局对象、常量来说,存放在静态区
- 对于局部常量、对象来说,存放在栈中
- 对于字面值常量来说,存放在常量区
const修改成员函数的目的是什么
- 不会对传入的参数,或者调用的对象进行修改
- 由于非常量对象,能调用非常量函数、常量函数,但是常量对象只能调用常量函数,因此添加上const,就可以令常量对象与非常量对象都可以调用这个函数,更加通用;