T:what
Y: why
W: how

继承

T

让某种类型对象获得另一个类型对象的属性和方法

Y

可以使用现有类的功能,并在无需重新编写原来类的情况下对这些功能进行扩展

W

继承的三重方式

实现:基类的方法和属性的使用

接口:仅仅使用基类的方法和属性的名称,子类需要提供代码的实现

可视:子窗口(类)使用基窗口(类)外观和代码实现

封装

T

将数据和代码捆绑在一起。把客观事物封装成抽象的类,并且类可以把⾃⼰的数据和⽅法只让可信的类或者对象操作,对不可信的进⾏信息隐藏。** 藏,**

Y

避免外界干扰和不确定访问;

多态

T

同一背景下表现出不同的事物的能力,也就是向不同对象发送同一消息,不同的对象在接受时产生不同的行为。

Y

允许将子类类型的指针赋值给父类类型的指针。

实现多态有两种方式,重载实现编译时的多态,虚函数(重写)实现运行时的多态

W

静态多态、动态多态、多态的实现原理、虚函数、虚函数表

  1. 静态多态 静态多态是编译器在编译期间完成的,编译器会根据实参类型来选择调用合适的函数,如果有合适的函数就调用,没有的话就会发出警告或者报错。静态多态有函数重载、运算符重载、泛型编程等。
  2. 动态多态 动态多态是在程序运行时根据基类的引用(指针)指向的对象来确定自己具体该调用哪一个类的虚函数。当父类指针(引用)指向 父类对象时,就调用父类中定义的虚函数;即当父类指针(引用)指向 子类对象时,就调用子类中定义的虚函数。
  3. 动态多态行为的表现效果为:同样的调用语句在实际运行时有多种不同的表现形态。 2. 实现动态多态的条件: - 要有继承关系 - 要有虚函数重写(被 virtual 声明的函数叫虚函数) - 要有父类指针(父类引用)指向子类对象 3. 动态多态的实现原理 当类中声明虚函数时,编译器会在类中生成一个虚函数表,虚函数表是一个存储类虚函数指针的数据结构, 虚函数表是由编译器自动生成与维护的。virtual 成员函数会被编译器放入虚函数表中,存在虚函数时,每个对象中都有一个指向虚函数表的指针(vptr 指针)。在多态调用时, vptr 指针就会根据这个对象在对应类的虚函数表中查找被调用的函数,从而找到函数的入口地址。

虚函数

virtual,

T

用virtual修饰的成员函数,虚函数依赖保存虚函数地址的虚函数表工作,当用基类指针指向派生类时,虚表指针指向派生类的虚函数表。

Y

不同继承关系的类对象,调用同一函数产生不同的行为,也就是为了实现动态多态,父类想要子类定义适合自己的方法

W

virtual typeReturn function()
{}
  • 普通函数(非类成员函数)不能是虚函数
  • 静态函数(static)不能是虚函数,原因在于static成员没有this指针,而虚函数依赖对象调用,用隐藏的this指针。
  • 构造函数不能是虚函数(因为在调用构造函数时,虚表指针并没有在对象的内存空间中,必须要构造函数调用完成后才会形成虚表指针)
  • 析构函数可以是虚函数,这在一个复杂类结构往往是必须的,这是为了在析构时先调用。
  • 内联成员函数只有在不表现多态的时候才能用virtual
#include <iostream>
using namespace std;
class Base
{public:inline virtual void who(){cout << "I am Base\n";}virtual ~Base() {}
};
class Derived : public Base
{public:inline void who()  // 不写inline时隐式内联{cout << "I am Derived\n";}
};int main()
{// 此处的虚函数 who(),是通过类(Base)的具体对象(b)来调用的,编译期间就能确定了,所以它可以是内联的,但最终是否内联取决于编译器。 Base b;b.who();// 此处的虚函数是通过指针调用的,呈现多态性,需要在运行时期间才能确定,所以不能为内联。  Base *ptr = new Derived();ptr->who();// 因为Base有虚析构函数(virtual ~Base() {}),所以 delete 时,会先调用派生类(Derived)析构函数,再调用基类(Base)析构函数,防止内存泄漏。delete ptr;ptr = nullptr;system("pause");return 0;
}

为什么构造函数不可以是虚函数

虚函数表,对象内存空间,指针引用实现;创建对象时自动调用

1.从存储角度

虚函数必须要有个虚函数表(vtable),而它又存储在对象的内存空间,而对象有需要通过类来实例化构造,而这就需要构造函数,所以构造函数不能够是虚函数,不然就没有vtable

2.从使用角度

虚函数的作用是作为调用子类成员函数重载的一个媒介,而这个媒介就是通过父类的指针或者引用来实现的.而构造函数是在创建对象时自动调用,不可能通过父类的指针或者引用去调用

