最近看高质量程序设计指南(第三版)林华,复习,学习C++对象知识,要更深入了解,估计得看Lippman的Inside the C++ Object Model 和 Thinking in C++了,也是本书的高频引用。
对象的内存布局
对象内存映像
- 用户内存区
- 静态数据区
- 代码段
无继承时的对象模型的规则
- 非静态成员变量,对象专有
- 静态成员变量,所有对象共享
- 静态和非静态成员函数被所有对象共享
构成对象本身的只有数据,任何成员函数不隶属于任何一个对象,非静态成员函数通过this指针与对象绑定。
有继承和虚函数的类的对象模型的规则
- 派生类继承基类的非静态成员变量和非静态成员函数。
- 为每一个多态类(含有虚函数的类)创建虚函数指针数组(虚函数表)vtable,该类所有虚函数地址都在表里,因为这是表示类所有的,所以它存储在静态数据区。
- 多态类的每一个对象都会新增一个指针成员vptr,类型为指向函数指针的指针,它指向类的vtable.vptr可以看成对象的隐含数据成员变量,一般在数据成员最前面。
- 基类已经插入vptr,派生类将继承和重用vptr。
- 如果是多重继承,就会有多个类型不一样的vptr和vtable。
- 为了支持RTTI,vtable第一个位置一般有一个typeinfo对象。
vtable和vptr的类型问题和初始化问题。(有点难,暂时略)
对象的初始化、拷贝和析构(Big-Three)
假设类A,如果不显示定义,C++编译器自动为A产生4个public inline的默认函数:
A() /*默认构造函数
A(const A&) /*默认拷贝构造函数
~A() /*析构函数
A&operator=(const A&a) /*默认赋值函数
默认拷贝构造,默认复制函数的“默认”方式:
- 按成员拷贝,依次调用每个数据成员的默认构造函数和默认赋值函数,直到所有成员都是基本类型为止。但常不如人意,如成员是指针,默认函数执行结果是两个对象的对应成员指向相同的对象。
构造函数的成员初始化列表
- 如果类存在继承关系,派生类可以在构造函数初始化列表里调用基类的特定构造函数以向它传递参数。
- 类的非静态const成员和引用成员智能在初始化列表里初始化,不能赋值初始化
- 列表初始化效率高。
对象的构造和析构次序
- 构造:先基后派生类,同一类中的成员初始化顺序由在类中声明顺序决定,与初始化列表顺序无关。
- 析构:先派生后基类,类比网络传输协议的封包和解包。
构造函数和赋值函数的重载
- 不能同时定义一个无参数的构造函数和一个参数全部有默认值的构造函数,否则会造成二义性。
- 拷贝构造函数参数只能是本类对象的引用,const 引用,volatile引用,const volatile引用,而不能是对象值,否则会陷入无限递归中。
区分赋值和拷贝函数
1.拷贝构造是对象被创建并用另一个已经存在的对象来初始化它时调用的。
2.赋值函数只能把一个对象赋给另一个已经存在的对象。
String a("hello")
String c("world")
String c=a //调用拷贝构造函数,最好写成c(a)
c=a //调用赋值函数
String类示例
需要掌握
String类
class String
{
public:
String(const char *str =""); // 普通构造函数 这里是空,而不是NULL,空字符串也是有效的字符串
String(const String &other); // 拷贝构造函数
~String(void); // 析构函数
String & operator = (const String &other); // 赋值函数
private:
size_t m_size; //保存当前长度
char *m_data; // 用于保存字符串
};
String的构造函数
String::String(const char *str)
{
if ( NULL == str)
{
m_data = new char[1]
*m_data = '\0';
m_size=0;
}
else
{
size_t len = strlen(str);
m_data = new char[len + 1];
strcpy(m_data,str);
m_size=len;
}
}
String的拷贝构造函数
String::String(const String &other)
{
size_t len = strlen(other.m_data); //strlen返回的是不带‘\0’的长度
m_data = new char[len+1]; //初始化,从没有到有,注意+1
strcpy(m_data,other.m_data);
m_size=len;
}
String的赋值函数
String & String::operator = (const String &other)
{
//检查自赋值
if (&other == this) //地址相同才是同一个对象
{
return *this;
}
//释放原有内存资源
delete [] m_data;
//分配新的内存资源,并复制内容
m_data = new char[strlen(other.m_data)+1];
strcpy(m_data,other.m_data);
m_size=strlen(other.m_data);
//返回本对象的引用
return *this;
}
String 的析构函数
String::~String(void)
{
delete []m_data;
}
如何实现派生类的基本函数
- 派生类的构造函数应在初始化列表里显式调用基类构造函数
- 基类的析构函数应定义为虚函数,否则会造成内存泄漏
- 编写派生类赋值函数时,不要忘记对基类的数据成员重新赋值,可以通过调用基类的赋值函数来实现。