default关键字可以显式要求编译器生成合成构造函数,防止在调用时相关构造函数类型没有定义而报错。
#include <iostream>
using namespace std;
class CString
{
public:
CString() = default; //语句1
//构造函数
CString(const char* pstr) : _str(pstr){}
//析构函数
~CString(){}
public:
string _str;
};
int main()
{
auto a = new CString(); //语句2
cout << "Hello World" <<endl;
return 0;
}
//运行结果
//Hello World
如果没有加语句1,语句2会报错,表示找不到参数为空的构造函数,将其设置为default可以解决这个问题。
delete关键字可以删除构造函数、赋值运算符函数等,这样在使用的时候会得到友善的提示。
#include <iostream>
using namespace std;
class CString
{
public:
void* operator new() = delete;//这样不允许使用new关键字
//析构函数
~CString(){}
};
int main()
{
auto a = new CString(); //语句1
cout << "Hello World" <<endl;
return 0;
}
在执行语句1时,会提示new方法已经被删除,如果将new设置为私有方法,则会报惨不忍睹的错误,因此使用delete关键字可以更加人性化的删除一些默认方法。
构造函数
拷贝构造函数
赋值运算符
#include <iostream>
using namespace std;
class A
{
public:
A()
{
cout << "我是构造函数" << endl;
}
A(const A& a)
{
cout << "我是拷贝构造函数" << endl;
}
A& operator = (A& a)
{
cout << "我是赋值操作符" << endl;
return *this;
}
~A() {};
};
int main()
{
A a1; //调用构造函数
A a2 = a1; //调用拷贝构造函数
a2 = a1; //调用赋值操作符
return 0;
}
//输出结果
//我是构造函数
//我是拷贝构造函数
//我是赋值操作符
Student s;
Student s1 = s; // 调用拷贝构造函数
Student s2;
s2 = s; // 赋值运算符操作
注: 类中有指针变量时要重写析构函数、拷贝构造函数和赋值运算符。
由于C++支持多继承,除了public、protected和private三种继承方式外,还支持虚拟(virtual)继承,举个例子:
#include <iostream>
using namespace std;
class A{}
class B : virtual public A{};
class C : virtual public A{};
class D : public B, public C{};
int main()
{
cout << "sizeof(A):" << sizeof A <<endl; // 1,空对象,只有一个占位
cout << "sizeof(B):" << sizeof B <<endl; // 4,一个bptr指针,省去占位,不需要对齐
cout << "sizeof(C):" << sizeof C <<endl; // 4,一个bptr指针,省去占位,不需要对齐
cout << "sizeof(D):" << sizeof D <<endl; // 8,两个bptr,省去占位,不需要对齐
}
上述代码所体现的关系是,B和C虚拟继承A,D又公有继承B和C,这种方式是一种菱形继承或者钻石继承,可以用如下图来表示:
虚拟继承的情况下,无论基类被继承多少次,只会存在一个实体。
虚拟继承基类的子类中,子类会增加某种形式的指针,或者指向虚基类子对象,或者指向一个相关的表格;
**表格中存放的不是虚基类子对象的地址,就是其偏移量,此类指针被称为bptr,**如上图所示。
如果既存在vptr又存在bptr,某些编译器会将其优化,合并为一个指针。
带有默认构造函数的类成员对象,如果一个类没有任何构造函数,但它含有一个成员对象,而后者有默认构造函数,那么编译器就为该类合成出一个默认构造函数。
不过这个合成操作只有在构造函数真正被需要的时候才会发生;
如果一个类A含有多个成员类对象的话,那么类A的每一个构造函数必须调用每一个成员对象的默认构造函数而且必须按照类对象在类A中的声明顺序进行;
带有默认构造函数的基类,如果一个没有任何构造函数的派生类派生自一个带有默认构造函数基类,那么该派生类会合成一个构造函数调用上一层基类的默认构造函数;
带有一个虚函数的类;
带有一个虚基类的类;
抽象类是一种特殊的类,它是为了抽象和设计的目的为建立的,它处于继承层次结构的较上层。
不可以!
因为 在编译时 模板并不能生成真正的二进制代码,而是在编译调用模板类或函数的CPP文件时才会去找对应的模板声明和实现,在这种情况下编译器是不知道实现模板类或函数的CPP文件的存在,所以它只能找到模板类或函数的声明而找不到实现,而只好创建一个符号寄希望于链接程序找地址。
但模板类或函数的实现并不能被编译成二进制代码,结果链接程序找不到地址只好报错了。
《C++编程思想》第15章(第300页)说明了原因:模板定义很特殊。由template<…>处理的任何东西都意味着编译器在当时不为它分配存储空间,它一直处于等待状态直到被一个模板实例告知。在编译器和连接器的某一处,有一机制能去掉指定模板的多重定义。
所以为了容易使用,几乎总是在头文件中放置全部的模板声明和定义。