特殊成员函数
特殊成员函数与普通函数、运算符重载函数的一个显著的区分方法就是——没有返回值。
C++98必要组成:构造函数、拷贝构造函、赋值构造函数、析构函数。
C++11 :多了两个——移动构造函数、移动赋值构造函数
编译器会自动生成上述特殊成员函数。
默认构造函数:编译器生成的无参数构造函数。只要程序猿定义了有参构造函数,编译器就不会再提供无参构造函数了,所以为了防止出错,在显示定义有参构造函数前,要先定义一个无参构造函数。
构造函数分三类:无参构造函数,有参构造函数
有参构造函数可分为:转换构造函数、普通构造函数
转换构造函数部分初始化了成员,普通构造函数所有成员的初始化则需要由程序员提供。
在程序员未提供构造函数时,编译器会提供默认的无参构造函数。
与构造函数相关的关键字
noexcept
异常 noexcept 在函数的后面加上noexcept,代表这个函数不会抛出异常,如果抛出异常程序就会终止。使用 noexcept 表明函数或操作不会发生异常,会给编译器更大的优化空间。然而,并不是加上noexcept就能提高效率。
以下情形鼓励使用noexcept,不出意外noexcept只用于这三种函数:
1 移动构造函数(move constructor)
2 移动分配函数(move assignment)
3 析构函数(destructor)
explicit
explicit 关闭隐式类型转换,该关键字只用于转换构造函数
什么时候会发生隐式类型转换:赋值操作”=”且没有类型转化关键字的时候。比如double a=1就发生了int到double的隐式类型转换。
转换构造函数
把普通类型转化为类类型。其结构同有参构造函数一样,只不过部分参数被默认初始化了。
explicit Complex(double real): m_real(real), m_imag(0.0){ } //把double类型转化为complex类型
Complex(double real): m_real(real), m_imag(0.0){ } //区别在于complex a=2.0 是合法的,因为进行了隐式类型转化;加了explicit后,complex a=2.0语法错误,因为不能进行隐式类型转换,正确的格式为 complex a = complex(2.0)
类型转换函数
普通类型到类类型转换,类型转换函数结构与重载运算符类似,重载int等关键字。与一般运算符重载不同的是,类放在关键字后的()里。
class A
{
public:
int a;
A() {}
explicit A(int a) :a(a) {}
operator int()
{
return a;
}
};
int main()
{
A test(3);
//用法1
int d = test;
//用法2
int c = 2 + int(test);
cout << d << endl;//3
cout << c << endl;//5
return 0;
}
类和类之间的类型转换
通过转换构造函数实现,与普通类型到类类型不同的是:1、编译器不提供隐式类型转换;2、未初始化的成员变量不一定只有一个,可能有多个。
=delete
C++11 中,可在想要 “禁止使用” 的特殊成员函数声明后加 “= delete”。比如编译器会生成多种构造函数,对于用不到的构造函数,后面添加delete即可。
=default
只要显式定义了任意一个特殊成员函数,该类就不属于POD类型了。但在显式定义的特殊成员函数后加上=default,该类就仍然是POD类型
为什么要区分出POD
POD,是Plain Old Data的缩写,普通旧数据类型,是C++中的一种数据类型概念。POD类型与C编程语言中使用的类型兼容,POD数据类型可以使用C库函数进行操作,也可以使用std::malloc创建,可以使用std::memmove等进行复制,并且可以使用C语言库直接进行二进制形式的数据交换。
什么是POD类型
当类或结构体同时满足如下几个条件时是普通类型:
没有虚函数或虚基类;
由C++编译器提供默认的特殊成员函数(默认的构造函数、拷贝构造函数、移动构造函数、赋值运算符、移动赋值运算符和析构函数);故自定义构造函数加一个=default,该类才属于pod类型。
判断是否为POD的方法
C++ 给定对象类型取决于其特定的内存布局方式,一个对象是普通、标准布局还是 POD 类型,可以根据标准库函数模板来判断:
#include
1)is_trivial
2)is_standard_layout
3)is_pod
调用时机
什么时候调用构造函数
声明类对象时调用构造函数,若声明时未初始化,则调用默认构造函数;若在声明时初始化,则调用相应的初始化构造函数。
class demo *ptr=new(demo a);//调用默认构造函数
class demo a=1;//调用类型转换构造函数
demo a(1);//调用有参构造函数
初始化形参时调用拷贝构造函数或者有参构造函数
什么时候调用析构函数
销毁类时调用析构函数
比如:delete时、离开函数作用域时、多线程下呢?
默认析构函数的缺陷
如果程序员没有定义析构函数,系统将自动生成和调用一个默认析构函数,默认析构函数只能释放对象的数据成员所占用的空间,但不包括堆内存空间。
故凡是存在指针成员的类,一定要自定义析构函数。
拷贝构造函数
浅拷贝(图1)和深拷贝(图2)
如图,浅拷贝就是简单的赋值,指针也是赋值;深拷贝,指针成员不是简单赋值,而是重新分配一块内存。浅拷贝存在的问题就是释放其中任意一个对象,另一个对象会产生野指针。
如图,释放stu2后,stu1便产生了野指针。
类的内存分配
虚表、static成员函数、其它成员函数、数据成员
首项存放虚函数表指针,包含虚函数的类才会有虚函数表,同属于一个类的对象共享虚函数表;
接下来的内存空间依次存放数据成员;
成员函数不占用类的内存空间,成员函数地址通过编译器找到,并没有存放在类中。static 成员变量和普通 static 变量一样,都在内存分区中的全局数据区分配内存,到程序结束时才释放。这就意味着,static 成员变量不随对象的创建而分配内存,也不随对象的销毁而释放内存。
静态成员
1. 一个类中可以有一个或多个静态成员变量,所有的对象都共享这些静态成员变量,都可以引用它。注意,是共享,共享,共享,也即是只会占同一份静态存储空间。
2. static 成员变量和普通 static 变量一样,都在内存分区中的全局数据区分配内存,到程序结束时才释放。这就意味着,static 成员变量不随对象的创建而分配内存,也不随对象的销毁而释放内存。而普通成员变量在对象创建时分配内存,在对象销毁时释放内存。根本原因是静态成员变量和对象的存储空间不同,是在不同的时期去分配的。
3. 静态成员变量必须初始化,而且只能在类体外进行。例如:int Point::count = 0;
虚表
虚表同static成员变量一样存放在全局数据区。
虚表指针并非指向虚表的表头,而是指向表头+16的位置。表头的前两个槽位(各占8个字节)。
然后存放type_info结构体指针,typeinfo内存放有与类的属性相关的信息。typeid(a)返回一个typeinfo对象,typeid(a).name()即typeinfo.name()返回a的类型名称。