为什么析构函数可以是虚函数

首先说明一点,一般虚函数在前面会添加virtual修饰符,但其实不管加不加,都在所有的派生类中称为虚函数.

编译器总是根据类型来调用类成员函数。一个派生类的指针可以安全地转化为一个基类的指针。这样删除一个基类的指针的时候,C++不管这个指针指向一个基类对象还是一个派生类的对象,调用的都是基类的析构函数而不是派生类的。如果依赖于派生类的析构函数的代码来释放资源,而没有重载析构函数,那么会有资源泄漏

     C++**不把虚析构函数直接作为默认值的原因是虚函数表的开销以及和C语言的类型的兼容性**。有虚函数的对象总是在开始的位置包含一个隐含的虚函数表指针成员。
#include <iostream>
using namespace std;class Box
{public:const char *name;int age;float score;Box() {cout << "调用构造函数Box!" << endl;}~Box() {cout << "调用析构函数Box!" << endl;}void say() {cout << name << "的年龄是" << age << ",成绩是" << score << endl;}
};class BigBox : public Box
{public:const char *name;int age;float score;BigBox() {cout << "调用构造函数BigBox!" << endl;}~BigBox() {cout << "调用析构函数BigBox!" << endl;}void say() {cout << name << "的年龄是" << age << ",成绩是" << score << endl;}
};int main()
{// new运算符,在堆上新建对象,调用构造函数,并返回该对象的指针Box* box = new BigBox;box->name = "ss";box->age = 18;box->score = 100;box->say();// delete运算符,释放堆上的对象,调用对象的析构函数delete box;getchar();return 0;
}

如果将基类设为虚函数,那么

#include <iostream>
using namespace std;class Box
{public:const char *name;int age;float score;Box() {cout << "调用构造函数Box!" << endl;}virtual ~Box() {cout << "调用析构函数Box!" << endl;}void say() {cout << name << "的年龄是" << age << ",成绩是" << score << endl;}
};class BigBox : public Box
{public:const char *name;int age;float score;BigBox() {cout << "调用构造函数BigBox!" << endl;}~BigBox() {cout << "调用析构函数BigBox!" << endl;}void say() {cout << name << "的年龄是" << age << ",成绩是" << score << endl;}
};int main()
{// new运算符,在堆上新建对象,调用构造函数,并返回该对象的指针Box* box = new BigBox;box->name = "ss";box->age = 18;box->score = 100;box->say();// delete运算符,释放堆上的对象,调用对象的析构函数delete box;getchar();return 0;
}

reference

为什么C++的构造函数不可以是虚函数,而析构函数可以是虚函数 - 知乎 (zhihu.com)

虚函数与纯虚函数的区别

是否实现,声明,抽象类

虚函数 纯虚函数
类中有无定义 无(只是一个接口)
声明 virtual type function(){} virtual type function()=0;
子类中是否实现 可不重载 必须实现
类被声明为抽象类
能直接生成对象 不能直接生成对象,只有被继承
#include<iostream>
using namespace std;class p{public:virtual void s()=0;
};
class c:public p{public:void s(){cout<<"gogo"<<endl;}
};int main()
{// cout<<"hello"<<endl;// p u;//errorp *b;c object;b=&object;b->s();object.s();return 0;
}
class Cperson
{protected:float mark;string name;
public:Cperson(string name, float mark){this->name = name;this->mark = mark;}~Cperson(){cout << "调用Cperson的析构函数" << endl;}virtual void ShowInf() = 0; // 纯虚函数声明形式// 纯虚函数未定义函数体无法分配存储空间,因此无法定义基类对象
};class Cstudent:public Cperson
{private:char sex;
public:Cstudent(char sex, string name, float mark) :Cperson(name, mark){this->sex = sex;}~Cstudent(){cout << "调用Cstudent析构函数" << endl;}void ShowInf(){cout << this->name << "性别:" << this->sex << ";分数为" << this->mark << "分" << endl;}
};// 使用基类指针调用派生类对象
void ShowInf(Cperson *person)
{person->ShowInf();
}void testsub(){//    A* ma=new A();//errorCstudent stud('m',"gogo",99);stud.ShowInf();ShowInf(&stud);}

gogo性别:m;分数为99分
gogo性别:m;分数为99分
调用Cstudent析构函数
调用Cperson的析构函数

一个空类的大小?为什么?

1,实例化

大小由编译器决定,在vs中为1个字节,因为必须要在内存中占有一定空间,才能声明该类的实例,否则实例无法使用。

如果类中添加构造函数和析构函数大小?

1,调用析构函数和构造函数只需要知道函数的地址即可,而这个地址由于类的地址关联,而与类的实例无关

如果析构函数是虚函数呢?

编译器会为类生成虚函数表,并为该类的每个实例添加一个指向虚函数表的指针,32位机器一个指针占4个字节,64位8个字节

虚继承

T

在继承方式后添加virtual关键字的继承

Y

  • 为了解决多继承命名冲突和数据冗余(rong)问题。
  • 为了让某个类做出声明,表示愿意共享它的基类。
#include"../main.hpp"
using namespace std;
//间接基类A
class A{protected:int m_a;
};//直接基类B
class B: virtual public A{  //虚继承
protected:int m_b;
};//直接基类C
class C: virtual public A{  //虚继承
protected:int m_c;
};//派生类D
class D: public B, public C{public:void seta(int a){ m_a = a; }  //正确//如果不使用虚继承,那么就会导致命名冲突//需要指定作用域,如B::m_a=a,C::m_a=a;void setb(int b){ m_b = b; }  //正确void setc(int c){ m_c = c; }  //正确void setd(int d){ m_d = d; }  //正确
private:int m_d;
};int main(){D d;return 0;
}

C++虚继承和虚基类详解 (biancheng.net)

C++类与结构体的区别

关键字不同,都能够继承

结构体
默认权限· 私有 公有
能否作为模板关键字
内存分配方面 引用类型 值类型
继承关系上 私有 公有
template<typename T, typename Y>    // 可以把typename 换成 class
int Func(const T& t, const Y& y) { //TODO
}

在C语言中结构体不能为空,并且只涉及到数据结构,不能够定义方法

值类型的变量直接存储数据,而引用类型的变量持有的是数据的引用,数据存储在数据堆中。

值类型(value type):byte,short,int,long,float,double,char,bool 和 struct 统称为值类型。值类型变量声明后,不管是否已经赋值,编译器为其分配内存。值类型的实例常在线程栈上分配(静态),但是某些情况下可以存在堆中。在内存管理方面具有更好的效率,但不支持多态,适合用做存储数据的载体。将一个值类型变量赋给另一个值类型变量时,将复制包含的值。

引用类型(reference type):string 和 class统称为引用类型当声明一个类时,只在栈中分配一小片内存用于容纳一个地址,而此时并没有为其分配堆上的内存空间。当使用 new 创建一个类的实例时,分配堆上的空间,并把堆上空间的地址保存到栈上分配的小片空间中。引用类型的对象总在进程堆中分配(动态),支持多态,适合用于定义应用程序的行为。

引用类型变量的赋值只复制对对象的引用,而不复制对象本身

(182条消息) 值类型和引用类型的区别,struct和class的区别_Christal_RJ的博客-CSDN博客_struct是值类型还是引用类型

简述拷贝赋值和移动赋值

lvalue 是“loactor value”的缩写,可意为存储在内存中、有明确存储地址(可寻址)的数据,而 rvalue 译为 “read value”,指的是那些可以提供数据值的数据(不一定可以寻址,例如存储于寄存器中的数据)。

T

拷贝赋值是通过拷贝构造函数,移动赋值通过移动构造函数赋值

二者最大的区别就是是否能够修改被拷贝对象,因为能够取地址的对象都是左值,不能够的是右值

移动赋值 拷贝赋值
形参 左值引用 右值引用
整个对象或变量的拷贝 生成一个指针指向源对象或变量的地址,接管源对象的内存
效率 高(节省时间和内存)
#include "../main.hpp"
using namespace std;class Person{// 使用一个已经创建完毕的对象来初始化一个新对象
//值传递的方式给函数参数传值
//以值方式返回局部对象classPerson{public:int mAge;Person(){cout<<"person default constructor"<<endl;}Person(int age){//有参构造mAge=age;}Person(const Person& p)//拷贝构造{cout<<"copy constructor"<<endl;mAge=p.mAge;}Person& operator=(const Person& person){cout<<" value constructor "<<endl;return *this;}Person(Person &&p)//移动构造{p.mAge=6;cout<<"move constructor"<<endl;}//destructor~Person(){cout<<"destructor"<<endl;}};
//call
void object(){Person man(100); //p对象已经创建完毕Person newman(man); //调用拷贝构造函数Person newman2 = man; //拷贝构造cout<<"newman2.mAge "<<newman2.mAge<<endl;//Person newman3;//newman3 = man; //不是调用拷贝构造函数,赋值操作}
//2. 值传递的方式给函数参数传值//相当于Person p1 = p;
void doWork(Person p1){p1.mAge=100;//不会影响调用函数的数据cout<<"p1.mAge:"<<p1.mAge<<endl;
}void valueTransfer(){Person p;doWork(p);//这个p与doWork中的P不一样cout<<"p.mAge:"<<p.mAge<<endl;
}
//3. 以值方式返回局部对象
Person doWork2(){Person p1;cout<<(int *)&p1<<endl;return p1;/*拷贝一个新的对象给外面*/
}void returnValue(){Person p=doWork2();cout<<(int *)&p<<endl;
}
Person* createPerson()
{Person* pPerson=new Person();return pPerson;
}int main(){Person* person=createPerson();delete person;Person person1;Person person2=move(person1);cout<<person1.mAge<<endl;//error// object();//valueTransfer();//returnValue();return 0;
}

输出:

person default constructor
destructor
person default constructor
move constructor
6
destructor
destructor

C++11 move()函数:将左值强制转换为右值 (biancheng.net)

(182条消息) c++: 移动构造/赋值 和 拷贝构造/赋值_EverNoob的博客-CSDN博客_移动赋值和拷贝赋值

delete this 合法吗

new创建, 最后一个调用this,

看情况:

如果this对象是通过new创建的(不是new[],不在栈上、或者其他成员对象)

delete this的成员函数是最后一个调用this的成员函数

必须保证成员函数的delete this后面没有调用this了

必须保证delete this 后没有人使用了

右值引用其实是一个左值

matrix k=a+b+c;

每次加法都会调用拷贝构造函数,消耗性能

这时需要移动构造函数;

class Matrix{public:int row,col;float** data=nullptr;Matrix(int _row,int _col):row(_row),col(_col){cout<<"call Matrix constructor"<<endl;data=new float* [row];for(int i=0;i<row;i++){data[i]=new float[col];for(int j=0;j<col;j++)data[i][j]=0;}}Matrix(const Matrix& mat){cout<<"call Matrix copy"<<endl;row=mat.row;col=mat.col;data= new float*[row];for(int i=0;i<row;i++){data[i]=new float[col];for(int j=0;j<col;j++){data[i][j]=mat.data[i][j];}}}Matrix(Matrix && mat)//当函数试图返回一个对象时,会调用移动构造函数,若无则调用拷贝构造函数{cout<<"call Matrix move"<<endl;row=mat.row;col=mat.col;data=mat.data;//无需重新分配内存mat.data=nullptr;//note}~Matrix(){cout<<"call ~Matrix"<<endl;if(data!=nullptr){for(int i=0;i<row;i++){if(data[i]){delete[] data[i];data[i]=nullptr;}}delete[] data;data=nullptr;}}// Matrix operator+(const Matrix& mat)// {//     if(mat.row!=row||mat.col!=col)//     {//         cout<<"error"<<endl;//     }//     Matrix res(mat.row,mat.col);//     for(int i=0;i<mat.row;i++)//     {//         for(int j=0;j<mat.col;j++)//         {//             res.data[i][j]=data[i][j]+mat.data[i][j];//         }//     }//     return res;// }//运算符有多个参数friend Matrix operator+(const Matrix& a,const Matrix& b)//Matrix operator+(const Matrix& mat)&&{cout<<"friend Matrix operator+(const Matrix& a,const Matrix & b)"<<endl;if(a.row!=b.row||a.col!=b.col){cout<<"error"<<endl;}Matrix res(a.row,a.col);for(int i=0;i<a.row;i++){for(int j=0;j<a.col;j++){res.data[i][j]=b.data[i][j]+a.data[i][j];}}return res;}friend Matrix operator+( Matrix&& a,const Matrix& b)//也可以用Matrix operator+(const Matrix& mat)&&{cout<<"friend Matrix operator+( Matrix&& a,const Matrix & b)"<<endl;if(a.row!=b.row||a.col!=b.col){cout<<"error"<<endl;}// Matrix res(a.row,a.col);// Matrix res=a;//Lvalue reference, 调用拷贝构造函数//但是这样效率降低Matrix res=std::move(a);//a的内容已经移动,后面不能够使用a.data[i][j];for(int i=0;i<a.row;i++){for(int j=0;j<a.col;j++){res.data[i][j]=b.data[i][j]+res.data[i][j];}}return res;}
};
void testMatrix()
{Matrix a(3,4),b(3,4),c(3,4),d(3,4);Matrix r=a+b+c+d;//右值引用是一个左值
}
call Matrix constructor
call Matrix constructor
call Matrix constructor
call Matrix constructor
friend Matrix operator+(const Matrix& a,const Matrix & b)
call Matrix constructor
friend Matrix operator+( Matrix&& a,const Matrix & b)
call Matrix move //第二次调用加法便采用移动构造,右值引用
friend Matrix operator+( Matrix&& a,const Matrix & b)
call Matrix move
call ~Matrix
call ~Matrix
call ~Matrix
call ~Matrix
call ~Matrix
call ~Matrix
call ~Matrix

【乔红】裤衩 C++ 之 右值引用(二)右值引用详解_哔哩哔哩_bilibili

重载与动态多态的区别

重载 多态
1.绑定方法 静态绑定,也称早绑定,在方法调用之前,编译器就确定了要调用的方法 动态绑定,也称晚绑定,只有方法要调用的时候,编译器才会确定
2参数 同名,参数不同 同名同参数,需要通过函数的实际类型来确定要调用那个函数。

函数重载 const修饰参数

https://www.nowcoder.com/discuss/1025870?type=2&channel=-1&source_id=discuss_terminal_discuss_hot_nctrack

如果参数类型使用const进行修饰,那么函数是否能重载成功呢?

当使用const修饰函数参数时,函数重载是否生效取决于是顶层const还是底层const,简单来说就是如果函数参数是顶层const,即对于编译器来说无法区分参数本身是否是常量,所以当参数是否是常量时,无法进行重载。但是当const修饰的是某种类型的引用或者指针时,那么就可以实现函数重载。

函数重载如果不在同一作用域,重载是否还会生效

重载不会生效,因为编译器在当前作用域下找不到对应参数的同名函数,所以造成重载失败。

C语言中有函数重载吗?

没有,编译时直接根据函数名来确定连接符号,而C++则需要参数的类型和个数

如何在C++项目中编译C语言代码呢?

将c函数的实现括在 extern "C"中

有没有办法让一段C函数代码既能在C编译器中编译,又能在C++编译器中编译。

使用 __cpluscplus这个宏名,当使用C++编译器时,将会将中间的代码作为C语言进行编译,而如果使用C语言编译器,编译器中没有对应的宏定义,所以代码上下的宏定义都是无效的,呈现给编译器的就是一段C语言代码。

#ifdef __cplusplus
extern "C"//告诉编译器,这部分代码按C语言的格式进行编译,而不是C++的
{#endifvoid printfs(){printf("gogo\n");}#ifdef  __cplusplus
}
#endif

c++中

extern "C"{void printfs(){printf("gogo\n");}}

c中

    void printfs(){printf("gogo\n");}

C++中为何要构造的时候基类先构造,子类后构造,析构的顺序相反呢?

继承,使用父类的成员

因为子类很有可能会用到从父类继承来的成员.

析构顺序这样安排的原因在于子类有可能使用父类的成员

C++ 的重载和重写是如何实现的

这个在静态多态里提过,重载是在编译期间完成的,通过命名倾轧技术,也就是根据函数形参的类型和顺序对函数重命名,可能不同编译器的命名标准不一样。

而重写则需要在基类函数加入virtual关键字,在派生类中重写该函数,根据作用域的不同调用不同类中的函数,具体实现涉及到虚表(类),虚函数指针,虚表指针(对象),这个在前面(虚函数表指针的偏移量是如何计算出来的?如何真正找到我想到访问的函数?)提过。

c++中的函数重载、函数重写、函数重定义 - PRO_Z - 博客园 (cnblogs.com)

(190条消息) typedef void (*Fun) (void) 的理解——函数指针——typedef函数指针_走在不归路上的博客-CSDN博客_typedef void

虚函数表指针的偏移量是如何计算出来的?如何真正找到我想到访问的函数?

对象实例化的地址得到虚函数表的地址。

如上图所示,虚函数表用来存储虚函数,而虚函数表指针是虚函数指针的指针,存放在对象的头部,通过对象实例化的地址得到虚函数表的地址。
虚函数表指针vptr指向的是第一个虚函数,而不是虚函数表首地址
计算;32位系统占4个字节,64位占8个字节,以后者为例:
第一个虚函数的地址=虚函数表指针的地址=虚函数表的首地址+16字节偏移

override 和 overload区别呢?

本质区别在于,override(重写)修饰的方法在调用的时候只有一个方法被使用

原因在于override一般用在子类继承父类,重写父类的方法,与父类中的某个方法的名称和参数完全相同

  1. 重写⽅法的参数列表,返回值,所抛出的异常与被重写⽅法⼀致
  2. 被重写的⽅法不能为private
  3. 静态⽅法不能被重写为⾮静态的⽅法
  4. 重写⽅法的访问修饰符⼀定要⼤于被重写⽅法的访问修饰符(public>protected>default>private)

overload是重载,这些方法的名称相同而参数形式不同

规则

  1. 不能通过访问权限、返回类型、抛出的异常进⾏重载
  2. 不同的参数类型可以是不同的参数类型,不同的参数个数,不同的参数顺序(参数类型必须不⼀样)
  3. ⽅法的异常类型和数⽬不会对重载造成影响

请你回答一下 C++ 类内可以定义引用数据成员吗?

初始化需要特殊处理

可以,但有三个条件

需要在初始化列表中初始化,而非构造函数

构造函数的形参必须是引用类型

必须自己提供构造函数来初始化成员变量。

简述一下什么是常函数,有什么作用

常函数

T:普通成员函数形参列表后面加上const的修饰符。

构造函数不能为常函数,其本身用于成员的的初始化,析构同理

全局函数和静态成员函数也不行,函数体没有this指针

函数内this只能对成员变量进行读取而不能修改。

如果想要对成员变量进行修改,需要mutable关键字修饰

构造函数和析构函数不能为常函数。全局函数和静态成员函数也不能。

常对象只能调用常函数,只能读成员,mutable除外

class Person {public:int m_Age;mutable int m_Height;Person(int age) {this->m_Age = age;}// this指针的本质  指针常量   Person* const this;// 如果想让this指针指向的对象的内容不能被修改,const Person* const this;void show() const{        // 常函数// this->m_Age = 200;    // 常函数中,不能对成员进行修改this->m_Height = 180;    // 常函数中,可以修改mutable修饰的成员cout << this->m_Age << endl;}void show1() {}
};int main() {Person p1(20);p1.show();// 常对象const Person p2(30);    // 常对象// p2.m_Age = 20;    // 常对象不能修改内容p2.m_Height = 190;    // 常对象可以修改mutable修饰的成员变量p2.show();    // 常对象可以调用常函数// p2.show1();    // 常对象不能调用普通函数return 0;
}

常量对象

作用在于常量对象可以调用常函数,而不能调用非常函数;另外这也是类设计的一种限定

ROS多线程

虚函数、虚表的原理?

虚函数的地址存储在虚函数表中,而类的对象内部会有指向类内部的虚表地址的指针,在运行期虚函数的多态调用会被编译器转换为对虚函数表的访问.

正常函数是强⾏计算地址;虚函数时通过偏移+的⽅式

C++派生类对象的初始化顺序,存在多个基类。

基类构造函数(按照在派生类出现的顺序构造)→成员类对象构造函数→派生类自身构造函数

简述下向上转型和向下转型

向上转型:子类转换为父类,使用dynamic_cast(expression),安全,数据不易丢失

向下转型: 父类转换为子类,可以使用强制转换,这种转换不安全,会导致数据的丢失

只定义析构函数,会自动生成哪些构造函数

编译器会自动生成拷贝构造和默认构造函数

一个空类,默认会生成哪些函数

无参构造函数-对象初始化;

拷贝构造函数-复制对象

赋值运算符重载

Empty& operator = (const Empty & copy)
{}

析构函数(非虚)

为何静态成员函数无法对对象中非静态成员进行访问?

不属于任何一个对象

因为对象在调用非静态成员函数时,系统会把对象的初始指针赋给成员函数的this指针,而静态成员函数不属于任何一个对象,也就没有this指针,所以无法访问非静态成员

c++11可变参数模板

概念、语法、模板参数包、展开参数包

在 C++11 之前,类模板和函数模板只能含有固定数量的模板参数。C++11 增强了模板功能,它对参数进行了高度泛化,允许模板定义中包含 0 到任意个、任意类型的模板参数,这就是可变参数模板。可变参数模板的加入使得 C++11 的功能变得更加强大,能够很有效的提升灵活性。

深拷贝与浅拷贝的区别

而且最根本的区别在于能够真正获取一个对象的复制实体,而不是引用,一般在拷贝构造函数和赋值运算符重载函数中涉及到。

默认一般是浅拷贝,如果源对象的成员在堆区里面开辟了一片内存,那么在析构函数释放的时候,二者指向相同的空间,如果是浅拷贝的话,会导致多次释放。

浅拷贝

如何定义一个只能在堆上(栈上)生成对象的类?

关键字: 静态,动态,私有析构函数,重载new,delete

1、只能建立在堆上

类对象只能建立在堆上,就是不能静态建立类对象,即不能直接调用类的构造函数。

容易想到将构造函数设为私有。在构造函数私有之后,无法在类外部调用构造函数来构造类对象,只能使用new运算符来建立对象。然而,前面已经说过,new运算符的执行过程分为两步,C++提供new运算符的重载,其实是只允许重载operator new()函数,而operator()函数用于分配内存,无法提供构造功能。因此,这种方法不可以。

当对象建立在栈上面时,是由编译器分配内存空间的,调用构造函数来构造栈对象。当对象使用完后,编译器会调用析构函数来释放栈对象所占的空间。编译器管理了对象的整个生命周期。如果编译器无法调用类的析构函数,情况会是怎样的呢?比如,类的析构函数是私有的,编译器无法调用析构函数来释放内存。所以,编译器在为类对象分配栈空间时,会先检查类的析构函数的访问性,其实不光是析构函数,只要是非静态的函数,编译器都会进行检查。如果类的析构函数是私有的,则编译器不会在栈空间上为类对象分配内存。

因此,将析构函数设为私有,类对象就无法建立在栈上了。代码如下:

class A
{public:A(){}void destory(){delete this;}
private:~A(){}
};

试着使用A a;来建立对象,编译报错,提示析构函数无法访问。这样就只能使用new操作符来建立对象,构造函数是公有的,可以直接调用。类中必须提供一个destory函数,来进行内存空间的释放。类对象使用完成后,必须调用destory函数。

上述方法的一个缺点就是,无法解决继承问题。如果A作为其它类的基类,则析构函数通常要设为virtual,然后在子类重写,以实现多态。因此析构函数不能设为private。还好C++提供了第三种访问控制,protected。将析构函数设为protected可以有效解决这个问题,类外无法访问protected成员,子类则可以访问。

另一个问题是,类的使用很不方便,使用new建立对象,却使用destory函数释放对象,而不是使用delete。(使用delete会报错,因为delete对象的指针,会调用对象的析构函数,而析构函数类外不可访问)这种使用方式比较怪异。为了统一,可以将构造函数设为protected,然后提供一个public的static函数来完成构造,这样不使用new,而是使用一个函数来构造,使用一个函数来析构。代码如下,类似于单例模式:

class A
{protected:A(){}~A(){}
public:static A* create(){return new A();}void destory(){delete this;}
};

这样,调用create()函数在堆上创建类A对象,调用destory()函数释放内存。

2、只能建立在栈上

只有使用new运算符,对象才会建立在堆上,因此,只要禁用new运算符就可以实现类对象只能建立在栈上。将operator new()设为私有即可。代码如下:

class  A
{
private :  void * operator  new ( size_t  t){}      // 注意函数的第一个参数和返回值都是固定的   void  operator  delete ( void * ptr){}  // 重载了new就需要重载delete   public :  A(){}  ~A(){}
};

如何定义一个只能在堆上(栈上)生成对象的类?

关键字: 静态,动态,私有析构函数,重载new,delete

1、只能建立在堆上

类对象只能建立在堆上,就是不能静态建立类对象,即不能直接调用类的构造函数。

容易想到将构造函数设为私有。在构造函数私有之后,无法在类外部调用构造函数来构造类对象,只能使用new运算符来建立对象。然而,前面已经说过,new运算符的执行过程分为两步,C++提供new运算符的重载,其实是只允许重载operator new()函数,而operator()函数用于分配内存,无法提供构造功能。因此,这种方法不可以。

当对象建立在栈上面时,是由编译器分配内存空间的,调用构造函数来构造栈对象。当对象使用完后,编译器会调用析构函数来释放栈对象所占的空间。编译器管理了对象的整个生命周期。如果编译器无法调用类的析构函数,情况会是怎样的呢?比如,类的析构函数是私有的,编译器无法调用析构函数来释放内存。所以,编译器在为类对象分配栈空间时,会先检查类的析构函数的访问性,其实不光是析构函数,只要是非静态的函数,编译器都会进行检查。如果类的析构函数是私有的,则编译器不会在栈空间上为类对象分配内存。

因此,将析构函数设为私有,类对象就无法建立在栈上了。代码如下:

class A
{public:A(){}void destory(){delete this;}
private:~A(){}
};

试着使用A a;来建立对象,编译报错,提示析构函数无法访问。这样就只能使用new操作符来建立对象,构造函数是公有的,可以直接调用。类中必须提供一个destory函数,来进行内存空间的释放。类对象使用完成后,必须调用destory函数。

上述方法的一个缺点就是,无法解决继承问题。如果A作为其它类的基类,则析构函数通常要设为virtual,然后在子类重写,以实现多态。因此析构函数不能设为private。还好C++提供了第三种访问控制,protected。将析构函数设为protected可以有效解决这个问题,类外无法访问protected成员,子类则可以访问。

另一个问题是,类的使用很不方便,使用new建立对象,却使用destory函数释放对象,而不是使用delete。(使用delete会报错,因为delete对象的指针,会调用对象的析构函数,而析构函数类外不可访问)这种使用方式比较怪异。为了统一,可以将构造函数设为protected,然后提供一个public的static函数来完成构造,这样不使用new,而是使用一个函数来构造,使用一个函数来析构。代码如下,类似于单例模式:

class A
{protected:A(){}~A(){}
public:static A* create(){return new A();}void destory(){delete this;}
};

这样,调用create()函数在堆上创建类A对象,调用destory()函数释放内存。

2、只能建立在栈上

只有使用new运算符,对象才会建立在堆上,因此,只要禁用new运算符就可以实现类对象只能建立在栈上。将operator new()设为私有即可。代码如下:

class  A
{
private :  void * operator  new ( size_t  t){}      // 注意函数的第一个参数和返回值都是固定的   void  operator  delete ( void * ptr){}  // 重载了new就需要重载delete   public :  A(){}  ~A(){}
};

C++问答2 三大特性相关推荐

  1. Java学习笔记二十五:Java面向对象的三大特性之多态

    Java面向对象的三大特性之多态 一:什么是多态: 多态是同一个行为具有多个不同表现形式或形态的能力. 多态就是同一个接口,使用不同的实例而执行不同操作. 多态性是对象多种表现形式的体现. 现实中,比 ...

  2. python加上子类的特性_Python--面向对象三大特性

    一.面向对象三大特性 什么是类的继承? 类的继承跟现实生活中的父.子.孙子.重孙子.继承关系一样,父类又称为基类. python中类的继承分为:单继承和多继承 1.继承 1 class ParentC ...

  3. 前端笔记(4)css,复合选择器,标签的显示模式,行高,css背景,css三大特性

    css样式表/层叠样式表(2) (1)css复合选择器 后代选择器 子元素选择器 交集选择器(不常用) 并集选择器 链接伪类选择器 (2)标签的显示模式 块级元素block-level 行内元素inl ...

  4. java三大特性:封装、继承、多态

    2019独角兽企业重金招聘Python工程师标准>>> 至今记得若干年前,去面试排了半天的队,到我的时候,面试官问我的第一个问题,java三大特性是什么~我支支吾吾的没有答全~0.0 ...

  5. Java的三大特性之继承

    此处我会分为这几个部分来理解继承是怎么样的: 1.区分封装.继承和多态 2.区分限定词的范围 3.区分隐藏.覆盖.重载 4.继承的理解 5.一道面试题的原型 --------------------- ...

  6. Java继承_Hachi君浅聊Java三大特性之 封装 继承 多态

    Hello,大家好~我是你们的Hachi君,一个来自某学院的资深java小白.最近利用暑假的时间,修得满腔java语言学习心得.今天小宇宙终于要爆发了,决定在知乎上来一场根本停不下来的Hachi君个人 ...

  7. 【Python学习笔记】面向对象三大特性

    2019独角兽企业重金招聘Python工程师标准>>> ★面向对象:封装.继承和多态是面向对象的三大特点★ 面向对象编程简称OOP,是一种程序设计思想.OOP把对象作为程序的基本单元 ...

  8. C#面向对象三大特性之二:继承

    面向对象的三大特性之一的封装,解决了将对同一对象所能操作的所有信息放在一起,实现统一对外调用,实现了同一对象的复用,降低了耦合. 但在实际应用中,有好多对象具有相同或者相似的属性,比如有一个对象 果树 ...

  9. 初步理解Java的三大特性——封装、继承和多态

    声明:整理自网络,如有雷同,请联系博主处理 一.封装 封装从字面上来理解就是包装的意思,专业点就是信息隐藏,是指利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体,数据被 ...

最新文章

  1. 小程序云开发,判断数据库表的两个字段匹配 云开发数据库匹配之 and 和 or 的配合使用
  2. 「译」有限状态机在 CSS 动画中的应用
  3. input子系统分析(转)
  4. MATLAB 利用plot 画图,加标题,保存图片
  5. Django(补充CBV,FBV)
  6. 微信小程序php get_php处理微信小程序request请求
  7. 畅享10e会有鸿蒙吗,功能虽小作用很大 华为畅享10e隐藏功能大揭秘
  8. Redis(一):什么是NoSQL与NoSQL分类
  9. leetcode--single number.
  10. 什么是动态链接库(DLL)以及常见问题
  11. Retrofit 使用flatmap操作符时处理错误、异常
  12. 敲一下enter键,完成iOS的打包工作
  13. 计算机网络工程师模拟题库,计算机网络工程师模拟题56.doc
  14. 华为手机服务器位置,华为手机怎么查看云服务器地址
  15. 英特尔首席工程师吴甘沙:一切弯路都是直路
  16. 招聘-中软国际外派中国移动(广州)
  17. 使用fail2ban解决暴力破解问题
  18. java SpringBoot 对接支付宝 APP支付 证书模式及非证书模式
  19. 猿辅导教研团队重磅推出人文博雅系列课程 2023年必读书单公布
  20. 迅猛扩张的字节跳动,踢到了一些铁板

热门文章

  1. 小O地图EXE版V0.9.5.5 - 功能总览
  2. 虚拟WIFI软件测试工程师,【Wifi测试工程师是什么职位】中互联zhl.com2021年Wifi测试工程师待遇怎么样-看准网...
  3. [测试通过]svn详细权限配置
  4. 系统升级到iOS9,真机运行报“was compiled with optimization - stepping may behave oddly...”,闪退
  5. iPhone的九宫格实现代码
  6. 基因功能分析——哈佛大学
  7. Android M的App Links实现详解
  8. java 级联删除_Mybatis 级联删除的实现
  9. layui个人中心html,Layui的简易入门教程
  10. 360众筹网_360众筹平台